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


import pygame
import pygame.gfxdraw
import random
import math
import sys

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

WIDTH, HEIGHT = 1180, 720
GRID_COLS, GRID_ROWS = 9, 5
CELL_W, CELL_H = 88, 96
GRID_X, GRID_Y = 270, 150
FPS = 60

screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Plants vs. Zombies 2: It's About Time - Python Edition")
clock = pygame.time.Clock()

f_tiny = pygame.font.SysFont("Verdana", 10, bold=True)
f_small = pygame.font.SysFont("Verdana", 13, bold=True)
f_med = pygame.font.SysFont("Verdana", 18, bold=True)
f_big = pygame.font.SysFont("Verdana", 32, bold=True)

# --- БАЗА ДАННЫХ РАСТЕНИЙ PVZ 2 ---
PLANTS_DB = {
    "peashooter":  {"cost": 100, "hp": 200, "cd": 100, "name": "Peashooter", "desc": "Базовый стрелок. Ульта: превращается в пулемет."},
    "sunflower":   {"cost": 50,  "hp": 150, "cd": 100, "name": "Sunflower",  "desc": "Дает солнце. Ульта: мгновенно взрывается фонтаном солнца."},
    "wallnut":     {"cost": 50,  "hp": 800, "cd": 350, "name": "Wall-nut",   "desc": "Задерживает толпу. Ульта: надевает железную броню."},
    "bloomerang":  {"cost": 175, "hp": 200, "cd": 120, "name": "Bloomerang", "desc": "Египет. Бумеранг летит вперед и назад, поражая до 3 врагов."},
    "bonkchoy":    {"cost": 150, "hp": 300, "cd": 120, "name": "Bonk Choy",  "desc": "Боксер ближнего боя. Быстро бьет зомби спереди и сзади."},
    "laserbean":   {"cost": 200, "hp": 200, "cd": 140, "name": "Laser Bean", "desc": "Будущее. Пронзает лучом всех зомби на полосе."},
    "cabbagepult": {"cost": 100, "hp": 200, "cd": 100, "name": "Cabbage-pult","desc": "Катапульта, забрасывает капусту прямо через преграды."}
}

# --- БАЗА ДАННЫХ ЗОМБИ PVZ 2 ---
ZOMBIES_DB = {
    "mummy":      {"name": "Mummy Zombie", "hp": 150,  "spd": 0.35, "desc": "Обычный оживший мертвец из гробниц Древнего Египта."},
    "ra":         {"name": "Ra Zombie",    "hp": 220,  "spd": 0.30, "desc": "Посохом солнца притягивает и похищает ваше солнце!"},
    "pharaoh":    {"name": "Pharaoh",      "hp": 900,  "spd": 0.20, "desc": "Защищен прочным саркофагом. Разбив саркофаг, бежит быстро!"},
    "pirate":     {"name": "Pirate Zombie","hp": 160,  "spd": 0.36, "desc": "Гроза Семи Морей, наступает решительным шагом."},
    "cowboy":     {"name": "Cowboy Zombie","hp": 180,  "spd": 0.34, "desc": "Лихой зомби с Дикого Запада в шляпе."},
    "gargantuar": {"name": "Gargantuar",   "hp": 2200, "spd": 0.20, "desc": "Гигант Древнего Египта! Несет саркофаг и крушит растения."}
}

# --- МИРЫ И УРОВНИ ---
WORLDS = [
    {
        "id": "egypt", "name": "Древний Египет", "color": (194, 158, 92), "alt_color": (180, 145, 80),
        "waves": [
            ["mummy"], 
            ["mummy", "ra"], 
            ["mummy", "ra", "pharaoh"], 
            ["mummy", "pharaoh", "gargantuar"]
        ]
    },
    {
        "id": "pirate", "name": "Пиратские Моря", "color": (95, 135, 145), "alt_color": (85, 120, 130),
        "waves": [
            ["pirate"], 
            ["pirate", "pirate"], 
            ["pirate", "pirate", "pharaoh"], 
            ["pirate", "gargantuar"]
        ]
    },
    {
        "id": "west", "name": "Дикий Запад", "color": (165, 115, 75), "alt_color": (150, 105, 65),
        "waves": [
            ["cowboy"], 
            ["cowboy", "cowboy"], 
            ["cowboy", "cowboy", "pharaoh"], 
            ["cowboy", "cowboy", "gargantuar"]
        ]
    }
]

def draw_circle_aa(surf, x, y, r, color):
    pygame.gfxdraw.aacircle(surf, int(x), int(y), int(r), color)
    pygame.gfxdraw.filled_circle(surf, int(x), int(y), int(r), color)

