Загрузка данных


import pygame
import random
import math
import sys
import os

pygame.init()
pygame.font.init()

# --- Конфигурация экрана и сетки ---
WIDTH, HEIGHT = 1150, 680
GRID_COLS, GRID_ROWS = 9, 5
CELL_W, CELL_H = 88, 96
GRID_X, GRID_Y = 280, 140

FPS = 60
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Plants vs. Zombies: Deluxe Python Edition")
clock = pygame.time.Clock()

# Шрифты
f_micro = pygame.font.SysFont("Verdana", 11, bold=True)
f_small = pygame.font.SysFont("Verdana", 14, bold=True)
f_med = pygame.font.SysFont("Verdana", 20, bold=True)
f_big = pygame.font.SysFont("Verdana", 38, bold=True)

# База данных всех доступных растений
PLANT_SPECS = {
    "peashooter": {"cost": 100, "hp": 200, "cd": 120, "name": "Peashooter"},
    "sunflower":  {"cost": 50,  "hp": 150, "cd": 120, "name": "Sunflower"},
    "cherrybomb": {"cost": 150, "hp": 600, "cd": 600, "name": "Cherry Bomb"},
    "wallnut":    {"cost": 50,  "hp": 800, "cd": 450, "name": "Wall-nut"},
    "snowpea":    {"cost": 175, "hp": 200, "cd": 150, "name": "Snow Pea"},
    "repeater":   {"cost": 200, "hp": 200, "cd": 150, "name": "Repeater"},
    "potatomine": {"cost": 25,  "hp": 100, "cd": 350, "name": "Potato Mine"},
    "chomper":    {"cost": 150, "hp": 250, "cd": 200, "name": "Chomper"}
}

