2026 秋软件工程个人作业(二)|一箭又一箭:纸上航线
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | H202601 软件工程与软件工程实践 |
| 这个作业要求在哪里 | 2026 秋软件工程个人作业(第二次) |
| 这个作业的目标 | 使用 Python 和 AIGC 辅助开发一个可运行、可测试的图形化箭头解谜游戏,实践需求分析、实现、测试和交付 |
| 学号 | 082403146 |
| GitHub 仓库 | Grim761 / ArrowGame |
项目概览: 3 个固定关卡,19 项自动测试通过;实现箭头飞行、失败重开、提示与自动演示,采用“纸上航线”界面和随项目分发的中文字体。
一、作品展示:一箭又一箭 · 纸上航线
这次用 Python 和 Pygame 做了一个箭头解谜小游戏,名字叫“一箭又一箭”。界面最后做成了“纸上航线”的样子:纸色背景、墨绿色箭头,配上文楷字体和一个小印章。界面里的图形都是用 Pygame 画的,没有另外找图片素材。
1. 开始界面
开始界面包含游戏标题、开始入口和简短玩法说明。

2. 游戏过程
棋盘是 6×6 网格。顶部显示关卡进度、剩余箭头和错误机会,游戏过程中可以查看提示、开启演示或重开当前关。

规则很简单:点击前方一直到棋盘边缘都没有其他箭头的箭头,它就会沿自身方向飞出;如果前方有其他箭头,当前箭头保留,并扣除一次错误机会。判断的是整条路径,而不只是相邻格。
每关初始有 3 次错误机会,耗尽后失败;清空棋盘后显示单关完成提示,点击进入下一步。第三关完成后进入全部完成界面,可以重新挑战。
3. 失败与全部完成
点击被阻挡的箭头会得到红色反馈,帮助玩家理解错误原因。失败后点击重开当前关;游戏过程中也可以按 R 或点击重开按钮。



