import sys
import random
import pygame
# 初始化pygame
pygame.init()
# 游戏常量
SIZE = 3 # 3x3 拼图
CELL = 120 # 每个格子的像素大小
WIDTH = CELL * SIZE
HEIGHT = CELL * SIZE
EMPTY = SIZE * SIZE # 空格的表示值
ANIMATION_SPEED = 8 # 动画速度(降低速度让移动更明显)
ANIMATION_FRAMES = 15 # 固定动画帧数
# 颜色定义
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (200, 200, 200)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
LIGHT_GRAY = (230, 230, 230)
# 创建游戏窗口
screen = pygame.display.set_mode((WIDTH, HEIGHT + 60))
pygame.display.set_caption("数字拼图(修复版)")
# 设置字体
def get_font(size):
"""获取合适的字体"""
try:
return pygame.font.SysFont(["SimHei", "WenQuanYi Micro Hei", "Heiti TC", "Arial"], size)
except:
return pygame.font.SysFont(None, size)
# 字体对象
font = get_font(72)
small_font = get_font(28)
class Animation:
"""处理方块移动动画 - 采用帧计数方式更稳定"""
def __init__(self, start_pos, end_pos, value):
self.start_pos = start_pos # (x, y)
self.end_pos = end_pos # (x, y)
self.current_pos = list(start_pos)
self.value = value
self.frame = 0 # 当前动画帧
self.complete = False
# 计算每帧的移动距离
self.dx = (end_pos[0] - start_pos[0]) / ANIMATION_FRAMES
self.dy = (end_pos[1] - start_pos[1]) / ANIMATION_FRAMES
def update(self):
"""更新动画位置"""
if self.complete:
return
self.frame += 1
# 计算当前位置
self.current_pos[0] = self.start_pos[0] + self.dx * self.frame
self.current_pos[1] = self.start_pos[1] + self.dy * self.frame
# 检查动画是否完成
if self.frame >= ANIMATION_FRAMES:
self.current_pos = list(self.end_pos)
self.complete = True
def create_board():
"""创建打乱的拼图,确保可解"""
board = list(range(1, SIZE * SIZE)) + [EMPTY]
while True:
random.shuffle(board)
if is_solvable(board):
break
return board
def is_solvable(board):
"""判断拼图是否可解"""
inversions = 0
for i in range(len(board)):
for j in range(i + 1, len(board)):
if board[i] != EMPTY and board[j] != EMPTY and board[i] > board[j]:
inversions += 1
if SIZE % 2 == 1:
return inversions % 2 == 0
else:
empty_row = SIZE - (board.index(EMPTY) // SIZE)
return (empty_row % 2 == 0) == (inversions % 2 == 1)
def draw_board(board, animations, is_ai_mode):
"""绘制游戏界面,包括动画"""
screen.fill(WHITE)
# 绘制模式信息和操作提示
mode_text = "AI自动模式" if is_ai_mode else "人工模式"
mode_surf = small_font.render(f"模式: {mode_text} (F1切换)", True, BLACK)
hint_surf = small_font.render("方向键移动空格(人工模式)", True, BLUE)
screen.blit(mode_surf, (10, 10))
screen.blit(hint_surf, (10, 35))
# 绘制格子背景
for i in range(SIZE):
for j in range(SIZE):
rect = pygame.Rect(j * CELL, i * CELL + 60, CELL, CELL)
pygame.draw.rect(screen, LIGHT_GRAY, rect)
pygame.draw.rect(screen, GRAY, rect, 2)
# 收集正在动画的数字,避免重复绘制
animating_values = [anim.value for anim in animations]
# 绘制静态方块(不在动画中的)
for i in range(SIZE):
for j in range(SIZE):
val = board[i * SIZE + j]
if val != EMPTY and val not in animating_values:
x = j * CELL + CELL // 2
y = i * CELL + 60 + CELL // 2
text_surf = font.render(str(val), True, BLACK)
text_rect = text_surf.get_rect(center=(x, y))
screen.blit(text_surf, text_rect)
# 绘制动画中的方块
for anim in animations:
if not anim.complete:
x, y = anim.current_pos
text_surf = font.render(str(anim.value), True, BLACK)
text_rect = text_surf.get_rect(center=(int(x), int(y)))
screen.blit(text_surf, text_rect)
pygame.display.flip()
def move_with_animation(board, direction):
"""移动空格并创建动画"""
idx = board.index(EMPTY)
row, col = idx // SIZE, idx % SIZE
animations = []
# 计算移动后的位置和动画 - 使用格子左上角坐标计算中心位置
if direction == 'UP' and row < SIZE - 1:
# 空格向上移动(数字向下移动)
num_idx = idx + SIZE
num_val = board[num_idx]
# 计算起始和结束位置(中心坐标)
start_x = col * CELL + CELL // 2
start_y = (row + 1) * CELL + 60 + CELL // 2
end_x = col * CELL + CELL // 2
end_y = row * CELL + 60 + CELL // 2
animations.append(Animation((start_x, start_y), (end_x, end_y), num_val))
board[idx], board[num_idx] = board[num_idx], board[idx]
elif direction == 'DOWN' and row > 0:
# 空格向下移动(数字向上移动)
num_idx = idx - SIZE
num_val = board[num_idx]
start_x = col * CELL + CELL // 2
start_y = (row - 1) * CELL + 60 + CELL // 2
end_x = col * CELL + CELL // 2
end_y = row * CELL + 60 + CELL // 2
animations.append(Animation((start_x, start_y), (end_x, end_y), num_val))
board[idx], board[num_idx] = board[num_idx], board[idx]
elif direction == 'LEFT' and col < SIZE - 1:
# 空格向左移动(数字向右移动)
num_idx = idx + 1
num_val = board[num_idx]
start_x = (col + 1) * CELL + CELL // 2
start_y = row * CELL + 60 + CELL // 2
end_x = col * CELL + CELL // 2
end_y = row * CELL + 60 + CELL // 2
animations.append(Animation((start_x, start_y), (end_x, end_y), num_val))
board[idx], board[num_idx] = board[num_idx], board[idx]
elif direction == 'RIGHT' and col > 0:
# 空格向右移动(数字向左移动)
num_idx = idx - 1
num_val = board[num_idx]
start_x = (col - 1) * CELL + CELL // 2
start_y = row * CELL + 60 + CELL // 2
end_x = col * CELL + CELL // 2
end_y = row * CELL + 60 + CELL // 2
animations.append(Animation((start_x, start_y), (end_x, end_y), num_val))
board[idx], board[num_idx] = board[num_idx], board[idx]
return animations
def is_win(board):
"""判断是否完成拼图"""
for i in range(SIZE * SIZE - 1):
if board[i] != i + 1:
return False
return True
# AI求解部分
def find_empty(board):
"""找到空格的位置"""
return board.index(EMPTY)
def heuristic(board, goal):
"""启发式函数:计算曼哈顿距离"""
distance = 0
for i in range(SIZE * SIZE):
if board[i] != EMPTY and board[i] != goal[i]:
val = board[i]
goal_idx = goal.index(val)
r1, c1 = i // SIZE, i % SIZE
r2, c2 = goal_idx // SIZE, goal_idx % SIZE
distance += abs(r1 - r2) + abs(c1 - c2)
return distance
def get_neighbors(board):
"""获取所有可能的下一步状态"""
neighbors = []
idx = find_empty(board)
row, col = idx // SIZE, idx % SIZE
# 上移
if row < SIZE - 1:
new_board = board[:]
new_board[idx], new_board[idx + SIZE] = new_board[idx + SIZE], new_board[idx]
neighbors.append(('UP', new_board))
# 下移
if row > 0:
new_board = board[:]
new_board[idx], new_board[idx - SIZE] = new_board[idx - SIZE], new_board[idx]
neighbors.append(('DOWN', new_board))
# 左移
if col < SIZE - 1:
new_board = board[:]
new_board[idx], new_board[idx + 1] = new_board[idx + 1], new_board[idx]
neighbors.append(('LEFT', new_board))
# 右移
if col > 0:
new_board = board[:]
new_board[idx], new_board[idx - 1] = new_board[idx - 1], new_board[idx]
neighbors.append(('RIGHT', new_board))
return neighbors
def a_star_solve(board):
"""使用A*算法求解拼图,返回移动方向序列"""
goal = list(range(1, SIZE * SIZE)) + [EMPTY]
if board == goal:
return []
open_set = [board]
came_from = {}
direction_from = {} # 记录到达该状态的移动方向
g_score = {tuple(board): 0}
f_score = {tuple(board): heuristic(board, goal)}
while open_set:
current = min(open_set, key=lambda x: f_score[tuple(x)])
if current == goal:
# 重建路径
path = []
while tuple(current) in came_from:
path.append(direction_from[tuple(current)])
current = came_from[tuple(current)]
return path[::-1] # 反转路径
open_set.remove(current)
for direction, neighbor in get_neighbors(current):
tentative_g = g_score[tuple(current)] + 1
if tuple(neighbor) not in g_score or tentative_g < g_score[tuple(neighbor)]:
came_from[tuple(neighbor)] = current
direction_from[tuple(neighbor)] = direction
g_score[tuple(neighbor)] = tentative_g
f_score[tuple(neighbor)] = tentative_g + heuristic(neighbor, goal)
if neighbor not in open_set:
open_set.append(neighbor)
return None
def main():
"""游戏主函数"""
board = create_board()
goal = list(range(1, SIZE * SIZE)) + [EMPTY]
is_ai_mode = False # 初始为人工模式
ai_moves = [] # AI的移动方向序列
current_animations = [] # 当前的动画列表
clock = pygame.time.Clock()
while True:
clock.tick(60) # 60 FPS
# 处理事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
# 按F1切换模式
if event.key == pygame.K_F1:
is_ai_mode = not is_ai_mode
if is_ai_mode:
# 计算AI移动序列
ai_moves = a_star_solve(board)
elif not is_ai_mode and not current_animations:
# 人工模式且没有动画时才能移动
if event.key == pygame.K_UP:
anims = move_with_animation(board, 'UP')
current_animations.extend(anims)
elif event.key == pygame.K_DOWN:
anims = move_with_animation(board, 'DOWN')
current_animations.extend(anims)
elif event.key == pygame.K_LEFT:
anims = move_with_animation(board, 'LEFT')
current_animations.extend(anims)
elif event.key == pygame.K_RIGHT:
anims = move_with_animation(board, 'RIGHT')
current_animations.extend(anims)
# AI模式且没有动画且有移动步骤时自动移动
if is_ai_mode and not current_animations and ai_moves:
direction = ai_moves.pop(0)
anims = move_with_animation(board, direction)
current_animations.extend(anims)
# 更新动画
for anim in current_animations[:]:
anim.update()
if anim.complete:
current_animations.remove(anim)
# 绘制游戏界面
draw_board(board, current_animations, is_ai_mode)
# 检查是否胜利且没有动画
if not current_animations and board == goal:
win_font = get_font(48)
win_surf = win_font.render("恭喜完成!", True, RED)
screen.blit(win_surf, (WIDTH // 2 - win_surf.get_width() // 2,
HEIGHT // 2 - win_surf.get_height() // 2 + 30))
pygame.display.flip()
pygame.time.wait(2000) # 显示2秒
# 重新开始游戏
board = create_board()
ai_moves = []
current_animations = []
if __name__ == "__main__":
main()