# --- Менеджер Ресурсов и Автогенератор Спрайтов ---
class AssetVault:
    def __init__(self):
        self.images = {}
        os.makedirs("assets", exist_ok=True)
        self.preload_all()

    def get(self, key):
        return self.images.get(key)

    def preload_all(self):
        needed = [
            "peashooter", "sunflower", "cherrybomb", "wallnut", 
            "snowpea", "repeater", "potatomine", "chomper",
            "zombie_regular", "zombie_cone", "zombie_bucket", "zombie_gargantuar",
            "shovel", "sun", "pea", "ice_pea", "lawnmower"
        ]
        for item in needed:
            path = os.path.join("assets", f"{item}.png")
            if os.path.exists(path):
                self.images[item] = pygame.image.load(path).convert_alpha()
            else:
                self.images[item] = self.synthesize_asset(item)

    def synthesize_asset(self, item):
        surf = pygame.Surface((CELL_W, CELL_H), pygame.SRCALPHA)
        cx, cy = CELL_W // 2, CELL_H // 2

        if item in ["peashooter", "repeater", "snowpea"]:
            col = (100, 210, 255) if item == "snowpea" else (60, 180, 50)
            pygame.draw.ellipse(surf, (30, 120, 30), (cx - 25, cy + 18, 50, 16))
            pygame.draw.line(surf, (40, 140, 40), (cx, cy + 20), (cx, cy), 6)
            pygame.draw.circle(surf, col, (cx - 4, cy - 8), 20)
            pygame.draw.rect(surf, col, (cx + 6, cy - 16, 20, 16), border_radius=4)
            pygame.draw.circle(surf, (20, 70, 20), (cx + 26, cy - 8), 8)
            pygame.draw.circle(surf, (255, 255, 255), (cx + 2, cy - 14), 5)
            pygame.draw.circle(surf, (0, 0, 0), (cx + 4, cy - 14), 2)
            if item == "repeater":
                # Листья на затылке Репитера
                pygame.draw.polygon(surf, (40, 150, 30), [(cx - 20, cy - 16), (cx - 36, cy - 26), (cx - 24, cy - 6)])
            elif item == "snowpea":
                pygame.draw.polygon(surf, (180, 240, 255), [(cx - 20, cy - 16), (cx - 32, cy - 22), (cx - 22, cy - 8)])

        elif item == "sunflower":
            for ang in range(0, 360, 30):
                rad = math.radians(ang)
                px = cx + math.cos(rad) * 22
                py = (cy - 6) + math.sin(rad) * 22
                pygame.draw.circle(surf, (255, 205, 10), (int(px), int(py)), 9)
            pygame.draw.circle(surf, (140, 75, 25), (cx, cy - 6), 18)
            pygame.draw.circle(surf, (0, 0, 0), (cx - 6, cy - 10), 3)
            pygame.draw.circle(surf, (0, 0, 0), (cx + 6, cy - 10), 3)
            pygame.draw.arc(surf, (255, 255, 255), (cx - 8, cy - 8, 16, 10), math.pi, 2 * math.pi, 2)

        elif item == "wallnut":
            pygame.draw.ellipse(surf, (165, 95, 45), (cx - 22, cy - 28, 44, 60))
            pygame.draw.circle(surf, (255, 255, 255), (cx - 7, cy - 12), 6)
            pygame.draw.circle(surf, (255, 255, 255), (cx + 7, cy - 12), 6)
            pygame.draw.circle(surf, (0, 0, 0), (cx - 5, cy - 12), 3)
            pygame.draw.circle(surf, (0, 0, 0), (cx + 9, cy - 12), 3)

        elif item == "cherrybomb":
            pygame.draw.line(surf, (20, 100, 20), (cx - 12, cy + 2), (cx, cy - 20), 4)
            pygame.draw.line(surf, (20, 100, 20), (cx + 12, cy - 4), (cx, cy - 20), 4)
            pygame.draw.circle(surf, (210, 25, 25), (cx - 14, cy + 6), 17)
            pygame.draw.circle(surf, (235, 30, 30), (cx + 14, cy), 16)

        elif item == "potatomine":
            pygame.draw.ellipse(surf, (180, 140, 90), (cx - 16, cy + 2, 32, 22))
            pygame.draw.circle(surf, (255, 50, 50), (cx, cy - 4), 5) # Индикатор мины

        elif item == "chomper":
            pygame.draw.ellipse(surf, (130, 40, 160), (cx - 22, cy - 26, 44, 46))
            pygame.draw.polygon(surf, (255, 255, 255), [(cx - 16, cy - 4), (cx - 10, cy + 6), (cx - 4, cy - 4)])
            pygame.draw.polygon(surf, (255, 255, 255), [(cx + 4, cy - 4), (cx + 10, cy + 6), (cx + 16, cy - 4)])

        elif "zombie" in item:
            tint = (130, 165, 125)
            # Тело и костюм
            pygame.draw.rect(surf, (70, 60, 55), (cx - 12, cy - 8, 24, 34), border_radius=4)
            pygame.draw.line(surf, (40, 40, 70), (cx - 6, cy + 24), (cx - 8, cy + 42), 6)
            pygame.draw.line(surf, (40, 40, 70), (cx + 6, cy + 24), (cx + 8, cy + 42), 6)
            pygame.draw.line(surf, tint, (cx - 4, cy - 2), (cx - 24, cy - 2), 5)
            # Голова
            pygame.draw.circle(surf, tint, (cx, cy - 22), 16)
            pygame.draw.circle(surf, (255, 0, 0), (cx - 6, cy - 24), 3)
            pygame.draw.circle(surf, (255, 0, 0), (cx + 4, cy - 24), 3)

            if "cone" in item:
                pygame.draw.polygon(surf, (255, 130, 0), [(cx - 12, cy - 32), (cx + 12, cy - 32), (cx, cy - 64)])
            elif "bucket" in item:
                pygame.draw.rect(surf, (170, 170, 180), (cx - 12, cy - 48, 24, 22), border_radius=2)
            elif "gargantuar" in item:
                s_boss = pygame.Surface((CELL_W + 30, CELL_H + 40), pygame.SRCALPHA)
                bcx, bcy = s_boss.get_width() // 2, s_boss.get_height() // 2
                pygame.draw.rect(s_boss, (50, 45, 40), (bcx - 26, bcy - 10, 52, 60), border_radius=6)
                pygame.draw.circle(s_boss, tint, (bcx, bcy - 30), 24)
                pygame.draw.rect(s_boss, (90, 50, 20), (bcx - 42, bcy - 60, 16, 95), border_radius=3)
                return s_boss

        elif item == "shovel":
            surf_sh = pygame.Surface((50, 50), pygame.SRCALPHA)
            pygame.draw.line(surf_sh, (150, 90, 40), (10, 40), (35, 15), 6)
            pygame.draw.polygon(surf_sh, (180, 190, 200), [(30, 20), (45, 5), (35, -5), (20, 10)])
            return surf_sh

        elif item == "sun":
            s_sun = pygame.Surface((50, 50), pygame.SRCALPHA)
            pygame.draw.circle(s_sun, (255, 170, 0), (25, 25), 22)
            pygame.draw.circle(s_sun, (255, 230, 40), (25, 25), 18)
            return s_sun

        elif item == "pea":
            s_pea = pygame.Surface((18, 18), pygame.SRCALPHA)
            pygame.draw.circle(s_pea, (50, 205, 50), (9, 9), 8)
            pygame.draw.circle(s_pea, (255, 255, 255), (6, 6), 2)
            return s_pea

        elif item == "ice_pea":
            s_ipea = pygame.Surface((18, 18), pygame.SRCALPHA)
            pygame.draw.circle(s_ipea, (80, 210, 255), (9, 9), 8)
            pygame.draw.circle(s_ipea, (255, 255, 255), (6, 6), 2)
            return s_ipea

        elif item == "lawnmower":
            s_mow = pygame.Surface((60, 50), pygame.SRCALPHA)
            pygame.draw.rect(s_mow, (200, 30, 30), (10, 15, 40, 25), border_radius=4)
            pygame.draw.circle(s_mow, (40, 40, 40), (18, 42), 8)
            pygame.draw.circle(s_mow, (40, 40, 40), (42, 42), 8)
            pygame.draw.line(s_mow, (180, 180, 180), (14, 15), (4, -2), 4)
            return s_mow

        return surf

