# kards.py  —— 单文件 KARDS 简化版（约 1200 行）
# 运行: python kards.py [可选中文字体路径]
# 依赖: pip install pygame
import pygame
import sys
import os
import random
import copy
import platform
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Optional


# ============================================================
# 一、中文字体加载
# ============================================================

class FontLoader:
    """跨平台中文字体自动查找与缓存"""

    WINDOWS = [
        r"C:\Windows\Fonts\msyh.ttc",
        r"C:\Windows\Fonts\msyhbd.ttc",
        r"C:\Windows\Fonts\simhei.ttf",
        r"C:\Windows\Fonts\simsun.ttc",
        r"C:\Windows\Fonts\Deng.ttf",
    ]
    MAC = [
        "/System/Library/Fonts/PingFang.ttc",
        "/System/Library/Fonts/STHeiti Medium.ttc",
        "/System/Library/Fonts/Hiragino Sans GB.ttc",
    ]
    LINUX = [
        "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
        "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
        "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
        "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf",
    ]
    MATCH_NAMES = [
        "microsoftyahei", "msyh", "simhei", "simsun",
        "pingfangsc", "stheiti", "hiraginosansgb",
        "notosanscjksc", "wenquanyimicrohei", "wenquanyizenhei",
        "arialunicodems", "droidsansfallback",
    ]

    _path: Optional[str] = None
    _searched: bool = False
    _cache: dict = {}

    @classmethod
    def find(cls) -> Optional[str]:
        if cls._searched:
            return cls._path
        cls._searched = True

        candidates = []
        env = os.environ.get("KARDS_FONT")
        if env and os.path.isfile(env):
            candidates.append(env)

        system = platform.system()
        if system == "Windows":
            candidates.extend(cls.WINDOWS)
        elif system == "Darwin":
            candidates.extend(cls.MAC)
        else:
            candidates.extend(cls.LINUX)

        for name in cls.MATCH_NAMES:
            try:
                p = pygame.font.match_font(name)
                if p:
                    candidates.append(p)
            except Exception:
                pass

        for p in candidates:
            if p and os.path.isfile(p):
                cls._path = p
                print(f"[字体] 已加载: {p}")
                return p
        print("[字体] 警告: 未找到中文字体，中文可能显示为方块")
        return None

    @classmethod
    def get(cls, size: int, bold: bool = False) -> pygame.font.Font:
        key = (size, bold)
        if key in cls._cache:
            return cls._cache[key]

        path = cls.find()
        font = None
        if path:
            try:
                font = pygame.font.Font(path, size)
                font.set_bold(bold)
            except Exception:
                font = None
        if font is None:
            font = pygame.font.Font(None, size)
            font.set_bold(bold)
        cls._cache[key] = font
        return font


def init_pygame():
    pygame.init()
    pygame.font.init()
    if len(sys.argv) > 1 and os.path.isfile(sys.argv[1]):
        os.environ["KARDS_FONT"] = sys.argv[1]


# ============================================================
# 二、枚举定义
# ============================================================

class Nation(Enum):
    GERMANY = "德国"
    UK = "英国"
    SOVIET = "苏联"
    USA = "美国"
    JAPAN = "日本"


class UnitType(Enum):
    INFANTRY = "步兵"
    TANK = "坦克"
    ARTILLERY = "炮兵"
    FIGHTER = "战斗机"
    BOMBER = "轰炸机"


class CardType(Enum):
    UNIT = "单位"
    ORDER = "指令"


class Rarity(Enum):
    COMMON = "普通"
    LIMITED = "限定"
    SPECIAL = "特殊"
    ELITE = "精英"


class Keyword(Enum):
    BLITZ = "闪击"
    GUARD = "守护"
    AMBUSH = "伏击"
    FURIOUS = "奋战"
    ARMOR = "重甲"
    SMOKE = "烟幕"
    PINCE = "钳击"
    MOBILIZE = "动员"


# ============================================================
# 三、数据类
# ============================================================

@dataclass
class Card:
    name: str
    nation: Nation
    card_type: CardType
    deploy_cost: int
    rarity: Rarity = Rarity.COMMON
    text: str = ""


@dataclass
class Unit(Card):
    unit_type: UnitType = UnitType.INFANTRY
    attack: int = 0
    health: int = 0
    action_cost: int = 1
    keywords: list = field(default_factory=list)
    armor_value: int = 0

    current_health: int = 0
    has_moved: bool = False
    has_attacked: bool = False
    attack_count: int = 0
    is_on_front: bool = False
    ambush_used: bool = False

    def __post_init__(self):
        self.current_health = self.health

    @property
    def max_attacks(self) -> int:
        return 2 if Keyword.FURIOUS in self.keywords else 1

    def take_damage(self, amount: int, is_combat: bool = True) -> int:
        actual = max(0, amount)
        if is_combat and Keyword.ARMOR in self.keywords:
            actual = max(0, actual - self.armor_value)
        self.current_health -= actual
        if Keyword.MOBILIZE in self.keywords:
            try:
                self.keywords.remove(Keyword.MOBILIZE)
            except ValueError:
                pass
        return actual

    def is_alive(self) -> bool:
        return self.current_health > 0

    def has_keyword(self, kw: Keyword) -> bool:
        return kw in self.keywords

    def reset_turn(self):
        self.has_moved = False
        self.has_attacked = False
        self.attack_count = 0
        self.ambush_used = False
        if self.has_keyword(Keyword.MOBILIZE):
            self.attack += 1
            self.health += 1
            self.current_health += 1


@dataclass
class Player:
    name: str
    main_nation: Nation

    cp_slots: int = 0
    cp_current: int = 0
    hq_defense: int = 20

    deck: list = field(default_factory=list)
    hand: list = field(default_factory=list)
    discard: list = field(default_factory=list)
    support_line: list = field(default_factory=list)

    fatigue_counter: int = 0

    def draw(self, count: int = 1):
        for _ in range(count):
            if not self.deck:
                self.fatigue_counter += 1
                self.hq_defense -= self.fatigue_counter
                continue
            card = self.deck.pop(0)
            if len(self.hand) < 9:
                self.hand.append(card)

    def start_turn(self):
        if self.cp_slots < 12:
            self.cp_slots += 1
        self.cp_current = self.cp_slots
        for unit in self.support_line:
            unit.reset_turn()

    def spend_cp(self, amount: int) -> bool:
        if self.cp_current < amount:
            return False
        self.cp_current -= amount
        return True

    @property
    def is_alive(self) -> bool:
        return self.hq_defense > 0


# ============================================================
# 四、战场管理
# ============================================================

