"""
死亡细胞 · 2D (Dead Cells 风格)
—— 贴地翻滚 + K 键重斩版本
运行前请安装 pygame：  pip install pygame
"""
import pygame
import math
import random
import sys
import os
import platform

# ============================================================
# 初始化
# ============================================================
pygame.init()
W, H = 960, 540
GY = 468
WORLD_W = 3000
GRAV = 2700
FPS = 60

screen = pygame.display.set_mode((W, H))
pygame.display.set_caption("死亡细胞 · 2D")
clock = pygame.time.Clock()

# ============================================================
# 中文字体（绕开 SysFont bug）
# ============================================================
def _find_cjk_font_file():
    sys_name = platform.system()
    cands = []
    if sys_name == "Windows":
        cands = ["C:/Windows/Fonts/msyh.ttc", "C:/Windows/Fonts/msyh.ttf",
                 "C:/Windows/Fonts/msyhbd.ttc", "C:/Windows/Fonts/simhei.ttf",
                 "C:/Windows/Fonts/simsun.ttc", "C:/Windows/Fonts/Deng.ttf"]
    elif sys_name == "Darwin":
        cands = ["/System/Library/Fonts/PingFang.ttc",
                 "/System/Library/Fonts/STHeiti Light.ttc"]
    else:
        cands = ["/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
                 "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc"]
    for p in cands:
        if os.path.isfile(p):
            return p
    return None

_FONT_FILE = _find_cjk_font_file()
_font_cache = {}
if _FONT_FILE:
    print("[字体] 使用中文字体:", _FONT_FILE)
else:
    print("[字体] 未找到中文字体，将使用默认字体")

def get_font(size, bold=False):
    key = (size, bold)
    if key in _font_cache:
        return _font_cache[key]
    try:
        f = pygame.font.Font(_FONT_FILE, size) if _FONT_FILE else pygame.font.Font(None, size)
    except Exception:
        f = pygame.font.Font(None, size)
    if bold:
        f.set_bold(True)
    _font_cache[key] = f
    return f

SUPPORT_ROUND = pygame.version.vernum >= (2, 0, 0)
def draw_round_rect(surface, color, rect, radius):
    if SUPPORT_ROUND and radius > 0:
        pygame.draw.rect(surface, color, rect, border_radius=radius)
    else:
        pygame.draw.rect(surface, color, rect)

# ============================================================
# 工具
# ============================================================
def clamp(v, a, b): return a if v < a else (b if v > b else v)
def lerp(a, b, t): return a + (b - a) * t

def make_ridge(seed, base_y, amp, step):
    rng = random.Random(seed)
    pts, x = [], -400
    while x <= WORLD_W + 800:
        pts.append((x, base_y - rng.random() * amp))
        x += step
    return pts

def make_gradient(w, h, stops):
    surf = pygame.Surface((w, h))
    for y in range(h):
        t = y / h
        c = stops[0][1]
        for i in range(len(stops) - 1):
            if stops[i][0] <= t <= stops[i + 1][0]:
                lt = (t - stops[i][0]) / (stops[i + 1][0] - stops[i][0])
                c1, c2 = stops[i][1], stops[i + 1][1]
                c = (int(c1[0] + (c2[0]-c1[0])*lt),
                     int(c1[1] + (c2[1]-c1[1])*lt),
                     int(c1[2] + (c2[2]-c1[2])*lt))
                break
        pygame.draw.line(surf, c, (0, y), (w - 1, y))
    return surf

# ============================================================
# 数据
# ============================================================
ATTACKS = [
    {"dur": 0.24, "from": 0.04, "to": 0.13, "dmg": 16, "range": 82,  "kb": 260},
    {"dur": 0.24, "from": 0.04, "to": 0.13, "dmg": 16, "range": 82,  "kb": 260},
    {"dur": 0.40, "from": 0.10, "to": 0.24, "dmg": 32, "range": 108, "kb": 560},
]

# 重斩参数
HEAVY = {
    "dur":   0.58,   # 总时长
    "wind":  0.24,   # 前摇结束
    "hit_a": 0.25,   # 判定开始
    "hit_b": 0.42,   # 判定结束
    "dmg":   52,
    "range": 138,
    "kb":    760,
    "cost":  22,
}

ENEMY_TYPES = {
    "grunt": dict(hp=50,  w=30, h=50,  speed=150, aggro=350, atk_range=64,
                  windup=0.36, attack_dur=0.20, recover=0.38, cooldown=0.65,
                  lunge=240, dmg=11, c1=(74, 40, 82),  c2=(38, 20, 48)),
    "brute": dict(hp=120, w=46, h=68,  speed=110, aggro=380, atk_range=80,
                  windup=0.55, attack_dur=0.28, recover=0.60, cooldown=1.05,
                  lunge=280, dmg=19, c1=(90, 55, 32),  c2=(46, 28, 16)),
    "boss":  dict(hp=440, w=72, h=100, speed=125, aggro=430, atk_range=108,
                  windup=0.52, attack_dur=0.32, recover=0.55, cooldown=0.85,
                  lunge=320, dmg=26, c1=(120, 30, 40), c2=(60, 12, 18)),
}

# ============================================================
# 配色
# ============================================================
COL_BG_TOP    = (10, 8, 20)
COL_BG_MID1   = (26, 22, 46)
COL_BG_MID2   = (45, 32, 64)
COL_BG_LOW    = (74, 40, 80)
COL_BG_BOT    = (30, 18, 32)
COL_GROUND_1  = (18, 14, 26)
COL_GROUND_2  = (8, 6, 14)
COL_GLOW      = (140, 240, 210)
COL_BLOOD     = (214, 46, 60)
COL_GOLD      = (255, 210, 90)

# ============================================================
# 全局状态
# ============================================================
state = "title"
player = None
enemies = []
particles = []
texts = []
floats = []
cam_x = 0
shake = 0
hit_stop = 0
time_acc = 0
boss_ref = None
win_timer = 0

ridge_far  = make_ridge(11,  335, 165, 110)
ridge_mid  = make_ridge(77,  405, 115, 85)
ridge_near = make_ridge(133, 452, 75,  65)

sky_surf = make_gradient(W, H, [
    (0.00, COL_BG_TOP), (0.35, COL_BG_MID1), (0.62, COL_BG_MID2),
    (0.82, COL_BG_LOW), (1.00, COL_BG_BOT),
])

