2026秋软件工程个人作业(第二次):用 Python 与 AIGC 开发《箭头小游戏》
| 项目内容 | 填写内容 |
|---|---|
| 这个作业属于哪个课程 | 202601 软件工程班级博客 |
| 这个作业要求在哪里 | 第二次个人作业要求 |
| 这个作业的目标 | 使用 Python 和 AIGC 完成“一箭又一箭”小游戏 |
| 学号 | 102401310 |
| GitHub 仓库 | https://github.com/Ikunmma/SofwareEngineering/tree/main/work/arrow-escape |
前言
最开始看到这个作业,我觉得规则很简单:点一支箭头,前面没东西就让它飞出去。真正做成游戏后才发现,路径判断只是一小步;界面状态、碰撞反馈、动画期间的重复点击、关卡是否有解,以及给别人运行的 EXE 是否兼容,都会影响作品能不能正常玩。
一、项目展示
本项目是一款使用 Python 和 Pygame 独立开发的小游戏。项目提供源码和 Windows 打包版本。Windows 用户可以从 Releases 页面下载游戏 ZIP,解压后运行;源码安装步骤见 README
项目展示如下:
演示视频主要展示随机挑战中的操作。随机挑战与固定关卡共用箭头点击、碰撞、提示和结算等流程
1. 开始界面

2. 冒险地图
点击开始游戏后进入冒险地图。玩家可以切换基础模式和进阶模式,查看关卡解锁状态、通关标记和历史最佳星级,也可以进入随机挑战。

已解锁的关卡会显示通关标记和历史最佳星级:

3. 游戏过程
游戏页显示生命、剩余箭头、时间和分数,并提供重开、静音、选关、提示和 AI 求解操作。
基础模式使用单格四方向箭头:

进阶模式使用多格彩色折线箭头:

随机挑战:

4. 通关与失败
清空本关所有箭头后进入通关页面,可以查看星级、用时和得分:

完成一个模式的最后一关后,还会显示全部通关页面:

三条生命全部耗尽后进入失败页面,可以重新挑战或返回选关:

