2026秋软件工程个人作业(第二次)——一箭又一箭(完善版)
作业信息
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | 2026 秋软件工程与软件工程实践 |
| 这个作业要求在哪里 | 2026 秋软件工程个人作业(第二次) |
| 这个作业的目标 | 使用 Python 和 AIGC 完成“一箭又一箭”小游戏,并完成测试、文档与发布 |
| 学号 | 032401339 |
| GitHub 仓库 | 2074599796-cpu/arrow-escape-game |
前言
刚看到作业时,我先想的是把基本玩法做出来。做到能正常运行以后,我又觉得普通箭头有点单调,正好我比较喜欢《剑来》,于是就把箭头换成了飞剑,把关卡叫作“境界”,生命值也改成了“剑心”,最后做成了现在这个《一箭又一箭·飞剑问道》。
这个游戏用 Python 自带的 Tkinter 完成,不需要再安装 Pygame。我前后断断续续做了五天,顺序大概是:先把路径判断、失败和过关做对,再慢慢加飞出动画、碰撞、五个关卡、提示、撤销、星级和存档。AI 确实帮我写了不少代码,但第一版通常不会刚好符合我的想法,后面很多时间其实都花在看效果、提修改和重新测试上。
一、项目展示
1. 开始界面
开始界面用了水墨风的人物海报,下面有“执剑入局”和“境界选择”两个按钮。原图是竖版的,直接放进窗口里会很别扭,所以我让 AI 把人物保留在中间,再把两边暗化延展,底部也压暗一点,避免文字和按钮看不清。

图片来源:小红书(原链接未保存),仅用于课程作业展示。飞剑透明素材由 AIGC 根据本人提供的参考图重新制作,项目没有直接使用商业游戏的代码、关卡或音效。
我最初使用的飞剑又短又胖,缩小后很难看出剑身细节,所以又在网上寻找了自己喜欢的飞剑造型作为参考,并交给 AI 处理成透明素材。之后我继续要求去掉飞剑周围圆形的一圈,保留细长剑身和清楚的剑尖方向,才形成现在的效果。
2. 境界选择
游戏一共有五关。开始时只能玩第一关,通关以后才会解锁下一关;已经解锁的关卡可以重新挑战,拿到的最好星级会保存在电脑里,下次打开游戏还能看到。

3. 游戏过程
游戏页面上面会显示当前是第几关、还剩多少飞剑、剑心、积分和时间。右上角有“提示”“撤销”和“重整”,也可以直接按 H、Ctrl+Z 和 R。

4. 提示和撤销
点“提示”后,棋盘会标出一把现在就能飞出去的剑,同时在下面显示它的位置。它只告诉我该点哪一把,不会自动替我操作。

“撤销”会把上一步的飞剑、剑心和积分一起恢复。我特地把提示文字也截了下来,这样能看出按钮确实有反应,不只是摆在界面上。

5. 飞出与碰撞
飞剑前面没有阻挡时,会沿着剑尖方向加速飞出去,后面还能看到剑气和光点。动画播完以后,这把剑才会真正从棋盘里删除。

如果前面有别的飞剑挡着,当前飞剑会先往前冲一下,撞出火花和冲击环,然后再弹回原来的位置,同时扣掉一点剑心。这个效果也是我觉得原版反馈不够明显以后继续加的。

6. 后期关卡和失败界面
越到后面棋盘越大,飞剑数量也越多。下面是第五关“十四境”,它是 9×9 棋盘,一共有 24 把飞剑。

连续点错三次以后剑心耗尽,会进入“问剑未成”页面,可以直接重新挑战当前关卡,也可以返回开始界面。

7. 过关结算
清空棋盘以后会显示星级、积分和用时,并保存这一关拿到的最好星级。普通关卡可以继续进入下一境,五关全部完成后会出现“五境皆破”的结算页。