ground_surf = make_gradient(W, H - GY, [
    (0.00, COL_GROUND_1), (0.25, COL_GROUND_2), (1.00, (3, 2, 6)),
])

scene_surf = pygame.Surface((W, H), pygame.SRCALPHA)

_moon_halo = None
def get_moon_halo():
    global _moon_halo
    if _moon_halo is None:
        _moon_halo = pygame.Surface((440, 440), pygame.SRCALPHA)
        for r in range(220, 0, -6):
            a = int(55 * (1 - r / 220) ** 2)
            pygame.draw.circle(_moon_halo, (160, 220, 255, a), (220, 220), r)
    return _moon_halo

_fog_surf = None
def get_fog_surf():
    global _fog_surf
    if _fog_surf is None:
        _fog_surf = pygame.Surface((W, 180), pygame.SRCALPHA)
        for y in range(180):
            a = int(24 * (y / 180))
            pygame.draw.line(_fog_surf, (100, 140, 200, a), (0, y), (W, y))
    return _fog_surf

# ============================================================
# 玩家
# ============================================================
class Player:
    def __init__(self):
        self.x = 150
        self.y = GY
        self.vx = 0
        self.vy = 0
        self.facing = 1
        self.on_ground = True
        self.jumps = 0
        self.hp = 100
        self.max_hp = 100
        self.st = 100
        self.max_st = 100
        self.st_delay = 0
        self.attack = 0
        self.attack_dur = 0
        self.combo = 0
        self.hit_set = set()
        # 重斩
        self.heavy = 0
        self.heavy_dur = 0
        self.heavy_hit = set()
        # 翻滚
        self.roll = 0
        self.inv = 0
        self.hurt = 0
        self.anim = 0
        self.dead = False

    def rect(self):
        return pygame.Rect(self.x - 15, self.y - 52, 30, 52)

    def update(self, dt, pressed):
        if self.dead:
            return
        self.anim += dt * 14

        for attr in ("attack", "heavy", "roll", "inv", "hurt", "st_delay"):
            if getattr(self, attr) > 0:
                setattr(self, attr, getattr(self, attr) - dt)

        if self.st_delay <= 0 and self.st < self.max_st:
            self.st = min(self.max_st, self.st + 40 * dt)

        attacking = self.attack > 0
        heavying  = self.heavy > 0
        rolling   = self.roll > 0
        busy = attacking or heavying or rolling

        keys = pygame.key.get_pressed()
        left  = keys[pygame.K_a] or keys[pygame.K_LEFT]
        right = keys[pygame.K_d] or keys[pygame.K_RIGHT]

        # ---------- 移动 ----------
        if not busy:
            ax = 0
            if left:  ax -= 1
            if right: ax += 1
            if ax != 0:
                self.facing = ax
            accel = 3800 if self.on_ground else 2400
            max_spd = 380
            if ax != 0:
                self.vx += ax * accel * dt
                self.vx = clamp(self.vx, -max_spd, max_spd)
            else:
                fr = (3800 if self.on_ground else 800) * dt
                if abs(self.vx) <= fr: self.vx = 0
                else: self.vx -= math.copysign(fr, self.vx)
        elif attacking:
            self.vx -= self.vx * min(1, 12 * dt)
        # heavying / rolling 的 vx 在下面独立处理

        # ---------- 跳跃（空格 / ↑） ----------
        if (pygame.K_SPACE in pressed or pygame.K_UP in pressed) and not busy:
            if self.on_ground:
                self.vy = -880
                self.on_ground = False
                self.jumps = 1
                spawn_dust(self.x, GY, 6)
            elif self.jumps < 2:
                self.vy = -760
                self.jumps = 2
                for _ in range(8):
                    particles.append({
                        "x": self.x, "y": self.y - 10,
                        "vx": random.uniform(-150, 150),
                        "vy": random.uniform(-40, 120),
                        "life": 0.4, "max": 0.4,
                        "size": random.uniform(2, 4),
                        "color": COL_GLOW, "grav": 0,
                    })

        # ---------- 贴地翻滚 ----------
        if ((pygame.K_l in pressed or pygame.K_LSHIFT in pressed)
                and self.roll <= 0 and self.st >= 30
                and not attacking and not heavying and self.on_ground):
            self.roll = 0.30
            self.inv = 0.32
            self.st -= 30
            self.st_delay = 0.55
            self.vx = self.facing * 700
            self.vy = 0
            spawn_dust(self.x, self.y - 6, 12, (140, 240, 210))

        # ---------- 重斩 ----------
        if (pygame.K_k in pressed and self.attack <= 0 and self.heavy <= 0
                and self.roll <= 0 and self.on_ground and self.st >= HEAVY["cost"]):
            self.heavy = HEAVY["dur"]
            self.heavy_dur = HEAVY["dur"]
            self.heavy_hit = set()
            self.st -= HEAVY["cost"]
            self.st_delay = 0.65
            self.vx = 0
            # 蓄力扬尘
            spawn_dust(self.x + self.facing * 10, GY, 10, (90, 200, 190))

        # ---------- 普通三段 ----------
        if (pygame.K_j in pressed and self.attack <= 0
                and self.heavy <= 0 and self.roll <= 0):
            self.combo = (self.combo % 3) + 1
            a = ATTACKS[self.combo - 1]
            self.attack = a["dur"]
            self.attack_dur = a["dur"]
            self.hit_set = set()
            self.st = max(0, self.st - 4)
            self.st_delay = 0.40
            self.vx += self.facing * (200 if self.combo == 3 else 110)

        # ---------- 普通攻击判定 ----------
        if self.attack > 0:
            a = ATTACKS[self.combo - 1]
            elapsed = self.attack_dur - self.attack
            if a["from"] <= elapsed <= a["to"]:
                if self.facing > 0:
                    hb = pygame.Rect(self.x + 4, self.y - 58, a["range"], 62)
                else:
                    hb = pygame.Rect(self.x - 4 - a["range"], self.y - 58, a["range"], 62)
                for e in enemies:
                    if e.dead or id(e) in self.hit_set: continue
                    if hb.colliderect(e.rect()):
                        self.hit_set.add(id(e))
                        damage_enemy(e, a["dmg"], self.facing, a["kb"])

        # ---------- 重斩判定 + 位移 ----------
        if self.heavy > 0:
            elapsed = self.heavy_dur - self.heavy
            if elapsed < HEAVY["wind"]:
                # 前摇：站定
                self.vx *= math.pow(0.002, dt)
            elif elapsed < HEAVY["hit_b"] + 0.02:
                # 挥砍前冲
                prog = (elapsed - HEAVY["wind"]) / (HEAVY["hit_b"] - HEAVY["wind"])
                self.vx = self.facing * 300 * (1 - prog)
            else:
                self.vx *= math.pow(0.01, dt)

            if HEAVY["hit_a"] <= elapsed <= HEAVY["hit_b"]:
                reach = HEAVY["range"]
                if self.facing > 0:
                    hb = pygame.Rect(self.x + 2, self.y - 66, reach, 74)
                else:
                    hb = pygame.Rect(self.x - 2 - reach, self.y - 66, reach, 74)
                for e in enemies:
                    if e.dead or id(e) in self.heavy_hit: continue
                    if hb.colliderect(e.rect()):
                        self.heavy_hit.add(id(e))
                        damage_enemy(e, HEAVY["dmg"], self.facing, HEAVY["kb"], heavy=True)
                        self.vx *= 0.35   # 命中顿挫

        # ---------- 物理 ----------
        if self.roll <= 0:
            self.vy += GRAV * dt
        else:
            self.vy *= math.pow(0.01, dt)

        self.x += self.vx * dt
        self.y += self.vy * dt

        self.on_ground = False
        if self.y >= GY:
            if self.vy > 500:
                spawn_dust(self.x, GY, 8)
            self.y = GY
            self.vy = 0
            self.on_ground = True
            self.jumps = 0

        self.x = clamp(self.x, 24, WORLD_W - 24)
        if self.y > H + 200:
            self.y = GY; self.vy = 0