这些 GIF 是运行游戏时用模拟点击录制的,展示了主要操作流程。另外,我自己也实际试玩了三个关卡。
二、运行方法与项目结构
项目使用 Python 和 Pygame。当前验证环境为 Windows、Python 3.13.13、Pygame 2.6.1。
下载仓库并进入项目目录后执行:
python -m pip install -r requirements.txt
python main.py
操作方式:
| 操作 | 功能 |
|---|---|
| 鼠标点击箭头 | 尝试让箭头沿自身方向飞出 |
| R / 重开按钮 | 重开当前关,恢复棋盘和错误机会 |
| H / 提示按钮 | 高亮一个可以移除的箭头 |
| A / 演示按钮 | 开始或停止当前关自动演示 |
| 结果界面点击 | 重开、进入下一步或重新挑战 |
动画或演示进行时会限制冲突输入,防止在箭头飞行期间重复处理同一次操作。
ArrowGame/
├── main.py # 窗口、绘制、输入、动画与辅助操作
├── game.py # 游戏规则和状态
├── levels.py # 三个固定关卡
├── solver.py # 求解器
├── requirements.txt
├── assets/fonts/ # 中文字体、许可证与来源说明
├── tests/
│ ├── test_game.py # T01~T06 与规则回归测试
│ ├── test_main.py # 实际事件循环回归测试
│ └── test_extensions.py # 提示、演示、反馈、字体测试
└── docs/ # 验收记录、附加功能说明与截图
拆开后,测试路径判断和错误次数就不用每次打开窗口了。代码主要是一些函数和一个 Game 类,测试用的是 Python 自带的 unittest。
三、核心设计与实现
1. 棋盘表示与路径判断
棋盘使用二维列表:None 表示空格,UP、DOWN、LEFT、RIGHT 表示箭头方向。
can_move() 先检查坐标是否有效、当前位置是否确实有箭头,再根据方向确定行列增量。例如向上为 (-1, 0),向右为 (0, 1)。随后从前方第一格扫描到边界:
r, c = row + dr, col + dc
while 0 <= r < len(board) and 0 <= c < len(board[0]):
if board[r][c] is not None:
return False
r += dr
c += dc
return True
途中遇到任意箭头就不能移动。位于边缘且朝外的箭头,前方第一格已经在棋盘外,循环不会执行,因此可以直接飞出,也不会访问越界坐标。
例如,一行中的局部排列为 RIGHT、None、UP,最左侧的箭头虽然紧邻空格,但向右扫描时仍会遇到 UP,因此不能移除。这里阻挡是否成立只取决于前方格子有没有箭头,与阻挡者自身朝向无关。只有等前方箭头被移除后,这条路径才可能畅通。
can_move() 只返回判断结果,不删除箭头、不扣除错误次数。把“能不能移动”和“点击后怎样更新状态”分开,可以让游戏、求解器和测试共用同一套路径规则。
2. 状态管理与重开
Game 保存当前关卡、当前棋盘、剩余错误机会和完成/失败状态。重开时逐行复制初始棋盘:
self.level = [row[:] for row in levels[self.current_level]]
这样删除当前棋盘中的箭头不会改坏原始关卡。重开同时恢复 3 次错误机会,并清除结束状态。
结束界面的点击单独处理,不会在切换关卡的同一次点击中又消除下一关的箭头。最后一关完成后进入全部完成状态,避免把索引继续增加而显示不存在的第四关。
一次正常点击的处理顺序可以概括为:先检查坐标,再判断是否为空格,最后调用 can_move()。点击空格或棋盘外不改变错误机会;合法点击将对应格子设为 None,并检查棋盘是否清空;被阻挡时保留箭头,错误机会减 1,减到 0 后进入失败状态。
当前关完成与全部关卡完成是两个不同的状态。第一、二关完成后,结果界面的点击会加载下一关;第三关完成后,点击进入全部完成界面,再点击才能从第一关重新挑战。重开当前关保留关卡编号,重新挑战则先把编号恢复为 0,再初始化棋盘和错误机会。
3. 飞行动画与规则层的配合
main.py 先判断箭头能否移动,播放飞行动画,动画结束后再调用 Game.click() 完成真正的消除和通关判断。这样画面播放和棋盘数据更新就不会混在一起。
验收时发现过一个实际问题:点击之前如果某一帧卡顿,新箭头会错误地继承这段时间,从而一下飞完。修复后,新动画从后续帧开始累计时间,并加入回归测试,避免“有动画代码,却看不到动画”的情况。
动画期间,界面记录箭头所在的行列、方向和当前位移,根据每帧经过的时间推进位置。正在飞行的箭头由动画绘制,棋盘原位置不再重复绘制,但规则层暂时保留该箭头。飞行结束后才提交消除,因此最后一支箭头不会还在路上就弹出通关提示。期间忽略冲突点击与重开操作,也能避免同一箭头被重复处理。
4. 求解器与三关验证
find_solution() 在棋盘副本上寻找可移除箭头,逐个删除并记录坐标;棋盘清空时返回移除顺序;如果仍有箭头却找不到合法移动,则返回 None。空棋盘返回空列表。
本规则下,移除一个箭头只会减少阻挡,不会让原本可以移动的箭头变得不能移动,所以求解器可以每次选取第一个合法箭头,不需要递归回溯。求解过程不会修改玩家当前棋盘。
| 关卡 | 初始箭头数 | 合法解长度 | 验证情况 |
|---|---|---|---|
| 第一关 | 12 | 12 | 求解器逐步验证通过,本人已试玩 |
| 第二关 | 12 | 12 | 求解器逐步验证通过,本人已试玩 |
| 第三关 | 16 | 16 | 求解器逐步验证通过,本人已试玩 |
验证一个解时,不只是检查“返回了多少步”,还会把坐标顺序放到新的棋盘副本上逐步执行:每一步都必须满足 can_move(),执行完后棋盘必须为空。这样可以检查解法是否合法,而不是只依赖求解器自己报告“有解”。
提示与自动演示也复用了这份结果:提示取当前解法的第一步,演示则按顺序播放。返回 None 与返回空列表需要区分,前者表示仍有箭头却无法继续,后者表示棋盘已经清空。
四、附加功能与界面改进
1. 下一步提示
按 H 或点击提示按钮,会用金色高亮一个当前合法的箭头。提示只展示建议,不直接删除箭头,也不扣除错误机会。

