import pygame
import math
import sys
import random

# ---------- 初始化 ----------
pygame.init()
WIDTH, HEIGHT = 900, 640
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("塔防 · 7种塔 · 20波")
clock = pygame.time.Clock()

# 字体
try:
    font_tiny = pygame.font.Font(None, 16)
    font_small = pygame.font.Font(None, 22)
    font_mid = pygame.font.Font(None, 28)
    font_large = pygame.font.Font(None, 64)
except:
    f = pygame.font.get_default_font()
    font_tiny = pygame.font.Font(f, 16)
    font_small = pygame.font.Font(f, 22)
    font_mid = pygame.font.Font(f, 28)
    font_large = pygame.font.Font(f, 64)

# ---------- 常量 ----------
TILE_SIZE = 40
UI_HEIGHT = 40
BOTTOM_HEIGHT = 80
COLS = WIDTH // TILE_SIZE
ROWS = (HEIGHT - UI_HEIGHT - BOTTOM_HEIGHT) // TILE_SIZE

# 颜色
BG_DARK = (26, 38, 57)
GRID_A = (44, 62, 78)
GRID_B = (38, 55, 70)
GRID_LINE = (29, 43, 54)
PATH_COLOR = (79, 62, 46)
PATH_DOT1 = (107, 90, 72)
PATH_DOT2 = (139, 122, 104)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREEN = (76, 217, 100)
YELLOW = (255, 204, 0)
RED = (255, 59, 48)
HP_BG = (17, 26, 34)
PANEL_BORDER = (70, 100, 130)
SELECT_COLOR = (255, 215, 0)

# ---------- 路径 ----------
waypoints = [
    (0, 6), (4, 6), (4, 2), (9, 2), (9, 10),
    (14, 10), (14, 4), (18, 4), (18, 8), (21, 8)
]

path_set = set()
for i in range(len(waypoints) - 1):
    x1, y1 = waypoints[i]
    x2, y2 = waypoints[i + 1]
    dx = 0 if x2 == x1 else (1 if x2 > x1 else -1)
    dy = 0 if y2 == y1 else (1 if y2 > y1 else -1)
    x, y = x1, y1
    path_set.add((x, y))
    while (x, y) != (x2, y2):
        x += dx
        y += dy
        path_set.add((x, y))