# ============================================================
# 敌人
# ============================================================
class Enemy:
    def __init__(self, x, etype):
        t = ENEMY_TYPES[etype]
        self.type = etype
        self.x = x
        self.y = GY
        self.vx = 0
        self.w = t["w"]; self.h = t["h"]; self.facing = -1
        self.hp = t["hp"]; self.max_hp = t["hp"]
        self.speed = t["speed"]; self.aggro = t["aggro"]; self.atk_range = t["atk_range"]
        self.windup_t = t["windup"]; self.attack_dur = t["attack_dur"]
        self.recover_t = t["recover"]; self.cooldown = t["cooldown"]
        self.lunge = t["lunge"]; self.dmg = t["dmg"]
        self.c1 = t["c1"]; self.c2 = t["c2"]
        self.state = "idle"
        self.timer = 0; self.cd = 0
        self.hit_done = False; self.lunge_dir = -1
        self.hurt = 0; self.flash = 0
        self.dead = False; self.dead_timer = 0

    def rect(self):
        return pygame.Rect(self.x - self.w / 2, self.y - self.h, self.w, self.h)

    def update(self, dt):
        if self.flash > 0: self.flash -= dt
        if self.dead:
            self.dead_timer += dt
            self.x += self.vx * dt
            self.vx *= math.pow(0.05, dt)
            return
        if self.hurt > 0:
            self.hurt -= dt
            self.x += self.vx * dt
            self.vx *= math.pow(0.02, dt)
            self.x = clamp(self.x, 20, WORLD_W - 20)
            return
        if self.cd > 0: self.cd -= dt
        if self.timer > 0: self.timer -= dt

        dx = player.x - self.x
        dist = abs(dx)
        direction = 1 if dx >= 0 else -1

        if self.state == "idle":
            self.vx = 0
            if dist < self.aggro and not player.dead:
                self.state = "chase"
        elif self.state == "chase":
            self.facing = direction
            if player.dead:
                self.state = "idle"
            elif dist < self.atk_range and self.cd <= 0:
                self.state = "windup"
                self.timer = self.windup_t
                self.vx = 0
            else:
                self.vx = direction * self.speed
        elif self.state == "windup":
            self.vx *= math.pow(0.01, dt)
            if self.timer <= 0:
                self.state = "attack"
                self.timer = self.attack_dur
                self.hit_done = False
                self.lunge_dir = direction
                self.facing = direction
        elif self.state == "attack":
            self.vx = self.lunge_dir * self.lunge
            if not self.hit_done:
                reach = self.atk_range + (20 if self.type == "boss" else 0)
                if self.facing > 0:
                    hb = pygame.Rect(self.x + 4, self.y - self.h, reach, self.h * 0.95)
                else:
                    hb = pygame.Rect(self.x - 4 - reach, self.y - self.h, reach, self.h * 0.95)
                if player.inv <= 0 and not player.dead and hb.colliderect(player.rect()):
                    self.hit_done = True
                    damage_player(self.dmg, self.lunge_dir)
            if self.timer <= 0:
                self.state = "recover"; self.timer = self.recover_t
        elif self.state == "recover":
            self.vx *= math.pow(0.01, dt)
            if self.timer <= 0:
                self.state = "chase"; self.cd = self.cooldown

        self.x += self.vx * dt
        self.x = clamp(self.x, 20, WORLD_W - 20)

# ============================================================
# 特效 / 伤害
# ============================================================
def spawn_dust(x, y, n=8, color=(100, 90, 130)):
    for _ in range(n):
        particles.append({
            "x": x + random.uniform(-10, 10), "y": y,
            "vx": random.uniform(-130, 130), "vy": random.uniform(-170, -30),
            "life": random.uniform(0.25, 0.55), "max": 0.55,
            "size": random.uniform(2, 5), "color": color, "grav": 420,
        })

def spawn_blood(x, y, direction, n=18):
    for _ in range(n):
        a = random.uniform(-math.pi, math.pi)
        sp = random.uniform(150, 560)
        particles.append({
            "x": x, "y": y,
            "vx": math.cos(a) * sp + direction * 180,
            "vy": math.sin(a) * sp - 70,
            "life": random.uniform(0.25, 0.65), "max": 0.65,
            "size": random.uniform(1.8, 4.5),
            "color": random.choice([COL_BLOOD, (255, 80, 80), (180, 30, 40)]),
            "grav": 1100,
        })

def add_text(x, y, s, color, size=20):
    texts.append({"x": x, "y": y, "str": str(s),
                  "color": color, "life": 0.8, "max": 0.8, "vy": -80,
                  "size": size})

