软工第二次作业_一箭又一箭
一箭又一箭 —— Python + AIGC 小游戏开发博客
作业基本信息
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | <https://edu.cnblogs.com/campus/fzu/202601SofwareEngineering/> |
| 这个作业要求在哪里 | <https://edu.cnblogs.com/campus/fzu/202601SofwareEngineering/homework/16717> |
| 这个作业的目标 | 使用 Python 和 AIGC 完成「一箭又一箭」小游戏 |
| 学号 | 102401333 |
| GitHub 仓库 | https://github.com/me2nto/yijianyouyijian |
一、项目展示
0. 一局演示(进阶模式通关)

1. 开始界面与关卡选择

开始界面提供了「经典模式(单格箭头)」和「进阶模式(线段箭头)」两种玩法入口。


关卡选择页展示每个关卡的尺寸、箭头数/线段数、失误次数与已获星级,难度一目了然。
2. 游戏界面

经典模式是题目要求的「单格箭头」,顶部显示当前关卡、剩余箭头、剩余失误与功能按钮,
中间是网格棋盘,箭头按方向着色(↑红 / ↓绿 / ←橙 / →蓝)。

进阶模式还原原版的「线段箭头」:每个箭头是一条可以拐弯的线,占据多个格子,
点击后整条线沿箭头方向滑出。

进阶第 6 关「终极蛇形」是 8×8、10 条线段共 33 格的终极挑战,失误只有 2 次。
3. 提示与碰撞反馈

点击「提示」会高亮一个当前可飞出的箭头(绿色脉冲描边)。

点击被阻挡的箭头时,箭头会变红并晃动,同时弹出「碰撞 -1」提示并迸发粒子特效,失误次数减一。
4. 通关与失败界面

清空全部箭头后显示通关界面,并根据失误次数给出 1~3 星评价。