二、项目介绍
1. 游戏规则
规则其实不复杂。每把飞剑只有上、下、左、右四个方向,剑尖指向哪里,它就往哪里飞。点击以后,程序会顺着这个方向检查同一行或同一列:
- 前方没有其他飞剑:播放飞出动画,动画结束后消除该飞剑,积分增加 100;
- 前方存在其他飞剑:飞剑碰撞回弹,不会消失,剑心减少 1,积分扣除 25(最低为 0);
- 清空本关所有飞剑:显示过关结果并解锁下一境;
- 三点剑心全部耗尽:显示失败页面,可以重新挑战当前境界;
- 点击空格、动画期间重复点击或在非游戏状态点击,不会错误删除飞剑。
这里有一点比较容易看错:挡路的那把剑朝哪个方向并不重要,而且两把剑中间就算隔着空格也还是会挡住。只要它在当前飞剑正前方的同一行或同一列上,就不能直接飞出去。
2. 五个境界
| 境界 | 棋盘大小 | 飞剑数量 | 设计特点 |
|---|---|---|---|
| 骊珠洞天 | 5×5 | 6 | 用较少飞剑熟悉四方向判断 |
| 山水问剑 | 6×6 | 11 | 增加隔空阻挡和交叉路线 |
| 剑气长城 | 7×7 | 16 | 多行多列相互限制,消除顺序更重要 |
| 倒悬山 | 8×8 | 18 | 棋盘扩大,边缘与中心路线同时出现 |
| 十四境 | 9×9 | 24 | 综合考察四方向、远距离阻挡和消除顺序 |
这些关卡不是随便把剑摆上去就算完成。每次改完布局,我都会让测试程序从当前能飞出去的剑开始,一步步尝试把棋盘清空。能找到完整顺序以后,我才会保留这个关卡。
3. 已实现功能
| 类型 | 功能 |
|---|---|
| 基础玩法 | 四方向飞剑、路径检测、三次失误机会、过关与失败、重新开始 |
| 界面反馈 | 飞出动画、剑气拖尾、碰撞冲击环、回弹、文字提示、提示音 |
| 关卡系统 | 五个固定可解关卡、逐关解锁、境界选择 |
| 辅助操作 | 安全飞剑提示、撤销上一步、快捷键操作 |
| 成绩系统 | 积分、计时、星级评价、最佳星级记录 |
| 本地保存 | 保存最高解锁关卡与各关最佳星级,损坏存档可安全恢复 |
| 测试检查 | 规则和界面分开测试、12 项自动化测试、GUI 动画检查 |
4. 运行方法
运行环境是 Python 3.10 或更高版本。下载仓库以后,在项目目录运行:
python main.py
如果是 Windows,也可以直接双击 run_game.bat。游戏不需要联网,也不用再装第三方库。
三、实现思路
1. 项目结构
为了后面修改起来方便,我把规则、界面、关卡和存档分成了几个文件,没有把所有东西都塞进 main.py:
arrow_escape_game/
├─ arrow_game/
│ ├─ model.py # 飞剑、方向、游戏状态与路径规则
│ ├─ levels.py # 五个固定关卡
│ ├─ progress.py # 解锁进度与最佳星级存档
│ └─ app.py # Tkinter 界面、动画、计时与交互
├─ assets/ # 四方向飞剑和开始背景
├─ screenshots/ # 博客展示截图
├─ tests/ # 自动化测试
├─ tools/ # 素材构建、截图和 GUI 冒烟测试工具
├─ main.py
└─ run_game.bat
其中 GameEngine 不负责画图,只管关卡、飞剑、剑心、积分和游戏状态;ArrowGameApp 负责画面、按钮和动画。这样我改界面时,不容易把原来的路径规则一起改坏。
2. 飞剑与方向表示
每把飞剑只需要记录行、列和方向。四个方向对应的行列变化写在同一个地方:
class Direction(str, Enum):
UP = "U"
DOWN = "D"
LEFT = "L"
RIGHT = "R"
@property
def delta(self):
return {
Direction.UP: (-1, 0),
Direction.DOWN: (1, 0),
Direction.LEFT: (0, -1),
Direction.RIGHT: (0, 1),
}[self]
例如向上就是行号减 1,向右就是列号加 1。关卡文件只管飞剑放在哪一格,真正显示时,再由界面把行列换算成画布坐标,并选择对应方向的飞剑图片。
3. 路径与最近阻挡物检测
我一开始以为要像动作游戏一样做碰撞框,后来发现这个游戏只需要看格子位置:有没有别的飞剑和它同行或同列,而且刚好在它前面。
def find_blocker(self, arrow: Arrow) -> Arrow | None:
dr, dc = arrow.direction.delta
blockers = []
for candidate in self._arrows.values():
if candidate == arrow:
continue
row_gap = candidate.row - arrow.row
col_gap = candidate.col - arrow.col
if dr and col_gap == 0 and row_gap * dr > 0:
blockers.append((abs(row_gap), candidate))
elif dc and row_gap == 0 and col_gap * dc > 0:
blockers.append((abs(col_gap), candidate))
return min(blockers, key=lambda item: item[0])[1] if blockers else None
代码里的乘法判断用来排除飞剑后面的对象,同行、同列判断则排除斜对角位置。如果前面有不止一把剑,就取最近的那一把,这样碰撞火花才会画在正确的位置。
4. 一次点击如何完成状态变化
一次有效点击按以下顺序处理:
点击飞剑
↓
保存操作前快照(用于撤销)
↓
查找前方最近阻挡物
├─ 无阻挡:播放飞出动画 → 删除飞剑 → 加分 → 判断是否过关
└─ 有阻挡:扣剑心与积分 → 播放冲刺/火花/回弹 → 判断是否失败
这一步最容易出问题。如果动画还没结束就马上删数据,或者允许连续点击,就可能一次删掉两把剑。所以飞出动画结束后才刷新棋盘,碰撞时则一直保留原来的飞剑;动画播放期间也暂时不接收新的棋盘点击。
5. 提示、撤销与存档
提示功能会找出一把当前没有被挡住的飞剑,只把它标出来,不会替玩家点击。撤销则是在每次有效操作前先保存一份 GameSnapshot,里面有飞剑、剑心、积分和当前状态;点撤销时把这份记录恢复回来。
进度用 JSON 文件保存在 Windows 用户目录,只记住解锁到第几关和每关最好星级,不保存玩到一半的棋盘。我还加了一个损坏存档测试:如果文件内容不是合法 JSON,游戏会回到只解锁第一关的状态,不会因为一个坏文件直接打不开。
6. 关卡可解性验证
五关的可解性用广度优先搜索检查。简单来说,就是每次找出所有能飞走的剑,分别尝试删除,再继续往下找,直到棋盘为空。这个办法能说明关卡至少有一种正确顺序,不过它只能证明“能通关”,不能判断这一关玩起来是不是太难或者太无聊。
四、从普通箭头到飞剑主题的迭代
这个游戏并不是一次就变成现在这样。下面几次修改是我自己感觉变化最明显的地方。
1. 第一版:先完成基础规则
最开始我只要求先把棋盘、四方向箭头、路径判断、过关和失败做出来。那一版虽然能玩,但是界面比较简单,看起来很像一个测试程序。
2. 第二版:替换为白玉金纹飞剑
后来我提出把箭头换成《剑来》风格的飞剑,并发了一张我喜欢的飞剑图片。第一次做出来的剑周围还有一圈圆形装饰,缩小后又短又胖,我觉得实在不好看,所以继续要求去掉圆圈,保留细长剑身和明显的剑尖,最后再转成上、下、左、右四张图片。
3. 第三版:补足“飞出去”和“撞回来”
只把箭头换成图片以后,点下去还是没有“飞剑”的感觉。我又要求它飞出去时越来越快,并加上剑气和光点;撞到别的剑时则往前冲、出现火花,再弹回原位。改完以后重新测试,能飞的剑会消失,被挡的剑还在,剑心也会正常减少。
4. 第四版:从完成作业到增强可玩性
基础玩法稳定以后,我把三关增加到五关,又加入境界选择、计时、积分、星级、提示、撤销和存档。每加完一项都会重新跑原来的测试,避免新功能把已经正常的路径判断弄坏。
五、AIGC 使用过程
记录一:需求拆分和结构设计
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 需求分析 | Codex | 将题目拆成规则、界面、关卡、测试和文档模块 | 得到清晰开发顺序 | 本人逐项对照作业要求复核 |
我的要求:先阅读完整作业要求,再一步一步确认对“一箭又一箭”玩法、基础要求和博客结构的理解,最后给出适合小型 Python 图形项目的实现方案,完成后交给我检查。
AI 给出的方案是把规则、界面和关卡分开写。开始时我不太清楚这样做有什么用,后面加动画和测试时才发现确实方便,所以把这个结构保留了下来,界面文字和主题则按我的想法改成飞剑风格。
记录二:路径检测实现
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 路径检测 | Codex | 生成四方向阻挡判断和最近阻挡物查找逻辑 | 自动测试通过 | 本人检查边界、对角线和后方误判 |
我的要求:实现同行、同列的飞剑路径检测,不能让斜对角飞剑造成误判,并找出距离最近的阻挡者。