def damage_enemy(e, dmg, direction, kb, heavy=False):
    global shake, hit_stop, win_timer
    e.hp -= dmg
    e.flash = 0.12 if heavy else 0.10
    e.hurt = 0.22 if heavy else 0.16
    e.vx = direction * kb
    e.state = "chase"
    e.timer = 0

    if heavy:
        shake = max(shake, 11)
        hit_stop = 0.09
        spawn_blood(e.x + direction * 12, e.y - e.h * 0.55, direction, 34)
        add_text(e.x, e.y - e.h - 10, int(round(dmg)), (255, 240, 150), size=30)
    else:
        shake = max(shake, 6)
        hit_stop = 0.05
        spawn_blood(e.x + direction * 10, e.y - e.h * 0.55, direction)
        add_text(e.x, e.y - e.h - 8, int(round(dmg)), (255, 210, 120))

    if e.hp <= 0:
        e.hp = 0; e.dead = True; e.dead_timer = 0
        e.vx = direction * kb * 1.6
        spawn_blood(e.x, e.y - e.h * 0.55, direction, 30)
        shake = max(shake, 12)
        if e is boss_ref:
            win_timer = 0.9

def damage_player(dmg, direction):
    global shake, hit_stop, state
    p = player
    if p.dead: return
    p.hp -= dmg
    p.inv = 0.70
    p.hurt = 0.35
    p.vx = direction * 320
    p.vy = -220
    p.attack = 0; p.combo = 0; p.roll = 0; p.heavy = 0
    shake = max(shake, 10)
    hit_stop = 0.07
    spawn_blood(p.x, p.y - 30, direction, 22)
    add_text(p.x, p.y - 60, "-" + str(dmg), (255, 90, 90))
    if p.hp <= 0:
        p.hp = 0; p.dead = True
        state = "dead"

# ============================================================
# 开局
# ============================================================
def start_game():
    global player, enemies, particles, texts, floats
    global cam_x, shake, hit_stop, boss_ref, state, win_timer
    player = Player()
    enemies = []
    particles = []
    texts = []
    floats = [{"x": random.random() * WORLD_W,
               "y": random.uniform(180, 455),
               "r": random.uniform(1, 2.4),
               "ph": random.random() * math.pi * 2,
               "sp": random.uniform(0.4, 1.2)} for _ in range(80)]
    cam_x = 0; shake = 0; hit_stop = 0; win_timer = 0

    enemies.append(Enemy(760,  "grunt"))
    enemies.append(Enemy(1130, "grunt"))
    enemies.append(Enemy(1460, "grunt"))
    enemies.append(Enemy(1760, "brute"))
    enemies.append(Enemy(2130, "grunt"))
    enemies.append(Enemy(2210, "grunt"))
    boss_ref = Enemy(2660, "boss")
    enemies.append(boss_ref)
    state = "playing"

# ============================================================
# 更新
# ============================================================
def update_particles(dt):
    global shake
    for i in range(len(particles) - 1, -1, -1):
        pt = particles[i]
        pt["life"] -= dt
        if pt["life"] <= 0:
            particles.pop(i); continue
        pt["vy"] += pt["grav"] * dt
        pt["x"] += pt["vx"] * dt
        pt["y"] += pt["vy"] * dt
        pt["vx"] *= math.pow(0.35, dt)
    for i in range(len(texts) - 1, -1, -1):
        t = texts[i]
        t["life"] -= dt
        t["y"] += t["vy"] * dt
        t["vy"] *= math.pow(0.15, dt)
        if t["life"] <= 0:
            texts.pop(i)
    if shake > 0:
        shake = max(0, shake - dt * 42)

def update(dt, pressed):
    global time_acc, hit_stop, cam_x, state, win_timer, shake
    time_acc += dt

    if state == "title":
        if (pygame.K_SPACE in pressed or pygame.K_RETURN in pressed
                or pygame.K_j in pressed or pygame.K_k in pressed):
            start_game()
        return
    if state in ("dead", "win"):
        if pygame.K_r in pressed or pygame.K_RETURN in pressed:
            start_game()
        update_particles(dt)
        return
    if hit_stop > 0:
        hit_stop -= dt
        if shake > 0:
            shake = max(0, shake - dt * 42)
        return

    player.update(dt, pressed)
    for e in enemies:
        e.update(dt)
    update_particles(dt)

    if win_timer > 0:
        win_timer -= dt
        if win_timer <= 0:
            state = "win"

    target = clamp(player.x - W * 0.5, 0, WORLD_W - W)
    cam_x = lerp(cam_x, target, min(1, dt * 8))

# ============================================================
# 绘制 - 背景
# ============================================================
def draw_moon(surface):
    mx = int(730 - cam_x * 0.04)
    my = 118
    surface.blit(get_moon_halo(), (mx - 220, my - 220))
    pygame.draw.circle(surface, (220, 235, 255), (mx, my), 42)
    spot = pygame.Surface((60, 60), pygame.SRCALPHA)
    pygame.draw.circle(spot, (120, 140, 180, 45), (30, 30), 12)
    pygame.draw.circle(spot, (120, 140, 180, 45), (45, 42), 8)
    surface.blit(spot, (mx - 30, my - 30))

def draw_ridge(surface, pts, parallax, color, extra=0):
    off = cam_x * parallax
    poly = [(p[0] - off, p[1] + extra) for p in pts]
    poly.append((pts[-1][0] - off, H + 60))
    poly.append((pts[0][0] - off, H + 60))
    pygame.draw.polygon(surface, color, poly)

def draw_floats(surface):
    for f in floats:
        x = f["x"] - cam_x * 0.85
        if x < -30 or x > W + 30: continue
        y = f["y"] + math.sin(time_acc * f["sp"] + f["ph"]) * 14
        a = 0.25 + 0.35 * abs(math.sin(time_acc * f["sp"] * 1.7 + f["ph"]))
        c = (int(140 * a), int(240 * a), int(210 * a))
        pygame.draw.circle(surface, c, (int(x), int(y)), max(1, int(f["r"])))

def draw_ground(surface):
    surface.blit(ground_surf, (0, GY))
    pygame.draw.line(surface, (90, 220, 200), (0, GY), (W, GY), 2)
    pygame.draw.line(surface, (40, 100, 120), (0, GY + 2), (W, GY + 2), 1)
    for i in range(28):
        bx = (i * 137 - cam_x) % (W + 200) - 100
        y = GY + 14 + (i % 5) * 14
        pygame.draw.line(surface, (30, 40, 70),
                         (bx, y), (bx + 26 + (i % 4) * 10, y + 4), 1)