# --- ПРОЦЕДУРНЫЙ РЕНДЕР МОДЕЛЕЙ И ИКОНОК ---
def draw_plant_art(surf, p_type, x, y, size=60, phase=0.0, has_armor=False):
    s = pygame.Surface((size, size), pygame.SRCALPHA)
    cx, cy = size // 2, size // 2
    bob = math.sin(phase) * (2 if size > 40 else 0)

    if p_type == "peashooter":
        pygame.draw.ellipse(s, (40, 130, 30), (cx - 14, cy + 12, 28, 10))
        pygame.draw.line(s, (45, 160, 45), (cx, cy + 15), (cx - 4, cy), 4)
        draw_circle_aa(s, cx - 2, cy - 6 + bob, 14, (60, 190, 50))
        pygame.draw.rect(s, (60, 190, 50), (cx + 6, cy - 12 + bob, 12, 10), border_radius=3)
        draw_circle_aa(s, cx + 18, cy - 7 + bob, 5, (20, 80, 20))
        draw_circle_aa(s, cx + 2, cy - 10 + bob, 3, (255, 255, 255))
        draw_circle_aa(s, cx + 3, cy - 10 + bob, 1, (0, 0, 0))

    elif p_type == "sunflower":
        for i in range(8):
            ang = phase * 0.5 + i * (math.pi / 4)
            px = cx + math.cos(ang) * 14
            py = cy - 4 + math.sin(ang) * 14 + bob
            draw_circle_aa(s, px, py, 6, (255, 200, 0))
        draw_circle_aa(s, cx, cy - 4 + bob, 12, (150, 80, 30))
        draw_circle_aa(s, cx - 4, cy - 7 + bob, 2, (0, 0, 0))
        draw_circle_aa(s, cx + 4, cy - 7 + bob, 2, (0, 0, 0))

    elif p_type == "wallnut":
        pygame.draw.ellipse(s, (160, 95, 45), (cx - 14, cy - 18 + bob, 28, 36))
        draw_circle_aa(s, cx - 5, cy - 9 + bob, 4, (255, 255, 255))
        draw_circle_aa(s, cx + 5, cy - 9 + bob, 4, (255, 255, 255))
        draw_circle_aa(s, cx - 4, cy - 9 + bob, 1, (0, 0, 0))
        draw_circle_aa(s, cx + 6, cy - 9 + bob, 1, (0, 0, 0))
        if has_armor:
            pygame.draw.rect(s, (180, 190, 200), (cx - 15, cy - 20 + bob, 30, 16), border_radius=4)
            pygame.draw.line(s, (255, 255, 255), (cx - 12, cy - 14 + bob), (cx + 12, cy - 14 + bob), 2)

    elif p_type == "bloomerang":
        draw_circle_aa(s, cx, cy - 4 + bob, 14, (220, 190, 70))
        pygame.draw.polygon(s, (200, 80, 40), [(cx, cy - 18 + bob), (cx - 8, cy - 4 + bob), (cx + 8, cy - 4 + bob)])
        pygame.draw.polygon(s, (240, 140, 40), [(cx - 16, cy + 4 + bob), (cx - 6, cy - 2 + bob), (cx - 4, cy + 10 + bob)])
        pygame.draw.polygon(s, (240, 140, 40), [(cx + 16, cy + 4 + bob), (cx + 6, cy - 2 + bob), (cx + 4, cy + 10 + bob)])

    elif p_type == "bonkchoy":
        draw_circle_aa(s, cx, cy + bob, 15, (100, 210, 50))
        # Боксерские перчатки-кулаки
        draw_circle_aa(s, cx - 14, cy - 8 + bob, 8, (60, 160, 40))
        draw_circle_aa(s, cx + 14, cy - 8 + bob, 8, (60, 160, 40))
        # Хмурые брови и глаза
        draw_circle_aa(s, cx - 4, cy - 2 + bob, 3, (0, 0, 0))
        draw_circle_aa(s, cx + 4, cy - 2 + bob, 3, (0, 0, 0))

    elif p_type == "laserbean":
        pygame.draw.ellipse(s, (50, 180, 160), (cx - 10, cy - 18 + bob, 20, 36))
        # Большой сияющий монокль/глаз
        draw_circle_aa(s, cx + 4, cy - 6 + bob, 8, (80, 240, 255))
        draw_circle_aa(s, cx + 4, cy - 6 + bob, 4, (255, 255, 255))

    elif p_type == "cabbagepult":
        pygame.draw.rect(s, (100, 70, 40), (cx - 12, cy + 8, 24, 12), border_radius=3)
        pygame.draw.line(s, (140, 90, 40), (cx - 6, cy + 8), (cx + 10, cy - 12 + bob), 4)
        draw_circle_aa(s, cx + 12, cy - 14 + bob, 8, (120, 210, 60))

    surf.blit(s, (x, y))

