# -*- coding: utf-8 -*-
"""
Pygame 八方向行走动画游戏
- 精灵图 boy.png 与本脚本放在同一目录
- 精灵图尺寸 296x300，按 8 列 x 8 行 切分（每帧约 37x38）
  → 8 行对应 8 个朝向，每行 8 帧动画
- 方向键 / WASD 移动，ESC 退出
"""
import os
import math
import pygame

# ---------- 资源配置 ----------
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
SPRITE_PATH = os.path.join(SCRIPT_DIR, "boy.png")
SCALE = 3            # 精灵缩放倍数（原始帧较小，放大显示）
FPS = 60

# 精灵图网格（探测得到 296x300，8x8 最整齐）
SPRITE_W, SPRITE_H = 296, 300
COLS, ROWS = 8, 8
CELL_W = SPRITE_W // COLS   # 37
CELL_H = SPRITE_H // ROWS   # 37（300//8=37，取整；余数 300-8*37=4 均摊到首行前 4 帧 +1）
# 300 = 37*4 + 38*4，更精确：前 4 行高 37，后 4 行高 38；这里用统一 37 并在 blit 时裁剪底部 1px 影响极小
CELL_H = 37

# 8 个朝向（行索引）对应的方向向量 (dx, dy)
# 行 0=下 1=右下 2=右 3=右上 4=上 5=左上 6=左 7=左下
DIR_ROW = [
    ( 0,  1),   # 0 下
    ( 1,  1),   # 1 右下
    ( 1,  0),   # 2 右
    ( 1, -1),   # 3 右上
    ( 0, -1),   # 4 上
    (-1, -1),   # 5 左上
    (-1,  0),   # 6 左
    (-1,  1),   # 7 左下
]

def angle_to_row(dx, dy):
    """根据移动方向 (dx,dy) 映射到 8 朝向行索引"""
    if dx == 0 and dy == 0:
        return 0
    ang = math.degrees(math.atan2(dy, dx))  # -180~180，0 为右
    # 将角度对齐到 8 个扇区中心：右0, 右下45, 下90 ...
    # row = round(ang/45) 需把 下(90)->row0 的映射调好
    # 我们构造：右=2, 右下=1, 下=0, 左下=7, 左=6, 左上=5, 上=4, 右上=3
    a = ang % 360
    # 扇区边界：每个扇区 45°，中心分别在 90(下),45(右下),0/360(右),315(右上),270(上)...
    centers = [(90,0),(45,1),(0,2),(315,3),(270,4),(225,5),(180,6),(135,7)]
    best, bd = 0, 360
    for c, row in centers:
        d = min((a-c)%360, (c-a)%360)
        if d < bd:
            bd, best = d, row
    return best

def main():
    pygame.init()
    pygame.display.set_caption("8-Direction Walk")
    info = pygame.display.Info()
    win_w, win_h = 800, 600
    screen = pygame.display.set_mode((win_w, win_h))
    clock = pygame.time.Clock()

    if not os.path.exists(SPRITE_PATH):
        raise FileNotFoundError(f"找不到精灵图：{SPRITE_PATH}\n请将 boy.png 与本脚本放在同一目录")

    sheet = pygame.image.load(SPRITE_PATH).convert_alpha()

    def get_frame(row, col):
        """从精灵图裁剪第 row 行、第 col 列的单帧，返回已缩放的 Surface"""
        x = col * CELL_W
        y = row * CELL_H
        # 高度取 min(CELL_H, 剩余) 防止越界
        h = min(CELL_H, SPRITE_H - y)
        frame = sheet.subsurface(pygame.Rect(x, y, CELL_W, h)).copy()
        if SCALE != 1:
            frame = pygame.transform.scale(frame, (CELL_W * SCALE, h * SCALE))
        return frame

    # 预加载所有帧：frames[row][col]
    frames = [[get_frame(r, c) for c in range(COLS)] for r in range(ROWS)]

    # 玩家状态
    px, py = win_w // 2, win_h // 2
    speed = 240  # 像素/秒
    row = 0
    frame_idx = 0
    anim_timer = 0.0
    frame_time = 0.08  # 每帧持续时间（秒）

    font = pygame.font.SysFont(None, 20)
    running = True
    while running:
        dt = clock.tick(FPS) / 1000.0
        for ev in pygame.event.get():
            if ev.type == pygame.QUIT:
                running = False
            elif ev.type == pygame.KEYDOWN and ev.key == pygame.K_ESCAPE:
                running = False

        keys = pygame.key.get_pressed()
        dx = (keys[pygame.K_d] or keys[pygame.K_RIGHT]) - (keys[pygame.K_a] or keys[pygame.K_LEFT])
        dy = (keys[pygame.K_s] or keys[pygame.K_DOWN]) - (keys[pygame.K_w] or keys[pygame.K_UP])
        moving = dx != 0 or dy != 0
        if moving:
            # 归一化，避免对角线加速
            if dx != 0 and dy != 0:
                inv = 1.0 / math.sqrt(2)
                dx *= inv; dy *= inv
            px += dx * speed * dt
            py += dy * speed * dt
            row = angle_to_row(dx, dy)
            anim_timer += dt
            if anim_timer >= frame_time:
                anim_timer -= frame_time
                frame_idx = (frame_idx + 1) % COLS
        else:
            frame_idx = 0
            anim_timer = 0.0

        # 边界限制
        fr = frames[row][frame_idx]
        fw, fh = fr.get_size()
        px = max(0, min(win_w - fw, px))
        py = max(0, min(win_h - fh, py))

        screen.fill((30, 30, 40))
        screen.blit(fr, (px, py))

        tip = font.render("方向键/WASD 移动 | ESC 退出", True, (220, 220, 220))
        screen.blit(tip, (10, 10))
        pygame.display.flip()

    pygame.quit()

if __name__ == "__main__":
    main()