# ============================================================
# 绘制 - 贴地翻滚（独立绘制，蜷缩成球贴地滚动）
# ============================================================
def draw_player_roll(surface, sx):
    p = player
    k = 1 - p.roll / 0.30              # 0 → 1
    ang = k * math.pi * 2 * p.facing   # 滚动角

    R = 20
    cx = sx
    cy = GY - R - 1                    # 球心贴地

    # 拖尾残影
    for i in range(1, 5):
        a = 75 - i * 15
        if a <= 0: continue
        ghost = pygame.Surface((44, 44), pygame.SRCALPHA)
        pygame.draw.circle(ghost, (90, 210, 190, a), (22, 22), R - i * 2)
        surface.blit(ghost, (cx - p.facing * i * 13 - 22, cy - 22))

    # 球身（蜷缩的躯体）
    pygame.draw.circle(surface, (16, 40, 56), (cx, cy), R)
    pygame.draw.circle(surface, (44, 110, 130), (cx, cy), R - 2)
    pygame.draw.circle(surface, (70, 170, 190), (cx - 6, cy - 6), 8)
    # 斗篷阴影（随滚动旋转的暗弧）
    arc_r = R - 5
    pygame.draw.arc(surface, (14, 34, 48),
                    (cx - arc_r, cy - arc_r, arc_r * 2, arc_r * 2),
                    ang + 0.3, ang + 2.8, 5)

    # 剑：贴着球边横扫（随滚动旋转）
    sdx = math.cos(ang) * (R + 12)
    sdy = math.sin(ang) * (R + 12)
    pygame.draw.line(surface, (90, 200, 190),
                     (cx, cy), (cx + sdx, cy + sdy), 5)
    pygame.draw.line(surface, (220, 255, 250),
                     (cx, cy), (cx + sdx, cy + sdy), 2)

    # 绿色火焰头：沿球体外缘旋转
    fx = cx + math.cos(ang) * (R - 5)
    fy = cy + math.sin(ang) * (R - 5)
    flicker = math.sin(time_acc * 26) * 1.5

    glow = pygame.Surface((40, 40), pygame.SRCALPHA)
    for r in range(19, 0, -3):
        a = int(120 * (1 - r / 19) ** 1.4)
        pygame.draw.circle(glow, (120, 255, 150, a), (20, 20), r)
    surface.blit(glow, (fx - 20, fy - 20))

    pygame.draw.circle(surface, (60, 210, 110), (int(fx), int(fy)), 6)
    pygame.draw.circle(surface, (220, 255, 230),
                       (int(fx), int(fy - 2 + flicker)), 3)

    # 地面摩擦尘
    if random.random() < 0.35:
        spawn_dust(p.x, GY, 1, (120, 200, 190))