class Board:
    SUPPORT_LIMIT = 4
    FRONT_LIMIT = 5

    def __init__(self, player: Player, opponent: Player):
        self.player = player
        self.opponent = opponent
        self.front_units: list = []
        self.front_owner: Optional[Player] = None

    # ---------- 部署 ----------

    def deploy_unit(self, unit: Unit, player: Player):
        if len(player.support_line) >= self.SUPPORT_LIMIT:
            return False, "支援阵线已满（最多4个）"
        if not player.spend_cp(unit.deploy_cost):
            return False, "指挥点不足"

        player.support_line.append(unit)

        if unit.has_keyword(Keyword.BLITZ):
            unit.has_moved = False
            unit.has_attacked = False
        else:
            unit.has_moved = True
            unit.has_attacked = True

        if unit.has_keyword(Keyword.PINCE):
            self._trigger_pince(unit, player)

        return True, f"{unit.name} 已部署"

    def _trigger_pince(self, unit: Unit, player: Player):
        idx = player.support_line.index(unit)
        neighbors = []
        if idx > 0:
            neighbors.append(player.support_line[idx - 1])
        if idx < len(player.support_line) - 1:
            neighbors.append(player.support_line[idx + 1])
        if neighbors:
            ally = random.choice(neighbors)
            for u in (unit, ally):
                u.attack += 1
                u.health += 1
                u.current_health += 1

    # ---------- 移动 ----------

    def move_to_front(self, unit: Unit, player: Player):
        if unit not in player.support_line:
            return False, "该单位不在支援阵线"
        if unit.has_moved:
            return False, "本回合已移动"
        if player.cp_current < unit.action_cost:
            return False, "指挥点不足"
        if self.front_owner is not None and self.front_owner is not player:
            return False, "前线被敌方控制"
        if len(self.front_units) >= self.FRONT_LIMIT:
            return False, "前线已满（最多5个）"

        player.spend_cp(unit.action_cost)
        player.support_line.remove(unit)
        self.front_units.append(unit)
        unit.is_on_front = True
        self.front_owner = player
        unit.has_moved = True

        # 坦克移动后仍可攻击，其他单位行动结束
        if unit.unit_type != UnitType.TANK:
            unit.has_attacked = True

        return True, f"{unit.name} 进入前线"

    # ---------- 攻击 ----------

    def can_attack_target(self, attacker, target, atk_owner, tgt_owner):
        if attacker.attack_count >= attacker.max_attacks:
            return False, "本回合攻击次数已达上限"
        if atk_owner.cp_current < attacker.action_cost:
            return False, "指挥点不足"

        if attacker.unit_type in (UnitType.INFANTRY, UnitType.TANK):
            if not attacker.is_on_front:
                return False, f"{attacker.unit_type.value}需先移动到前线"

        if isinstance(target, Unit):
            if target.has_keyword(Keyword.SMOKE):
                return False, "目标处于烟幕保护中"
            if self._is_protected_by_guard(target, attacker, tgt_owner):
                return False, "目标被守护单位保护"

        # 轰炸机拦截
        if attacker.unit_type == UnitType.BOMBER and isinstance(target, Unit):
            if target.unit_type != UnitType.FIGHTER:
                line = (tgt_owner.support_line
                        if target in tgt_owner.support_line
                        else self.front_units)
                if any(u.unit_type == UnitType.FIGHTER for u in line):
                    return False, "同阵线存在敌方战斗机拦截"

        return True, ""

    def _is_protected_by_guard(self, target, attacker, tgt_owner):
        if attacker.unit_type in (UnitType.ARTILLERY, UnitType.BOMBER):
            return False
        if target.has_keyword(Keyword.GUARD):
            return False

        if target in tgt_owner.support_line:
            line = tgt_owner.support_line
        elif target in self.front_units:
            line = self.front_units
        else:
            return False

        idx = line.index(target)
        neighbors = []
        if idx > 0:
            neighbors.append(line[idx - 1])
        if idx < len(line) - 1:
            neighbors.append(line[idx + 1])
        return any(u.has_keyword(Keyword.GUARD) for u in neighbors)

    def execute_attack(self, attacker, target, atk_owner, tgt_owner):
        events = []
        if not atk_owner.spend_cp(attacker.action_cost):
            return events

        attacker.attack_count += 1
        if attacker.max_attacks <= 1:
            attacker.has_attacked = True

        target_is_unit = isinstance(target, Unit)
        atk_damage = attacker.attack
        counter_damage = 0

        if target_is_unit:
            # 伏击
            if (target.has_keyword(Keyword.AMBUSH)
                    and not target.ambush_used
                    and not target.has_attacked):
                counter_damage = target.attack
                target.ambush_used = True
                target.has_attacked = True
                events.append(f"{target.name} 触发伏击")

            actual = target.take_damage(atk_damage, is_combat=True)
            events.append(f"{attacker.name}→{target.name} 造成 {actual} 伤害")

            # 反击伤害计算
            if counter_damage == 0:
                if attacker.unit_type == UnitType.ARTILLERY:
                    counter_damage = 0
                elif attacker.unit_type == UnitType.BOMBER:
                    counter_damage = (target.attack
                                      if target.unit_type == UnitType.FIGHTER
                                      else 0)
                elif target.unit_type == UnitType.BOMBER:
                    counter_damage = 0
                else:
                    counter_damage = target.attack

                if Keyword.ARMOR in attacker.keywords:
                    counter_damage = max(0, counter_damage - attacker.armor_value)

            if counter_damage > 0:
                actual_counter = attacker.take_damage(counter_damage, True)
                events.append(f"{target.name} 反击造成 {actual_counter} 伤害")
        else:
            tgt_owner.hq_defense -= atk_damage
            events.append(f"{attacker.name} 攻击总部 {atk_damage} 伤害")

        if target_is_unit and not target.is_alive():
            events.append(f"{target.name} 被消灭")
            self._handle_death(target, tgt_owner)
        if not attacker.is_alive():
            events.append(f"{attacker.name} 被消灭")
            self._handle_death(attacker, atk_owner)

        return events

    def _handle_death(self, unit: Unit, owner: Player):
        if unit in owner.support_line:
            owner.support_line.remove(unit)
        if unit in self.front_units:
            self.front_units.remove(unit)
            if not self.front_units:
                self.front_owner = None
        owner.discard.append(unit)

    def check_game_over(self) -> Optional[Player]:
        p_alive = self.player.is_alive
        o_alive = self.opponent.is_alive
        if not p_alive and not o_alive:
            return None
        if not p_alive:
            return self.opponent
        if not o_alive:
            return self.player
        return None

    def refresh(self, player: Player, opponent: Player):
        self.player = player
        self.opponent = opponent


# ============================================================
# 五、游戏状态
# ============================================================

class GameState:

    def __init__(self, p1: Player, p2: Player):
        self.players = [p1, p2]
        self.current_idx = 0
        self.turn_number = 1
        self.game_over = False
        self.winner: Optional[Player] = None
        self.board = Board(p1, p2)

        p1.draw(4)
        p2.draw(5)
        self._start_turn()

    @property
    def current_player(self) -> Player:
        return self.players[self.current_idx]

    @property
    def opponent_player(self) -> Player:
        return self.players[1 - self.current_idx]

    def _start_turn(self):
        p = self.current_player
        p.start_turn()
        # 先手首回合不抽牌
        if not (self.turn_number == 1 and self.current_idx == 0):
            p.draw(1)
        self.board.refresh(p, self.opponent_player)

    def end_turn(self):
        winner = self.board.check_game_over()
        if winner:
            self.game_over = True
            self.winner = winner
            return
        self.current_idx = 1 - self.current_idx
        if self.current_idx == 0:
            self.turn_number += 1
        self._start_turn()

    def play_unit(self, hand_index: int):
        p = self.current_player
        if hand_index >= len(p.hand):
            return False, "无效索引"
        card = p.hand[hand_index]
        if not isinstance(card, Unit):
            return False, "不是单位卡"
        ok, msg = self.board.deploy_unit(card, p)
        if ok:
            p.hand.pop(hand_index)
        return ok, msg


# ============================================================
# 六、卡牌数据库
# ============================================================

def make_unit(name, nation, cost, utype, atk, hp, ac=1,
              rarity=Rarity.COMMON, kws=None, armor=0):
    return Unit(
        name=name, nation=nation, card_type=CardType.UNIT,
        deploy_cost=cost, rarity=rarity,
        unit_type=utype, attack=atk, health=hp, action_cost=ac,
        keywords=kws or [], armor_value=armor,
    )


