import pygame
import random
import sys
from collections import deque

pygame.init()
SCREEN_W = 1200
SCREEN_H = 700
screen = pygame.display.set_mode((SCREEN_W, SCREEN_H))
pygame.display.set_caption("Escape From Principal Chen 随机迷宫")
clock = pygame.time.Clock()
FPS = 60

WORLD_W = 10000
WORLD_H = 7000

try:
    font_big = pygame.font.SysFont("Arial", 60)
    font_mid = pygame.font.SysFont("Arial", 36)
    font_small = pygame.font.SysFont("Arial", 24)
except Exception:
    font_big = pygame.font.Font(pygame.font.get_default_font(), 60)
    font_mid = pygame.font.Font(pygame.font.get_default_font(), 36)
    font_small = pygame.font.Font(pygame.font.get_default_font(), 24)

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
SKY = (135, 206, 235)
RED = (220, 30, 30)
GREEN = (30, 200, 30)
YELLOW = (255, 210, 0)
WALL_COLOR = (55, 55, 55)

GRID_SIZE_LIST = [160, 110, 70]
grid_w = 0
grid_h = 0

player_size = 30
player_speed = 6

enemy_size = 45
enemy_speed_base = 2.2
path_update_timer = 0
enemy_path = []

camera_x = 0
camera_y = 0
score = 0
stage = 1
game_over = False
level_complete = False
wall_rects = []
end_pos = [0, 0]