AI 根据方向比较行列差,再筛选飞剑前方候选项。我通过 T01~T03 检查无阻挡飞出、有阻挡保留和边缘位置,并将最近阻挡物接入碰撞效果。
记录三:测试与关卡检查
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 测试设计 | Codex | 生成核心功能测试和关卡可解性搜索思路 | 12 项测试可重复执行 | 本人复跑测试并核对输出 |
我的要求:按照作业给出的 T01~T06 场景编写自动化测试,并避免设计出无法通关的关卡。
AI 按照作业要求先写了 T01~T06,后来我又让它增加关卡可解性、提示、撤销、选关和存档测试,最后一共 12 项。这样不只是看到游戏能打开,还能检查每个功能有没有真的改变正确的数据。
记录四:飞剑素材与动画
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 主题美术 | 图像生成与 Codex | 根据参考图生成透明白玉金纹飞剑,并制作四方向素材和动画 | 剑尖清晰、无圆形底座 | 对比两版后保留细长比例并调整动画 |
我的要求:把普通箭头改为参考图中的白玉金纹飞剑,去掉飞剑周围的圆圈,并体现飞剑飞出棋盘的速度感。

第一版素材缩小以后还是显得太圆,我又要求继续去掉外面的装饰。飞出距离、速度、拖尾数量和碰撞回弹也改了几次。最后看起来比较像一把剑真正飞出去,而不是图片突然消失。
记录五:扩展功能与状态恢复
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 扩展功能 | Codex | 增加选关、积分计时、星级、提示、撤销和本机存档 | 操作更完整,测试保持通过 | 保留简单按钮和 JSON 存档,便于理解 |
我的要求:在基础功能正确的前提下,按照附加分示例增加实用功能,但不要让代码和操作变得难以解释。

