2026 秋软件工程个人作业(第二次)
2026 秋软件工程个人作业(第二次)
| 这个作业属于哪个课程 | H202601 软件工程与软件工程实践 |
|---|---|
| 这个作业要求在哪里 | 2026 秋软件工程个人作业(第二次) |
| 这个作业的目标 | 使用 Python 和 AIGC 完成“一箭又一箭”小游戏 |
| 学号 | 102402127 |
| GitHub 仓库 | 代码在本地仓库 arrow-after-arrow,共六次提交。公开地址还没建好 |
本次作业我用 Python 和 Pygame 做了一个基础版“一箭又一箭”。路径检测、关卡和截图脚本都是和 Grok Build 一起改出来的。仓库里现在有六次提交,规则测试 19 项通过,六个关卡都能清空。
一、项目展示
开始界面有标题、四条规则和开始按钮。四种方向的箭头只作为装饰放在四角,不参与判定。

第一关叫“出门见山”。棋盘是 3×3,四支箭头分别朝上、下、左、右。顶部显示关卡名、剩余箭头、剩余失误和用时。底部有撤销、提示、重新开始和返回菜单。

第二关点中被挡住的右箭头以后,格子变红并晃一下,格子上方浮出 -1,剩余失误从 3 变成 2。箭头还留在原位。

清空一关后出现通关卡片,可以进入下一关。

失误次数耗尽后出现失败卡片,可以重新挑战本关。棋盘上的箭头不会在失败时被清掉。

第六关清空后进入全部通关。

第一关的通关过程做成了 GIF,方便核对飞出动画。