vault = AssetVault()

# --- Сущности Игры ---
class Sun:
    def __init__(self, x, y, target_y=None):
        self.x, self.y = x, y
        self.target_y = target_y if target_y else y
        self.radius = 22
        self.alive = True
        self.timer = 0

    def update(self):
        if self.y < self.target_y:
            self.y += 2.5
        self.timer += 1
        if self.timer > 650:
            self.alive = False

    def draw(self, surf):
        img = vault.get("sun")
        surf.blit(img, (self.x - img.get_width() // 2, self.y - img.get_height() // 2))

class Projectile:
    def __init__(self, x, y, row, is_ice=False):
        self.x, self.y = x, y
        self.row = row
        self.is_ice = is_ice
        self.speed = 7.5
        self.damage = 22
        self.alive = True

    def update(self):
        self.x += self.speed
        if self.x > WIDTH:
            self.alive = False

    def draw(self, surf):
        img = vault.get("ice_pea" if self.is_ice else "pea")
        surf.blit(img, (self.x - 9, self.y - 9))

class Lawnmower:
    def __init__(self, row):
        self.row = row
        self.x = GRID_X - 65
        self.y = GRID_Y + row * CELL_H + 20
        self.active = False
        self.used = False

    def update(self, zombies):
        if self.active:
            self.x += 12
            for z in zombies:
                if z.row == self.row and abs(z.x - self.x) < 45:
                    z.take_damage(9999)
            if self.x > WIDTH + 60:
                self.used = True
        else:
            for z in zombies:
                if z.row == self.row and z.x <= self.x + 35:
                    self.active = True
                    break

    def draw(self, surf):
        if not self.used:
            surf.blit(vault.get("lawnmower"), (self.x, self.y))

class Plant:
    def __init__(self, col, row, p_type):
        self.col, self.row = col, row
        self.type = p_type
        self.hp = PLANT_SPECS[p_type]["hp"]
        self.x = GRID_X + col * CELL_W
        self.y = GRID_Y + row * CELL_H
        self.action_timer = 0
        self.alive = True
        self.armed = False if p_type == "potatomine" else True
        self.chewing_timer = 0

    def update(self, game):
        self.action_timer += 1

        if self.type == "sunflower" and self.action_timer >= 450:
            game.suns.append(Sun(self.x + CELL_W // 2, self.y + 20))
            self.action_timer = 0

        elif self.type in ["peashooter", "snowpea", "repeater"]:
            has_zombie = any(z.row == self.row and z.x > self.x for z in game.zombies)
            if has_zombie:
                if self.type == "repeater" and (self.action_timer == 90 or self.action_timer == 105):
                    game.projectiles.append(Projectile(self.x + CELL_W - 10, self.y + 35, self.row, False))
                    if self.action_timer == 105:
                        self.action_timer = 0
                elif self.type in ["peashooter", "snowpea"] and self.action_timer >= 90:
                    is_ice = (self.type == "snowpea")
                    game.projectiles.append(Projectile(self.x + CELL_W - 10, self.y + 35, self.row, is_ice))
                    self.action_timer = 0

        elif self.type == "cherrybomb" and self.action_timer >= 100:
            self.alive = False
            game.trigger_explosion(self.x + CELL_W // 2, self.y + CELL_H // 2, self.col, self.row)

        elif self.type == "potatomine":
            if not self.armed and self.action_timer >= 300: # 5 сек взвод
                self.armed = True
            if self.armed:
                for z in game.zombies:
                    if z.row == self.row and abs((self.x + CELL_W // 2) - z.x) < 30:
                        z.take_damage(1800)
                        self.alive = False
                        break

        elif self.type == "chomper":
            if self.chewing_timer > 0:
                self.chewing_timer -= 1
            else:
                for z in game.zombies:
                    if z.row == self.row and 0 < (z.x - self.x) < CELL_W * 1.4:
                        if z.type != "gargantuar":
                            z.take_damage(9999)
                            self.chewing_timer = 600 # 10 сек пережевывания
                            break

    def draw(self, surf):
        img = vault.get(self.type)
        if img:
            surf.blit(img, (self.x, self.y))

class Zombie:
    def __init__(self, row, z_type="regular"):
        self.row = row
        self.type = z_type
        self.x = WIDTH + random.randint(20, 70)
        self.y = GRID_Y + row * CELL_H + 8
        self.alive = True
        self.frozen_timer = 0

        if z_type == "regular":
            self.hp, self.speed, self.dps = 150, 0.35, 0.6
        elif z_type == "conehead":
            self.hp, self.speed, self.dps = 320, 0.35, 0.6
        elif z_type == "buckethead":
            self.hp, self.speed, self.dps = 650, 0.35, 0.6
        elif z_type == "gargantuar":
            self.hp, self.speed, self.dps = 1800, 0.22, 999
            self.y -= 30

    def take_damage(self, dmg, freeze=False):
        self.hp -= dmg
        if freeze:
            self.frozen_timer = 180
        if self.hp <= 0:
            self.alive = False

    def update(self, plants):
        if self.frozen_timer > 0:
            self.frozen_timer -= 1
            spd = self.speed * 0.5
        else:
            spd = self.speed

        eating = False
        for p in plants:
            if p.row == self.row and abs(self.x - (p.x + 30)) < 25:
                eating = True
                p.hp -= self.dps
                break

        if not eating:
            self.x -= spd

    def draw(self, surf):
        name = f"zombie_{self.type.replace('head', '')}"
        img = vault.get(name if self.type != "regular" else "zombie_regular")
        if img:
            if self.frozen_timer > 0:
                tinted = img.copy()
                tinted.fill((60, 120, 255, 120), special_flags=pygame.BLEND_RGBA_ADD)
                surf.blit(tinted, (self.x - 30, self.y))
            else:
                surf.blit(img, (self.x - 30, self.y))

# --- Главный Контроллер Игры ---
class GameManager:
    def __init__(self):
        self.state = "MENU" # MENU, PLAY, ALMANAC, PAUSE, GAMEOVER, WIN
        self.sun = 150
        self.selected_tool = None # тип семени или 'shovel'
        self.deck = ["peashooter", "sunflower", "wallnut", "cherrybomb", "snowpea", "repeater", "potatomine", "chomper"]
        self.cooldowns = {k: 0 for k in self.deck}
        
        self.grid = [[None for _ in range(GRID_COLS)] for _ in range(GRID_ROWS)]
        self.plants = []
        self.zombies = []
        self.projectiles = []
        self.suns = []
        self.mowers = []
        
        self.wave_idx = 0
        self.wave_timer = 0
        self.sky_sun_timer = 0
        self.level_waves = [
            ["regular", "regular"],
            ["regular", "conehead"],
            ["conehead", "buckethead", "regular"],
            ["buckethead", "conehead", "buckethead"],
            ["gargantuar", "buckethead", "conehead", "regular"]
        ]

    def reset_battle(self):
        self.sun = 200
        self.selected_tool = None
        self.grid = [[None for _ in range(GRID_COLS)] for _ in range(GRID_ROWS)]
        self.plants.clear()
        self.zombies.clear()
        self.projectiles.clear()
        self.suns.clear()
        self.mowers = [Lawnmower(r) for r in range(GRID_ROWS)]
        self.cooldowns = {k: 0 for k in self.deck}
        self.wave_idx = 0
        self.wave_timer = 300
        self.state = "PLAY"

    def trigger_explosion(self, cx, cy, col, row):
        for r in range(max(0, row - 1), min(GRID_ROWS, row + 2)):
            for z in self.zombies:
                if z.row == r and abs(z.x - cx) < CELL_W * 1.5:
                    z.take_damage(1800)

    def handle_click(self, pos):
        if self.state == "MENU":
            if 460 <= pos[0] <= 690 and 280 <= pos[1] <= 340:
                self.reset_battle()
            elif 460 <= pos[0] <= 690 and 360 <= pos[1] <= 420:
                self.state = "ALMANAC"
            elif 460 <= pos[0] <= 690 and 440 <= pos[1] <= 500:
                pygame.quit(); sys.exit()
            return

        if self.state == "ALMANAC":
            if 500 <= pos[0] <= 650 and 590 <= pos[1] <= 640:
                self.state = "MENU"
            return

        if self.state == "PLAY":
            # Сбор солнца
            for s in self.suns:
                if math.hypot(pos[0] - s.x, pos[1] - s.y) <= s.radius + 12:
                    self.sun += 25
                    s.alive = False
                    return

            # Выбор Лопаты (координаты: x=750, y=15, w=60, h=65)
            shovel_r = pygame.Rect(750, 15, 60, 65)
            if shovel_r.collidepoint(pos):
                self.selected_tool = "shovel" if self.selected_tool != "shovel" else None
                return

            # Выбор карты семян
            for idx, p_type in enumerate(self.deck):
                r = pygame.Rect(110 + idx * 76, 12, 70, 92)
                if r.collidepoint(pos):
                    cost = PLANT_SPECS[p_type]["cost"]
                    if self.sun >= cost and self.cooldowns[p_type] == 0:
                        self.selected_tool = p_type
                    return

            # Клик по сетке газона
            if GRID_X <= pos[0] < GRID_X + GRID_COLS * CELL_W and GRID_Y <= pos[1] < GRID_Y + GRID_ROWS * CELL_H:
                col = (pos[0] - GRID_X) // CELL_W
                row = (pos[1] - GRID_Y) // CELL_H

                # Использование Лопаты
                if self.selected_tool == "shovel":
                    if self.grid[row][col] is not None:
                        plant_to_del = self.grid[row][col]
                        self.plants.remove(plant_to_del)
                        self.grid[row][col] = None
                        self.selected_tool = None
                    return

                # Посадка Растения
                if self.selected_tool and self.selected_tool != "shovel":
                    if self.grid[row][col] is None:
                        cost = PLANT_SPECS[self.selected_tool]["cost"]
                        plant = Plant(col, row, self.selected_tool)
                        self.plants.append(plant)
                        self.grid[row][col] = plant
                        self.sun -= cost
                        self.cooldowns[self.selected_tool] = PLANT_SPECS[self.selected_tool]["cd"]
                        self.selected_tool = None

    def update(self):
        if self.state != "PLAY":
            return

        for k in self.cooldowns:
            if self.cooldowns[k] > 0:
                self.cooldowns[k] -= 1

        self.sky_sun_timer += 1
        if self.sky_sun_timer >= 320:
            tx = random.randint(GRID_X, GRID_X + GRID_COLS * CELL_W - 40)
            ty = random.randint(GRID_Y + 50, HEIGHT - 80)
            self.suns.append(Sun(tx, 0, ty))
            self.sky_sun_timer = 0

        self.wave_timer += 1
        if self.wave_timer >= 520:
            if self.wave_idx < len(self.level_waves):
                wave = self.level_waves[self.wave_idx]
                for z_type in wave:
                    self.zombies.append(Zombie(random.randint(0, GRID_ROWS - 1), z_type))
                self.wave_idx += 1
                self.wave_timer = 0
            elif len(self.zombies) == 0:
                self.state = "WIN"

        for p in self.plants: p.update(self)
        for pr in self.projectiles: pr.update()
        for z in self.zombies: z.update(self.plants)
        for s in self.suns: s.update()
        for m in self.mowers: m.update(self.zombies)

        # Коллизии Снаряд <-> Зомби
        for pr in self.projectiles:
            for z in self.zombies:
                if pr.row == z.row and abs(pr.x - z.x) < 25:
                    z.take_damage(pr.damage, freeze=pr.is_ice)
                    pr.alive = False
                    break

        for z in self.zombies:
            if z.x < GRID_X - 60:
                self.state = "GAMEOVER"

        # Очистка мертвых
        for p in self.plants:
            if p.hp <= 0 or not p.alive:
                self.grid[p.row][p.col] = None
        self.plants = [p for p in self.plants if p.hp > 0 and p.alive]
        self.projectiles = [pr for pr in self.projectiles if pr.alive]
        self.zombies = [z for z in self.zombies if z.alive]
        self.suns = [s for s in self.suns if s.alive]

    def draw(self, surf):
        if self.state == "MENU":
            surf.fill((40, 75, 30))
            t = f_big.render("PLANTS VS. ZOMBIES", True, (240, 210, 40))
            surf.blit(t, (WIDTH // 2 - t.get_width() // 2, 120))

            # Кнопки
            for idx, text in enumerate(["START ADVENTURE", "ALMANAC", "EXIT"]):
                btn_r = pygame.Rect(460, 280 + idx * 80, 230, 58)
                pygame.draw.rect(surf, (140, 80, 40), btn_r, border_radius=10)
                pygame.draw.rect(surf, (60, 30, 10), btn_r, 3, border_radius=10)
                lbl = f_small.render(text, True, (255, 255, 255))
                surf.blit(lbl, (btn_r.centerx - lbl.get_width() // 2, btn_r.centery - lbl.get_height() // 2))
            return

        if self.state == "ALMANAC":
            surf.fill((35, 45, 50))
            lbl = f_big.render("SUBURBAN ALMANAC", True, (240, 210, 40))
            surf.blit(lbl, (WIDTH // 2 - lbl.get_width() // 2, 30))
            
            for idx, (p_type, spec) in enumerate(PLANT_SPECS.items()):
                x = 100 + (idx % 4) * 240
                y = 120 + (idx // 4) * 220
                pygame.draw.rect(surf, (60, 75, 80), (x, y, 220, 190), border_radius=8)
                img = vault.get(p_type)
                if img: surf.blit(img, (x + 15, y + 15))
                n = f_small.render(spec["name"], True, (255, 255, 255))
                c = f_micro.render(f"Cost: {spec['cost']} | HP: {spec['hp']}", True, (255, 220, 80))
                surf.blit(n, (x + 10, y + 125))
                surf.blit(c, (x + 10, y + 155))

            b_back = pygame.Rect(500, 590, 150, 45)
            pygame.draw.rect(surf, (140, 60, 40), b_back, border_radius=6)
            txt = f_small.render("BACK", True, (255, 255, 255))
            surf.blit(txt, (b_back.centerx - txt.get_width() // 2, b_back.centery - txt.get_height() // 2))
            return

        # Игровой газон
        surf.fill((65, 115, 45))
        for r in range(GRID_ROWS):
            for c in range(GRID_COLS):
                col = (110, 180, 35) if (r + c) % 2 == 0 else (95, 165, 30)
                pygame.draw.rect(surf, col, (GRID_X + c * CELL_W, GRID_Y + r * CELL_H, CELL_W, CELL_H))
                pygame.draw.rect(surf, (65, 125, 20), (GRID_X + c * CELL_W, GRID_Y + r * CELL_H, CELL_W, CELL_H), 1)

        for m in self.mowers: m.draw(surf)
        for p in self.plants: p.draw(surf)
        for z in self.zombies: z.draw(surf)
        for pr in self.projectiles: pr.draw(surf)
        for s in self.suns: s.draw(surf)

        # Верхняя панель инвентаря
        pygame.draw.rect(surf, (100, 65, 35), (0, 0, WIDTH, 115))
        
        # Счетчик солнца
        pygame.draw.rect(surf, (220, 200, 150), (15, 12, 80, 90), border_radius=6)
        surf.blit(vault.get("sun"), (30, 18))
        s_lbl = f_med.render(str(self.sun), True, (50, 30, 10))
        surf.blit(s_lbl, (55 - s_lbl.get_width() // 2, 70))

        # Карточки семян
        for idx, p_type in enumerate(self.deck):
            x = 110 + idx * 76
            card_r = pygame.Rect(x, 12, 70, 92)
            cost = PLANT_SPECS[p_type]["cost"]
            cd = self.cooldowns[p_type]
            can_afford = self.sun >= cost and cd == 0
            
            col = (230, 220, 180) if can_afford else (130, 120, 110)
            if self.selected_tool == p_type: col = (255, 255, 120)
            pygame.draw.rect(surf, col, card_r, border_radius=6)
            pygame.draw.rect(surf, (40, 25, 10), card_r, 2, border_radius=6)

            t_name = f_micro.render(PLANT_SPECS[p_type]["name"][:7], True, (0, 0, 0))
            t_cost = f_small.render(str(cost), True, (160, 20, 20))
            surf.blit(t_name, (x + 35 - t_name.get_width() // 2, 18))
            surf.blit(t_cost, (x + 35 - t_cost.get_width() // 2, 76))

            if cd > 0:
                h = int(92 * (cd / PLANT_SPECS[p_type]["cd"]))
                cd_surf = pygame.Surface((70, h), pygame.SRCALPHA)
                cd_surf.fill((0, 0, 0, 160))
                surf.blit(cd_surf, (x, 12 + (92 - h)))

        # Лопата
        sh_r = pygame.Rect(750, 15, 60, 65)
        sh_col = (255, 255, 120) if self.selected_tool == "shovel" else (140, 90, 50)
        pygame.draw.rect(surf, sh_col, sh_r, border_radius=6)
        pygame.draw.rect(surf, (40, 20, 10), sh_r, 2, border_radius=6)
        surf.blit(vault.get("shovel"), (755, 22))

        # Индикатор волн
        pygame.draw.rect(surf, (30, 30, 30), (WIDTH - 220, 55, 190, 20), border_radius=5)
        prog = min(1.0, self.wave_idx / len(self.level_waves))
        pygame.draw.rect(surf, (40, 220, 40), (WIDTH - 220, 55, int(190 * prog), 20), border_radius=5)
        w_lbl = f_micro.render("WAVE PROGRESS", True, (255, 255, 255))
        surf.blit(w_lbl, (WIDTH - 125 - w_lbl.get_width() // 2, 58))

        # Модальные окна
        if self.state == "PAUSE":
            s = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
            s.fill((0, 0, 0, 160))
            surf.blit(s, (0, 0))
            txt = f_big.render("GAME PAUSED", True, (255, 255, 255))
            surf.blit(txt, (WIDTH // 2 - txt.get_width() // 2, HEIGHT // 2 - 40))

        elif self.state == "GAMEOVER":
            s = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
            s.fill((100, 0, 0, 200))
            surf.blit(s, (0, 0))
            txt = f_big.render("THE ZOMBIES ATE YOUR BRAINS!", True, (255, 80, 80))
            surf.blit(txt, (WIDTH // 2 - txt.get_width() // 2, HEIGHT // 2 - 30))

        elif self.state == "WIN":
            s = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
            s.fill((20, 90, 20, 200))
            surf.blit(s, (0, 0))
            txt = f_big.render("VICTORY! LAWN DEFENDED!", True, (240, 220, 50))
            surf.blit(txt, (WIDTH // 2 - txt.get_width() // 2, HEIGHT // 2 - 30))

# --- Запуск ---
def main():
    gm = GameManager()
    running = True

    while running:
        clock.tick(FPS)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key in [pygame.K_p, pygame.K_ESCAPE]:
                    if gm.state == "PLAY": gm.state = "PAUSE"
                    elif gm.state == "PAUSE": gm.state = "PLAY"
                elif event.key == pygame.K_r and gm.state in ["GAMEOVER", "WIN"]:
                    gm.reset_battle()
            elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                gm.handle_click(pygame.mouse.get_pos())

        gm.update()
        gm.draw(screen)
        pygame.display.flip()

    pygame.quit()
    sys.exit()

if __name__ == "__main__":
    main()