2. 自动求解演示
按 A 或点击演示按钮后,程序利用本地求解器逐步播放当前关的消除过程。停止演示时会完成正在飞行的箭头,然后停止后续步骤。演示不会自动跳过关卡结果界面。
使用过提示或演示的关卡会显示辅助通关标记,避免把辅助完成与独立完成混在一起。这些功能调用的是本地算法,不是运行时的大模型服务。

3. 反馈与便携字体
除了飞行动画,还增加了阻挡位置的红色反馈和文字提示。开始、失败、单关完成和全部完成使用统一的视觉语言。
字体采用项目内置的霞鹜文楷,通过相对于 main.py 的路径加载,移除了个人电脑上的 Windows 字体绝对路径。即使从其他工作目录启动,也能够找到项目字体。目前已在 Windows 验证,其他系统仍待实测。
字体来源为 LXGW WenKai 官方仓库,采用 SIL Open Font License 1.1;原始字体、OFL.txt 和 SOURCE.txt 一同保留在项目中。提示和自动演示是这次另外加上的功能。
五、AIGC 辅助开发记录
以下记录根据实际开发对话整理。ChatGPT 主要辅助解释与梳理,Codex 主要辅助代码修改和验证。
记录 1:先检查规则,再拆分代码
工具: ChatGPT、Codex。
当时的需求: “重点检查 can_move、find_solution、三关切换、失败重开和全部通关逻辑”,并要求先说明问题与拆分方案,不改变规则,保持代码适合 Python 初学者阅读。
AI 的帮助与效果: 解释路径扫描与状态管理,将界面、规则、关卡、求解器及测试拆开。拆分后可以直接测试规则,不必每次打开游戏窗口。
我的工作与核对: 明确“保留三关及现有玩法”的范围,结合代码理解为什么需要逐行复制棋盘,以及结束界面的点击为何要单独处理。代码拆分由 Codex 辅助完成。
记录 2:对能运行的游戏做完整验收
工具: Codex。
当时的需求: 运行游戏与全部自动测试,检查模块接口、三关可解性和完整流程,不为代码风格而重写功能。
AI 的帮助与效果: 发现新动画会继承点击前卡顿时间的问题,修复计时方式,并增加“长帧不能跳过新动画”的回归测试。验收也覆盖失败重开、下一关和全部完成。
我的工作与核对: 限定修改范围,要求不通过修改测试来掩盖错误,并实际试玩三个关卡。自动化检查与回归测试由 Codex 辅助执行。
记录 3:让界面有特点,让字体能随项目运行
工具: Codex。
当时的需求: “只优化 UI”“把游戏字体换成更通用的而不是我的本地路径上的字体”“风格也可以独特一点”,并希望实现与作业相关的附加功能。
AI 的帮助与效果: 实现“纸上航线”风格、统一结果界面与项目内字体,补充提示、自动演示和阻挡反馈。字体加载不再依赖个人电脑上的绝对路径。
我的工作与核对: 提出视觉方向、便携性和保持规则的要求,核对功能说明与试玩体验。UI 和辅助功能由 Codex 辅助实现。
记录 4:核对文档与真实版本
工具: ChatGPT、Codex。
问题: 功能更新后,旧文档仍存在“12 项测试”和“固定 Windows 字体路径”等过期描述。
AI 的帮助与效果: 对照当前项目,将文档统一为 19 项测试和项目内置字体,并整理截图与博客结构。
我的工作与核对: 确认学号、本人实际试玩情况及耗时估算。这个过程也提醒我:代码修改完成后,还要检查 README 和验收记录是否同步更新。
六、测试与验收
执行完整自动测试:
python -m unittest discover -s tests -v
当前完整测试结果为 19 项通过:10 项规则与求解器测试、2 项事件循环测试、7 项附加功能测试。一次验收输出为:
Ran 19 tests in 0.858s
OK
耗时会随环境变化,19 项通过是本次记录的结果。
| 编号 | 测试场景 | 预期结果 | 实际结果 |
|---|---|---|---|
| T01 | 点击前方无遮挡的箭头 | 箭头能够飞出并消失 | 规则自动测试通过;界面飞行由事件循环测试和试玩补充验证 |
| T02 | 点击前方被其他箭头阻挡的箭头 | 箭头保留,错误机会减少 1 次 | 通过;另有阻挡反馈测试 |
| T03 | 点击位于边缘、方向朝外的箭头 | 正常移除,不发生越界错误 | 通过 |
| T04 | 清除当前关全部箭头 | 出现通关状态,可进入下一关 | 通过;三关流程由事件循环测试补充验证 |
| T05 | 连续错误直至机会耗尽 | 进入失败状态,可重开当前关 | 通过 |
| T06 | 游戏中重开 | 恢复初始棋盘、3 次错误机会和正常状态 | 通过 |
另外检查了三关求解结果可逐步执行、无解棋盘返回 None、求解器不修改输入、全部完成后的状态,以及提示、演示停止、字体相对路径等回归场景。
除了自动测试,我也试玩了三个关卡,检查实际操作时的画面和动画。详细验证记录见仓库 docs/acceptance.md。
七、PSP 耗时记录(回顾估算)
开发过程中没有持续逐项计时。下表由 AI 提供初始建议,本人结合开发过程确认采用;“实际”栏为回顾估算,“预计”栏为回顾性对照,并非开发前留存的计划。各阶段合并统计,数据可能存在误差。
| PSP 阶段 | 预计耗时(分钟) | 实际耗时(分钟,回顾估算) | 差值(实际-预计,分钟) |
|---|---|---|---|
| 需求分析 | 30 | 45 | +15 |
| 设计(棋盘、关卡与状态设计) | 45 | 60 | +15 |
| 编码(界面、规则、动画及辅助功能) | 180 | 260 | +80 |
| 测试与修复 | 90 | 140 | +50 |
| 文档与发布准备 | 60 | 75 | +15 |
| 合计 | 405 | 580 | +175 |
预计合计 6 小时 45 分钟,实际回顾估算 9 小时 40 分钟,相差 2 小时 55 分钟。AIGC 辅助、图形库学习和关卡调试分散在上述阶段中,没有另行重复计时。差异主要集中在编码与测试:初步能运行之后,还需要处理结束状态、动画计时、字体路径和界面输入之间的配合。
八、总结与后续改进
做完后回头看,路径判断其实比较直接:沿着箭头方向一直找,遇到其他箭头就停下。需要反复检查的反而是几个界面之间的切换,比如最后一个箭头飞完后什么时候算通关,第三关结束后又该显示什么。
这次比较有用的做法是把规则单独放进 game.py。改界面时可以先跑测试,看看有没有影响原来的玩法。重开时复制棋盘、动画结束后再删除箭头,这些看起来很小的处理,也直接影响游戏能不能正常运行。
还有两件事这次做得不够好:Git 到后期才开始整理,耗时也没有及时记录。下次准备每做完一个小功能就提交一次,同时记下花了多久。目前游戏只有三个固定关卡,也只在 Windows 上验证过,其他系统还需要再试。
项目代码、字体许可、运行说明和测试记录均见 GitHub 仓库。

浙公网安备 33010602011771号