# ============================================================
# 绘制 - 玩家（站立 / 攻击 / 重斩）
# ============================================================
def draw_player(surface):
    p = player
    if p is None: return
    sx = int(p.x - cam_x)
    sy = int(p.y)

    # 影子
    shadow = pygame.Surface((50, 14), pygame.SRCALPHA)
    pygame.draw.ellipse(shadow, (0, 0, 0, 110), (0, 0, 50, 14))
    surface.blit(shadow, (sx - 25, GY - 5))

    # 贴地翻滚：单独绘制并返回
    if p.roll > 0:
        draw_player_roll(surface, sx)
        return

    SZ = 160
    local = pygame.Surface((SZ, SZ), pygame.SRCALPHA)
    ox, oy = SZ // 2, 140

    flashing = p.hurt > 0 and int(p.hurt * 30) % 2 == 0

    c_body    = (255, 255, 255) if flashing else (44, 110, 130)
    c_body_hi = (255, 255, 255) if flashing else (70, 170, 190)
    c_body_d  = (200, 200, 200) if flashing else (24, 60, 78)
    c_hood    = (255, 255, 255) if flashing else (24, 40, 60)
    c_fire_a  = (255, 255, 255) if flashing else (120, 255, 150)
    c_fire_b  = (255, 255, 255) if flashing else (60, 210, 110)
    c_blade   = (220, 255, 250)

    running = p.on_ground and abs(p.vx) > 30
    leg_swing = math.sin(p.anim) * 12 if running else (0 if p.on_ground else 6)

    # 腿
    pygame.draw.line(local, c_body_d,
                     (ox - 5, oy - 20), (ox - 5 + leg_swing * 0.55, oy - 1), 7)
    pygame.draw.line(local, c_body_d,
                     (ox + 6, oy - 20), (ox + 6 - leg_swing * 0.55, oy - 1), 7)
    pygame.draw.line(local, (10, 15, 22),
                     (ox - 5 + leg_swing * 0.55, oy - 2),
                     (ox - 1 + leg_swing * 0.55, oy - 1), 5)
    pygame.draw.line(local, (10, 15, 22),
                     (ox + 6 - leg_swing * 0.55, oy - 2),
                     (ox + 10 - leg_swing * 0.55, oy - 1), 5)

    # 躯干
    draw_round_rect(local, c_body, (ox - 12, oy - 44, 24, 27), 6)
    pygame.draw.rect(local, c_body_hi, (ox - 10, oy - 42, 6, 22))
    pygame.draw.polygon(local, c_hood, [
        (ox - 12, oy - 44), (ox + 12, oy - 44),
        (ox + 14, oy - 20), (ox - 14, oy - 20),
    ])
    pygame.draw.rect(local, (10, 15, 22), (ox - 12, oy - 28, 24, 3))
    pygame.draw.rect(local, COL_GOLD, (ox - 3, oy - 30, 6, 6))

    pygame.draw.line(local, c_body, (ox - 9, oy - 40),
                     (ox - 15, oy - 26), 5)

    # 绿色火焰头
    head_y = -58
    flicker = math.sin(time_acc * 22) * 2
    flicker2 = math.cos(time_acc * 17 + 1) * 2

    glow = pygame.Surface((60, 60), pygame.SRCALPHA)
    for r in range(28, 0, -3):
        a = int(120 * (1 - r / 28) ** 1.5)
        pygame.draw.circle(glow, (*c_fire_a, a), (30, 30), r)
    local.blit(glow, (ox - 30, oy + head_y - 30))

    fpts = [
        (ox - 8 + flicker, oy + head_y + 10),
        (ox + 8 + flicker2, oy + head_y + 10),
        (ox + 6 + flicker2, oy + head_y - 6),
        (ox + 2, oy + head_y - 14 - abs(flicker)),
        (ox - 3, oy + head_y - 8),
        (ox - 6 + flicker, oy + head_y - 2),
    ]
    pygame.draw.polygon(local, c_fire_b, fpts)
    pygame.draw.polygon(local, c_fire_a, [
        (ox - 5, oy + head_y + 6),
        (ox + 5, oy + head_y + 6),
        (ox + 3, oy + head_y - 4),
        (ox,     oy + head_y - 9 - abs(flicker2)),
        (ox - 3, oy + head_y - 4),
    ])
    pygame.draw.circle(local, (220, 255, 230),
                       (ox + int(flicker * 0.5), oy + head_y - 2), 2)

    # ---------- 武器姿态 ----------
    heavy_windup = False
    if p.heavy > 0:
        elapsed = p.heavy_dur - p.heavy
        if elapsed < HEAVY["wind"]:
            # 前摇：高举蓄力
            t = elapsed / HEAVY["wind"]
            angle = lerp(-0.9, -2.55, t)
            heavy_windup = True
        elif elapsed < HEAVY["hit_b"]:
            # 下劈
            t = (elapsed - HEAVY["wind"]) / (HEAVY["hit_b"] - HEAVY["wind"])
            angle = lerp(-2.55, 1.45, t)
        else:
            # 收招
            t = min(1, (elapsed - HEAVY["hit_b"]) / 0.14)
            angle = lerp(1.45, -0.5, t)
    elif p.attack > 0:
        a = ATTACKS[p.combo - 1]
        t = clamp((p.attack_dur - p.attack) / a["dur"], 0, 1)
        if t < 0.22:
            angle = lerp(-0.6, -2.0, t / 0.22)
        else:
            angle = lerp(-2.0, 1.2, (t - 0.22) / 0.78)
    else:
        angle = -0.5 + math.sin(p.anim * 0.6) * 0.05

    scx, scy = ox + 10, oy - 34
    half = 40 if p.heavy > 0 else 36
    dx = math.cos(angle) * half
    dy = math.sin(angle) * half

    # 剑柄
    hx = -math.cos(angle) * 8
    hy = -math.sin(angle) * 8
    pygame.draw.line(local, (20, 20, 30),
                     (scx + hx - dx*0.2, scy + hy - dy*0.2),
                     (scx + hx, scy + hy), 6)
    pygame.draw.line(local, (255, 210, 90),
                     (scx + hx*0.7, scy + hy*0.7),
                     (scx + hx*1.2, scy + hy*1.2), 3)

    # 剑身（重斩时更粗更亮）
    if p.heavy > 0:
        widths = [(11, (90, 200, 190, 80)),
                  (8,  (140, 240, 220, 140)),
                  (5,  (220, 255, 250))]
    else:
        widths = [(9, (90, 200, 190, 90)),
                  (6, (140, 240, 220, 150)),
                  (3, c_blade)]
    for w_, col in widths:
        pygame.draw.line(local, col,
                         (scx + hx*0.5, scy + hy*0.5),
                         (scx + dx, scy + dy), w_)
    pygame.draw.circle(local, (220, 255, 250), (int(scx + dx), int(scy + dy)), 3)

    # 重斩前摇：剑尖聚光
    if heavy_windup:
        t = (p.heavy_dur - p.heavy) / HEAVY["wind"]
        rr = int(6 + 12 * t)
        tip_glow = pygame.Surface((rr*2+8, rr*2+8), pygame.SRCALPHA)
        for r in range(rr, 0, -2):
            a = int(150 * (1 - r / rr) ** 1.3)
            pygame.draw.circle(tip_glow, (160, 255, 230, a),
                               (rr + 4, rr + 4), r)
        local.blit(tip_glow,
                   (int(scx + dx) - rr - 4, int(scy + dy) - rr - 4))

    # 重斩挥砍刀光弧
    if p.heavy > 0:
        elapsed = p.heavy_dur - p.heavy
        if HEAVY["hit_a"] - 0.02 <= elapsed <= HEAVY["hit_b"] + 0.06:
            prog = clamp((elapsed - HEAVY["hit_a"]) / 0.20, 0, 1)
            arc = pygame.Surface((260, 260), pygame.SRCALPHA)
            pts = []
            for i in range(16):
                t = i / 15
                aa = lerp(-2.3, 1.5, t)
                rr = 62 + 18 * math.sin(t * math.pi)
                pts.append((130 + math.cos(aa) * rr,
                            130 + math.sin(aa) * rr))
            alpha = int(200 * (1 - prog * 0.8))
            for i in range(len(pts) - 1):
                w_ = max(1, int(7 * (1 - abs(i/15 - 0.5))))
                pygame.draw.line(arc, (160, 255, 230, alpha),
                                 pts[i], pts[i+1], w_)
            local.blit(arc, (scx - 130, scy - 130))

    # 前手
    pygame.draw.line(local, c_body, (ox + 9, oy - 40), (ox + 11, oy - 34), 5)

    if p.facing < 0:
        local = pygame.transform.flip(local, True, False)

    surface.blit(local, (sx - SZ // 2, sy - oy))

# ============================================================
# 绘制 - 敌人
# ============================================================
def draw_enemy(surface, e):
    if e.dead and e.dead_timer > 1.4: return
    sx = int(e.x - cam_x)
    sy = int(e.y)

    shadow = pygame.Surface((int(e.w * 1.1) + 4, 14), pygame.SRCALPHA)
    pygame.draw.ellipse(shadow, (0, 0, 0, 110), (0, 0, shadow.get_width(), 14))
    surface.blit(shadow, (sx - shadow.get_width() // 2, GY - 5))

    SZ = 200
    local = pygame.Surface((SZ, SZ), pygame.SRCALPHA)
    ox, oy = SZ // 2, int(e.h) + 20

    flash = e.flash > 0
    body = (255, 255, 255) if flash else e.c1
    dark = (220, 220, 220) if flash else e.c2

    if e.state == "windup" and not e.dead:
        pulse = 0.35 + 0.35 * math.sin(time_acc * 22)
        gr = pygame.Surface((90, 90), pygame.SRCALPHA)
        for r in range(45, 0, -3):
            a = int(255 * pulse * (1 - r / 45) ** 1.5)
            pygame.draw.circle(gr, (255, 60, 40, a), (45, 45), r)
        local.blit(gr, (ox - 45, oy - 70 - 45))

    pygame.draw.line(local, dark, (ox - 5, oy - 16), (ox - 7, oy - 1), 6)
    pygame.draw.line(local, dark, (ox + 5, oy - 16), (ox + 7, oy - 1), 6)

    draw_round_rect(local, body, (ox - 11, oy - 38, 22, 24), 6)
    pygame.draw.rect(local, dark, (ox - 11, oy - 20, 22, 6))

    arm_a = 0.9 if e.state == "attack" else (-1.2 if e.state == "windup" else 0.15)
    pygame.draw.line(local, body, (ox - 9, oy - 33),
                     (ox - 15 - math.sin(arm_a) * 5,
                      oy - 22 + math.cos(arm_a) * 6), 6)
    pygame.draw.line(local, body, (ox + 9, oy - 33),
                     (ox + 16 + math.sin(arm_a) * 6,
                      oy - 22 + math.cos(arm_a) * 8), 6)

    pygame.draw.circle(local, body, (ox, oy - 45), 10)
    pygame.draw.circle(local, dark, (ox, oy - 42), 6)

    pygame.draw.polygon(local, dark, [
        (ox - 8, oy - 50), (ox - 13, oy - 64), (ox - 3, oy - 53)])
    pygame.draw.polygon(local, dark, [
        (ox + 8, oy - 50), (ox + 13, oy - 64), (ox + 3, oy - 53)])

    if not e.dead:
        eye_col = (255, 255, 255) if flash else (255, 80, 60)
        pygame.draw.circle(local, eye_col, (ox - 4, oy - 47), 2)
        pygame.draw.circle(local, eye_col, (ox + 4, oy - 47), 2)

    if e.facing < 0:
        local = pygame.transform.flip(local, True, False)

    if e.dead:
        alpha = max(0, 1 - max(0, e.dead_timer - 0.7) / 0.7)
        local.set_alpha(int(255 * alpha))
        k = min(1, e.dead_timer * 4)
        rotated = pygame.transform.rotate(local, -90 * k)
        surface.blit(rotated, (sx - rotated.get_width() // 2,
                               sy - rotated.get_height() + 30))
    else:
        surface.blit(local, (sx - SZ // 2, sy - oy))

    if not e.dead and e.type != "boss" and e.hp < e.max_hp:
        bw = int(e.w * 1.3)
        bx = sx - bw // 2
        by = sy - e.h - 16
        pygame.draw.rect(surface, (0, 0, 0), (bx - 1, by - 1, bw + 2, 6))
        ratio = e.hp / e.max_hp
        pygame.draw.rect(surface, COL_BLOOD, (bx, by, int(bw * ratio), 4))

def draw_particles(surface):
    for pt in particles:
        a = clamp(pt["life"] / pt["max"], 0, 1)
        c = pt["color"]
        col = (int(c[0] * a), int(c[1] * a), int(c[2] * a))
        pygame.draw.circle(surface, col,
                           (int(pt["x"] - cam_x), int(pt["y"])),
                           max(1, int(pt["size"] * a)))

def draw_texts(surface):
    for t in texts:
        size = t.get("size", 20)
        f = get_font(size, bold=True)
        a = clamp(t["life"] / t["max"], 0, 1)
        surf = f.render(t["str"], True, t["color"])
        surf.set_alpha(int(255 * a))
        shadow = f.render(t["str"], True, (0, 0, 0))
        shadow.set_alpha(int(180 * a))
        tx = int(t["x"] - cam_x) - surf.get_width() // 2
        ty = int(t["y"]) - surf.get_height() // 2
        surface.blit(shadow, (tx + 1, ty + 1))
        surface.blit(surf, (tx, ty))

# ============================================================
# HUD
# ============================================================
def draw_hud(surface):
    if state == "title" or player is None: return
    p = player

    # 血条
    bx, by, bw, bh = 30, 26, 280, 18
    draw_round_rect(surface, (0, 0, 0), (bx - 5, by - 5, bw + 10, bh + 10), 5)
    pygame.draw.rect(surface, (30, 15, 20), (bx, by, bw, bh))
    ratio = clamp(p.hp / p.max_hp, 0, 1)
    hw = int(bw * ratio)
    if hw > 0:
        hp_surf = pygame.Surface((hw, bh), pygame.SRCALPHA)
        for y in range(bh):
            t = y / bh
            c = (int(lerp(230, 150, t)), int(lerp(50, 20, t)), int(lerp(60, 30, t)))
            pygame.draw.line(hp_surf, c, (0, y), (hw, y))
        surface.blit(hp_surf, (bx, by))
    pygame.draw.rect(surface, (255, 220, 180), (bx, by, bw, bh), 1)
    pygame.draw.line(surface, (255, 220, 180),
                     (bx + bw, by), (bx + bw + 6, by + bh // 2), 1)
    pygame.draw.line(surface, (255, 220, 180),
                     (bx + bw, by + bh), (bx + bw + 6, by + bh // 2), 1)

    f = get_font(12, bold=True)
    surface.blit(f.render("生命", True, (240, 200, 190)), (bx + bw + 18, by + 3))

    # 能量条
    sy2 = by + 28
    sh = 8
    draw_round_rect(surface, (0, 0, 0), (bx - 5, sy2 - 4, bw + 10, sh + 8), 4)
    pygame.draw.rect(surface, (15, 30, 35), (bx, sy2, bw, sh))
    st_ratio = clamp(p.st / p.max_st, 0, 1)
    sw = int(bw * st_ratio)
    if sw > 0:
        st_surf = pygame.Surface((sw, sh), pygame.SRCALPHA)
        for y in range(sh):
            t = y / sh
            c = (int(lerp(70, 30, t)), int(lerp(230, 160, t)), int(lerp(200, 130, t)))
            pygame.draw.line(st_surf, c, (0, y), (sw, y))
        surface.blit(st_surf, (bx, sy2))
    f2 = get_font(11, bold=True)
    surface.blit(f2.render("能量", True, (140, 230, 200)), (bx + bw + 18, sy2))

    # 状态提示
    f3 = get_font(18, bold=True)
    if p.heavy > 0:
        surface.blit(f3.render("重 斩", True, (160, 255, 230)),
                     (bx, by + 58))
    elif p.roll > 0:
        surface.blit(f3.render("翻 滚", True, (140, 240, 210)),
                     (bx, by + 58))
    elif p.combo > 0 and p.attack > 0:
        names = ["斩", "影", "破"]
        surface.blit(f3.render(names[p.combo - 1] + " · " + str(p.combo),
                               True, (255, 210, 120)),
                     (bx, by + 58))

    # Boss 血条
    if boss_ref and not boss_ref.dead and abs(boss_ref.x - player.x) < 700:
        bbw, bbh = 620, 18
        bbx = (W - bbw) // 2
        bby = H - 56
        draw_round_rect(surface, (0, 0, 0),
                        (bbx - 6, bby - 6, bbw + 12, bbh + 12), 6)
        pygame.draw.rect(surface, (30, 10, 15), (bbx, bby, bbw, bbh))
        ratio = clamp(boss_ref.hp / boss_ref.max_hp, 0, 1)
        rw = int(bbw * ratio)
        if rw > 0:
            bg = pygame.Surface((rw, bbh), pygame.SRCALPHA)
            for y in range(bbh):
                t = y / bbh
                c = (int(lerp(210, 90, t)), int(lerp(40, 12, t)), int(lerp(50, 20, t)))
                pygame.draw.line(bg, c, (0, y), (rw, y))
            surface.blit(bg, (bbx, bby))
        pygame.draw.rect(surface, (255, 200, 130), (bbx, bby, bbw, bbh), 1)
        f4 = get_font(16, bold=True)
        txt = f4.render("守卫者 · 黑月", True, (255, 220, 190))
        surface.blit(txt, (W // 2 - txt.get_width() // 2, bby - 24))

# ============================================================
# 覆盖层
# ============================================================
def draw_overlay(surface):
    if state == "playing": return

    if state == "title":
        ov = pygame.Surface((W, H), pygame.SRCALPHA)
        ov.fill((4, 4, 14, 150))
        surface.blit(ov, (0, 0))

        f_big = get_font(76, bold=True)
        title = f_big.render("死亡细胞", True, (140, 240, 200))
        shadow = f_big.render("死亡细胞", True, (20, 60, 60))
        tx = W // 2 - title.get_width() // 2
        ty = 190 - title.get_height() // 2
        surface.blit(shadow, (tx + 4, ty + 4))
        surface.blit(title, (tx, ty))

        f_sub = get_font(34, bold=True)
        sub = f_sub.render("· 2D ·", True, (255, 210, 120))
        surface.blit(sub, (W // 2 - sub.get_width() // 2, 232))

        pygame.draw.line(surface, (90, 220, 200),
                         (W // 2 - 220, 278), (W // 2 + 220, 278), 1)

        f_txt = get_font(17)
        lines = [
            "A / D  或  ← →    快速移动",
            "空格 / ↑    跳跃（可二段跳）",
            "J    挥剑攻击（三段连斩）",
            "K    重  斩（高伤大范围）",
            "L / Shift    贴地翻滚（全程无敌）",
        ]
        for i, s in enumerate(lines):
            t = f_txt.render(s, True, (200, 220, 240))
            surface.blit(t, (W // 2 - t.get_width() // 2, 308 + i * 30))

        blink = 0.45 + 0.55 * abs(math.sin(time_acc * 3))
        f_hint = get_font(22, bold=True)
        hint = f_hint.render("按  空格  开始逃亡", True, (140, 240, 200))
        hint.set_alpha(int(255 * blink))
        surface.blit(hint, (W // 2 - hint.get_width() // 2, 482))
        return

    if state == "dead":
        ov = pygame.Surface((W, H), pygame.SRCALPHA)
        ov.fill((30, 0, 0, 160))
        surface.blit(ov, (0, 0))
        f_big = get_font(90, bold=True)
        t = f_big.render("死", True, (220, 60, 60))
        surface.blit(t, (W // 2 - t.get_width() // 2, 190))
        f_txt = get_font(20)
        t = f_txt.render("囚徒倒下了，但灵魂仍在前行", True, (220, 200, 200))
        surface.blit(t, (W // 2 - t.get_width() // 2, 320))
        blink = 0.45 + 0.55 * abs(math.sin(time_acc * 3))
        f_hint = get_font(20, bold=True)
        hint = f_hint.render("按  R  重新开始", True, (140, 240, 200))
        hint.set_alpha(int(255 * blink))
        surface.blit(hint, (W // 2 - hint.get_width() // 2, 390))
        return

    if state == "win":
        ov = pygame.Surface((W, H), pygame.SRCALPHA)
        ov.fill((0, 10, 20, 160))
        surface.blit(ov, (0, 0))
        f_big = get_font(86, bold=True)
        t = f_big.render("斩", True, (140, 240, 200))
        surface.blit(t, (W // 2 - t.get_width() // 2, 190))
        f_txt = get_font(20)
        t = f_txt.render("黑月已灭，前路依旧黑暗", True, (220, 220, 240))
        surface.blit(t, (W // 2 - t.get_width() // 2, 320))
        blink = 0.45 + 0.55 * abs(math.sin(time_acc * 3))
        f_hint = get_font(20, bold=True)
        hint = f_hint.render("按  R  继续前行", True, (140, 240, 200))
        hint.set_alpha(int(255 * blink))
        surface.blit(hint, (W // 2 - hint.get_width() // 2, 390))

# ============================================================
# 渲染
# ============================================================
def render():
    screen.fill((0, 0, 0))
    shx = shy = 0
    if shake > 0:
        shx = random.uniform(-shake, shake)
        shy = random.uniform(-shake, shake)

    screen.blit(sky_surf, (0, 0))

    scene_surf.fill((0, 0, 0, 0))
    draw_moon(scene_surf)
    draw_ridge(scene_surf, ridge_far,  0.16, (34, 26, 58))
    draw_ridge(scene_surf, ridge_mid,  0.36, (22, 18, 44), 6)
    draw_ridge(scene_surf, ridge_near, 0.62, (14, 12, 28), 10)
    scene_surf.blit(get_fog_surf(), (0, GY - 140))
    draw_floats(scene_surf)
    draw_ground(scene_surf)

    if state in ("playing", "dead", "win"):
        for e in enemies:
            draw_enemy(scene_surf, e)
        draw_player(scene_surf)
        draw_particles(scene_surf)
        draw_texts(scene_surf)

    screen.blit(scene_surf, (int(shx), int(shy)))

    draw_hud(screen)
    draw_overlay(screen)

# ============================================================
# 主循环
# ============================================================
def main():
    global player
    player = Player()
    running = True
    while running:
        dt = clock.tick(FPS) / 1000.0
        dt = min(dt, 0.05)
        pressed = set()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                pressed.add(event.key)
        update(dt, pressed)
        render()
        pygame.display.flip()
    pygame.quit()
    sys.exit()

if __name__ == "__main__":
    main()