这些画面由 capture_screens.py 在 SDL_VIDEODRIVER=dummy 下直接从 Pygame Surface 导出。第一次导出时,碰撞图漏掉了,全部通关图也是错的。原因写在后面的 AIGC 记录里。
二、项目介绍
2.1 规则
棋盘是矩形网格,一格最多放一支箭头,方向只有上、下、左、右。点击一支箭头以后,程序沿着它的方向看它和棋盘边界之间有没有别的箭头。
作业里的两个例子可以直接对应到代码。
→ · · ↑ ·
左边这支朝右,右侧还有 ↑,不能飞。
↑ · · · →
右边这支朝右,右侧已经是边界,可以飞出。
贴边朝外的箭头同样可以飞。例如第 0 行的 ↑,下一步已经走出棋盘,路径是空的,判定为畅通。点空格子不会扣失误,也不会写入撤销记录。
本关全部箭头消失后进入下一关。失误次数减到 0 时本关失败,可以重新开始。重新开始会恢复本关的初始布局和失误次数。
2.2 界面和功能
开始、游戏、通关、失败四类界面都有。游戏界面固定显示当前关卡、棋盘、剩余箭头、剩余失误和重新开始。飞出时箭头沿自身方向移出棋盘并淡出。碰撞时变红、左右晃动,并浮出 -1。
基础功能之外,我加了三件小东西。
- 撤销上一步,连一次错误点击也可以收回。
- 提示,把当前盘面交给求解器,高亮它给出的第一步。
- 关卡在提交前都用深度优先搜索核过,确认存在清空顺序。
图形全部用 Pygame 画矩形和三角形,没有使用原游戏素材,也没有外部图片或音效文件。
2.3 运行方式
python -m pip install -r requirements.txt
python main.py
python -m unittest -v
本地验证环境是 Python 3.12.12 和 Pygame 2.6.1。中文界面优先加载本机的 Noto Sans CJK。
三、实现思路
规则和画面分成两层。arrow_logic.py 只处理格子、方向和点击结果。game.py 负责绘图、动画和菜单。这样路径对不对可以用单元测试直接核对,不必先打开窗口。
方向用四个字母表示,对应的行列位移写在 DIRS 里。
DIRS = {
"U": (-1, 0),
"D": (1, 0),
"L": (0, -1),
"R": (0, 1),
}
第 0 行在屏幕上方,所以“向上”是行号减一。关卡数据写成字符串,. 是空格,箭头用 ↑↓←→,读入时再转成 U/D/L/R。手工改关卡时比较直观。
路径检测是整个作业最要紧的一段。扫描从下一格开始,沿方向一格一格走,还在棋盘内就继续,碰到箭头就判定被挡,下一步会走出棋盘就判定畅通。
def can_fly(grid, row, col):
if grid[row][col] not in DIRS:
return False
for nr, nc in ray_cells(grid, row, col):
if grid[nr][nc] in DIRS:
return False
return True
贴边朝外的箭头第一步就越界,循环体一次都不进,返回畅通,不会去读 grid[-1]。点击成功时先把当前棋盘和失误次数压进历史栈,再改状态。动画只是把已经算完的结果演一遍。动画播放期间会挡住新的点击,避免连点把一次碰撞扣成两次。
求解器是带访问记录的深度优先搜索。每次合法点击都会让箭头数量严格减少,状态空间不大。提示按钮直接取解的第一步。六个关卡在测试里都会跑一遍求解器,解完后棋盘必须为空。
四、AIGC 使用过程
开发时用的是 Grok Build。下面三次是开发过程里实际发生过的,写博客时按当时改过的代码和测试回忆。
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 路径检测与规则测试 | Grok Build | 写出 can_fly、四个方向的位移,以及作业要求的 T01 到 T06 |
检测函数本身能用。第一版测试把 ←.→ 理解反了,把右侧朝右的箭头当成被挡 |
按“只看自己朝向的那一侧”改断言。后来补了一组对指布局 →.← 和 ↓/↑ |
| 关卡设计 | Grok Build | 先手写“十字路口”“回廊”“夹缝”,又写了一个随机生成器筛可解布局 | 三个手写关卡都被求解器判成死局。随机生成能出可解盘,但形状比较散 | 删掉死局。保留“出门见山”“先让路”“让一让”“夹道”,再换上已经解过的“错位”和“绕路” |
| 无窗口截图 | Grok Build | 写 capture_screens.py,用 dummy 显示驱动从 Surface 导出 PNG 和 GIF |
第一关四支箭头都能飞,碰撞图根本没生成。全部通关图用 state = "all_clear" 硬切,画面还停在第二关,文案还写着五关 |
碰撞改到第二关拍摄。全部通关改成真正打完第六关。通关卡片上的按钮也改成只有点中卡片按钮才切关 |
这三次里,AI 都能很快给出能跑的草稿。我花时间最多的地方是核对它有没有做对。关卡看起来“像一箭又一箭”,求解器却说死局。截图脚本能出图,图里的状态却和文件名对不上。这些错都是后来用测试和读图发现的。
五、测试结果
规则层用 unittest 跑,当前 19 项全部通过。T01 到 T06 对应作业要求的手工场景,另外还测了路径、撤销、重开和六个关卡的可解性。
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出并消失 | kind == "fly",格子变为 . |
通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误减 1 | kind == "blocked",箭头仍在,失误 3 变 2 |
通过 |
| T03 | 点击贴边朝外的箭头 | 正常消失,不越界 | 第 0 行 ↑ 和第 2 行 ↓ 都能飞出 | 通过 |
| T04 | 消除本关全部箭头 | 报告通关 | 只剩一支时点击,返回 cleared |
通过 |
| T05 | 失误次数耗尽 | 报告失败,允许重开 | 失误上限为 1 时,一次阻挡返回 failed |
通过 |
| T06 | 游戏中途重新开始 | 布局和失误恢复 | reset() 后格子和失误都回到关卡数据 |
通过 |
界面层用 capture_screens.py 走了一遍开始、对局、碰撞、通关、失败和全部通关。第一关存在求解器给出的四步清空顺序。第二关用被挡的右箭头验证碰撞和失败。第六关按求解器顺序清空后出现全部通关卡片。
本地跑测试时的输出是这样。
python -m unittest -v
Ran 19 tests in 0.001s
OK
六、PSP 表格
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 0.5 | 0.4 | -0.1 |
| Python 与图形库学习 | 0.5 | 0.2 | -0.3 |
| 游戏界面实现 | 1.5 | 0.8 | -0.7 |
| 路径与碰撞逻辑实现 | 1.0 | 0.5 | -0.5 |
| 关卡设计 | 0.8 | 0.6 | -0.2 |
| AIGC 辅助开发 | 1.0 | 0.8 | -0.2 |
| 测试与修改 | 1.0 | 0.6 | -0.4 |
| README 与博客撰写 | 1.2 | 0.7 | -0.5 |
| 合计 | 7.5 | 4.6 | -2.9 |
预估按自己从零写一版来算。实际更短,因为界面骨架和测试可以由 AI 先铺出来。时间并没有省在“不用看代码”上,而是省在少写重复的绘制和断言。关卡死局、截图状态错误和测试断言写反,这三处仍然要自己对着棋盘和画面看。
七、心得体会
路径检测写出来只有十几行,错却经常出在“朝哪边看”。AI 第一次给测试时,把同一行里方向相反的两支箭头搞混了。人如果只看代码像不像,也会一起错。后来我把作业原文里的两个例子写成测试,对指的情况再单独测一遍,这块才稳住。
关卡更明显。我让 AI 直接排一个十字和一个回廊,盘面好看,求解器却返回 None。随机生成能保证可解,生成出来的形状又比较乱。最后采用的办法很土,先用手排出前几关,再只收下求解器能走完的布局。作业要求“每个关卡都应由本人实际试玩”,求解器只能证明存在顺序,不能代替自己点一遍。
截图脚本是这次最具体的教训。文件名写着 collision 和 all-clear,图里却可能还是另一关。AI 会为了交文件而直接改状态。后来改成真正去点格子、等动画结束再存盘,图才和规则对上。
AI 在这次作业里适合做三件事。铺 Pygame 窗口和按钮,补边界条件和单元测试,以及根据报错改一处具体代码。它不适合在没有求解器的情况下直接定关卡,也不适合在没有读图的情况下宣称“界面已经拍好”。代码进仓库以后,路径对不对、关卡能不能过、画面是不是当前状态,仍然要由提交的人负责。
浙公网安备 33010602011771号