waypoints_world = [(gx * TILE_SIZE + TILE_SIZE // 2,
                    gy * TILE_SIZE + TILE_SIZE // 2 + UI_HEIGHT) for gx, gy in waypoints]

path_segments = []
total_path_length = 0.0
for i in range(len(waypoints_world) - 1):
    ax, ay = waypoints_world[i]
    bx, by = waypoints_world[i + 1]
    length = math.hypot(bx - ax, by - ay)
    path_segments.append({
        "from": (ax, ay), "to": (bx, by),
        "length": length, "start": total_path_length
    })
    total_path_length += length


def get_position_at_distance(dist):
    if dist <= 0:
        return waypoints_world[0]
    if dist >= total_path_length:
        return waypoints_world[-1]
    for seg in path_segments:
        if dist <= seg["start"] + seg["length"]:
            t = (dist - seg["start"]) / seg["length"]
            fx, fy = seg["from"]
            tx, ty = seg["to"]
            return (fx + (tx - fx) * t, fy + (ty - fy) * t)
    return waypoints_world[-1]


# ---------- 7种防御塔 ----------
TOWER_TYPES = [
    {"name": "箭塔", "cost": 50, "damage": 15, "range": 110,
     "cooldown": 0.5, "color": (110, 200, 255),
     "type": "single", "desc": "基础单体"},
    {"name": "炮塔", "cost": 80, "damage": 40, "range": 130,
     "cooldown": 1.2, "color": (255, 140, 90),
     "type": "splash", "splash_radius": 50, "desc": "范围溅射"},
    {"name": "冰塔", "cost": 90, "damage": 8, "range": 100,
     "cooldown": 0.8, "color": (150, 220, 255),
     "type": "slow", "slow_factor": 0.5, "slow_duration": 2.0, "desc": "减速敌人"},
    {"name": "火塔", "cost": 100, "damage": 30, "range": 120,
     "cooldown": 0.7, "color": (255, 90, 60),
     "type": "burn", "burn_damage": 8, "burn_duration": 3.0, "desc": "持续灼烧"},
    {"name": "电塔", "cost": 120, "damage": 25, "range": 140,
     "cooldown": 0.9, "color": (200, 200, 80),
     "type": "chain", "chain_count": 3, "chain_range": 90, "desc": "连锁闪电"},
    {"name": "毒塔", "cost": 110, "damage": 12, "range": 115,
     "cooldown": 0.6, "color": (140, 220, 120),
     "type": "poison", "poison_damage": 15, "poison_duration": 4.0, "desc": "剧毒伤害"},
    {"name": "狙塔", "cost": 150, "damage": 100, "range": 260,
     "cooldown": 2.0, "color": (230, 100, 230),
     "type": "sniper", "desc": "超远高伤"},
]


# ---------- 敌人 ----------
class Enemy:
    def __init__(self, health, speed, reward, color, size=14, armor=0.0, is_boss=False):
        self.max_health = health
        self.health = health
        self.base_speed = speed
        self.speed = speed
        self.reward = reward
        self.color = color
        self.size = size
        self.dist = 0.0
        self.alive = True
        self.armor = armor
        self.is_boss = is_boss
        self.slow_timer = 0.0
        self.slow_factor = 1.0
        self.burn_timer = 0.0
        self.burn_dps = 0.0
        self.poison_timer = 0.0
        self.poison_dps = 0.0

    def update(self, dt):
        if not self.alive:
            return False
        if self.slow_timer > 0:
            self.slow_timer -= dt
            if self.slow_timer <= 0:
                self.slow_factor = 1.0
        self.speed = self.base_speed * self.slow_factor

        if self.burn_timer > 0:
            self.burn_timer -= dt
            self.health -= self.burn_dps * dt
            if self.health <= 0:
                self.alive = False
                return False
        if self.poison_timer > 0:
            self.poison_timer -= dt
            self.health -= self.poison_dps * dt
            if self.health <= 0:
                self.alive = False
                return False

        self.dist += self.speed * dt
        if self.dist >= total_path_length:
            self.alive = False
            return True
        return False

    def get_pos(self):
        return get_position_at_distance(self.dist)

    def take_damage(self, amount):
        if not self.alive:
            return False
        actual = amount * (1.0 - self.armor)
        self.health -= actual
        if self.health <= 0:
            self.alive = False
            return True
        return False

    def apply_slow(self, factor, duration):
        if factor < self.slow_factor or self.slow_timer <= 0:
            self.slow_factor = factor
        self.slow_timer = max(self.slow_timer, duration)

    def apply_burn(self, dps, duration):
        self.burn_dps = max(self.burn_dps, dps)
        self.burn_timer = max(self.burn_timer, duration)

    def apply_poison(self, dps, duration):
        self.poison_dps = max(self.poison_dps, dps)
        self.poison_timer = max(self.poison_timer, duration)

    def draw(self, surf):
        if not self.alive:
            return
        x, y = self.get_pos()
        x, y = int(x), int(y)
        draw_color = list(self.color)
        if self.slow_timer > 0:
            draw_color[2] = min(255, draw_color[2] + 80)
        if self.burn_timer > 0:
            draw_color[0] = min(255, draw_color[0] + 80)
        if self.poison_timer > 0:
            draw_color[1] = min(255, draw_color[1] + 60)

        glow_r = self.size + 8
        glow = pygame.Surface((glow_r * 2, glow_r * 2), pygame.SRCALPHA)
        pygame.draw.circle(glow, (draw_color[0], draw_color[1], draw_color[2], 70),
                           (glow_r, glow_r), glow_r)
        surf.blit(glow, (x - glow_r, y - glow_r))

        pygame.draw.circle(surf, draw_color, (x, y), self.size)
        border_c = (255, 215, 0) if self.is_boss else (0, 0, 0)
        pygame.draw.circle(surf, border_c, (x, y), self.size, 2)

        bar_w = self.size * 2 + 6
        bar_x = x - bar_w / 2
        bar_y = y - self.size - 12
        ratio = max(0.0, self.health / self.max_health)
        pygame.draw.rect(surf, HP_BG, (bar_x, bar_y, bar_w, 6))
        if ratio > 0.6:
            hp_color = GREEN
        elif ratio > 0.3:
            hp_color = YELLOW
        else:
            hp_color = RED
        pygame.draw.rect(surf, hp_color, (bar_x, bar_y, bar_w * ratio, 6))
        pygame.draw.rect(surf, (0, 0, 0), (bar_x, bar_y, bar_w, 6), 1)


# ---------- 防御塔 ----------
class Tower:
    def __init__(self, gx, gy, tower_type):
        self.gx = gx
        self.gy = gy
        self.x = gx * TILE_SIZE + TILE_SIZE // 2
        self.y = gy * TILE_SIZE + TILE_SIZE // 2 + UI_HEIGHT
        self.type_info = tower_type
        self.name = tower_type["name"]
        self.damage = tower_type["damage"]
        self.range = tower_type["range"]
        self.cooldown_max = tower_type["cooldown"]
        self.timer = 0.0
        self.color = tower_type["color"]
        self.ttype = tower_type["type"]
        self.splash_radius = tower_type.get("splash_radius", 0)
        self.slow_factor = tower_type.get("slow_factor", 1.0)
        self.slow_duration = tower_type.get("slow_duration", 0)
        self.burn_damage = tower_type.get("burn_damage", 0)
        self.burn_duration = tower_type.get("burn_duration", 0)
        self.chain_count = tower_type.get("chain_count", 1)
        self.chain_range = tower_type.get("chain_range", 0)
        self.poison_damage = tower_type.get("poison_damage", 0)
        self.poison_duration = tower_type.get("poison_duration", 0)
        self.attack_flash = 0.0

    def find_target(self, enemies):
        closest = None
        min_dist = float('inf')
        for e in enemies:
            if not e.alive:
                continue
            ex, ey = e.get_pos()
            d = math.hypot(ex - self.x, ey - self.y)
            if d <= self.range and d < min_dist:
                min_dist = d
                closest = e
        return closest

    def deal_damage(self, enemy, state, damage=None):
        dmg = damage if damage is not None else self.damage
        killed = enemy.take_damage(dmg)
        if killed:
            state.gold += enemy.reward
        return killed

    def update(self, dt, enemies, state):
        if state.game_over or state.game_win:
            return
        if self.attack_flash > 0:
            self.attack_flash -= dt
        self.timer -= dt
        if self.timer <= 0:
            target = self.find_target(enemies)
            if target:
                self.attack(target, enemies, state)
                self.timer = self.cooldown_max
                self.attack_flash = 0.1

    def attack(self, target, enemies, state):
        if self.ttype in ("single", "sniper"):
            self.deal_damage(target, state)
        elif self.ttype == "splash":
            tx, ty = target.get_pos()
            self.deal_damage(target, state)
            for e in enemies:
                if e is target or not e.alive:
                    continue
                ex, ey = e.get_pos()
                if math.hypot(ex - tx, ey - ty) <= self.splash_radius:
                    self.deal_damage(e, state, self.damage * 0.6)
        elif self.ttype == "slow":
            self.deal_damage(target, state)
            target.apply_slow(self.slow_factor, self.slow_duration)
        elif self.ttype == "burn":
            self.deal_damage(target, state)
            target.apply_burn(self.burn_damage, self.burn_duration)
        elif self.ttype == "poison":
            self.deal_damage(target, state)
            target.apply_poison(self.poison_damage, self.poison_duration)
        elif self.ttype == "chain":
            hit = [target]
            self.deal_damage(target, state)
            current = target
            for _ in range(self.chain_count - 1):
                cx, cy = current.get_pos()
                next_target = None
                min_d = float('inf')
                for e in enemies:
                    if e in hit or not e.alive:
                        continue
                    ex, ey = e.get_pos()
                    d = math.hypot(ex - cx, ey - cy)
                    if d <= self.chain_range and d < min_d:
                        min_d = d
                        next_target = e
                if next_target:
                    self.deal_damage(next_target, state, self.damage * 0.7)
                    hit.append(next_target)
                    current = next_target
                else:
                    break

    def draw(self, surf):
        range_surf = pygame.Surface((self.range * 2, self.range * 2), pygame.SRCALPHA)
        pygame.draw.circle(range_surf, (self.color[0], self.color[1], self.color[2], 25),
                           (self.range, self.range), self.range)
        surf.blit(range_surf, (self.x - self.range, self.y - self.range))

        pygame.draw.circle(surf, (30, 50, 70), (self.x, self.y), 17)
        pygame.draw.circle(surf, self.color, (self.x, self.y), 15)

        if self.attack_flash > 0:
            flash = pygame.Surface((50, 50), pygame.SRCALPHA)
            pygame.draw.circle(flash, (self.color[0], self.color[1], self.color[2], 180),
                               (25, 25), 22)
            surf.blit(flash, (self.x - 25, self.y - 25))

        pygame.draw.circle(surf, WHITE, (self.x, self.y), 8)
        pygame.draw.circle(surf, self.color, (self.x, self.y), 6)


# ---------- 游戏状态 ----------
class GameState:
    def __init__(self):
        self.reset()

    def reset(self):
        self.lives = 20
        self.gold = 300
        self.game_over = False
        self.game_win = False
        self.towers = []
        self.enemies = []
        self.wave_index = 0
        self.wave_active = False
        self.current_wave = []
        self.wave_spawn_counter = 0
        self.spawn_timer = 0
        self.selected_tower = 0
        self.wave_countdown = 5.0


ENEMY_COLORS = [
    (242, 139, 130), (251, 188, 4), (204, 153, 102),
    (212, 140, 255), (255, 107, 139), (100, 200, 180),
    (255, 160, 100), (180, 180, 255),
]


def generate_waves():
    waves = []
    for i in range(15):
        waves.append({
            "count": 4 + i * 2,
            "health": int(30 + i * 40 + i * i * 5),
            "speed": 45 + i * 3,
            "reward": 15 + i * 2,
            "color": ENEMY_COLORS[i % len(ENEMY_COLORS)],
            "size": 12 + i // 3,
            "delay": max(0.3, 1.0 - i * 0.04),
            "armor": min(0.5, i * 0.03),
            "is_boss": False,
        })
    elite_configs = [
        {"count": 12, "health": 1500, "speed": 55, "reward": 60, "size": 20, "armor": 0.35, "delay": 0.5},
        {"count": 14, "health": 2200, "speed": 60, "reward": 70, "size": 22, "armor": 0.40, "delay": 0.45},
        {"count": 16, "health": 3200, "speed": 65, "reward": 80, "size": 24, "armor": 0.45, "delay": 0.4},
        {"count": 18, "health": 4500, "speed": 70, "reward": 90, "size": 26, "armor": 0.50, "delay": 0.35},
    ]
    for i, cfg in enumerate(elite_configs):
        waves.append({
            "count": cfg["count"],
            "health": cfg["health"],
            "speed": cfg["speed"],
            "reward": cfg["reward"],
            "color": (200 - i * 20, 50 + i * 30, 200),
            "size": cfg["size"],
            "delay": cfg["delay"],
            "armor": cfg["armor"],
            "is_boss": False,
        })
    waves.append({
        "count": 3, "health": 25000, "speed": 40, "reward": 500,
        "color": (255, 50, 50), "size": 40, "delay": 3.0,
        "armor": 0.6, "is_boss": True,
    })
    return waves


WAVES = generate_waves()


def start_next_wave(state):
    if state.wave_index >= len(WAVES):
        return
    wave = WAVES[state.wave_index]
    state.current_wave = [wave] * wave["count"]
    state.wave_spawn_counter = 0
    state.wave_active = True
    state.spawn_timer = 0.0


def spawn_enemy(state, config):
    enemy = Enemy(
        config["health"], config["speed"], config["reward"],
        config["color"], config["size"],
        config.get("armor", 0.0), config.get("is_boss", False)
    )
    state.enemies.append(enemy)


def update_waves(state, dt):
    if state.game_over or state.game_win:
        return
    if not state.wave_active and len(state.enemies) == 0:
        if state.wave_index < len(WAVES):
            state.wave_countdown -= dt
            if state.wave_countdown <= 0:
                start_next_wave(state)
                state.wave_countdown = 5.0
        else:
            state.game_win = True
            return
    if state.wave_active:
        wave = WAVES[state.wave_index]
        if state.wave_spawn_counter < len(state.current_wave):
            state.spawn_timer -= dt
            if state.spawn_timer <= 0:
                spawn_enemy(state, state.current_wave[state.wave_spawn_counter])
                state.wave_spawn_counter += 1
                state.spawn_timer = wave["delay"]
        else:
            state.wave_active = False
            state.wave_index += 1


# ---------- 绘制 ----------
def draw_grid(surf):
    for row in range(ROWS):
        for col in range(COLS):
            x = col * TILE_SIZE
            y = row * TILE_SIZE + UI_HEIGHT
            color = GRID_A if (row + col) % 2 == 0 else GRID_B
            pygame.draw.rect(surf, color, (x, y, TILE_SIZE, TILE_SIZE))
            pygame.draw.rect(surf, GRID_LINE, (x, y, TILE_SIZE, TILE_SIZE), 1)
    for (col, row) in path_set:
        x = col * TILE_SIZE
        y = row * TILE_SIZE + UI_HEIGHT
        pygame.draw.rect(surf, PATH_COLOR, (x, y, TILE_SIZE, TILE_SIZE))
        cx, cy = x + TILE_SIZE // 2, y + TILE_SIZE // 2
        pygame.draw.circle(surf, PATH_DOT1, (cx, cy), 6)
        pygame.draw.circle(surf, PATH_DOT2, (cx, cy), 3)
    sx, sy = waypoints_world[0]
    ex, ey = waypoints_world[-1]
    pygame.draw.circle(surf, (58, 155, 94), (int(sx), int(sy)), 14)
    pygame.draw.circle(surf, (196, 75, 75), (int(ex), int(ey)), 17)


def draw_top_ui(surf, state):
    bar = pygame.Surface((WIDTH, UI_HEIGHT), pygame.SRCALPHA)
    bar.fill((15, 26, 36, 235))
    surf.blit(bar, (0, 0))
    pygame.draw.line(surf, PANEL_BORDER, (0, UI_HEIGHT), (WIDTH, UI_HEIGHT), 2)

    surf.blit(font_small.render(f"生命: {state.lives}", True, (240, 200, 200)), (15, 10))
    surf.blit(font_small.render(f"金币: {state.gold}", True, (245, 215, 66)), (170, 10))
    wave_num = min(state.wave_index + 1, len(WAVES))
    surf.blit(font_small.render(f"波次: {wave_num}/{len(WAVES)}", True, (180, 220, 255)), (340, 10))
    surf.blit(font_small.render(f"塔数: {len(state.towers)}", True, (200, 255, 200)), (520, 10))
    if not state.wave_active and len(state.enemies) == 0 and state.wave_index < len(WAVES):
        surf.blit(font_small.render(f"下一波: {max(0, state.wave_countdown):.1f}s",
                                    True, (255, 200, 100)), (680, 10))
    surf.blit(font_small.render("1-7选塔 | R重开", True, (150, 180, 210)), (WIDTH - 180, 10))


def draw_bottom_ui(surf, state):
    y0 = HEIGHT - BOTTOM_HEIGHT
    panel = pygame.Surface((WIDTH, BOTTOM_HEIGHT), pygame.SRCALPHA)
    panel.fill((15, 26, 36, 240))
    surf.blit(panel, (0, y0))
    pygame.draw.line(surf, PANEL_BORDER, (0, y0), (WIDTH, y0), 2)

    card_w = 110
    card_h = 66
    total_w = len(TOWER_TYPES) * card_w + (len(TOWER_TYPES) - 1) * 8
    start_x = (WIDTH - total_w) // 2
    card_y = y0 + 8

    for i, ttype in enumerate(TOWER_TYPES):
        x = start_x + i * (card_w + 8)
        affordable = state.gold >= ttype["cost"]
        bg_color = (35, 55, 75) if affordable else (45, 40, 45)
        pygame.draw.rect(surf, bg_color, (x, card_y, card_w, card_h), border_radius=8)
        if i == state.selected_tower:
            pygame.draw.rect(surf, SELECT_COLOR, (x, card_y, card_w, card_h), 3, border_radius=8)
        else:
            pygame.draw.rect(surf, PANEL_BORDER, (x, card_y, card_w, card_h), 1, border_radius=8)

        pygame.draw.circle(surf, ttype["color"], (x + 20, card_y + 22), 12)
        pygame.draw.circle(surf, WHITE, (x + 20, card_y + 22), 12, 1)

        name_color = WHITE if affordable else (130, 130, 130)
        surf.blit(font_tiny.render(ttype["name"], True, name_color), (x + 38, card_y + 8))
        price_color = (245, 215, 66) if affordable else (180, 100, 100)
        surf.blit(font_tiny.render(f"{ttype['cost']}金", True, price_color), (x + 38, card_y + 26))
        surf.blit(font_tiny.render(f"[{i+1}]", True, (150, 180, 210)), (x + 38, card_y + 44))


def draw_status(surf, state):
    if state.game_over:
        overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 140))
        surf.blit(overlay, (0, 0))
        text = font_large.render("GAME OVER", True, (255, 107, 107))
        surf.blit(text, (WIDTH // 2 - text.get_width() // 2, HEIGHT // 2 - 50))
        sub = font_mid.render("按 R 重新开始", True, WHITE)
        surf.blit(sub, (WIDTH // 2 - sub.get_width() // 2, HEIGHT // 2 + 30))
    elif state.game_win:
        overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        overlay.fill((0, 40, 0, 130))
        surf.blit(overlay, (0, 0))
        text = font_large.render("VICTORY!", True, (126, 240, 162))
        surf.blit(text, (WIDTH // 2 - text.get_width() // 2, HEIGHT // 2 - 50))
        sub = font_mid.render("你成功守住了所有20波敌人!", True, WHITE)
        surf.blit(sub, (WIDTH // 2 - sub.get_width() // 2, HEIGHT // 2 + 30))


def draw_preview(surf, state, mx, my):
    if state.game_over or state.game_win:
        return
    if my < UI_HEIGHT or my >= HEIGHT - BOTTOM_HEIGHT:
        return
    gx = mx // TILE_SIZE
    gy = (my - UI_HEIGHT) // TILE_SIZE
    if gx < 0 or gx >= COLS or gy < 0 or gy >= ROWS:
        return

    ttype = TOWER_TYPES[state.selected_tower]
    affordable = state.gold >= ttype["cost"]
    valid = (gx, gy) not in path_set and \
            not any(t.gx == gx and t.gy == gy for t in state.towers) and affordable

    px = gx * TILE_SIZE
    py = gy * TILE_SIZE + UI_HEIGHT

    color = (100, 255, 100) if valid else (255, 80, 80)
    s = pygame.Surface((TILE_SIZE, TILE_SIZE), pygame.SRCALPHA)
    s.fill((color[0], color[1], color[2], 90))
    surf.blit(s, (px, py))
    pygame.draw.rect(surf, color, (px, py, TILE_SIZE, TILE_SIZE), 2)

    if valid:
        cx = px + TILE_SIZE // 2
        cy = py + TILE_SIZE // 2
        r = ttype["range"]
        rng = pygame.Surface((r * 2, r * 2), pygame.SRCALPHA)
        pygame.draw.circle(rng, (ttype["color"][0], ttype["color"][1], ttype["color"][2], 40),
                           (r, r), r)
        pygame.draw.circle(rng, (ttype["color"][0], ttype["color"][1], ttype["color"][2], 120),
                           (r, r), r, 2)
        surf.blit(rng, (cx - r, cy - r))


# ---------- 主逻辑 ----------
def main():
    state = GameState()
    running = True

    while running:
        dt = clock.tick(60) / 1000.0
        dt = min(dt, 0.05)
        mx, my = pygame.mouse.get_pos()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

            elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                if not state.game_over and not state.game_win:
                    y0 = HEIGHT - BOTTOM_HEIGHT
                    if my >= y0:
                        # 点击底部选择栏
                        card_w = 110
                        card_h = 66
                        total_w = len(TOWER_TYPES) * card_w + (len(TOWER_TYPES) - 1) * 8
                        start_x = (WIDTH - total_w) // 2
                        card_y = y0 + 8
                        for i in range(len(TOWER_TYPES)):
                            cx = start_x + i * (card_w + 8)
                            if cx <= mx <= cx + card_w and card_y <= my <= card_y + card_h:
                                state.selected_tower = i
                                break
                    elif UI_HEIGHT <= my < HEIGHT - BOTTOM_HEIGHT:
                        # 点击地图建造塔
                        gx = mx // TILE_SIZE
                        gy = (my - UI_HEIGHT) // TILE_SIZE
                        if 0 <= gx < COLS and 0 <= gy < ROWS:
                            if (gx, gy) not in path_set:
                                if not any(t.gx == gx and t.gy == gy for t in state.towers):
                                    ttype = TOWER_TYPES[state.selected_tower]
                                    if state.gold >= ttype["cost"]:
                                        state.gold -= ttype["cost"]
                                        state.towers.append(Tower(gx, gy, ttype))

            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_r:
                    state.reset()
                elif pygame.K_1 <= event.key <= pygame.K_7:
                    idx = event.key - pygame.K_1
                    if idx < len(TOWER_TYPES):
                        state.selected_tower = idx

        # 更新
        if not state.game_over and not state.game_win:
            for tower in state.towers:
                tower.update(dt, state.enemies, state)

            for i in range(len(state.enemies) - 1, -1, -1):
                enemy = state.enemies[i]
                if not enemy.alive:
                    state.enemies.pop(i)
                    continue
                reached = enemy.update(dt)
                if reached:
                    state.lives -= 1
                    state.enemies.pop(i)
                    if state.lives <= 0:
                        state.lives = 0
                        state.game_over = True
                elif not enemy.alive:
                    state.enemies.pop(i)

            update_waves(state, dt)

            if (not state.game_over and state.wave_index >= len(WAVES)
                    and len(state.enemies) == 0 and not state.wave_active):
                state.game_win = True

        # 绘制
        screen.fill(BG_DARK)
        draw_grid(screen)
        for tower in state.towers:
            tower.draw(screen)
        for enemy in state.enemies:
            enemy.draw(screen)
        draw_preview(screen, state, mx, my)
        draw_top_ui(screen, state)
        draw_bottom_ui(screen, state)
        draw_status(screen, state)

        pygame.display.flip()

    pygame.quit()
    sys.exit()


if __name__ == "__main__":
    main()