最后做出来的效果是:提示只标出一把安全的剑,撤销会恢复刚才的操作,通关以后能解锁下一关并保存星级。加完第四、第五关后又重新检查了一遍可解性,存档损坏的情况也单独测试了。
AIGC 使用总结
AIGC 最大的帮助就是能很快把我的想法做成一个可以看的版本,但“能运行”和“我满意”还是两回事。比如第一版飞剑有圆圈、飞出去不够快、碰撞也不明显,这些都是我实际看到以后再继续要求修改的。至于关卡能不能通,也不能只听 AI 说,需要测试程序真的找到清空顺序。
这次项目里 AI 写了比较多代码,我主要负责定规则、选主题、找参考图、看运行效果,再把不满意的地方说清楚让它继续改。我重点看懂了方向变化、同行同列判断、最近阻挡物、撤销快照和 JSON 存档,因为这些都是老师问到时必须能说明白的部分。
六、测试过程与结果
1. 测试环境
- 操作系统:Windows
- Python:3.13.0
- 图形库:Tkinter(Python 标准库)
- 单元测试命令:
python -m unittest discover -s tests -v - 最近复测:2026-09-21,12 项全部通过,用时 24.102 秒
2. 作业要求与扩展测试
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的飞剑 | 飞剑飞出棋盘并消失 | 删除对应飞剑,剩余数量减 1 | 通过 |
| T02 | 点击前方有阻挡的飞剑 | 飞剑不消失,失误次数减 1 | 返回最近阻挡者,飞剑保留,剑心减 1 | 通过 |
| T03 | 点击边缘且朝向棋盘外的飞剑 | 正常消失,不发生越界错误 | 正确完成最后一步并进入结算 | 通过 |
| T04 | 清除本关全部飞剑 | 显示通关并可进入下一关 | 状态变为本关完成,下一关可载入 | 通过 |
| T05 | 失误次数耗尽 | 显示失败并允许重开 | 剑心归零后进入失败状态 | 通过 |
| T06 | 游戏进行中重新开始 | 布局和失误次数恢复 | 飞剑、剑心和状态恢复初始值 | 通过 |
| T07 | 验证全部固定关卡 | 五关均存在合法清空顺序 | 状态搜索找到空棋盘 | 通过 |
| T08 | 请求提示 | 返回一把当前可安全飞出的飞剑 | 返回结果前方无阻挡 | 通过 |
| T09 | 撤销上一步 | 恢复飞剑、剑心和积分 | 快照内容完整恢复 | 通过 |
| T10 | 从选关页进入指定境界 | 正确加载对应关卡 | 关卡编号和状态正确 | 通过 |
| T11 | 保存并重新读取进度 | 解锁关卡和最佳星级一致 | JSON 写入、读取结果一致 | 通过 |
| T12 | 读取损坏的存档文件 | 程序不崩溃并恢复初始进度 | 返回第一关和空星级记录 | 通过 |
3. GUI 动画专项检查
单元测试只能检查数据,看不到动画,所以我又运行了 python tools/smoke_gui.py。它会先点击一把被挡住的飞剑,确认回弹后飞剑还在、剑心从 3 变成 2;再点一把能飞出的剑,确认动画结束后它确实消失;最后继续检查积分、撤销和提示。输出为:
GUI smoke test OK: collision, flight, score, undo and hint all finished
博客里的游戏截图都是当前程序实际打开以后截下来的,不是单独画出来的效果图。为了让博客展示得更完整,我又补截了提示、撤销、第五关、失败和五境通关页面。
自动测试可以反复检查规则,但它不知道按钮好不好点、飞剑方向清不清楚,这些还是要打开游戏以后自己看。所以我把自动测试结果和界面截图分开记录,没有把“程序能找到解”直接写成“我已经手动通关”。
七、PSP 表格
这个作业是我分五天断断续续做完的。下面的时间是按开发过程回忆整理的,实际大约用了 18.5 小时。
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 0.8 | 1.2 | +0.4 |
| Python 与图形库学习 | 1.0 | 1.5 | +0.5 |
| 游戏界面实现 | 2.0 | 4.0 | +2.0 |
| 路径与碰撞逻辑实现 | 1.5 | 2.5 | +1.0 |
| 关卡设计 | 1.0 | 2.0 | +1.0 |
| AIGC 辅助开发 | 1.0 | 1.5 | +0.5 |
| 测试与修改 | 1.5 | 2.7 | +1.2 |
| README 与博客撰写 | 1.5 | 3.1 | +1.6 |
| 合计 | 10.3 | 18.5 | +8.2 |
最后比我一开始估计的多了 8.2 小时,主要都花在界面、动画和测试上。路径判断本身没有特别长,但飞剑缩小以后好不好看、动画快不快、撞击够不够明显,都要反复打开游戏才能知道。开始背景和飞剑素材也改了几版;加了提示、撤销和存档以后,还要重新跑原来的测试并补截图,所以实际时间比一开始想的长不少。
八、心得体会
这是我第一次把一个小游戏从规则、界面、素材一直做到测试和博客展示。刚开始我最在意的是“画面像不像一个游戏”,做到后面才发现,什么时候能点击、怎样算被挡住、动画结束前要不要删掉飞剑、重新开始要恢复什么,这些规则如果没想清楚,界面做得越多越容易出错。
把 GameEngine 和 Tkinter 界面分开,是我这次觉得最有用的一点。这样不用每次都手动点游戏,也能检查路径、失败、过关、提示和撤销。如果所有判断都写在鼠标点击里面,后面改动画时我可能根本不知道是画面错了,还是游戏数据错了。
AIGC 确实让我少走了很多弯路,不过它不是输入一句话就直接给我成品。飞剑外观、背景、碰撞回弹、五关布局和博客都改了很多次。它可以很快给出一版,我还是要自己判断好不好看、符不符合题目、能不能运行,以及我自己能不能解释清楚。
如果以后继续做,我想增加可以选难度的随机关卡,再加一个能开关的背景音乐(一开始没想到)保证五关可解、点错以后有明显反馈、测试能够重复运行,比盲目加更多功能更重要。最后看到普通箭头真的变成飞剑飞出去时,还是挺有成就感的。
感谢阅读。

浙公网安备 33010602011771号