def dfs_generate_maze(cell_w, cell_h, level):
    grid = [[1 for _ in range(cell_h)] for _ in range(cell_w)]
    stack = [(1, 1)]
    grid[1][1] = 0
    # 加宽出生位置通道，防止开局卡死
    grid[2][1] = 0
    grid[1][2] = 0
    dirs = [(2, 0), (-2, 0), (0, 2), (0, -2)]
    while len(stack) > 0:
        cx, cy = stack.pop()
        random.shuffle(dirs)
        for dx, dy in dirs:
            nx = cx + dx
            ny = cy + dy
            if 0 < nx < cell_w - 1 and 0 < ny < cell_h - 1 and grid[nx][ny] == 1:
                grid[cx + dx // 2][cy + dy // 2] = 0
                grid[nx][ny] = 0
                stack.append((nx, ny))
    return grid


def refresh_maze():
    global wall_rects, grid_w, grid_h, end_pos
    wall_rects.clear()
    gs = GRID_SIZE_LIST[stage - 1]
    grid_w = WORLD_W // gs
    grid_h = WORLD_H // gs
    maze_data = dfs_generate_maze(grid_w, grid_h, stage)

    wall_rects.append(pygame.Rect(0, 0, WORLD_W, gs))
    wall_rects.append(pygame.Rect(0, WORLD_H - gs, WORLD_W, gs))
    wall_rects.append(pygame.Rect(0, 0, gs, WORLD_H))
    wall_rects.append(pygame.Rect(WORLD_W - gs, 0, gs, WORLD_H))

    for gx in range(grid_w):
        for gy in range(grid_h):
            if maze_data[gx][gy] == 1:
                wall_rects.append(pygame.Rect(gx * gs, gy * gs, gs - 4, gs - 4))

    end_pos[0] = (grid_w - 2) * gs
    end_pos[1] = (grid_h - 2) * gs


def get_grid_pos(px, py):
    gs = GRID_SIZE_LIST[stage - 1]
    return (int(px // gs), int(py // gs))


def is_wall(gx, gy):
    gs = GRID_SIZE_LIST[stage - 1]
    rect = pygame.Rect(gx * gs, gy * gs, gs, gs)
    for w in wall_rects:
        if rect.colliderect(w):
            return True
    return False


def bfs_path(start, goal):
    sx, sy = start
    gx, gy = goal
    if is_wall(gx, gy):
        return []
    visited = [[False] * grid_h for _ in range(grid_w)]
    prev = [[None] * grid_h for _ in range(grid_w)]
    q = deque([(sx, sy)])
    visited[sx][sy] = True
    dirs = [(1, 0), (-1, 0), (0, 1), (0, -1)]
    while q:
        x, y = q.popleft()
        if x == gx and y == gy:
            break
        for dx, dy in dirs:
            nx, ny = x + dx, y + dy
            if 0 <= nx < grid_w and 0 <= ny < grid_h and not visited[nx][ny] and not is_wall(nx, ny):
                visited[nx][ny] = True
                prev[nx][ny] = (x, y)
                q.append((nx, ny))
    path = []
    cx, cy = gx, gy
    while prev[cx][cy] is not None:
        gs = GRID_SIZE_LIST[stage - 1]
        path.append((cx * gs + gs / 2, cy * gs + gs / 2))
        cx, cy = prev[cx][cy]
    path.reverse()
    return path


def draw_stickman(surf, world_x, world_y, color, scale=1.0):
    sx = world_x - camera_x
    sy = world_y - camera_y
    head_r = int(12 * scale)
    pygame.draw.circle(surf, color, (int(sx), int(sy - head_r * 2)), head_r, 3)
    pygame.draw.line(surf, color, (sx, sy - head_r), (sx, sy + 25 * scale), 3)
    pygame.draw.line(surf, color, (sx, sy), (sx - 18 * scale, sy + 10 * scale), 3)
    pygame.draw.line(surf, color, (sx, sy), (sx + 18 * scale, sy + 10 * scale), 3)
    pygame.draw.line(surf, color, (sx, sy + 25 * scale), (sx - 15 * scale, sy + 50 * scale), 3)
    pygame.draw.line(surf, color, (sx, sy + 25 * scale), (sx + 15 * scale, sy + 50 * scale), 3)


def reset_stage():
    global player_x, player_y, enemy_x, enemy_y, enemy_path, path_update_timer, enemy_speed_base, level_complete
    gs = GRID_SIZE_LIST[stage - 1]
    # 设置在安全的空白格子中心，不会贴着墙壁
    player_x = gs * 1 + gs / 2
    player_y = gs * 1 + gs / 2
    enemy_x = gs * 4
    enemy_y = gs * 4
    enemy_path = []
    path_update_timer = 0
    enemy_speed_base = 2.0 + stage * 0.6
    level_complete = False
    refresh_maze()


def full_game_reset():
    global stage, score, game_over
    stage = 1
    score = 0
    game_over = False
    reset_stage()


reset_stage()
running = True

while running:
    screen.fill(SKY)
    clock.tick(FPS)
    keys = pygame.key.get_pressed()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    px, py = player_x, player_y
    player_rect = pygame.Rect(px, py, player_size, player_size)

    camera_x = player_x - SCREEN_W // 2
    camera_y = player_y - SCREEN_H // 2
    camera_x = max(0, min(WORLD_W - SCREEN_W, camera_x))
    camera_y = max(0, min(WORLD_H - SCREEN_H, camera_y))

    if not game_over and not level_complete:
        score += 0.15
        mx, my = 0, 0
        if keys[pygame.K_LEFT]:
            mx -= player_speed
        if keys[pygame.K_RIGHT]:
            mx += player_speed
        if keys[pygame.K_UP]:
            my -= player_speed
        if keys[pygame.K_DOWN]:
            my += player_speed

        test_x = pygame.Rect(px + mx, py, player_size, player_size)
        if not any(test_x.colliderect(w) for w in wall_rects):
            player_x += mx
        test_y = pygame.Rect(player_x, py + my, player_size, player_size)
        if not any(test_y.colliderect(w) for w in wall_rects):
            player_y += my

        ex, ey = end_pos
        if abs(player_x - ex) < 60 and abs(player_y - ey) < 60:
            level_complete = True

        path_update_timer += 1
        if path_update_timer > 30:
            enemy_path = bfs_path(get_grid_pos(enemy_x, enemy_y), get_grid_pos(player_x, player_y))
            path_update_timer = 0

        if len(enemy_path) > 0:
            tx, ty = enemy_path[0]
            dx = tx - enemy_x
            dy = ty - enemy_y
            dist = (dx ** 2 + dy ** 2) ** 0.5
            spd = enemy_speed_base + score / 45
            if dist > spd:
                enemy_x += dx / dist * spd
                enemy_y += dy / dist * spd
            else:
                enemy_path.pop(0)

        enemy_rect = pygame.Rect(enemy_x, enemy_y, enemy_size, enemy_size)
        if player_rect.colliderect(enemy_rect):
            game_over = True

    for wall in wall_rects:
        sx = wall.x - camera_x
        sy = wall.y - camera_y
        if -wall.width < sx < SCREEN_W and -wall.height < sy < SCREEN_H:
            pygame.draw.rect(screen, WALL_COLOR, (sx, sy, wall.width, wall.height))

    e_sx = end_pos[0] - camera_x
    e_sy = end_pos[1] - camera_y
    pygame.draw.circle(screen, YELLOW, (int(e_sx), int(e_sy)), 35)
    screen.blit(font_small.render("GOAL", True, BLACK), (e_sx - 18, e_sy - 16))

    draw_stickman(screen, player_x, player_y, BLACK, 0.9)
    draw_stickman(screen, enemy_x, enemy_y, RED, 1.2)

    screen.blit(font_small.render("You", True, WHITE), (player_x - camera_x + 8, player_y - camera_y - 24))
    screen.blit(font_small.render("Principal Chen", True, WHITE),
                (enemy_x - camera_x + 6, enemy_y - camera_y - 24))

    screen.blit(font_mid.render(f"Stage:{stage}  Survive:{int(score)}", True, BLACK), (15, 15))

    if level_complete:
        screen.blit(font_big.render("Level Cleared!", True, GREEN), (400, 260))
        screen.blit(font_mid.render("Press SPACE‑Next Stage", True, BLACK), (380, 360))
        if keys[pygame.K_SPACE]:
            if stage < 3:
                stage += 1
                reset_stage()
            else:
                screen.blit(font_big.render("ALL 3 STAGES WIN!", True, YELLOW), (280, 260))

    if game_over:
        screen.blit(font_big.render("Caught By Principal Chen", True, RED), (220, 260))
        screen.blit(font_mid.render("SPACE‑Restart Game", True, BLACK), (360, 380))
        if keys[pygame.K_SPACE]:
            full_game_reset()

    pygame.display.update()
pygame.quit()
sys.exit()