CARD_POOL = [
    # ---- 德国 ----
    make_unit("35(t)坦克", Nation.GERMANY, 2, UnitType.TANK, 2, 2, 1,
              Rarity.COMMON, [Keyword.BLITZ]),
    make_unit("第7装甲师", Nation.GERMANY, 4, UnitType.TANK, 3, 4, 2,
              Rarity.LIMITED, [Keyword.GUARD]),
    make_unit("88毫米高射炮", Nation.GERMANY, 3, UnitType.ARTILLERY, 2, 2),
    make_unit("Bf 109 战斗机", Nation.GERMANY, 3, UnitType.FIGHTER, 3, 2),
    make_unit("Ju 87 斯图卡", Nation.GERMANY, 4, UnitType.BOMBER, 3, 2, 1,
              Rarity.LIMITED),
    # ---- 苏联 ----
    make_unit("第5步兵连", Nation.SOVIET, 2, UnitType.INFANTRY, 2, 4, 2),
    make_unit("T-34 坦克", Nation.SOVIET, 4, UnitType.TANK, 4, 4, 2,
              Rarity.LIMITED, [Keyword.BLITZ]),
    make_unit("喀秋莎火箭炮", Nation.SOVIET, 5, UnitType.ARTILLERY, 3, 2, 2,
              Rarity.SPECIAL),
    make_unit("雅克-9 战斗机", Nation.SOVIET, 3, UnitType.FIGHTER, 3, 3),
    make_unit("伊尔-2 攻击机", Nation.SOVIET, 4, UnitType.BOMBER, 3, 3),
    # ---- 英国 ----
    make_unit("25磅炮", Nation.UK, 3, UnitType.ARTILLERY, 2, 2, 1,
              Rarity.LIMITED),
    make_unit("喷火战斗机", Nation.UK, 4, UnitType.FIGHTER, 3, 3, 1,
              Rarity.LIMITED),
    make_unit("兰开斯特轰炸机", Nation.UK, 6, UnitType.BOMBER, 5, 4, 2,
              Rarity.SPECIAL),
    make_unit("丘吉尔坦克", Nation.UK, 5, UnitType.TANK, 4, 6, 2,
              Rarity.COMMON, [Keyword.ARMOR], 1),
    # ---- 美国 ----
    make_unit("谢尔曼坦克", Nation.USA, 4, UnitType.TANK, 3, 4, 2),
    make_unit("P-51 野马", Nation.USA, 5, UnitType.FIGHTER, 4, 3, 1,
              Rarity.LIMITED),
    make_unit("B-17 空中堡垒", Nation.USA, 7, UnitType.BOMBER, 6, 5, 2,
              Rarity.SPECIAL),
    make_unit("101空降师", Nation.USA, 3, UnitType.INFANTRY, 3, 3, 1,
              Rarity.COMMON, [Keyword.BLITZ]),
    # ---- 日本 ----
    make_unit("零式战斗机", Nation.JAPAN, 3, UnitType.FIGHTER, 3, 2),
    make_unit("九七式坦克", Nation.JAPAN, 2, UnitType.TANK, 2, 3, 1,
              Rarity.COMMON, [Keyword.BLITZ]),
    make_unit("山本五十六", Nation.JAPAN, 6, UnitType.BOMBER, 5, 5, 1,
              Rarity.ELITE, [Keyword.FURIOUS]),
]

NATIONS = [Nation.GERMANY, Nation.UK, Nation.SOVIET, Nation.USA, Nation.JAPAN]
RARITY_MAX = {
    Rarity.COMMON: 4,
    Rarity.LIMITED: 3,
    Rarity.SPECIAL: 2,
    Rarity.ELITE: 1,
}


def build_random_deck(nation: Nation, seed: Optional[int] = None) -> list:
    rng = random.Random(seed)
    pool = [u for u in CARD_POOL if u.nation == nation] or CARD_POOL
    deck = [copy.deepcopy(rng.choice(pool)) for _ in range(40)]
    rng.shuffle(deck)
    return deck


# ============================================================
# 七、配色与工具函数
# ============================================================

WINDOW_W, WINDOW_H = 1400, 900

NATION_COLOR = {
    Nation.GERMANY: (90, 90, 105),
    Nation.UK: (130, 95, 65),
    Nation.SOVIET: (145, 50, 50),
    Nation.USA: (65, 95, 70),
    Nation.JAPAN: (175, 175, 185),
}

BG = (25, 30, 40)
PANEL = (30, 38, 52)
LINE_BG = (40, 50, 65)
FRONT_BG = (55, 65, 85)
SLOT_BG = (50, 60, 75)
SLOT_BORDER = (100, 115, 140)
SLOT_HOVER = (80, 100, 130)

TEXT = (240, 240, 240)
TEXT_DIM = (160, 160, 170)
GOLD = (255, 215, 100)
RED = (220, 70, 70)
GREEN = (100, 220, 100)
BLUE = (100, 170, 255)