二、项目介绍
1. 游戏规则
玩家点击箭头后,程序沿箭头方向检查它与棋盘边界之间的路径:
- 如果前方没有其他箭头,当前箭头会播放飞出动画并从棋盘消失;
- 如果前方存在阻挡,箭头不会消失,并会变红、晃动和触发红色脉冲,同时扣除一条生命;
- 每关初始有三条生命,清空全部箭头即可通关,生命耗尽则挑战失败;
- 游戏进行中可以重开本关,恢复初始布局、生命、提示次数、计时和本关得分。
基础模式采用作业要求的单格箭头规则,只判断同一行或同一列。进阶模式把箭头扩展为多格折线,点击路线任意位置都能选中整条箭头,再检查头部前进方向上的射线。
2. 固定关卡
我为两个模式分别设置了五关。基础模式逐关增加行列数,采用满格箭阵;进阶模式逐关扩大折线活动区域。下表中的数量来自当前关卡数据,进阶模式统计的是“整条折线箭头”的数量,不是折线占用的格数。
| 关卡 | 基础模式(棋盘/箭头) | 进阶模式(棋盘/折线) |
|---|---|---|
| 第 1 关 | 5×5/25 支 | 16×12/24 条 |
| 第 2 关 | 6×6/36 支 | 18×14/22 条 |
| 第 3 关 | 7×7/49 支 | 20×16/34 条 |
| 第 4 关 | 8×8/64 支 | 22×18/31 条 |
| 第 5 关 | 9×9/81 支 | 24×20/42 条 |
箭头数量并不是衡量难度的唯一标准。尤其在进阶模式中,折线长度、转弯和头部出口的相互阻挡也会改变消除顺序。固定关卡采用确定的随机种子,重新开始会恢复同一布局;程序求解器验证每关存在完整消除顺序。
3. 主要功能与特色
| 功能 | 具体实现 |
|---|---|
| 双模式关卡 | 基础和进阶模式各有五个固定关卡,棋盘随关卡逐步扩大 |
| 关卡解锁 | 每种模式默认开放第一关,通关后解锁下一关,并记录勾选和最佳星级 |
| 得分与评价 | 每关从 0 分开始,消除加分、碰撞扣分,通关后按时间和剩余生命评定 1~3 星 |
| 下一步提示 | 从当前局面寻找可安全飞出的箭头;每次提示消耗一次提示机会和一条生命 |
| 随机挑战 | 两种模式都能生成带独立种子的随机棋盘,生成后先由求解器验证可通关 |
| AI 自动求解 | 对当前完整棋盘或残局搜索消除顺序,支持自动演示、暂停和重新规划 |
| 自动保存 | 保存累计分数、两种模式的解锁记录、最佳星级、上次选择和静音状态 |
| 动画与音效 | 提供平滑飞出、残影、粒子、碰撞反馈和程序实时合成的短音效 |
| Windows 打包 | 使用 PyInstaller 生成单文件 EXE,并内置图片和中文字体 |
4. 开发与运行环境
| 项目 | 环境 |
|---|---|
| 开发语言 | Python 3.10 及以上 |
| 图形库 | Pygame 2.6.1 |
| 测试环境 | Windows 11、Python 3.12 |
| 测试框架 | unittest |
| 打包工具 | PyInstaller 6 |
源码运行命令如下:
cd work/arrow-escape
py -m pip install -r requirements.txt
py main.py
三、实现思路
1. 项目结构
arrow-escape/
├── main.py # 游戏入口、界面、交互、动画、音效和存档
├── game_logic.py # 基础模式路径判断、消除逻辑和 DFS 求解器
├── levels.py # 基础模式方向定义、关卡数据和随机生成
├── advanced_logic.py # 进阶模式折线箭头逻辑和求解器
├── advanced_levels.py # 进阶模式关卡数据和随机生成
│
├── tests/
│ ├── __init__.py
│ ├── test_game_logic.py # 基础模式与路径检测测试
│ ├── test_advanced_logic.py # 进阶模式与关卡可解性测试
│ └── test_app_state.py # 页面、动画、存档和完整流程测试
│
├── assets/
│ ├── LICENSES.md # 素材与字体许可说明
│ ├── fonts/
│ │ ├── NotoSansSC-Regular.otf
│ │ └── OFL.txt
│ └── kenney_ui/
│ ├── Blue/ # 蓝色按钮、箭头和图标
│ ├── Green/ # 绿色按钮、箭头和图标
│ ├── Grey/ # 灰色按钮、箭头和图标
│ ├── Red/ # 红色按钮、箭头和图标
│ ├── Yellow/ # 黄色按钮、箭头和图标
│ ├── Kenney Future.ttf
│ └── License.txt
│
├── README.md # 项目介绍和运行说明
├── BLOG.md # 本次课程作业博客
├── requirements.txt # 游戏运行依赖
├── requirements-build.txt # EXE 打包依赖
├── ArrowEscape.spec # PyInstaller 打包配置
└── build_exe.bat # Windows 一键打包脚本
2. 箭头和棋盘表示
基础棋盘使用二维列表保存。每个单元格是 up、down、left、right 或 None,其中 None 表示空白。方向被统一转换成行列增量:
DIRECTION_VECTORS = {
"up": (-1, 0),
"down": (1, 0),
"left": (0, -1),
"right": (0, 1),
}
固定关卡使用不可变数据保存,进入游戏时再复制成可修改列表。这样既能安全删除箭头,也能在重开时从原始数据恢复棋盘。
进阶模式把一支折线箭头表示成一串相邻坐标,同时保存方向和颜色。当前仍存在的折线使用编号集合记录,消除时删除对应编号即可。
3. 路径检测
路径检测是基础玩法的核心。它只回答一个问题:这支箭头与边界之间,是否还有任何箭头? 假设同一行是 → · · ↑ ·,从最左边的 → 向右扫描,虽然中间隔了两个空格,仍会遇到 ↑,所以不能飞出;阻挡者自己的朝向不影响结果。
实现时从当前箭头前方的相邻格开始,沿方向向量逐格检查:遇到非空单元格就返回“被阻挡”;直到走出棋盘仍未遇到箭头,就返回“无阻挡”。关键代码来自 game_logic.py:
row_step, col_step = DIRECTION_VECTORS[direction]
check_row = row + row_step
check_col = col + col_step
while 0 <= check_row < self.rows and 0 <= check_col < self.cols:
if self.board[check_row][check_col] is not None:
return True
check_row += row_step
check_col += col_step
return False
四个方向共用这一段代码,避免分别编写四套判断。每次先检查坐标是否在棋盘内,再读取格子,避免向上或向左时误用 Python 的负索引。边缘朝外箭头的下一格已经在棋盘外,循环不会执行,直接返回无阻挡。is_blocked() 只判断,不改棋盘;remove_arrow() 仅在无阻挡时把该格设为 None。一次检测最多扫描所在的一行或一列,因此时间复杂度是 O(max(行数, 列数)),不需要额外保存整条路径。
进阶模式沿用同一思路,但从折线箭头头部开始检查,并把其他折线身体占用的全部格子组成集合。头部射线与集合相交时,整条箭头不能飞出。
4. 可通关关卡生成
如果直接随机填充方向,很容易生成相互阻挡的死局。因此基础模式采用“正向构造合法消除顺序”的方式:从满格坐标集合开始,每次只选择当前能够直接飞出剩余棋盘的坐标和方向,再将它从集合中移除。全部坐标都被处理后,记录下来的选择顺序就是一条已知解。
进阶模式先生成出口射线畅通的路线,再随机延长尾部,并限制身体不能破坏必须保留的出口。生成器会尝试多张候选棋盘并选择覆盖率较高的一张,使画面更密集。固定关卡在自动化测试中逐关求解验证;随机挑战则在进入游戏前调用求解器验证。
5. AI 自动求解
求解器采用深度优先搜索。每个状态中先找出所有当前可消除箭头,再分别尝试移除并递归搜索。无解状态会加入失败缓存,避免重复搜索。
基础模式使用不可变二维元组作为状态键,进阶模式使用剩余路线编号的不可变集合。求解器只负责返回步骤序列,界面负责逐步播放原有飞出动画。暂停求解后允许玩家手动操作;再次继续时会根据最新残局重新计算答案。
6. 页面状态、动画与存档
程序使用一个主事件循环管理开始、选关、游戏、通关、失败、随机通关和全部通关等页面。动画期间锁定会破坏状态的操作,动画结束后再真正修改棋盘。
存档使用 JSON 格式。源码版保存在项目目录,EXE 版保存在 %LOCALAPPDATA%\ArrowEscape\save_data.json。程序会检查存档字段和取值范围,内容损坏时使用默认进度启动。清除进度必须经过二次确认,避免误操作。
四、AIGC 使用过程
本项目使用 Codex 辅助分析、编码、调试和测试。下面记录的是我在开发过程中提出的问题、AI 给出的修改,以及我对结果的检查;不是把 AI 一次生成的代码直接当成最终作品。
| 子任务 | 使用工具 | 提出的要求 | AI 提供的帮助 | 实际效果与人工调整 |
|---|---|---|---|---|
| 需求拆分与项目结构 | Codex | 把作业按天拆开,每完成一个独立功能提醒我 Commit | 给出按页面、核心逻辑、关卡、测试和文档划分的开发计划 | 后续根据实际进度继续调整功能安排,并由我决定何时提交和推送 |
| 界面重构 | Codex | 不使用复杂主页,保留彩色标题、动态箭头、开始和玩法说明,并美化游戏页 | 将界面重构为 Pygame,绘制渐变背景、按钮、冒险地图、状态栏和结果页 | 初版页面过空、按钮颜色不统一;我根据运行截图多次提出修改,最终确定当前布局 |
| 密集且可解的关卡 | Codex | 两种模式都要像参考图一样密集和随机,同时必须能通关 | 设计按合法消除步骤生成棋盘的方法,并加入 DFS 可解性验证 | 初版进阶棋盘空白较多且出现难以判断的布局;我试玩并反馈后,提高生成密度并保留出口射线 |
| AI 求解暂停 | Codex | 自动求解能够暂停,暂停后玩家还可以手动完成 | 增加暂停状态、操作解锁和残局重新规划 | 调整为先完成当前飞行动画再暂停,避免箭头停在半空;手动操作后不复用旧答案 |
| EXE 兼容修复 | Codex | 解决其他电脑启动时 pygame.font.SysFont 扫描系统字体崩溃 |
定位字体注册表兼容问题,改为内置 Noto Sans SC,并增加回归测试 | 重新打包并完成启动检查,同时把字体许可证加入项目 |
以下选择其中三次具有代表性的协作过程,分别展示功能完善、交互优化和兼容性修复:
AIGC 代表过程一:增加关卡通关标记
我提出“已通关的关卡可以在冒险地图上做些标记”,AI 根据要求为已通关节点增加了绿色对勾、金色外圈和最佳星级,并分别保存基础模式与进阶模式的通关记录。