def draw_zombie_art(surf, z_type, x, y, size=70, phase=0.0):
    s = pygame.Surface((size, size + 20), pygame.SRCALPHA)
    cx, cy = size // 2, (size + 20) // 2
    tint = (135, 165, 130)
    bob = math.sin(phase) * 3

    if z_type == "gargantuar":
        pygame.draw.rect(s, (60, 50, 40), (cx - 18, cy - 12, 36, 44), border_radius=5)
        draw_circle_aa(s, cx, cy - 24 + bob, 18, tint)
        draw_circle_aa(s, cx - 5, cy - 26 + bob, 3, (255, 0, 0))
        # Саркофаг вместо дубины
        pygame.draw.rect(s, (210, 180, 70), (cx - 28, cy - 35, 12, 65), border_radius=3)
        pygame.draw.rect(s, (0, 0, 0), (cx - 28, cy - 35, 12, 65), 1, border_radius=3)
    else:
        # Ноги и походка
        pygame.draw.line(s, (50, 50, 70), (cx - 5, cy + 12), (cx - 7, cy + 28), 5)
        pygame.draw.line(s, (50, 50, 70), (cx + 5, cy + 12), (cx + 7, cy + 28), 5)
        
        # Торс
        body_col = (180, 160, 130) if z_type in ["mummy", "ra", "pharaoh"] else (70, 65, 60)
        pygame.draw.rect(s, body_col, (cx - 10, cy - 8, 20, 22), border_radius=3)
        pygame.draw.line(s, tint, (cx - 4, cy - 2), (cx - 18, cy - 2 + bob), 4)

        # Голова
        draw_circle_aa(s, cx, cy - 18 + bob, 12, tint)
        draw_circle_aa(s, cx - 4, cy - 20 + bob, 2, (255, 0, 0))
        draw_circle_aa(s, cx + 4, cy - 20 + bob, 2, (255, 0, 0))

        if z_type == "ra":
            # Золотой головной убор и посох Солнца
            pygame.draw.polygon(s, (240, 200, 20), [(cx - 8, cy - 26 + bob), (cx + 8, cy - 26 + bob), (cx, cy - 38 + bob)])
            pygame.draw.line(s, (200, 170, 0), (cx - 16, cy + 20), (cx - 16, cy - 20 + bob), 3)
            draw_circle_aa(s, cx - 16, cy - 22 + bob, 6, (255, 140, 0))
        elif z_type == "pharaoh":
            # Защитный саркофаг
            pygame.draw.rect(s, (220, 185, 50), (cx - 14, cy - 32 + bob, 28, 48), border_radius=4)
            pygame.draw.line(s, (0, 100, 200), (cx - 10, cy - 20 + bob), (cx + 10, cy - 20 + bob), 3)
        elif z_type == "cowboy":
            # Ковбойская шляпа
            pygame.draw.ellipse(s, (120, 70, 30), (cx - 18, cy - 28 + bob, 36, 10))
            pygame.draw.rect(s, (120, 70, 30), (cx - 8, cy - 36 + bob, 16, 12), border_radius=2)
        elif z_type == "pirate":
            # Пиратская бандана и повязка
            pygame.draw.circle(s, (180, 20, 20), (cx, cy - 22 + bob), 12, draw_top_left=True, draw_top_right=True)
            draw_circle_aa(s, cx - 4, cy - 20 + bob, 3, (0, 0, 0))

    surf.blit(s, (x, y))

# --- ОБЪЕКТЫ МИРА И ЭФФЕКТЫ ---
class PlantFoodOrb:
    def __init__(self, x, y):
        self.x, self.y = x, y
        self.alive = True
        self.timer = 0

    def update(self):
        self.timer += 1
        if self.timer > 600:
            self.alive = False

    def draw(self, surf):
        pulse = math.sin(self.timer * 0.1) * 3
        draw_circle_aa(surf, self.x, self.y, 14 + pulse, (50, 255, 100))
        draw_circle_aa(surf, self.x, self.y, 9, (255, 255, 255))
        # Листик в центре
        pygame.draw.ellipse(surf, (30, 180, 50), (self.x - 4, self.y - 7, 8, 14))

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 = 20
        self.alive = True
        self.stolen_by = None
        self.timer = 0

    def update(self):
        if self.stolen_by:
            # Зомби Ра притягивает солнце к себе
            self.x += (self.stolen_by.x - self.x) * 0.05
            self.y += (self.stolen_by.y - self.y) * 0.05
            if math.hypot(self.x - self.stolen_by.x, self.y - self.stolen_by.y) < 20:
                self.alive = False
            return

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

    def draw(self, surf):
        draw_circle_aa(surf, self.x, self.y, self.radius + 3, (255, 170, 0))
        draw_circle_aa(surf, self.x, self.y, self.radius, (255, 235, 40))