def draw_text(screen, font, text, pos, color=TEXT, center=False):
    surf = font.render(text, True, color)
    if center:
        pos = (pos[0] - surf.get_width() // 2,
               pos[1] - surf.get_height() // 2)
    screen.blit(surf, pos)
    return surf


def draw_button(screen, rect, font, text, hover=False,
                primary=True, disabled=False):
    if disabled:
        bg, border, fg = (45, 45, 50), (70, 70, 75), (110, 110, 110)
    elif primary:
        bg = (70, 120, 90) if hover else (55, 90, 70)
        border = (140, 230, 170) if hover else (110, 180, 130)
        fg = TEXT
    else:
        bg = (75, 90, 115) if hover else (55, 70, 90)
        border = (140, 180, 240) if hover else (100, 130, 170)
        fg = TEXT
    pygame.draw.rect(screen, bg, rect, border_radius=8)
    pygame.draw.rect(screen, border, rect, 2, border_radius=8)
    draw_text(screen, font, text, rect.center, fg, center=True)


def draw_slot(screen, rect, hover=False):
    color = SLOT_HOVER if hover else SLOT_BG
    pygame.draw.rect(screen, color, rect, border_radius=8)
    pygame.draw.rect(screen, SLOT_BORDER, rect, 2, border_radius=8)


# ============================================================
# 八、主菜单
# ============================================================

class MainMenu:

    def __init__(self, app):
        self.app = app
        cx = WINDOW_W // 2
        bw, bh, by, gap = 320, 62, 320, 22
        self.btn_deck = pygame.Rect(cx - bw // 2, by, bw, bh)
        self.btn_ai = pygame.Rect(cx - bw // 2, by + bh + gap, bw, bh)
        self.btn_pvp = pygame.Rect(cx - bw // 2, by + 2 * (bh + gap), bw, bh)
        self.btn_quit = pygame.Rect(cx - bw // 2, by + 3 * (bh + gap), bw, bh)
        self.hover = None

    def handle_events(self, events):
        for e in events:
            if e.type == pygame.MOUSEMOTION:
                self.hover = None
                buttons = [
                    ("deck", self.btn_deck),
                    ("ai", self.btn_ai),
                    ("pvp", self.btn_pvp),
                    ("quit", self.btn_quit),
                ]
                for name, rect in buttons:
                    if rect.collidepoint(e.pos):
                        self.hover = name
                        break
            elif e.type == pygame.MOUSEBUTTONDOWN and e.button == 1:
                p = e.pos
                if self.btn_deck.collidepoint(p):
                    self.app.go("deck")
                elif self.btn_ai.collidepoint(p):
                    self.app.start_game("ai")
                elif self.btn_pvp.collidepoint(p):
                    self.app.start_game("pvp")
                elif self.btn_quit.collidepoint(p):
                    self.app.running = False

    def draw(self):
        s = self.app.screen
        s.fill(BG)

        pygame.draw.rect(s, (35, 45, 65), pygame.Rect(0, 100, WINDOW_W, 160))
        pygame.draw.line(s, GOLD, (0, 100), (WINDOW_W, 100), 2)
        pygame.draw.line(s, GOLD, (0, 260), (WINDOW_W, 260), 2)

        draw_text(s, self.app.f_huge, "KARDS", (WINDOW_W // 2, 160),
                  GOLD, center=True)
        draw_text(s, self.app.f_large, "二战卡牌策略游戏 · Python 版",
                  (WINDOW_W // 2, 225), TEXT_DIM, center=True)

        draw_button(s, self.btn_deck, self.app.f_large, "卡组构筑",
                    self.hover == "deck", primary=False)
        draw_button(s, self.btn_ai, self.app.f_large, "对战 AI",
                    self.hover == "ai", primary=True)
        draw_button(s, self.btn_pvp, self.app.f_large, "双人模式",
                    self.hover == "pvp", primary=True)
        draw_button(s, self.btn_quit, self.app.f_large, "退出游戏",
                    self.hover == "quit", primary=False)

        if self.app.saved_deck:
            d = self.app.saved_deck
            ally = f" + {d['ally'].value}" if d['ally'] else ""
            info = f"当前卡组: {d['main'].value}{ally}  ({len(d['cards'])}/40)"
            draw_text(s, self.app.f_medium, info,
                      (WINDOW_W // 2, 720), GREEN, center=True)
        else:
            draw_text(s, self.app.f_medium, "未保存卡组（游戏将使用随机卡组）",
                      (WINDOW_W // 2, 720), TEXT_DIM, center=True)

        draw_text(s, self.app.f_small,
                  "提示：卡组构筑完成并保存后，对战将使用你的卡组",
                  (WINDOW_W // 2, 820), TEXT_DIM, center=True)


# ============================================================
# 九、卡组构筑
# ============================================================

class DeckBuilder:

    CARD_W, CARD_H = 118, 158
    CARD_GAP = 12
    COLS = 7

    def __init__(self, app):
        self.app = app
        self.main_nation = Nation.GERMANY
        self.ally_nation: Optional[Nation] = None
        self.counts: dict = {}

        self.grid_x, self.grid_y = 300, 130
        self.scroll = 0
        self.hover_card = None

        self.nation_btns = {
            n: pygame.Rect(20 + i * 130, 20, 120, 40)
            for i, n in enumerate(NATIONS)
        }
        self.ally_btns = {
            n: pygame.Rect(20 + i * 90, 68, 80, 28)
            for i, n in enumerate(NATIONS)
        }
        self.btn_back = pygame.Rect(WINDOW_W - 110, 20, 90, 40)
        self.btn_save = pygame.Rect(20, 260, 240, 55)
        self.btn_clear = pygame.Rect(20, 330, 240, 42)
        self.btn_random = pygame.Rect(20, 385, 240, 42)

        self.flash_msg = ""
        self.flash_timer = 0.0

    # ---------- 数据 ----------

    def get_pool(self) -> list:
        nations = {self.main_nation}
        if self.ally_nation:
            nations.add(self.ally_nation)
        return [c for c in CARD_POOL if c.nation in nations]

    def get_total(self) -> int:
        return sum(self.counts.values())

    def get_ally_count(self) -> int:
        total = 0
        for name, count in self.counts.items():
            for card in CARD_POOL:
                if card.name == name and card.nation == self.ally_nation:
                    total += count
                    break
        return total

    def flash(self, msg, t=1.6):
        self.flash_msg = msg
        self.flash_timer = t

    def add_card(self, card):
        if card.nation != self.main_nation and card.nation != self.ally_nation:
            return self.flash("该卡不属于当前国家")
        if self.get_total() >= 40:
            return self.flash("卡组已满 40 张")
        if card.nation == self.ally_nation:
            if card.rarity == Rarity.ELITE:
                return self.flash("盟国不能使用精英卡")
            if self.get_ally_count() >= 12:
                return self.flash("盟国卡牌已达 12 张上限")
        cur = self.counts.get(card.name, 0)
        if cur >= RARITY_MAX[card.rarity]:
            return self.flash(f"'{card.name}' 已达上限")
        self.counts[card.name] = cur + 1

    def remove_card(self, card):
        cur = self.counts.get(card.name, 0)
        if cur > 1:
            self.counts[card.name] = cur - 1
        elif cur == 1:
            del self.counts[card.name]

    def clear(self):
        self.counts.clear()
        self.flash("已清空")

    def random_fill(self):
        self.counts.clear()
        pool = self.get_pool()
        while self.get_total() < 40:
            candidates = [
                c for c in pool
                if self.counts.get(c.name, 0) < RARITY_MAX[c.rarity]
                and (c.nation != self.ally_nation
                     or (c.rarity != Rarity.ELITE
                         and self.get_ally_count() < 12))
            ]
            if not candidates:
                break
            self.add_card(random.choice(candidates))

    def save(self):
        if self.get_total() != 40:
            return self.flash(f"需要 40 张，当前 {self.get_total()} 张")
        cards = []
        for name, count in self.counts.items():
            for _ in range(count):
                for c in CARD_POOL:
                    if c.name == name:
                        cards.append(copy.deepcopy(c))
                        break
        self.app.saved_deck = {
            "main": self.main_nation,
            "ally": self.ally_nation,
            "cards": cards,
        }
        self.flash("卡组已保存！")

    # ---------- 事件 ----------

    def handle_events(self, events):
        for e in events:
            if e.type == pygame.MOUSEBUTTONDOWN:
                if e.button == 1:
                    self._on_left_click(e.pos)
                elif e.button == 3:
                    idx = self._card_at(e.pos)
                    if idx is not None:
                        self.remove_card(self.get_pool()[idx])
            elif e.type == pygame.MOUSEMOTION:
                self.hover_card = self._card_at(e.pos)
            elif e.type == pygame.MOUSEWHEEL:
                self.scroll -= e.y * 40
                self._clamp_scroll()

    def _on_left_click(self, pos):
        if self.btn_back.collidepoint(pos):
            return self.app.go("menu")
        if self.btn_save.collidepoint(pos):
            return self.save()
        if self.btn_clear.collidepoint(pos):
            self.counts.clear()
            return self.flash("已清空")
        if self.btn_random.collidepoint(pos):
            return self.random_fill()

        for n, r in self.nation_btns.items():
            if r.collidepoint(pos):
                if self.main_nation != n:
                    self.main_nation = n
                    if self.ally_nation == n:
                        self.ally_nation = None
                    self.counts.clear()
                return
        for n, r in self.ally_btns.items():
            if r.collidepoint(pos):
                self.ally_nation = None if n == self.main_nation else n
                self.counts.clear()
                return

        idx = self._card_at(pos)
        if idx is not None:
            self.add_card(self.get_pool()[idx])

    def _card_rect(self, i):
        col = i % self.COLS
        row = i // self.COLS
        x = self.grid_x + col * (self.CARD_W + self.CARD_GAP)
        y = self.grid_y + row * (self.CARD_H + self.CARD_GAP) - self.scroll
        if y + self.CARD_H < 110 or y > WINDOW_H:
            return None
        return pygame.Rect(x, y, self.CARD_W, self.CARD_H)

    def _card_at(self, pos):
        pool = self.get_pool()
        for i in range(len(pool)):
            r = self._card_rect(i)
            if r and r.collidepoint(pos):
                return i
        return None

    def _clamp_scroll(self):
        rows = (len(self.get_pool()) + self.COLS - 1) // self.COLS
        max_scroll = max(0, rows * (self.CARD_H + self.CARD_GAP)
                         - (WINDOW_H - self.grid_y - 30))
        self.scroll = max(0, min(self.scroll, max_scroll))

    # ---------- 更新与绘制 ----------

    def update(self, dt):
        if self.flash_timer > 0:
            self.flash_timer -= dt

    def draw(self):
        s = self.app.screen
        s.fill(BG)

        # 顶部栏
        pygame.draw.rect(s, (32, 40, 55), pygame.Rect(0, 0, WINDOW_W, 110))
        pygame.draw.line(s, GOLD, (0, 110), (WINDOW_W, 110), 2)

        draw_text(s, self.app.f_small, "主国", (20, 4), TEXT_DIM)
        for n, r in self.nation_btns.items():
            active = n == self.main_nation
            pygame.draw.rect(s, NATION_COLOR[n] if active else (45, 55, 70),
                             r, border_radius=6)
            pygame.draw.rect(s, GOLD if active else (80, 90, 110),
                             r, 2, border_radius=6)
            draw_text(s, self.app.f_small, n.value, r.center, TEXT, center=True)

        draw_text(s, self.app.f_small, "盟国（最多 12 张）", (20, 50), TEXT_DIM)
        for n, r in self.ally_btns.items():
            if n == self.main_nation:
                bg, border = (35, 40, 50), (60, 65, 75)
            elif n == self.ally_nation:
                bg, border = NATION_COLOR[n], GOLD
            else:
                bg, border = (45, 55, 70), (80, 90, 110)
            pygame.draw.rect(s, bg, r, border_radius=5)
            pygame.draw.rect(s, border, r, 2, border_radius=5)
            draw_text(s, self.app.f_tiny, n.value, r.center, TEXT, center=True)

        draw_button(s, self.btn_back, self.app.f_small, "返回", primary=False)
        draw_text(s, self.app.f_large, "卡组构筑",
                  (WINDOW_W - 180, 65), GOLD)

        # 左侧面板
        pygame.draw.rect(s, PANEL, pygame.Rect(0, 110, 280, WINDOW_H - 110))
        total = self.get_total()
        draw_text(s, self.app.f_huge, f"{total}/40",
                  (20, 140), GREEN if total == 40 else TEXT)
        draw_text(s, self.app.f_small, "卡牌总数", (20, 210), TEXT_DIM)
        ally_c = self.get_ally_count()
        draw_text(s, self.app.f_medium, f"盟国: {ally_c}/12",
                  (20, 235), RED if ally_c > 12 else TEXT)

        draw_button(s, self.btn_save, self.app.f_large, "保存卡组",
                    primary=True, disabled=(total != 40))
        draw_button(s, self.btn_clear, self.app.f_medium, "清空", primary=False)
        draw_button(s, self.btn_random, self.app.f_medium, "随机填充",
                    primary=False)
        draw_text(s, self.app.f_tiny, "左键点击卡牌：添加", (20, 450), TEXT_DIM)
        draw_text(s, self.app.f_tiny, "右键点击卡牌：移除", (20, 470), TEXT_DIM)

        # 卡牌网格
        pool = self.get_pool()
        for i, card in enumerate(pool):
            r = self._card_rect(i)
            if r:
                self._draw_mini_card(s, card, r, self.hover_card == i)

        # 右侧已选列表
        panel_x = WINDOW_W - 240
        pygame.draw.rect(s, PANEL,
                         pygame.Rect(panel_x, 110, 240, WINDOW_H - 110))
        draw_text(s, self.app.f_medium, "已选卡牌", (panel_x + 15, 120), GOLD)
        y = 160
        for name, count in sorted(self.counts.items()):
            if y > WINDOW_H - 30:
                break
            text = f"{count} × {name[:12]}"
            draw_text(s, self.app.f_small, text, (panel_x + 15, y), TEXT)
            y += 22

        # 消息提示
        if self.flash_timer > 0:
            surf = self.app.f_medium.render(self.flash_msg, True, GOLD)
            bg = pygame.Surface((surf.get_width() + 40,
                                 surf.get_height() + 20), pygame.SRCALPHA)
            bg.fill((0, 0, 0, 200))
            x = (WINDOW_W - bg.get_width()) // 2
            s.blit(bg, (x, 4))
            s.blit(surf, (x + 20, 14))

    def _draw_mini_card(self, s, card, rect, hover):
        draw_rect = rect.copy()
        if hover:
            draw_rect.y -= 6

        pygame.draw.rect(s, NATION_COLOR[card.nation], draw_rect, border_radius=8)
        count = self.counts.get(card.name, 0)
        if count > 0:
            pygame.draw.rect(s, GOLD, draw_rect, 3, border_radius=8)
        else:
            pygame.draw.rect(s, (140, 150, 170), draw_rect, 1, border_radius=8)

        # 费用圆圈
        cost_pos = (draw_rect.x + 20, draw_rect.y + 20)
        pygame.draw.circle(s, (20, 20, 60), cost_pos, 17)
        pygame.draw.circle(s, (150, 180, 255), cost_pos, 17, 2)
        draw_text(s, self.app.f_medium, str(card.deploy_cost),
                  cost_pos, TEXT, center=True)

        draw_text(s, self.app.f_tiny, card.unit_type.value,
                  (draw_rect.x + 6, draw_rect.y + 42), TEXT)
        draw_text(s, self.app.f_tiny, card.name[:8],
                  (draw_rect.x + 6, draw_rect.y + 62), TEXT)
        if card.keywords:
            kws = "·".join(k.value for k in card.keywords)
            draw_text(s, self.app.f_tiny, kws,
                      (draw_rect.x + 6, draw_rect.y + 80), (255, 240, 150))

        draw_text(s, self.app.f_medium, str(card.attack),
                  (draw_rect.x + 8, draw_rect.bottom - 34), RED)
        draw_text(s, self.app.f_medium, str(card.health),
                  (draw_rect.right - 22, draw_rect.bottom - 34), GREEN)

        if count > 0:
            max_c = RARITY_MAX[card.rarity]
            txt = f"{count}/{max_c}"
            surf = self.app.f_tiny.render(txt, True, (20, 20, 20))
            badge = pygame.Rect(draw_rect.right - surf.get_width() - 14,
                                draw_rect.y + 4,
                                surf.get_width() + 10, 18)
            pygame.draw.rect(s, GOLD, badge, border_radius=4)
            s.blit(surf, (badge.x + 5, badge.y + 2))


# ============================================================
# 十、游戏界面
# ============================================================

class GameScreen:

    SLOT_W, SLOT_H, SLOT_GAP = 150, 160, 20
    ENEMY_Y, FRONT_Y, PLAYER_Y = 60, 250, 440
    CARD_W, CARD_H, CARD_GAP = 130, 190, 12
    HAND_Y = 650

    def __init__(self, app, mode: str):
        self.app = app
        self.mode = mode  # "ai" 或 "pvp"

        self._build_players()
        self.game = GameState(self.p1, self.p2)
        self.board = self.game.board

        # UI 状态
        self.selected_hand = -1
        self.selected_unit = None
        self.hover_slot = None
        self.hover_card = -1
        self.message = ""
        self.message_timer = 0

        # AI 队列
        self.ai_queue = None
        self.ai_timer = 0.0

        # 交接遮罩
        self.handoff_timer = 1.5 if mode == "pvp" else 0.0

        self.end_turn_btn = pygame.Rect(WINDOW_W - 200, 595, 180, 48)
        self.back_btn = pygame.Rect(WINDOW_W - 100, 15, 85, 34)

        self._calc_rects()

    def _build_players(self):
        a = self.app
        if a.saved_deck:
            d = a.saved_deck
            self.p1 = Player("玩家1", d["main"])
            self.p1.deck = [copy.deepcopy(c) for c in d["cards"]]
            random.shuffle(self.p1.deck)
        else:
            self.p1 = Player("玩家1", Nation.GERMANY)
            self.p1.deck = build_random_deck(Nation.GERMANY,
                                             random.randint(0, 999999))

        if self.mode == "ai":
            self.p2 = Player("电脑 AI", Nation.SOVIET)
            self.p2.deck = build_random_deck(Nation.SOVIET,
                                             random.randint(0, 999999))
        else:
            self.p2 = Player("玩家2", Nation.SOVIET)
            self.p2.deck = build_random_deck(Nation.SOVIET,
                                             random.randint(0, 999999))

    def _calc_rects(self):
        total = 4 * self.SLOT_W + 3 * self.SLOT_GAP
        sx = (WINDOW_W - total) // 2
        self.enemy_support_rects = [
            pygame.Rect(sx + i * (self.SLOT_W + self.SLOT_GAP),
                        self.ENEMY_Y, self.SLOT_W, self.SLOT_H)
            for i in range(4)
        ]
        self.player_support_rects = [
            pygame.Rect(sx + i * (self.SLOT_W + self.SLOT_GAP),
                        self.PLAYER_Y, self.SLOT_W, self.SLOT_H)
            for i in range(4)
        ]
        total = 5 * self.SLOT_W + 4 * self.SLOT_GAP
        sx = (WINDOW_W - total) // 2
        self.front_rects = [
            pygame.Rect(sx + i * (self.SLOT_W + self.SLOT_GAP),
                        self.FRONT_Y, self.SLOT_W, self.SLOT_H)
            for i in range(5)
        ]

    # ---------- 事件 ----------

    def handle_events(self, events):
        for e in events:
            if self.game.game_over:
                if e.type == pygame.MOUSEBUTTONDOWN:
                    self.app.go("menu")
                continue
            if e.type == pygame.MOUSEMOTION:
                self._on_hover(e.pos)
            elif e.type == pygame.MOUSEBUTTONDOWN:
                if e.button == 1:
                    self._on_left_click(e.pos)
                elif e.button == 3:
                    self.selected_hand = -1
                    self.selected_unit = None

    def _on_hover(self, pos):
        self.hover_slot = None
        self.hover_card = -1
        for rects in (self.enemy_support_rects,
                      self.player_support_rects,
                      self.front_rects):
            for i, r in enumerate(rects):
                if r.collidepoint(pos):
                    self.hover_slot = (id(rects), i)
                    return
        for i, r in enumerate(self._hand_rects()):
            if r.collidepoint(pos):
                self.hover_card = i
                return

    def _on_left_click(self, pos):
        if self.back_btn.collidepoint(pos):
            return self.app.go("menu")
        if self.end_turn_btn.collidepoint(pos):
            return self._end_turn()

        # AI 回合禁止玩家操作
        if self.mode == "ai" and self.game.current_idx != 0:
            return

        for i, r in enumerate(self._hand_rects()):
            if r.collidepoint(pos):
                return self._pick_hand(i)
        for i, r in enumerate(self.player_support_rects):
            if r.collidepoint(pos):
                return self._pick_support(i)
        for i, r in enumerate(self.front_rects):
            if r.collidepoint(pos):
                return self._pick_front(i)
        for i, r in enumerate(self.enemy_support_rects):
            if r.collidepoint(pos):
                return self._pick_enemy(i)

    def _pick_hand(self, idx):
        p = self.game.current_player
        if idx >= len(p.hand):
            return
        card = p.hand[idx]
        if not isinstance(card, Unit):
            return
        if card.deploy_cost > p.cp_current:
            return self._flash("指挥点不足")
        if len(p.support_line) >= Board.SUPPORT_LIMIT:
            return self._flash("支援阵线已满")
        self.selected_hand = idx
        self.selected_unit = None

    def _pick_support(self, idx):
        p = self.game.current_player
        if self.selected_hand >= 0:
            if idx == len(p.support_line):
                ok, msg = self.game.play_unit(self.selected_hand)
                self._flash(msg)
                self.selected_hand = -1
            else:
                self._flash("请点击下一个空槽")
            return
        if idx < len(p.support_line):
            unit = p.support_line[idx]
            if self.selected_unit is unit:
                self.selected_unit = None
            else:
                self.selected_unit = unit
                self.selected_hand = -1

    def _pick_front(self, idx):
        p = self.game.current_player
        if self.selected_hand >= 0:
            return self._flash("请先部署单位")
        if self.selected_unit is not None:
            unit = self.selected_unit
            if idx == len(self.board.front_units):
                if unit in p.support_line:
                    ok, msg = self.board.move_to_front(unit, p)
                    self._flash(msg)
                    if ok:
                        self.selected_unit = None
                else:
                    self._flash("该单位不在支援阵线")
            else:
                self._flash("请点击前线空槽")
            return
        if idx < len(self.board.front_units):
            unit = self.board.front_units[idx]
            if unit in p.support_line:
                self.selected_unit = unit
            else:
                self._flash("这是敌方单位")

    def _pick_enemy(self, idx):
        opp = self.game.opponent_player
        if self.selected_unit is None:
            return self._flash("请先选择攻击单位")
        if idx >= len(opp.support_line):
            return
        self._try_attack(opp.support_line[idx])

    def _try_attack(self, target):
        p = self.game.current_player
        opp = self.game.opponent_player
        attacker = self.selected_unit
        if attacker is None:
            return
        ok, reason = self.board.can_attack_target(attacker, target, p, opp)
        if not ok:
            return self._flash(reason)
        events = self.board.execute_attack(attacker, target, p, opp)
        self._flash(" | ".join(events[:2]))
        self.selected_unit = None

    def _flash(self, msg, t=1.8):
        self.message = msg
        self.message_timer = t

    def _end_turn(self):
        self.selected_hand = -1
        self.selected_unit = None
        self.ai_queue = None
        self.game.end_turn()
        if self.mode == "pvp" and not self.game.game_over:
            self.handoff_timer = 1.2

    # ---------- 更新 ----------

    def update(self, dt):
        if self.message_timer > 0:
            self.message_timer -= dt
        if self.handoff_timer > 0:
            self.handoff_timer -= dt

        if self.game.game_over:
            return
        if self.mode == "ai" and self.game.current_idx == 1:
            self._update_ai(dt)

    def _update_ai(self, dt):
        if self.ai_queue is None:
            self.ai_queue = self._build_ai_actions()
            self.ai_timer = 0.6
            return
        self.ai_timer -= dt
        if self.ai_timer > 0:
            return
        if self.ai_queue:
            action = self.ai_queue.pop(0)
            action()
            self.ai_timer = 0.3
        else:
            self.ai_queue = None
            self.game.end_turn()

    def _build_ai_actions(self):
        actions = []
        p = self.game.current_player
        opp = self.game.opponent_player
        board = self.board
        board.refresh(p, opp)

        # 1. 部署
        while True:
            found = False
            for i, card in enumerate(p.hand):
                if (isinstance(card, Unit)
                        and card.deploy_cost <= p.cp_current
                        and len(p.support_line) < Board.SUPPORT_LIMIT):
                    def deploy(idx=i):
                        self.game.play_unit(idx)
                    actions.append(deploy)
                    found = True
                    break
            if not found:
                break

        # 2. 移动
        for u in list(p.support_line):
            if p.cp_current >= u.action_cost and not u.has_moved:
                def move(unit=u):
                    board.move_to_front(unit, p)
                actions.append(move)

        # 3. 攻击
        attackers = []
        for u in board.front_units:
            if u in p.support_line:
                attackers.append(u)
        for u in p.support_line:
            if u.unit_type in (UnitType.ARTILLERY, UnitType.FIGHTER,
                               UnitType.BOMBER):
                attackers.append(u)

        for a in attackers:
            if a.attack_count >= a.max_attacks:
                continue
            if p.cp_current < a.action_cost:
                break
            targets = list(opp.support_line) + [
                u for u in board.front_units if u not in p.support_line
            ]
            seen = set()
            unique = []
            for t in targets:
                if id(t) not in seen:
                    seen.add(id(t))
                    unique.append(t)
            killable = [t for t in unique if t.current_health <= a.attack]
            chosen = killable[0] if killable else None
            if chosen is None:
                def attack_hq(att=a):
                    board.execute_attack(att, "HQ", p, opp)
                actions.append(attack_hq)
            else:
                def attack_unit(att=a, t=chosen):
                    board.execute_attack(att, t, p, opp)
                actions.append(attack_unit)
        return actions

    # ---------- 绘制 ----------

    def _hand_rects(self):
        p = self.game.current_player
        n = len(p.hand)
        if n == 0:
            return []
        total_w = n * self.CARD_W + (n - 1) * self.CARD_GAP
        max_w = WINDOW_W - 60
        if total_w > max_w:
            shrink = max_w / total_w
            cw = int(self.CARD_W * shrink)
            gap = int(self.CARD_GAP * shrink)
        else:
            cw, gap = self.CARD_W, self.CARD_GAP
        total_w = n * cw + (n - 1) * gap
        sx = (WINDOW_W - total_w) // 2
        return [
            pygame.Rect(sx + i * (cw + gap), self.HAND_Y, cw, self.CARD_H)
            for i in range(n)
        ]

    def draw(self):
        s = self.app.screen
        s.fill(BG)

        # 阵线背景
        pygame.draw.rect(s, LINE_BG, pygame.Rect(0, self.ENEMY_Y - 10,
                                                 WINDOW_W, self.SLOT_H + 20))
        pygame.draw.rect(s, FRONT_BG, pygame.Rect(0, self.FRONT_Y - 10,
                                                  WINDOW_W, self.SLOT_H + 20))
        pygame.draw.rect(s, LINE_BG, pygame.Rect(0, self.PLAYER_Y - 10,
                                                 WINDOW_W, self.SLOT_H + 20))

        draw_text(s, self.app.f_small, "敌方支援阵线",
                  (10, self.ENEMY_Y + 5), TEXT_DIM)
        draw_text(s, self.app.f_small, "前  线",
                  (10, self.FRONT_Y + 5), TEXT_DIM)
        draw_text(s, self.app.f_small, "我方支援阵线",
                  (10, self.PLAYER_Y + 5), TEXT_DIM)

        self._draw_top_bar(s)
        self._draw_enemy_support(s)
        self._draw_front(s)
        self._draw_player_support(s)
        self._draw_bottom_bar(s)
        self._draw_hand(s)

        # 消息提示
        if self.message_timer > 0:
            surf = self.app.f_medium.render(self.message, True, GOLD)
            bg = pygame.Surface((surf.get_width() + 30,
                                 surf.get_height() + 16), pygame.SRCALPHA)
            bg.fill((0, 0, 0, 200))
            x = (WINDOW_W - bg.get_width()) // 2
            s.blit(bg, (x, 4))
            s.blit(surf, (x + 15, 12))

        # 回合指示
        if self.mode == "pvp":
            text = f"回合 {self.game.turn_number} - {self.game.current_player.name} 行动"
            color = GOLD
        elif self.game.current_idx == 0:
            text = f"回合 {self.game.turn_number} - 你的回合"
            color = GREEN
        else:
            text = f"回合 {self.game.turn_number} - AI 思考中…"
            color = RED
        draw_text(s, self.app.f_medium, text, (WINDOW_W // 2, 14),
                  color, center=True)

        if self.game.game_over:
            self._draw_game_over(s)
        elif self.handoff_timer > 0:
            self._draw_handoff(s)

    def _draw_top_bar(self, s):
        opp = self.game.opponent_player
        info = (f"{opp.name}  总部 {opp.hq_defense}/20   "
                f"指挥 {opp.cp_current}/{opp.cp_slots}   "
                f"手牌 {len(opp.hand)}   牌库 {len(opp.deck)}")
        draw_text(s, self.app.f_medium, info, (20, 12), TEXT)
        draw_button(s, self.back_btn, self.app.f_small, "菜单", primary=False)

    def _draw_bottom_bar(self, s):
        p = self.game.current_player
        info = (f"{p.name}  总部 {p.hq_defense}/20   "
                f"指挥 {p.cp_current}/{p.cp_slots}   "
                f"手牌 {len(p.hand)}   牌库 {len(p.deck)}")
        draw_text(s, self.app.f_medium, info,
                  (20, self.PLAYER_Y + self.SLOT_H + 15), TEXT)

        my_turn = (not self.game.game_over
                   and (self.mode == "pvp" or self.game.current_idx == 0))
        draw_button(s, self.end_turn_btn, self.app.f_large, "结束回合",
                    hover=my_turn, primary=my_turn, disabled=not my_turn)

    def _draw_enemy_support(self, s):
        opp = self.game.opponent_player
        for i, r in enumerate(self.enemy_support_rects):
            draw_slot(s, r, hover=False)
            if i < len(opp.support_line):
                self._draw_unit(s, opp.support_line[i], r)

    def _draw_player_support(self, s):
        p = self.game.current_player
        for i, r in enumerate(self.player_support_rects):
            hover = (self.hover_slot
                     and self.hover_slot[0] == id(self.player_support_rects)
                     and self.hover_slot[1] == i)
            draw_slot(s, r, hover and self.selected_hand >= 0)
            if i < len(p.support_line):
                u = p.support_line[i]
                self._draw_unit(s, u, r, selected=(u is self.selected_unit))

    def _draw_front(self, s):
        p = self.game.current_player
        for i, r in enumerate(self.front_rects):
            hover = (self.hover_slot
                     and self.hover_slot[0] == id(self.front_rects)
                     and self.hover_slot[1] == i)
            draw_slot(s, r, hover and self.selected_unit is not None)
            if i < len(self.board.front_units):
                u = self.board.front_units[i]
                is_mine = u in p.support_line
                self._draw_unit(s, u, r,
                                selected=(u is self.selected_unit),
                                highlight=is_mine)

    def _draw_unit(self, s, unit, rect, selected=False, highlight=False):
        base = NATION_COLOR[unit.nation]
        if (unit.has_attacked
                and unit.attack_count >= unit.max_attacks):
            base = tuple(int(c * 0.55) for c in base)

        inner = rect.inflate(-10, -10)
        pygame.draw.rect(s, base, inner, border_radius=8)

        if selected:
            border, width = GOLD, 4
        elif highlight:
            border, width = BLUE, 2
        else:
            border, width = (180, 180, 180), 2
        pygame.draw.rect(s, border, inner, width, border_radius=8)

        # 兵种标签
        type_rect = pygame.Rect(inner.x + 4, inner.y + 4, inner.width - 8, 30)
        pygame.draw.rect(s, (0, 0, 0, 100), type_rect, border_radius=4)
        draw_text(s, self.app.f_small, unit.unit_type.value,
                  type_rect.center, TEXT, center=True)

        draw_text(s, self.app.f_tiny, unit.name[:9],
                  (inner.x + 6, inner.y + 40), TEXT)
        if unit.keywords:
            kws = "·".join(k.value for k in unit.keywords)
            draw_text(s, self.app.f_tiny, kws,
                      (inner.x + 6, inner.y + 58), (255, 240, 150))

        draw_text(s, self.app.f_large, str(unit.attack),
                  (inner.x + 10, inner.bottom - 42), RED)
        hp_color = (RED if unit.current_health < unit.health else GREEN)
        draw_text(s, self.app.f_large, str(unit.current_health),
                  (inner.right - 38, inner.bottom - 42), hp_color)
        draw_text(s, self.app.f_tiny, f"行动{unit.action_cost}",
                  (inner.x + 6, inner.bottom - 18), (200, 220, 255))

        if (unit.has_moved and unit.has_attacked
                and unit.attack_count >= unit.max_attacks):
            ov = pygame.Surface(inner.size, pygame.SRCALPHA)
            ov.fill((0, 0, 0, 90))
            s.blit(ov, inner.topleft)

        if unit.has_keyword(Keyword.GUARD):
            pygame.draw.circle(s, GOLD, (inner.x + 16, inner.y + 16), 9)
            draw_text(s, self.app.f_tiny, "守",
                      (inner.x + 16, inner.y + 16), (30, 30, 30), center=True)

    def _draw_hand(self, s):
        p = self.game.current_player
        my = (self.mode == "pvp") or self.game.current_idx == 0
        for i, r in enumerate(self._hand_rects()):
            if i >= len(p.hand):
                break
            card = p.hand[i]
            playable = (card.deploy_cost <= p.cp_current
                        and len(p.support_line) < Board.SUPPORT_LIMIT
                        and my)
            self._draw_card(s, card, r,
                            selected=(i == self.selected_hand),
                            hover=(i == self.hover_card),
                            playable=playable)

    def _draw_card(self, s, card, rect, selected=False, hover=False,
                   playable=True):
        base = NATION_COLOR[card.nation]
        if not playable:
            base = tuple(int(c * 0.55) for c in base)

        draw_rect = rect.copy()
        if hover:
            draw_rect.y -= 15

        pygame.draw.rect(s, base, draw_rect, border_radius=10)
        if selected:
            border, width = GOLD, 4
        elif hover:
            border, width = (255, 255, 255), 3
        else:
            border, width = (180, 180, 180), 2
        pygame.draw.rect(s, border, draw_rect, width, border_radius=10)

        cost_pos = (draw_rect.x + 22, draw_rect.y + 22)
        pygame.draw.circle(s, (20, 20, 60), cost_pos, 20)
        pygame.draw.circle(s, (150, 180, 255), cost_pos, 20, 2)
        draw_text(s, self.app.f_cost, str(card.deploy_cost),
                  cost_pos, TEXT, center=True)

        draw_text(s, self.app.f_small, card.unit_type.value,
                  (draw_rect.x + 8, draw_rect.y + 50), TEXT)
        draw_text(s, self.app.f_small, card.name[:9],
                  (draw_rect.x + 8, draw_rect.y + 74), TEXT)
        if card.keywords:
            kws = "·".join(k.value for k in card.keywords)
            draw_text(s, self.app.f_tiny, kws,
                      (draw_rect.x + 8, draw_rect.y + 98), (255, 240, 150))

        draw_text(s, self.app.f_large, str(card.attack),
                  (draw_rect.x + 12, draw_rect.bottom - 62), RED)
        draw_text(s, self.app.f_large, str(card.health),
                  (draw_rect.right - 40, draw_rect.bottom - 62), GREEN)
        draw_text(s, self.app.f_small, f"行动{card.action_cost}",
                  (draw_rect.x + 8, draw_rect.bottom - 28), (200, 220, 255))

    def _draw_handoff(self, s):
        alpha = min(220, int(self.handoff_timer * 200))
        ov = pygame.Surface((WINDOW_W, WINDOW_H), pygame.SRCALPHA)
        ov.fill((10, 15, 25, alpha))
        s.blit(ov, (0, 0))
        draw_text(s, self.app.f_huge,
                  f"轮到 {self.game.current_player.name}",
                  (WINDOW_W // 2, WINDOW_H // 2 - 40), GOLD, center=True)
        draw_text(s, self.app.f_medium, "请对方玩家准备，手牌即将显示…",
                  (WINDOW_W // 2, WINDOW_H // 2 + 40), TEXT, center=True)

    def _draw_game_over(self, s):
        ov = pygame.Surface((WINDOW_W, WINDOW_H), pygame.SRCALPHA)
        ov.fill((0, 0, 0, 190))
        s.blit(ov, (0, 0))

        if self.game.winner is None:
            text, color = "平局", TEXT
        elif self.game.winner is self.p1:
            text = "玩家1 胜利！" if self.mode == "pvp" else "胜利！"
            color = GOLD
        else:
            text = "玩家2 胜利！" if self.mode == "pvp" else "失败……"
            color = RED

        draw_text(s, self.app.f_huge, text,
                  (WINDOW_W // 2, WINDOW_H // 2 - 40), color, center=True)
        info = (f"总部防御  玩家1: {self.p1.hq_defense}   "
                f"{self.p2.name}: {self.p2.hq_defense}")
        draw_text(s, self.app.f_large, info,
                  (WINDOW_W // 2, WINDOW_H // 2 + 40), TEXT, center=True)
        draw_text(s, self.app.f_medium, "点击任意位置返回主菜单",
                  (WINDOW_W // 2, WINDOW_H // 2 + 110), TEXT_DIM, center=True)


# ============================================================
# 十一、应用主控
# ============================================================

class App:

    def __init__(self):
        init_pygame()
        self.screen = pygame.display.set_mode((WINDOW_W, WINDOW_H))
        pygame.display.set_caption("KARDS 简化版")
        self.clock = pygame.time.Clock()
        self.running = True

        self.f_huge = FontLoader.get(64, bold=True)
        self.f_large = FontLoader.get(30, bold=True)
        self.f_medium = FontLoader.get(22)
        self.f_small = FontLoader.get(17)
        self.f_tiny = FontLoader.get(13)
        self.f_cost = FontLoader.get(24, bold=True)

        self.saved_deck: Optional[dict] = None
        self.menu = MainMenu(self)
        self.builder = DeckBuilder(self)
        self.game_screen: Optional[GameScreen] = None
        self.state = "menu"

    def go(self, state: str):
        self.state = state

    def start_game(self, mode: str):
        self.game_screen = GameScreen(self, mode)
        self.state = "game"

    def loop(self):
        while self.running:
            dt = self.clock.tick(60) / 1000.0
            events = pygame.event.get()
            for e in events:
                if e.type == pygame.QUIT:
                    self.running = False
                    break

            if self.state == "menu":
                self.menu.handle_events(events)
                self.menu.draw()
            elif self.state == "deck":
                self.builder.handle_events(events)
                self.builder.update(dt)
                self.builder.draw()
            elif self.state == "game" and self.game_screen:
                self.game_screen.handle_events(events)
                self.game_screen.update(dt)
                self.game_screen.draw()

            pygame.display.flip()

        pygame.quit()
        sys.exit()


def main():
    App().loop()


if __name__ == "__main__":
    main()