失误次数耗尽后显示失败界面,可选择重新开始或返回菜单。
二、项目介绍
游戏规则
「一箭又一箭」是一款点击式箭头解谜游戏:
- 棋盘中摆放若干带方向的箭头,方向分为上、下、左、右四种;
- 点击箭头后,程序检查其前进方向(同一行或同一列)与棋盘边界之间是否存在其他箭头;
- 前方无阻挡:箭头飞出棋盘并被消除;
- 前方有阻挡:箭头不能消除,播放碰撞反馈(变红 + 晃动),消耗一次失误机会;
- 清除本关全部箭头即可通关,进入下一关;
- 失误次数耗尽则本关失败,可重新开始。
例如:
→ · · ↑ · 第一个箭头朝右,右侧仍有其他箭头 → 不能飞出
↑ · · · → 最后一个箭头朝右,右侧无箭头 → 可以飞出
除题目要求的「单格箭头」基础版外,我还实现了原版的线段箭头:一个箭头是占据
多个格子、可以拐弯的线,点击后整条线沿箭头方向平移,撞到其它线段则退回并记失误,
全部移出边界则消除。单格箭头本质上是长度为 1 的线段,两种玩法统一在同一套判定逻辑里。
界面设计
- 开始界面:游戏标题、规则说明、「经典模式 / 进阶模式 / 退出」入口;
- 关卡选择界面:关卡卡片(尺寸、箭头数/线段数、失误次数、星级)+ 随机挑战;
- 游戏界面:顶栏(关卡信息 + 状态芯片 + 功能按钮)+ 网格棋盘;
- 通关 / 失败界面:结果展示 + 星级评价 + 下一步操作。
主要功能与特色
- 两种模式:经典单格箭头(对应题目基础要求)+ 进阶线段箭头(还原原版);
- 完整的四方向路径检测与边界处理,线段箭头支持「整条线平移扫过的区域」判定;
- 清晰的飞出、碰撞动画与视觉反馈(拖尾、晃动、粒子);
- 失误次数机制与胜负判定;
- 14 个难度递增、经过可解性验证的关卡(经典 8 个 + 进阶 6 个);
- 随机关卡生成器,两种模式均可一键生成「必定可解」的关卡;
- 附加功能:关卡选择、撤销、提示、星级评价、键盘快捷键;
- 现代扁平化界面:渐变背景、圆角卡片与柔和阴影;
- 核心逻辑与界面解耦,可脱离图形界面进行单元测试。
三、实现思路
1. 箭头与方向的表示
方向用枚举字符串 "up" / "down" / "left" / "right" 表示,对应行列增量:
DIR_DELTA = {
"up": (-1, 0),
"down": (1, 0),
"left": (0, -1),
"right": (0, 1),
}
箭头用不可变数据类 Arrow(row, col, direction, body=()) 表示:(row, col) 是箭头头部,
body 是依次向尾部延伸的其它格子。body 为空即单格箭头,非空则为可弯曲的线段箭头。
2. 棋盘与路径检测(重点)
棋盘用「格子 → 箭头」的占用表 occupied 记录每个箭头占据的所有格子。路径检测核心是 is_blocked():
def is_blocked(self, arrow):
dr, dc = DIR_DELTA[arrow.direction]
for (r, c) in arrow.cells: # 对线段的每一个格子
rr, cc = r + dr, c + dc
while self.in_bounds(rr, cc): # 边界保护,避免数组越界
other = self.occupied.get((rr, cc))
if other is not None and other is not arrow:
return True
rr += dr
cc += dc
return False
对单格箭头,arrow.cells 只有一个格子,函数退化为「同一行/列、箭头与边界之间是否有
其它箭头」,正是题目要求的基础判定;对线段箭头,则对整条线的每个格子都沿方向扫描,
等价于检查整条线平移扫过的区域是否被占据。in_bounds() 从根本上避免数组越界。
3. 关卡表示
每关用一个字符串网格描述,U/D/L/R 表示四个方向,. 表示空格:
Level("初识方向", [
".R..",
"LL..",
"....",
".D.D",
], mistakes=3)
这种表示直观、易读、易改,便于手工设计关卡。进阶模式的线段箭头则用
seg(direction, head, body...) 直接构造,Level.from_arrows() 组装成关卡:
4. 可解性求解器
def find_solution(level):
board = level.new_board()
while board.remaining > 0:
removable = board.removable()
if not removable: # 有剩余却无可飞出的箭头 → 死锁,无法通关
return None
board.remove(removable[0])
return order
关键性质:消除只会解除阻挡、不会新增阻挡,因此贪心策略是完备的。
每一关都用求解器验证过存在通关顺序,保证玩家一定能通关。
5. 随机关卡生成(逆向构造法)
def generate_level(rows, cols, n_arrows, seed=None):
board = Board(rows, cols, [])
while len(placed) < n_arrows:
r, c = 随机空格; d = 随机方向
if not board.is_blocked(Arrow(r, c, d)): # 前方无阻挡才加入
board.add(Arrow(r, c, d))
从空棋盘开始,每次只加入「前进方向当前无阻挡」的箭头。这样按加入顺序的逆序
就是一条合法消除顺序,后加入的箭头会挡住先加入的箭头,自然形成层层解锁的链条,
从而保证生成的关卡一定可解。我用它生成经典模式第 4~8 关,并另写了一个
generate_segment_level()(同样的逆向构造,只是每次加入的是随机拐弯的线段)
来生成进阶关卡和「随机挑战」模式。
6. 界面与动画
- 界面采用状态机(
menu / select / game / win / lose)组织; - 飞出动画:
Flyer记录箭头像素位置,沿方向匀速移动直到飞出屏幕; - 碰撞动画:
Hit记录起始时间,箭头做衰减的正弦晃动并变红,同时显示浮动文字; - 所有图形均由 pygame 代码绘制,不依赖外部素材。
四、AIGC 使用过程
本项目全程借助 Claude Code 完成需求分析、代码编写、关卡设计、调试与测试。
以下是 6 次具有代表性的协作过程:
记录 1:路径检测
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 路径检测 | Claude Code | 生成四个方向的路径检测代码,并内置边界保护 | 一次通过,正确覆盖边界情况 | 补充了边缘、角落朝外的越界测试用例,确认四种方向都不越界 |
我提出需求:「判断箭头前进方向、棋盘边界内是否存在其他箭头,注意不能越界」。
AI 给出了 DIR_DELTA 方向增量表和 is_blocked() 逐格检查的代码,并用 in_bounds()
做边界保护。我随后针对「位于边缘且朝外的箭头」单独写了测试用例(T03),
在单格棋盘、四个角上分别验证,确认不会发生数组越界。
记录 2:关卡设计
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 关卡设计 | Claude Code | 生成字符串网格关卡 + 可解性求解器 | 初版某一关存在死锁,求解器返回 None | 手工调整箭头方向与位置,重新验证 8 关均可通关 |
我让 AI 生成 8 个难度递增的关卡数据,并写一个 find_solution() 求解器来验证。
运行后发现初版某关存在「有剩余箭头却没有任何可飞出的箭头」的死锁(无法通关)。
我根据求解器提示,手工调整了该关中箭头的方向和位置,反复运行求解器,
最终 8 关全部通过验证,并亲自试玩确认每关都有合理的通关顺序。
记录 5:随机关卡生成器
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 随机关卡生成 | Claude Code | 用逆向构造法生成「必定可解」的随机关卡 | 一次通过,生成的关卡均可解 | 调整了生成参数(棋盘尺寸、箭头密度、失误次数)以控制难度 |
为了让难度有上限(也对应作业的「随机生成可通关的关卡」附加项),我提出:
「有没有一种方法能保证随机生成的关卡一定可解?」AI 给出了「逆向构造法」——
每次只加入前进方向当前无阻挡的箭头,逆序即消除顺序。我验证了生成的关卡确实
都能被求解器给出完整顺序,并把它接入「随机挑战」按钮。
记录 6:线段箭头(还原原版)
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 线段箭头玩法 | Claude Code | 调研原版玩法,把箭头扩展成可弯曲的线段并统一路径判定 | 一次通过,进阶关卡均可解 | 手工挑选/调整了若干关卡,保证既美观又有合理的难度 |
我提出:「能不能做成原版那种弯弯曲曲的线?」AI 先调研了原版《一箭又一箭》的
玩法,确认其箭头是「占据多个格子、可以拐弯的线段,点击后整条线沿箭头方向平移」,
然后建议把 Arrow 加上 body 字段、把 is_blocked() 从「只判断头部前方」推广到
「对整条线每个格子沿方向扫描」。这样单格箭头退化为长度 1 的线段,两种玩法共用
同一套判定。我用逆向构造法生成了 6 个进阶关卡并逐一验证可解。
记录 3:碰撞动画与界面
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 碰撞动画 | Claude Code | 补全飞出/晃动动画、多界面状态机、中文 UI | 基本可用 | 调整飞出速度(约 1000px/s)、晃动衰减幅度、配色与布局 |
我提出需求:「箭头飞出要有动画,碰撞要有晃动和变色反馈,界面用中文显示」。
AI 生成了 Flyer 飞出动画、Hit 碰撞晃动、浮动文字提示,以及开始/关卡选择/游戏/通关/失败
五个界面的状态机。初版动画速度偏快、晃动幅度偏大,我调整了速度与衰减参数,
并统一了配色(方向用红/绿/橙/蓝区分)和按钮布局,使反馈更清晰。
记录 4:测试用例
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 编写测试用例 | Claude Code | 生成 T01~T06 的 unittest 测试 | 发现「撤销」测试场景设计问题 | 改用两个箭头的棋盘,把「撤销飞出」与「通关」两种场景分离 |
我让 AI 按作业要求编写 T01~T06 六项测试。运行时发现「撤销上一步」这条测试最初用了
只有一个箭头的棋盘——点击后立刻通关,导致撤销被禁止、断言失败。这其实是测试场景
设计不当而非游戏逻辑错误。我改用两个箭头的棋盘,先飞出一个(尚未通关)再撤销,
成功通过,也让测试更贴近真实玩法。
五、测试结果
除手动试玩外,我为核心逻辑编写了自动化单元测试(tests/test_game.py),
可在无显示环境下运行:
python -m unittest discover -s tests -v
20 项测试全部通过。对应作业要求的测试结果如下:
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出并消失,失误不变 | 箭头被消除,失误次数不变 | ✅ |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误减 1 | 箭头保留,失误次数减 1 | ✅ |
| T03 | 点击边缘且朝外的箭头 | 正常消失,不越界 | 正常消失,无越界错误 | ✅ |
| T04 | 消除本关全部箭头 | 显示通关并进入下一关 | 状态变为「通关」,可进入下一关 | ✅ |
| T05 | 失误次数耗尽 | 显示失败并允许重新开始 | 状态变为「失败」,可重新开始 | ✅ |
| T06 | 游戏进行中重新开始 | 箭头布局和失误次数恢复 | 布局与失误次数均恢复初始 | ✅ |
此外,我还测试了经典 + 进阶共 14 个关卡的可解性(求解器都能给出完整消除顺序),
以及线段箭头、撤销、提示、随机关卡生成等扩展功能,均表现正常。
六、PSP 表格
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 2 | 2 | 0 |
| Python 与图形库学习 | 4 | 5 | +1 |
| 游戏界面实现 | 6 | 6 | 0 |
| 路径与碰撞逻辑实现 | 5 | 4 | -1 |
| 关卡设计 | 3 | 3 | 0 |
| AIGC 辅助开发 | 4 | 5 | +1 |
| 测试与修改 | 3 | 3 | 0 |
| README 与博客撰写 | 3 | 3 | 0 |
| 合计 | 30 | 31 | +1 |
注:请根据自己的真实情况调整上面的耗时数值。
七、心得体会
这次作业让我第一次完整地体验了「借助 AIGC 工具开发一个小游戏」的全流程,收获很大:
-
AI 提升了效率,但不能替代理解。AI 很快就能给出路径检测、动画、界面等代码,
但如果我不理解「消除只会解除阻挡」这一关键性质,就无法判断求解器为何正确,
也就无法发现初版关卡的死锁问题。真正有价值的是理解之后再让 AI 帮忙实现。 -
测试暴露了意想不到的问题。最初我以为路径检测很简单,结果是在写 T03 的
边界测试时才真正把「边缘箭头朝外」这种情况验证清楚。而「撤销」那条测试的失败
更是提醒我:测试场景本身也要设计合理。 -
关卡设计比想象中更难。要让每一关「有合理的通关顺序」又不至于一眼看穿,
需要反复试玩和用求解器验证。这次用字符串网格 + 求解器的方式,让设计迭代变得很快。 -
解耦设计的好处。把核心逻辑(
game.py)和界面(main.py)分开,
让我可以在不启动图形界面的情况下直接跑单元测试,调试效率高了很多。
总的来说,这次作业不仅让我学会了 pygame 的基本使用,更让我理解了「用 AI 辅助开发」
的正确姿势:AI 负责快速生成与查错,人负责理解、验证和做决策。这段经历对我今后的
学习和工作都很有帮助。

浙公网安备 33010602011771号