AIGC 代表过程二:完善 AI 求解暂停功能
我提出“暂停 AI 求解后还可以手动求解”,AI 修改了自动求解的状态控制:暂停后允许玩家继续点击箭头、使用提示或重新开始;再次继续求解时,会根据当前棋盘重新计算步骤,避免执行已经失效的旧方案。

AIGC 代表过程三:修复 EXE 跨电脑字体报错
其他电脑运行 EXE 时出现了 pygame.font.SysFont 相关报错,我把完整错误信息交给 AI 分析。AI 判断问题来自 Pygame 扫描 Windows 系统字体时遇到异常记录,于是将字体加载方式改为直接读取程序内置的中文字体,避免依赖不同电脑的字体环境。

五、测试过程与结果
1. 测试方法
核心逻辑和页面状态使用 unittest 自动化测试,命令为:
py -m unittest discover -s tests -v
最后一次执行 py -m unittest discover -s tests -v,共运行 62 项自动化测试,测试结果全部通过,终端结果如下图所示:

2. 基础测试
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 路径判定为畅通,动画结束后箭头从棋盘移除 | 通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误次数减 1 | 箭头变红并晃动,棋盘内容不变,生命从 3 减为 2 | 通过 |
| T03 | 点击边缘且朝向棋盘外的箭头 | 正常消失,不发生越界错误 | 边界循环安全结束,箭头成功消除且没有抛出异常 | 通过 |
| T04 | 消除本关全部箭头 | 显示通关并进入下一关 | 最后一支箭头消除后显示通关页,点击按钮可加载下一关 | 通过 |
| T05 | 失误次数耗尽 | 显示失败并允许重新开始 | 第三次碰撞后显示失败页,重新挑战可恢复当前关卡 | 通过 |
| T06 | 游戏过程中重新开始 | 布局和失误次数恢复 | 棋盘、三条生命、提示次数、计时和本关分数全部恢复 | 通过 |
3. 扩展测试
| 编号 | 测试内容 | 实际结果 |
|---|---|---|
| T07 | 四方向远距离阻挡与空白格 | 能识别远处箭头,点击空白格不改变状态 |
| T08 | 所有固定关卡可解性 | 基础和进阶模式全部固定关卡均得到可执行解 |
| T09 | 随机挑战 | 两种模式生成后均通过求解器验证,相同种子可复现 |
| T10 | AI 暂停与手动操作 | 暂停后可手动消除,继续时按最新残局重新规划 |
| T11 | 存档异常与清除进度 | 损坏数据安全回退,清除操作必须二次确认 |
| T12 | EXE 中文字体 | 禁止调用系统字体扫描时仍能正常渲染中文 |
4. 问题修复与人工验收
自动化测试之外,开发时我还根据实际画面和其他电脑的运行反馈修改了两个问题:
| 发现的问题 | 修改前 | 调整与验证 |
|---|---|---|
| 进阶棋盘空白多、可解性不直观 | 随机折线虽然有颜色,但布局松散,难以确认通关顺序 | 增加覆盖率筛选和出口射线约束;用求解器验证每关,再查看游戏截图检查密度 |
| 别人电脑上的 EXE 启动报字体错误 | pygame.font.SysFont 需要读取 Windows 系统字体列表,某些环境中因此异常退出 |
改为内置 Noto Sans SC,增加“禁用系统字体扫描仍可绘制中文”的回归测试,并对新 EXE 做启动检查 |
六、PSP 表格
实际耗时是根据开发过程回顾估算的近似值,并非逐分钟计时;界面调整、测试和文档工作有交叉。
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1.0 | 1.5 | +0.5 |
| Python 与图形库学习 | 1.0 | 1.5 | +0.5 |
| 游戏界面实现 | 3.0 | 4.0 | +1.0 |
| 路径与碰撞逻辑实现 | 2.5 | 3.0 | +0.5 |
| 关卡设计 | 2.5 | 3.0 | +0.5 |
| AIGC 辅助开发 | 1.5 | 2.0 | +0.5 |
| 测试与修改 | 3.0 | 3.5 | +0.5 |
| README 与博客撰写 | 2.5 | 2.0 | -0.5 |
| 合计 | 17.0 | 20.5 | +3.5 |
七、心得体会
这次作业实际做起来比我一开始想象中复杂很多。原本以为只要实现箭头的路径判断就差不多了,后来才发现动画、碰撞反馈、关卡切换、存档、音效和程序打包等细节同样重要。AIGC 在开发过程中帮我快速完成了代码框架、界面和测试,但生成的初版并不一定符合我的要求,例如页面布局不够美观、关卡空白太多以及其他电脑运行 EXE 时出现字体错误等问题,都需要我实际运行程序、查看效果并继续提出修改。通过这次开发,我学习了如何使用二维列表表示棋盘、用方向向量统一处理四种箭头,也接触了 DFS 自动求解、随机可解关卡、JSON 存档、Pygame 动画和 PyInstaller 打包。这个过程让我明白,AI 可以提高开发效率,但不能代替自己的理解、测试和判断。虽然中间遇到了不少问题,也反复修改了很多次,但看到游戏最后拥有完整的界面、关卡和扩展功能,还是让我很有成就感。

八、项目资料与说明
- 项目运行、下载和完整操作说明见仓库中的
README.md; - 按钮、箭头和英文字体使用 CC0 授权的 Kenney UI Pack;
- 中文字体使用 SIL OFL 1.1 授权的 Noto Sans SC;
- 核心玩法参考微信小游戏《一箭又一箭》,未使用其代码、关卡、图片或音效。
浙公网安备 33010602011771号