import pygame
import sys
import random

# ========== 初始化 ==========
pygame.init()
WIDTH, HEIGHT = 500, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("🧩 15 Puzzle – Classic Sliding Game")
clock = pygame.time.Clock()

# ========== 配色方案 ==========
BG_COLOR = (30, 30, 40)
TILE_COLOR = (70, 130, 180)
TILE_HOVER = (90, 150, 210)
TEXT_COLOR = (240, 240, 250)
SHADOW_COLOR = (10, 10, 15)
WIN_COLOR = (80, 200, 120)

# ========== 字体 ==========
font_tile = pygame.font.Font(None, 72)
font_small = pygame.font.Font(None, 36)
font_med = pygame.font.Font(None, 42)
font_title = pygame.font.Font(None, 56)

# ========== 游戏常量 ==========
ROWS, COLS = 4, 4
TILE_SIZE = 100
GAP = 6
BOARD_X = (WIDTH - (COLS * (TILE_SIZE + GAP))) // 2
BOARD_Y = 80

# ========== 游戏逻辑 ==========
class FifteenPuzzle:
    def __init__(self):
        self.board = []        # 二维列表，0表示空格
        self.empty_pos = (0, 0)
        self.moves = 0
        self.win = False
        self.generate_board()

    def generate_board(self):
        """生成一个可解的随机棋盘"""
        # 从正确排列开始，随机滑动空格
        self.board = [[1, 2, 3, 4],
                     [5, 6, 7, 8],
                     [9, 10, 11, 12],
                     [13, 14, 15, 0]]
        self.empty_pos = (3, 3)
        # 随机打乱（保证可解性：通过模拟滑动空格）
        for _ in range(200):
            moves = self.get_possible_moves()
            r, c = self.empty_pos
            dr, dc = random.choice(moves)
            nr, nc = r + dr, c + dc
            self.swap(r, c, nr, nc)
            self.empty_pos = (nr, nc)
        self.moves = 0
        self.win = False

    def get_possible_moves(self):
        r, c = self.empty_pos
        moves = []
        if r > 0: moves.append((-1, 0))
        if r < ROWS - 1: moves.append((1, 0))
        if c > 0: moves.append((0, -1))
        if c < COLS - 1: moves.append((0, 1))
        return moves

    def swap(self, r1, c1, r2, c2):
        self.board[r1][c1], self.board[r2][c2] = self.board[r2][c2], self.board[r1][c1]

    def try_move(self, row, col):
        """尝试将指定数字块滑向空格"""
        if self.win:
            return
        # 检查是否与空格相邻
        er, ec = self.empty_pos
        if (abs(row - er) + abs(col - ec)) == 1:
            self.swap(row, col, er, ec)
            self.empty_pos = (row, col)
            self.moves += 1
            # 检查胜利
            self.win = self.check_win()

    def check_win(self):
        correct = 1
        for r in range(ROWS):
            for c in range(COLS):
                if r == ROWS - 1 and c == COLS - 1:
                    if self.board[r][c] != 0:
                        return False
                else:
                    if self.board[r][c] != correct:
                        return False
                    correct += 1
        return True

    def get_tile_rect(self, row, col):
        x = BOARD_X + col * (TILE_SIZE + GAP)
        y = BOARD_Y + row * (TILE_SIZE + GAP)
        return pygame.Rect(x, y, TILE_SIZE, TILE_SIZE)

# ========== 游戏渲染 ==========
def draw_board(game):
    screen.fill(BG_COLOR)

    # 标题
    title_text = font_title.render("15 Puzzle", True, TEXT_COLOR)
    screen.blit(title_text, (WIDTH//2 - title_text.get_width()//2, 15))

    # 步数
    moves_text = font_small.render(f"Moves: {game.moves}", True, TEXT_COLOR)
    screen.blit(moves_text, (BOARD_X, BOARD_Y - 40))

    # 重置按钮
    reset_rect = pygame.Rect(WIDTH - 120, BOARD_Y - 35, 100, 40)
    pygame.draw.rect(screen, (60, 60, 80), reset_rect, border_radius=8)
    pygame.draw.rect(screen, TEXT_COLOR, reset_rect, 2, border_radius=8)
    reset_text = font_small.render("Reset", True, TEXT_COLOR)
    screen.blit(reset_text, (reset_rect.x + 12, reset_rect.y + 5))

    # 胜利提示
    if game.win:
        win_text = font_med.render("You Win!", True, WIN_COLOR)
        screen.blit(win_text, (WIDTH//2 - win_text.get_width()//2, HEIGHT - 60))

    # 绘制网格背景
    for r in range(ROWS):
        for c in range(COLS):
            rect = game.get_tile_rect(r, c)
            # 绘制阴影
            shadow_rect = rect.move(3, 3)
            pygame.draw.rect(screen, SHADOW_COLOR, shadow_rect, border_radius=10)
            # 如果胜利，所有格子变绿
            if game.win:
                color = WIN_COLOR
            else:
                color = TILE_COLOR
            pygame.draw.rect(screen, color, rect, border_radius=10)
            pygame.draw.rect(screen, TEXT_COLOR, rect, 2, border_radius=10)

    # 绘制数字块（空格不绘制）
    for r in range(ROWS):
        for c in range(COLS):
            val = game.board[r][c]
            if val != 0:
                rect = game.get_tile_rect(r, c)
                # 鼠标悬停高亮（仅当未胜利且块可移动时）
                mouse_pos = pygame.mouse.get_pos()
                if not game.win and rect.collidepoint(mouse_pos):
                    # 检查是否可移动
                    er, ec = game.empty_pos
                    if abs(r - er) + abs(c - ec) == 1:
                        # 高亮
                        hover_rect = rect.inflate(6, 6)
                        pygame.draw.rect(screen, TILE_HOVER, hover_rect, border_radius=10)
                        pygame.draw.rect(screen, TEXT_COLOR, hover_rect, 3, border_radius=10)

                num_text = font_tile.render(str(val), True, TEXT_COLOR)
                screen.blit(num_text, (rect.x + TILE_SIZE//2 - num_text.get_width()//2,
                                       rect.y + TILE_SIZE//2 - num_text.get_height()//2))

# ========== 主循环 ==========
def main():
    game = FifteenPuzzle()
    running = True

    while running:
        mouse_pos = pygame.mouse.get_pos()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

            elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                # 检查重置按钮
                reset_rect = pygame.Rect(WIDTH - 120, BOARD_Y - 35, 100, 40)
                if reset_rect.collidepoint(mouse_pos):
                    game.generate_board()
                    continue

                # 检查数字块点击
                for r in range(ROWS):
                    for c in range(COLS):
                        if game.board[r][c] != 0:
                            rect = game.get_tile_rect(r, c)
                            if rect.collidepoint(mouse_pos):
                                game.try_move(r, c)
                                break

            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_r:
                    game.generate_board()

        draw_board(game)
        pygame.display.flip()
        clock.tick(60)

    pygame.quit()
    sys.exit()

if __name__ == "__main__":
    main()