使用Pygame实现数字益智小游戏
简介
这款小游戏将经典的迷宫寻路与数学运算相结合,通过算法确保可玩性,是一款兼具趣味性和教育意义的数字益智游戏。使用 Pygame 绘制界面。
运行截图:
游戏核心机制
玩家 在迷宫格子上移动,每走一步会消耗 步数。
数字格(绿色)存储一个整数值,当玩家 持有运算符 时踏上数字格,会对玩家当前数字执行运算。
运算符格(黄/红/紫/青)分别代表 +、−、×、÷,踏上后 拾取 该运算符(替换旧运算符),但 不立即运算。
目标格(橙色)显示目标数字,玩家抵达时若当前数字与目标一致则 过关,否则 失败。
障碍格(深灰)不可通过,增加路径选择难度。
计分规则:通关奖励 = max(0, 1000 - 步数 × 10),鼓励用最少步数完成。
迷宫生成与可解性保证
1 随机生成
使用二维列表 maze 表示迷宫,每个格子记录类型和数值(若有)。
起点(左上角)和终点(右下角)固定为数字格,其余格子按概率随机分配类型(含障碍),并随等级动态调整数值范围。
2 通路检查(is_reachable)
利用 广度优先搜索(BFS) 检查是否至少有一条物理通路(无视数字运算)从起点到终点,若无则重新生成。
3 可解性验证(check_solvable)
在迷宫生成后,再次使用 BFS 搜索 状态空间。每个状态为 (x, y, 当前数字, 持有的运算符)。
模拟所有可能的移动和运算,检查是否存在一条路径使玩家到达终点时数字等于目标。
若不存在,则抛弃该迷宫并重新生成,直到有解为止。这保证了每一关都有解法(但玩家仍需自己寻找)。
玩家移动与运算逻辑
玩家移动时,代码通过 方向向量(上下左右)计算新坐标,并检查越界和障碍。
若目标格子是普通数字格且玩家持有运算符,则执行对应运算(除法仅当整除时生效,否则保留运算符)。
若目标格子是运算符格,则替换玩家持有的运算符。
每次移动都会记录步数,并存储路径(用于可能的回溯/显示)。
自动演示功能
利用 BFS 搜索完整解路径(find_solution),不仅验证可解,还记录每一步的动作(方向)。启动演示后,程序按一定帧延迟逐步执行该动作序列,模拟玩家自动通关。
演示期间键盘输入被屏蔽,按 ESC 可终止并复位玩家到起点。
依赖库及安装说明
|---------------------|-------------------------------------------|--------------------|
| 库名 | 功能简介 | 是否需要安装 |
| pygame | 提供游戏窗口、图形绘制、事件处理(键盘/退出)、字体渲染、音频(预留)等核心功能。 | 需要安装(非标准库) |
| random | 生成随机数,用于迷宫格子类型和数字的随机分配。 | 标准库,无需额外安装 |
| sys | 提供 sys.exit() 用于退出程序。 | 标准库,无需额外安装 |
| enum | 提供 Enum 基类,用于定义游戏状态和格子类型枚举。 | 标准库,无需额外安装 |
| collections | 提供 deque(双端队列),用于 BFS 搜索中的队列。 | 标准库,无需额外安装 |
安装 pygame 的命令
在命令行中运行:
pip install pygame
验证安装,在shell中运行:
import pygame
print(pygame.version.ver)
关于Python中的pygame游戏模块的介绍,可参见 https://blog.csdn.net/cnds123/article/details/119514520
源码
(由DeepSeek辅助生成)
python
# =============================================================================
# 数字迷宫冒险 (Number Maze Adventure)
# 一款结合迷宫探索与数字运算的益智游戏。
# 玩家在迷宫中移动,拾取运算符并与数字格运算,最终使到达终点时的数字等于目标值。
# 使用 Pygame 实现,自动生成有解的迷宫,并支持自动演示功能。
# =============================================================================
import pygame
import random
import sys
from enum import Enum
from collections import deque
# ----------------------------- Pygame 初始化 ----------------------------------
pygame.init()
pygame.mixer.init() # 初始化音频(预留)
# ------------------------------ 屏幕设置 -------------------------------------
WIDTH, HEIGHT = 1000, 700
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("数字迷宫冒险")
# ------------------------------ 颜色定义 -------------------------------------
# 使用 RGB 元组定义游戏中使用的颜色,便于统一调整主题
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 50, 50)
GREEN = (50, 255, 100)
BLUE = (80, 180, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 165, 0)
PURPLE = (180, 80, 220)
CYAN = (80, 220, 220)
GRAY = (150, 150, 150)
DARK_GRAY = (80, 80, 80)
# ------------------------------ 字体设置 -------------------------------------
# 使用系统宋体以支持中文显示,若系统无此字体可替换为其他中文字体
font = pygame.font.SysFont("simsun", 30) # 普通文本
big_font = pygame.font.SysFont("simsun", 60) # 大标题/弹窗
small_font = pygame.font.SysFont("simsun", 22) # 格子内数字/运算符
info_font = pygame.font.SysFont("simsun", 26) # 顶部信息栏
rule_font = pygame.font.SysFont("simsun", 20) # 右侧规则面板
# ------------------------------ 游戏状态枚举 --------------------------------
class GameState(Enum):
"""游戏当前所处的状态"""
PLAYING = 1 # 正常进行中
GAME_OVER = 2 # 失败
LEVEL_COMPLETE = 3 # 关卡通过
MENU = 4 # 菜单(预留)
# ------------------------------ 格子类型枚举 --------------------------------
class NumberType(Enum):
"""迷宫格子类型"""
NORMAL = 1 # 普通数字格:包含一个整数值,可参与运算
PLUS = 2 # 加法运算符格
MINUS = 3 # 减法运算符格
MULTIPLY = 4 # 乘法运算符格
DIVIDE = 5 # 除法运算符格
GOAL = 6 # 目标格(终点),显示目标数字
OBSTACLE = 7 # 障碍格,不可通行
# =============================================================================
# 玩家类
# =============================================================================
class Player:
"""表示玩家角色,包含位置、当前数字、持有的运算符、移动步数等状态"""
def __init__(self, x, y, start_number):
"""
初始化玩家
:param x: 列索引(水平位置)
:param y: 行索引(垂直位置)
:param start_number: 起点的初始数字
"""
self.x = x
self.y = y
self.number = start_number # 当前数字
self.operator = None # 当前持有的运算符,为 None 或 NumberType 中的 PLUS/MINUS/MULTIPLY/DIVIDE
self.moves = 0 # 累计移动次数
self.path = [(x, y)] # 走过的路径(坐标列表)
def move(self, dx, dy, maze):
"""
尝试向 (dx, dy) 方向移动,并处理格子效果。
:param dx: 水平偏移量(-1,0,1)
:param dy: 垂直偏移量(-1,0,1)
:param maze: 迷宫二维列表
:return: 是否成功移动(True/False)
"""
new_x, new_y = self.x + dx, self.y + dy
# 边界检查
if not (0 <= new_x < len(maze[0]) and 0 <= new_y < len(maze)):
return False
cell = maze[new_y][new_x]
# 障碍物检查
if cell['type'] == NumberType.OBSTACLE:
return False
# 执行移动,更新位置和路径
self.x, self.y = new_x, new_y
self.path.append((self.x, self.y))
self.moves += 1
# 根据目标格子类型处理
if cell['type'] == NumberType.NORMAL:
# 普通数字格:若持有运算符,则进行运算,然后清除运算符
if self.operator is not None:
num = cell['value']
if self.operator == NumberType.PLUS:
self.number += num
self.operator = None
elif self.operator == NumberType.MINUS:
self.number -= num
self.operator = None
elif self.operator == NumberType.MULTIPLY:
self.number *= num
self.operator = None
elif self.operator == NumberType.DIVIDE:
# 除法仅当能整除时才执行,否则保留运算符不变
if num != 0 and self.number % num == 0:
self.number //= num
self.operator = None
# 不能整除则保留运算符,不改变数字
elif cell['type'] in [NumberType.PLUS, NumberType.MINUS,
NumberType.MULTIPLY, NumberType.DIVIDE]:
# 运算符格:拾取运算符(覆盖旧运算符)
self.operator = cell['type']
# 目标格(GOAL)不做任何处理,由游戏主逻辑判断胜负
return True
def reset_to_start(self, start_number):
"""
重置玩家到起点,用于重新开始或演示结束。
:param start_number: 起点的数字
"""
self.x = 0
self.y = 0
self.number = start_number
self.operator = None
self.moves = 0
self.path = [(0, 0)]
def draw(self, surface, cell_size, maze_start_x, maze_start_y):
"""
在屏幕上绘制玩家。
:param surface: pygame 表面
:param cell_size: 格子像素大小
:param maze_start_x: 迷宫区域左上角 x 偏移
:param maze_start_y: 迷宫区域左上角 y 偏移
"""
# 计算玩家中心坐标
x_pos = maze_start_x + self.x * cell_size + cell_size // 2
y_pos = maze_start_y + self.y * cell_size + cell_size // 2
# 绘制蓝色圆形(玩家本体)
pygame.draw.circle(surface, BLUE, (x_pos, y_pos), cell_size // 3)
pygame.draw.circle(surface, WHITE, (x_pos, y_pos), cell_size // 3, 2)
# 显示当前数字
text = small_font.render(str(self.number), True, WHITE)
text_rect = text.get_rect(center=(x_pos, y_pos))
surface.blit(text, text_rect)
# 若持有运算符,在玩家右上角显示运算符符号
if self.operator:
op_symbols = {
NumberType.PLUS: "+",
NumberType.MINUS: "-",
NumberType.MULTIPLY: "×",
NumberType.DIVIDE: "÷"
}
op_text = small_font.render(op_symbols[self.operator], True, YELLOW)
op_rect = op_text.get_rect(center=(x_pos + cell_size // 4, y_pos - cell_size // 4))
surface.blit(op_text, op_rect)
# =============================================================================
# 游戏主类
# =============================================================================
class NumberMazeGame:
"""游戏核心控制类,管理迷宫生成、玩家状态、游戏循环等"""
def __init__(self):
"""初始化游戏参数,并开始第一关"""
# 迷宫显示参数
self.cell_size = 60 # 每个格子的像素尺寸
self.maze_width = 10 # 迷宫列数
self.maze_height = 8 # 迷宫行数
self.maze_start_x = 10 # 迷宫区域左上角 x 偏移(留边距)
self.maze_start_y = 100 # 迷宫区域左上角 y 偏移(为标题留空间)
# 自动演示相关
self.demo_mode = False # 是否正在演示
self.demo_actions = [] # 演示动作序列 [(dx, dy), ...]
self.demo_index = 0 # 当前执行到的动作索引
self.demo_timer = 0 # 帧计数器
self.demo_delay = 15 # 每多少帧执行一步(值越大演示越慢)
self.reset() # 开始游戏(等级1,分数0)
def reset(self):
"""完全重置游戏(从第一关开始)"""
self.level = 1
self.score = 0
self.reset_level()
def reset_level(self):
"""重置当前关卡(保留等级和分数),生成新迷宫并重置玩家"""
self.state = GameState.PLAYING
self.generate_maze()
self.demo_mode = False
self.demo_actions = []
self.demo_index = 0
# ---------------------- 迷宫生成与可解性检查 --------------------------
def is_reachable(self, maze):
"""
使用 BFS 检查迷宫是否存在物理通路(忽略数字运算)。
:param maze: 迷宫数据
:return: True/False
"""
start = (0, 0)
end = (self.maze_width - 1, self.maze_height - 1)
visited = set()
queue = deque([start])
visited.add(start)
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
while queue:
x, y = queue.popleft()
if (x, y) == end:
return True
for dx, dy in directions:
nx, ny = x + dx, y + dy
if (0 <= nx < self.maze_width and 0 <= ny < self.maze_height and
(nx, ny) not in visited and
maze[ny][nx]['type'] != NumberType.OBSTACLE):
visited.add((nx, ny))
queue.append((nx, ny))
return False
def check_solvable(self, start_num, target_num):
"""
使用 BFS 搜索状态空间,检查是否存在一条路径可使玩家到达终点时数字等于目标。
状态为 (x, y, 当前数字, 持有的运算符)。
:param start_num: 起点数字
:param target_num: 目标数字
:return: True 表示有解,False 表示无解
"""
start_state = (0, 0, start_num, None)
visited = set([start_state])
q = deque([start_state])
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
while q:
x, y, num, op = q.popleft()
# 到达终点且数字匹配
if x == self.maze_width - 1 and y == self.maze_height - 1:
if num == target_num:
return True
# 限制数字范围,防止无限增长
if abs(num) > 1000:
continue
for dx, dy in directions:
nx, ny = x + dx, y + dy
if not (0 <= nx < self.maze_width and 0 <= ny < self.maze_height):
continue
cell = self.maze[ny][nx]
if cell['type'] == NumberType.OBSTACLE:
continue
new_num = num
new_op = op
# 模拟移动到该格子的效果
if cell['type'] == NumberType.NORMAL:
if new_op is not None:
val = cell['value']
if new_op == NumberType.PLUS:
new_num += val
new_op = None
elif new_op == NumberType.MINUS:
new_num -= val
new_op = None
elif new_op == NumberType.MULTIPLY:
new_num *= val
new_op = None
elif new_op == NumberType.DIVIDE:
if val != 0 and new_num % val == 0:
new_num //= val
new_op = None
elif cell['type'] in [NumberType.PLUS, NumberType.MINUS,
NumberType.MULTIPLY, NumberType.DIVIDE]:
new_op = cell['type']
state = (nx, ny, new_num, new_op)
if state not in visited:
visited.add(state)
q.append(state)
return False
def find_solution(self, start_num, target_num):
"""
使用 BFS 搜索并返回一条可行路径的动作序列。
与 check_solvable 类似,但额外记录父状态以回溯路径。
:param start_num: 起点数字
:param target_num: 目标数字
:return: 动作列表 [(dx, dy), ...] 或 None(若未找到)
"""
start_state = (0, 0, start_num, None)
parent = {start_state: (None, None)} # state -> (前驱state, 动作)
q = deque([start_state])
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
end_state = None
while q:
x, y, num, op = q.popleft()
if x == self.maze_width - 1 and y == self.maze_height - 1:
if num == target_num:
end_state = (x, y, num, op)
break
if abs(num) > 1000:
continue
for dx, dy in directions:
nx, ny = x + dx, y + dy
if not (0 <= nx < self.maze_width and 0 <= ny < self.maze_height):
continue
cell = self.maze[ny][nx]
if cell['type'] == NumberType.OBSTACLE:
continue
new_num = num
new_op = op
if cell['type'] == NumberType.NORMAL:
if new_op is not None:
val = cell['value']
if new_op == NumberType.PLUS:
new_num += val
new_op = None
elif new_op == NumberType.MINUS:
new_num -= val
new_op = None
elif new_op == NumberType.MULTIPLY:
new_num *= val
new_op = None
elif new_op == NumberType.DIVIDE:
if val != 0 and new_num % val == 0:
new_num //= val
new_op = None
elif cell['type'] in [NumberType.PLUS, NumberType.MINUS,
NumberType.MULTIPLY, NumberType.DIVIDE]:
new_op = cell['type']
new_state = (nx, ny, new_num, new_op)
if new_state not in parent:
parent[new_state] = ((x, y, num, op), (dx, dy))
q.append(new_state)
if end_state is None:
return None
# 回溯路径,从终点反向到起点
actions = []
state = end_state
while parent[state][0] is not None:
prev_state, action = parent[state]
actions.append(action) # action 是 (dx, dy)
state = prev_state
actions.reverse()
return actions
def generate_maze(self):
"""
生成迷宫,要求:
1. 有物理通路(is_reachable)
2. 有数字解(check_solvable)
若不满足则重新生成,直至成功。
生成后初始化玩家。
"""
while True:
# 构建迷宫二维列表
self.maze = []
for y in range(self.maze_height):
row = []
for x in range(self.maze_width):
if x == 0 and y == 0:
# 起点:普通数字格,随机生成起点数字,范围随等级提升
start_num = random.randint(1, 3 + self.level)
cell = {'type': NumberType.NORMAL, 'value': start_num}
elif x == self.maze_width - 1 and y == self.maze_height - 1:
# 终点(目标格):目标数字基于起点数字生成,确保有挑战性
min_target = max(1, start_num - 5 * self.level)
max_target = start_num + 10 * self.level
target_num = random.randint(min_target, max_target)
cell = {'type': NumberType.GOAL, 'value': target_num}
else:
# 其他格子:按概率随机分配类型
r = random.random()
if r < 0.10:
cell = {'type': NumberType.OBSTACLE, 'value': None}
elif r < 0.45:
cell = {'type': NumberType.NORMAL, 'value': random.randint(1, 4 + self.level // 2)}
elif r < 0.65:
cell = {'type': NumberType.PLUS, 'value': None}
elif r < 0.80:
cell = {'type': NumberType.MINUS, 'value': None}
elif r < 0.92:
cell = {'type': NumberType.MULTIPLY, 'value': None}
else:
cell = {'type': NumberType.DIVIDE, 'value': None}
row.append(cell)
self.maze.append(row)
# 检查物理通路
if not self.is_reachable(self.maze):
continue
# 提取起点和终点的数字
start_num = self.maze[0][0]['value']
target_num = self.maze[self.maze_height - 1][self.maze_width - 1]['value']
# 检查数字可解性
if self.check_solvable(start_num, target_num):
self.start_number = start_num
self.target_number = target_num
self.player = Player(0, 0, start_num)
break # 生成成功,退出循环
# -------------------------- 自动演示 ----------------------------------
def start_demo(self):
"""启动自动演示:寻找一条解法路径,并逐步执行"""
if self.demo_mode or self.state != GameState.PLAYING:
return
actions = self.find_solution(self.start_number, self.target_number)
if actions is None:
return # 理论上不会发生(因为生成时保证了有解)
self.demo_actions = actions
self.demo_index = 0
self.demo_timer = self.demo_delay
self.demo_mode = True
self.player.reset_to_start(self.start_number)
def stop_demo(self):
"""停止演示并重置玩家到起点"""
self.demo_mode = False
self.demo_actions = []
self.demo_index = 0
self.player.reset_to_start(self.start_number)
# -------------------------- 事件处理 ----------------------------------
def handle_events(self):
"""处理所有 pygame 事件(键盘、退出等)"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
# 演示模式下,只允许 ESC 取消演示
if self.demo_mode:
if event.key == pygame.K_ESCAPE:
self.stop_demo()
continue
# 游戏进行中:方向键控制移动,A 启动演示
if self.state == GameState.PLAYING:
if event.key == pygame.K_UP:
self.player.move(0, -1, self.maze)
elif event.key == pygame.K_DOWN:
self.player.move(0, 1, self.maze)
elif event.key == pygame.K_LEFT:
self.player.move(-1, 0, self.maze)
elif event.key == pygame.K_RIGHT:
self.player.move(1, 0, self.maze)
elif event.key == pygame.K_a:
self.start_demo()
# R 键:在结束状态(失败/通关)时重置当前关卡
if event.key == pygame.K_r:
if self.state in [GameState.GAME_OVER, GameState.LEVEL_COMPLETE]:
self.reset_level()
# N 键:通关后进入下一关
if event.key == pygame.K_n and self.state == GameState.LEVEL_COMPLETE:
# 计算奖励分数并累加,等级+1
self.score += max(0, 1000 - self.player.moves * 10)
self.level += 1
self.reset_level()
# -------------------------- 更新逻辑 ----------------------------------
def update(self):
"""
更新游戏逻辑:
- 演示模式下逐步执行动作
- 非演示模式检查玩家是否到达终点,判定胜负
"""
# 演示模式更新
if self.demo_mode:
if self.demo_index < len(self.demo_actions):
self.demo_timer -= 1
if self.demo_timer <= 0:
dx, dy = self.demo_actions[self.demo_index]
self.player.move(dx, dy, self.maze)
self.demo_index += 1
self.demo_timer = self.demo_delay
else:
self.stop_demo() # 演示完成
return
# 非演示模式
if self.state != GameState.PLAYING:
return
# 检查是否到达终点
target_x, target_y = self.maze_width - 1, self.maze_height - 1
if self.player.x == target_x and self.player.y == target_y:
if self.player.number == self.target_number:
self.state = GameState.LEVEL_COMPLETE
else:
self.state = GameState.GAME_OVER
# -------------------------- 绘制界面 ----------------------------------
def draw(self):
"""绘制所有图形界面元素"""
screen.fill(BLACK)
# ---------- 标题 ----------
title_text = big_font.render("数字迷宫冒险", True, YELLOW)
screen.blit(title_text, (WIDTH // 2 - title_text.get_width() // 2, 3))
# ---------- 顶部信息栏 ----------
info_bg = pygame.Rect(0, 62, WIDTH, 30)
pygame.draw.rect(screen, DARK_GRAY, info_bg)
score_text = info_font.render(f"分数:{self.score}", True, WHITE)
level_text = info_font.render(f"关卡:{self.level}", True, YELLOW)
moves_text = info_font.render(f"步数:{self.player.moves}", True, GREEN)
current_str = f"当前:{self.player.number}"
if self.player.operator:
op_sym = {NumberType.PLUS: "+", NumberType.MINUS: "-",
NumberType.MULTIPLY: "×", NumberType.DIVIDE: "÷"}
current_str += f" {op_sym[self.player.operator]}"
current_text = info_font.render(current_str, True, CYAN)
target_text = info_font.render(f"目标:{self.target_number}", True, ORANGE)
# 调整各信息项的水平位置
screen.blit(score_text, (10, 60))
screen.blit(level_text, (180, 60))
screen.blit(moves_text, (300, 60))
screen.blit(current_text, (450, 60))
screen.blit(target_text, (680, 60))
# ---------- 绘制迷宫网格 ----------
for y, row in enumerate(self.maze):
for x, cell in enumerate(row):
rect_x = self.maze_start_x + x * self.cell_size
rect_y = self.maze_start_y + y * self.cell_size
# 根据格子类型选择颜色和边框
if cell['type'] == NumberType.NORMAL:
color = GREEN
border = WHITE
elif cell['type'] == NumberType.PLUS:
color = YELLOW
border = ORANGE
elif cell['type'] == NumberType.MINUS:
color = RED
border = DARK_GRAY
elif cell['type'] == NumberType.MULTIPLY:
color = PURPLE
border = CYAN
elif cell['type'] == NumberType.DIVIDE:
color = CYAN
border = BLUE
elif cell['type'] == NumberType.GOAL:
color = ORANGE
border = YELLOW
elif cell['type'] == NumberType.OBSTACLE:
color = DARK_GRAY
border = GRAY
else:
color = GRAY
border = DARK_GRAY
# 绘制底色和边框
pygame.draw.rect(screen, color, (rect_x, rect_y, self.cell_size, self.cell_size))
pygame.draw.rect(screen, border, (rect_x, rect_y, self.cell_size, self.cell_size), 2)
# 绘制格子内容(文字或符号)
if cell['type'] != NumberType.OBSTACLE:
if cell['type'] == NumberType.PLUS:
text = "+"
elif cell['type'] == NumberType.MINUS:
text = "-"
elif cell['type'] == NumberType.MULTIPLY:
text = "×"
elif cell['type'] == NumberType.DIVIDE:
text = "÷"
elif cell['type'] == NumberType.GOAL:
# 目标格显示"目标"和数字两行
lines = ["目标", str(cell['value'])]
for i, line in enumerate(lines):
t = small_font.render(line, True, BLACK)
t_rect = t.get_rect(center=(rect_x + self.cell_size // 2,
rect_y + self.cell_size // 2 - 10 + i * 22))
screen.blit(t, t_rect)
continue
else: # NORMAL
text = str(cell['value'])
t = small_font.render(text, True, BLACK)
t_rect = t.get_rect(center=(rect_x + self.cell_size // 2, rect_y + self.cell_size // 2))
screen.blit(t, t_rect)
# ---------- 绘制玩家 ----------
self.player.draw(screen, self.cell_size, self.maze_start_x, self.maze_start_y)
# ---------- 右侧规则面板 ----------
panel_x = self.maze_start_x + self.maze_width * self.cell_size + 15
panel_y = self.maze_start_y
panel_width = WIDTH - panel_x - 10
panel_height = HEIGHT - self.maze_start_y - 120 # 底部留出控制栏空间
# 半透明深色背景
panel_bg = pygame.Rect(panel_x, panel_y, panel_width, panel_height)
pygame.draw.rect(screen, (20, 20, 30, 200), panel_bg) # 半透明
pygame.draw.rect(screen, GRAY, panel_bg, 2) # 边框
# 规则标题
rule_title = rule_font.render("--- 游戏规则 ---", True, YELLOW)
screen.blit(rule_title, (panel_x + 10, panel_y + 10))
# 规则内容列表
rules = [
"目标:到达终点时数字等于目标值",
"运算符格:拾取运算符(覆盖旧)",
"数字格:使用拾取的运算符进行运算",
"除法:仅当整除时才生效,否则保留",
"运算符",
"障碍:不可通过",
"方向键:移动玩家",
"R:重置当前关卡(等级/分数不变)",
"N:通关后进入下一关",
"A:自动演示,此时按ESC取消"
]
# 逐行渲染
y_offset = panel_y + 50
for line in rules:
text = rule_font.render(line, True, WHITE)
screen.blit(text, (panel_x + 10, y_offset))
y_offset += 30
# ---------- 底部控制栏 ----------
ctrl_bg = pygame.Rect(0, HEIGHT - 40, WIDTH, 40)
pygame.draw.rect(screen, DARK_GRAY, ctrl_bg)
tips = [
"方向键:移动",
"R:重新开始本关",
"N:下一关",
"A:自动演示"
]
for i, tip in enumerate(tips):
t = info_font.render(tip, True, GRAY)
screen.blit(t, (WIDTH // 2 - 400 + i * 220, HEIGHT - 35))
# 演示模式提示
if self.demo_mode:
demo_info = info_font.render("演示中... (按 ESC 取消)", True, RED)
screen.blit(demo_info, (WIDTH // 2 - demo_info.get_width() // 2, 45))
# ---------- 游戏状态弹窗(遮罩 + 文字) ----------
if self.state == GameState.GAME_OVER:
# 半透明黑色遮罩
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 180))
screen.blit(overlay, (0, 0))
over_text = big_font.render("任务失败!", True, (255, 200, 200))
over_rect = over_text.get_rect(center=(WIDTH//2, HEIGHT//2 - 80))
screen.blit(over_text, over_rect)
msg_text = font.render(f"你的数字是 {self.player.number},需要 {self.target_number}", True, WHITE)
msg_rect = msg_text.get_rect(center=(WIDTH//2, HEIGHT//2 - 10))
screen.blit(msg_text, msg_rect)
restart_text = font.render("按 R 重新开始本关", True, (255, 255, 100))
restart_rect = restart_text.get_rect(center=(WIDTH//2, HEIGHT//2 + 60))
screen.blit(restart_text, restart_rect)
elif self.state == GameState.LEVEL_COMPLETE:
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 160))
screen.blit(overlay, (0, 0))
complete_text = big_font.render("关卡完成!", True, (200, 255, 200))
complete_rect = complete_text.get_rect(center=(WIDTH//2, HEIGHT//2 - 80))
screen.blit(complete_text, complete_rect)
bonus = max(0, 1000 - self.player.moves * 10)
bonus_text = font.render(f"奖励分数:{bonus}", True, YELLOW)
bonus_rect = bonus_text.get_rect(center=(WIDTH//2, HEIGHT//2 - 10))
screen.blit(bonus_text, bonus_rect)
next_text = font.render("按 N 进入下一关", True, (100, 255, 255))
next_rect = next_text.get_rect(center=(WIDTH//2, HEIGHT//2 + 60))
screen.blit(next_text, next_rect)
# 更新显示
pygame.display.flip()
# -------------------------- 主循环 ----------------------------------
def run(self):
"""游戏主循环,以 60 FPS 运行"""
clock = pygame.time.Clock()
while True:
self.handle_events()
self.update()
self.draw()
clock.tick(60)
# =============================================================================
# 程序入口
# =============================================================================
if __name__ == "__main__":
game = NumberMazeGame()
game.run()