class Projectile:
    def __init__(self, x, y, row, p_type="pea", vx=7.0):
        self.x, self.y = x, y
        self.row = row
        self.type = p_type
        self.vx = vx
        self.damage = 25
        self.alive = True
        self.hits_left = 3 if p_type == "boomerang" else 1
        self.returning = False
        self.start_x = x

    def update(self):
        self.x += self.vx
        if self.type == "boomerang":
            if not self.returning and self.x > self.start_x + CELL_W * 5:
                self.returning = True
                self.vx = -self.vx
            elif self.returning and self.x < self.start_x:
                self.alive = False
        else:
            if self.x > WIDTH:
                self.alive = False

    def draw(self, surf):
        if self.type == "boomerang":
            pygame.draw.arc(surf, (240, 160, 40), (self.x - 8, self.y - 8, 16, 16), 0, math.pi * 1.5, 3)
        elif self.type == "cabbage":
            draw_circle_aa(surf, self.x, self.y, 8, (120, 220, 50))
        else:
            draw_circle_aa(surf, self.x, self.y, 7, (60, 210, 50))

class PlantEntity:
    def __init__(self, col, row, p_type):
        self.col, self.row = col, row
        self.type = p_type
        self.hp = PLANTS_DB[p_type]["hp"]
        self.x = GRID_X + col * CELL_W
        self.y = GRID_Y + row * CELL_H
        self.timer = 0
        self.phase = random.uniform(0, 6.0)
        self.alive = True
        self.has_armor = False
        self.plant_food_active = 0

    def apply_plant_food(self, game):
        self.plant_food_active = 180
        if self.type == "sunflower":
            for _ in range(5):
                game.suns.append(Sun(self.x + random.randint(10, 50), self.y + random.randint(10, 50)))
            self.plant_food_active = 0
        elif self.type == "wallnut":
            self.has_armor = True
            self.hp = 2500
            self.plant_food_active = 0

    def update(self, game):
        self.timer += 1
        self.phase += 0.06

        # Логика ульты (Plant Food)
        if self.plant_food_active > 0:
            self.plant_food_active -= 1
            if self.type == "peashooter":
                if self.timer % 4 == 0:
                    game.projectiles.append(Projectile(self.x + CELL_W - 10, self.y + 35, self.row, "pea", vx=12.0))
            elif self.type == "bonkchoy":
                for z in game.zombies:
                    if z.row == self.row and abs(z.x - self.x) < CELL_W * 2:
                        z.take_damage(8.0)
            elif self.type == "laserbean":
                # Лазер уничтожает полосу
                pygame.draw.line(screen, (100, 255, 255), (self.x, self.y + 40), (WIDTH, self.y + 40), 18)
                for z in game.zombies:
                    if z.row == self.row and z.x > self.x:
                        z.take_damage(6.0)
            return

        # Стандартные атаки
        if self.type == "sunflower" and self.timer >= 420:
            game.suns.append(Sun(self.x + CELL_W // 2, self.y + 20))
            self.timer = 0

        elif self.type == "peashooter":
            if any(z.row == self.row and z.x > self.x for z in game.zombies) and self.timer >= 90:
                game.projectiles.append(Projectile(self.x + CELL_W - 10, self.y + 35, self.row, "pea"))
                self.timer = 0

        elif self.type == "bloomerang":
            if any(z.row == self.row and z.x > self.x for z in game.zombies) and self.timer >= 120:
                game.projectiles.append(Projectile(self.x + CELL_W - 10, self.y + 35, self.row, "boomerang"))
                self.timer = 0

        elif self.type == "cabbagepult":
            if any(z.row == self.row and z.x > self.x for z in game.zombies) and self.timer >= 110:
                game.projectiles.append(Projectile(self.x + 30, self.y + 20, self.row, "cabbage", vx=6.0))
                self.timer = 0

        elif self.type == "bonkchoy":
            for z in game.zombies:
                if z.row == self.row and abs((self.x + 35) - z.x) < 55:
                    if self.timer % 15 == 0:
                        z.take_damage(18)
                    break

        elif self.type == "laserbean":
            if any(z.row == self.row and z.x > self.x for z in game.zombies) and self.timer >= 130:
                for z in game.zombies:
                    if z.row == self.row and z.x > self.x:
                        z.take_damage(65)
                # Рисуем лазерную вспышку
                pygame.draw.line(screen, (80, 240, 255), (self.x + 40, self.y + 40), (WIDTH, self.y + 40), 6)
                self.timer = 0

    def draw(self, surf):
        draw_plant_art(surf, self.type, self.x + (CELL_W - 60) // 2, self.y + (CELL_H - 60) // 2, 60, self.phase, self.has_armor)
        if self.plant_food_active > 0:
            draw_circle_aa(surf, self.x + CELL_W // 2, self.y + CELL_H // 2, 35, (100, 255, 150))

class ZombieEntity:
    def __init__(self, row, z_type="mummy"):
        self.row = row
        self.type = z_type
        self.x = WIDTH + random.randint(20, 70)
        self.y = GRID_Y + row * CELL_H + 12
        self.hp = ZOMBIES_DB[z_type]["hp"]
        self.speed = ZOMBIES_DB[z_type]["spd"]
        self.alive = True
        self.phase = random.uniform(0, 5.0)
        self.carries_food = (random.random() < 0.25)

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

    def update(self, game):
        self.phase += 0.06

        # Механика Ра: крадет солнце лучом
        if self.type == "ra":
            for s in game.suns:
                if s.stolen_by is None and math.hypot(self.x - s.x, self.y - s.y) < 220:
                    s.stolen_by = self

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

        if not eating:
            self.x -= self.speed

    def draw(self, surf):
        if self.carries_food:
            draw_circle_aa(surf, self.x, self.y + 10, 24, (50, 255, 120))
        draw_zombie_art(surf, self.type, self.x - 30, self.y - 10, 60, self.phase)

# --- ИГРОВОЙ ДВИЖОК PVZ 2 ---
class PVZ2Game:
    def __init__(self):
        self.state = "MENU" # MENU, WORLD_SELECT, ALMANAC, PLAY, GAMEOVER, WIN
        self.coins = 500
        self.plant_food = 3
        self.deck = list(PLANTS_DB.keys())
        self.cooldowns = {k: 0 for k in PLANTS_DB}
        
        self.sun = 200
        self.selected_tool = None # тип семени, 'shovel', 'plant_food', 'power_zap'
        self.world_idx = 0
        self.wave_idx = 0
        self.wave_timer = 0
        self.sky_sun_timer = 0

        self.grid = [[None for _ in range(GRID_COLS)] for _ in range(GRID_ROWS)]
        self.plants = []
        self.zombies = []
        self.projectiles = []
        self.suns = []
        self.food_orbs = []

        self.almanac_tab = "plants"
        self.almanac_selected = "peashooter"

    def start_world(self, idx):
        self.world_idx = idx
        self.sun = 200
        self.plant_food = 3
        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.food_orbs.clear()
        self.cooldowns = {k: 0 for k in PLANTS_DB}
        self.wave_idx = 0
        self.wave_timer = 250
        self.state = "PLAY"

    def handle_click(self, pos):
        if self.state == "MENU":
            if 460 <= pos[0] <= 720 and 320 <= pos[1] <= 375: self.state = "WORLD_SELECT"
            elif 460 <= pos[0] <= 720 and 400 <= pos[1] <= 455: self.state = "ALMANAC"
            elif 460 <= pos[0] <= 720 and 480 <= pos[1] <= 535: pygame.quit(); sys.exit()
            return

        elif self.state == "WORLD_SELECT":
            for idx, w in enumerate(WORLDS):
                r = pygame.Rect(180 + idx * 280, 240, 250, 140)
                if r.collidepoint(pos):
                    self.start_world(idx)
                    return
            if 520 <= pos[0] <= 660 and 580 <= pos[1] <= 630: self.state = "MENU"
            return

        elif self.state == "ALMANAC":
            # Переключение вкладок
            if 100 <= pos[0] <= 240 and 80 <= pos[1] <= 115:
                self.almanac_tab = "plants"
                self.almanac_selected = "peashooter"
            elif 250 <= pos[0] <= 390 and 80 <= pos[1] <= 115:
                self.almanac_tab = "zombies"
                self.almanac_selected = "mummy"

            # Выбор в сетке
            if self.almanac_tab == "plants":
                for idx, p in enumerate(PLANTS_DB.keys()):
                    x = 100 + (idx % 4) * 105
                    y = 140 + (idx // 4) * 105
                    if pygame.Rect(x, y, 90, 90).collidepoint(pos):
                        self.almanac_selected = p
            else:
                for idx, z in enumerate(ZOMBIES_DB.keys()):
                    x = 100 + (idx % 4) * 105
                    y = 140 + (idx // 4) * 105
                    if pygame.Rect(x, y, 90, 90).collidepoint(pos):
                        self.almanac_selected = z

            if 520 <= pos[0] <= 660 and 640 <= pos[1] <= 685: self.state = "MENU"
            return

        elif self.state == "PLAY":
            # Сбор удобрения
            for fo in self.food_orbs:
                if math.hypot(pos[0] - fo.x, pos[1] - fo.y) < 22:
                    if self.plant_food < 3: self.plant_food += 1
                    fo.alive = False
                    return

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

            # Выбор Удобрения (Листик)
            if pygame.Rect(720, 16, 60, 65).collidepoint(pos):
                if self.plant_food > 0:
                    self.selected_tool = "plant_food" if self.selected_tool != "plant_food" else None
                return

            # Суперспособность Power Zap (Молния за 150 монет)
            if pygame.Rect(800, 16, 75, 65).collidepoint(pos):
                if self.coins >= 150:
                    self.selected_tool = "power_zap" if self.selected_tool != "power_zap" else None
                return

            # Лопата
            if pygame.Rect(895, 16, 60, 65).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 * 78, 14, 70, 92)
                if r.collidepoint(pos):
                    cost = PLANTS_DB[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 == "power_zap":
                    self.coins -= 150
                    for z in self.zombies:
                        if z.row == row and abs(z.x - pos[0]) < CELL_W * 1.5:
                            z.take_damage(500)
                    self.selected_tool = None
                    return

                # Применение удобрения к растению
                if self.selected_tool == "plant_food":
                    if self.grid[row][col] is not None:
                        self.grid[row][col].apply_plant_food(self)
                        self.plant_food -= 1
                        self.selected_tool = None
                    return

                # Выкапывание лопатой
                if self.selected_tool == "shovel":
                    if self.grid[row][col] is not None:
                        self.plants.remove(self.grid[row][col])
                        self.grid[row][col] = None
                        self.selected_tool = None
                    return

                # Посадка
                if self.selected_tool and self.selected_tool not in ["shovel", "plant_food", "power_zap"]:
                    if self.grid[row][col] is None:
                        cost = PLANTS_DB[self.selected_tool]["cost"]
                        p = PlantEntity(col, row, self.selected_tool)
                        self.plants.append(p)
                        self.grid[row][col] = p
                        self.sun -= cost
                        self.cooldowns[self.selected_tool] = PLANTS_DB[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 + 40, HEIGHT - 80)
            self.suns.append(Sun(tx, 0, ty))
            self.sky_sun_timer = 0

        # Спавн волн
        waves = WORLDS[self.world_idx]["waves"]
        self.wave_timer += 1
        if self.wave_timer >= 520:
            if self.wave_idx < len(waves):
                for z_type in waves[self.wave_idx]:
                    self.zombies.append(ZombieEntity(random.randint(0, GRID_ROWS - 1), z_type))
                self.wave_idx += 1
                self.wave_timer = 0
            elif len(self.zombies) == 0:
                self.coins += 250
                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)
        for s in self.suns: s.update()
        for fo in self.food_orbs: fo.update()

        # Столкновения снарядов
        for pr in self.projectiles:
            for z in self.zombies:
                if z.row == pr.row and abs(pr.x - z.x) < 25:
                    z.take_damage(pr.damage)
                    if pr.type != "boomerang":
                        pr.alive = False
                    break

        # Выпадение удобрений из особых светящихся зомби
        for z in self.zombies:
            if not z.alive and z.carries_food:
                self.food_orbs.append(PlantFoodOrb(z.x, z.y))

        # Поражение
        for z in self.zombies:
            if z.x < GRID_X - 50:
                self.state = "GAMEOVER"

        # Очистка
        for p in self.plants:
            if p.hp <= 0: self.grid[p.row][p.col] = None
        self.plants = [p for p in self.plants if p.hp > 0]
        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]
        self.food_orbs = [fo for fo in self.food_orbs if fo.alive]

    def draw(self, surf):
        # 1. МЕНЮ
        if self.state == "MENU":
            surf.fill((25, 45, 30))
            t = f_big.render("PLANTS VS. ZOMBIES 2", True, (255, 215, 0))
            st = f_med.render("IT'S ABOUT TIME - PYTHON DELUXE", True, (150, 255, 150))
            surf.blit(t, (WIDTH // 2 - t.get_width() // 2, 100))
            surf.blit(st, (WIDTH // 2 - st.get_width() // 2, 160))

            btns = ["ПУТЕШЕСТВИЕ ВО ВРЕМЕНИ", "БЕСТИАРИЙ / АЛЬМАНАХ", "ВЫХОД"]
            for idx, text in enumerate(btns):
                r = pygame.Rect(460, 320 + idx * 80, 260, 55)
                pygame.draw.rect(surf, (140, 80, 40), r, border_radius=10)
                pygame.draw.rect(surf, (60, 30, 10), r, 3, border_radius=10)
                lbl = f_small.render(text, True, (255, 255, 255))
                surf.blit(lbl, (r.centerx - lbl.get_width() // 2, r.centery - lbl.get_height() // 2))
            return

        # 2. ВЫБОР ЭПОХ (МИРОВ)
        elif self.state == "WORLD_SELECT":
            surf.fill((30, 40, 45))
            t = f_big.render("ВЫБЕРИТЕ ВРЕМЕННУЮ ЭПОХУ", True, (255, 215, 0))
            surf.blit(t, (WIDTH // 2 - t.get_width() // 2, 80))

            for idx, w in enumerate(WORLDS):
                r = pygame.Rect(180 + idx * 280, 240, 250, 140)
                pygame.draw.rect(surf, w["color"], r, border_radius=10)
                pygame.draw.rect(surf, (20, 20, 20), r, 3, border_radius=10)
                lbl = f_med.render(w["name"], True, (255, 255, 255))
                surf.blit(lbl, (r.centerx - lbl.get_width() // 2, r.y + 50))

            b_back = pygame.Rect(520, 580, 140, 50)
            pygame.draw.rect(surf, (140, 60, 40), b_back, border_radius=8)
            surf.blit(f_med.render("НАЗАД", True, (255, 255, 255)), (b_back.centerx - 30, b_back.centery - 10))
            return

        # 3. АЛЬМАНАХ / БЕСТИАРИЙ С ВКЛАДКАМИ РАСТЕНИЙ И ЗОМБИ
        elif self.state == "ALMANAC":
            surf.fill((35, 45, 50))
            t = f_big.render("ПРИГОРОДНЫЙ АЛЬМАНАХ ВРЕМЕНИ", True, (245, 215, 30))
            surf.blit(t, (WIDTH // 2 - t.get_width() // 2, 25))

            # Кнопки вкладок
            tab1 = pygame.Rect(100, 80, 140, 35)
            tab2 = pygame.Rect(250, 80, 140, 35)
            pygame.draw.rect(surf, (70, 130, 60) if self.almanac_tab == "plants" else (50, 60, 65), tab1, border_radius=4)
            pygame.draw.rect(surf, (150, 80, 60) if self.almanac_tab == "zombies" else (50, 60, 65), tab2, border_radius=4)
            surf.blit(f_small.render("РАСТЕНИЯ", True, (255, 255, 255)), (tab1.centerx - 38, tab1.centery - 8))
            surf.blit(f_small.render("ЗОМБИ", True, (255, 255, 255)), (tab2.centerx - 25, tab2.centery - 8))

            info_r = pygame.Rect(600, 135, 480, 460)
            pygame.draw.rect(surf, (45, 60, 65), info_r, border_radius=8)

            if self.almanac_tab == "plants":
                for idx, p in enumerate(PLANTS_DB.keys()):
                    x = 100 + (idx % 4) * 105
                    y = 135 + (idx // 4) * 105
                    r = pygame.Rect(x, y, 90, 90)
                    sel = (self.almanac_selected == p)
                    pygame.draw.rect(surf, (100, 160, 80) if sel else (55, 70, 75), r, border_radius=6)
                    draw_plant_art(surf, p, x + 15, y + 15, 60)

                spec = PLANTS_DB[self.almanac_selected]
                draw_plant_art(surf, self.almanac_selected, 630, 160, 100)
                surf.blit(f_big.render(spec["name"], True, (255, 255, 255)), (750, 170))
                surf.blit(f_med.render(f"Солнце: {spec['cost']}", True, (255, 215, 0)), (750, 220))
                surf.blit(f_med.render(f"Прочность: {spec['hp']}", True, (150, 255, 150)), (750, 250))
                surf.blit(f_small.render(spec["desc"], True, (220, 220, 220)), (630, 340))

            elif self.almanac_tab == "zombies":
                for idx, z in enumerate(ZOMBIES_DB.keys()):
                    x = 100 + (idx % 4) * 105
                    y = 135 + (idx // 4) * 105
                    r = pygame.Rect(x, y, 90, 90)
                    sel = (self.almanac_selected == z)
                    pygame.draw.rect(surf, (160, 100, 80) if sel else (55, 70, 75), r, border_radius=6)
                    draw_zombie_art(surf, z, x + 15, y + 10, 60)

                spec = ZOMBIES_DB[self.almanac_selected]
                draw_zombie_art(surf, self.almanac_selected, 630, 160, 100)
                surf.blit(f_big.render(spec["name"], True, (255, 255, 255)), (750, 170))
                surf.blit(f_med.render(f"Здоровье: {spec['hp']}", True, (255, 100, 100)), (750, 220))
                surf.blit(f_med.render(f"Скорость: {spec['spd']}", True, (100, 200, 255)), (750, 250))
                surf.blit(f_small.render(spec["desc"], True, (220, 220, 220)), (630, 340))

            b_back = pygame.Rect(520, 640, 140, 45)
            pygame.draw.rect(surf, (140, 60, 40), b_back, border_radius=8)
            surf.blit(f_med.render("НАЗАД", True, (255, 255, 255)), (b_back.centerx - 30, b_back.centery - 10))
            return

        # 4. БОЕВОЙ ЭКРАН ЭПОХИ
        w_cfg = WORLDS[self.world_idx]
        surf.fill(w_cfg["color"])
        for r in range(GRID_ROWS):
            for c in range(GRID_COLS):
                col = w_cfg["color"] if (r + c) % 2 == 0 else w_cfg["alt_color"]
                pygame.draw.rect(surf, col, (GRID_X + c * CELL_W, GRID_Y + r * CELL_H, CELL_W, CELL_H))
                pygame.draw.rect(surf, (0, 0, 0, 40), (GRID_X + c * CELL_W, GRID_Y + r * CELL_H, CELL_W, CELL_H), 1)

        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)
        for fo in self.food_orbs: fo.draw(surf)

        # Верхняя панель колоды
        pygame.draw.rect(surf, (90, 60, 35), (0, 0, WIDTH, 120))

        # Солнце
        pygame.draw.rect(surf, (220, 200, 150), (15, 12, 80, 95), border_radius=6)
        draw_circle_aa(surf, 55, 42, 18, (255, 200, 0))
        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 * 78
            card_r = pygame.Rect(x, 14, 70, 92)
            cost = PLANTS_DB[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=5)
            pygame.draw.rect(surf, (40, 25, 10), card_r, 2, border_radius=5)

            draw_plant_art(surf, p_type, x + 10, 18, 50)
            c_txt = f_small.render(str(cost), True, (160, 20, 20))
            surf.blit(c_txt, (x + 35 - c_txt.get_width() // 2, 72))

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

        # Кнопка Plant Food (Удобрение)
        pf_r = pygame.Rect(720, 16, 60, 65)
        pygame.draw.rect(surf, (50, 160, 80) if self.selected_tool == "plant_food" else (30, 100, 50), pf_r, border_radius=6)
        draw_circle_aa(surf, 750, 48, 14, (80, 255, 120))
        surf.blit(f_small.render(f"x{self.plant_food}", True, (255, 255, 255)), (768, 40))

        # Суперспособность Молния (Power Zap)
        zap_r = pygame.Rect(800, 16, 75, 65)
        pygame.draw.rect(surf, (80, 160, 220) if self.selected_tool == "power_zap" else (40, 90, 140), zap_r, border_radius=6)
        surf.blit(f_tiny.render("POWER ZAP", True, (255, 255, 255)), (805, 22))
        surf.blit(f_tiny.render("150 $", True, (255, 220, 50)), (822, 54))

        # Лопата
        sh_r = pygame.Rect(895, 16, 60, 65)
        pygame.draw.rect(surf, (255, 255, 120) if self.selected_tool == "shovel" else (140, 90, 50), sh_r, border_radius=6)
        pygame.draw.line(surf, (150, 90, 40), (905, 66), (940, 26), 6)

        # Монеты и волны
        pygame.draw.rect(surf, (20, 20, 20), (WIDTH - 200, 20, 180, 35), border_radius=4)
        c_disp = f_small.render(f"Монеты: {self.coins} $", True, (255, 215, 0))
        surf.blit(c_disp, (WIDTH - 185, 28))

        if self.state == "GAMEOVER":
            s = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
            s.fill((100, 0, 0, 200))
            surf.blit(s, (0, 0))
            surf.blit(f_big.render("ЗОМБИ ЗАХВАТИЛИ ЭПОХУ!", True, (255, 80, 80)), (WIDTH // 2 - 240, 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))
            surf.blit(f_big.render("ЭПОХА ЗАЩИЩЕНА!", True, (240, 220, 50)), (WIDTH // 2 - 180, HEIGHT // 2 - 30))

# --- ТОЧКА ВХОДА ---
def main():
    game = PVZ2Game()
    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 == pygame.K_ESCAPE:
                    game.state = "MENU"
                elif event.key == pygame.K_r and game.state in ["GAMEOVER", "WIN"]:
                    game.start_world(game.world_idx)
            elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                game.handle_click(pygame.mouse.get_pos())

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

    pygame.quit()
    sys.exit()

if __name__ == "__main__":
    main()