2026秋软件工程个人作业(第二次):一箭又一箭
一箭又一箭:用 Python + AIGC 完成"箭头消除"小游戏
作业信息
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/fzu/202601SofwareEngineering |
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/fzu/202601SofwareEngineering/homework/16717 |
| 这个作业的目标 | 使用 Python 和 AIGC 完成"一箭又一箭"小游戏 |
| 学号 | 052401304 |
| GitHub 仓库 | https://github.com/147lsq/arrow-game |
一、项目展示
演示视频(GIF):

开始界面:

第 1 关 / 第 2 关:


通关 / 失败界面:


二、项目介绍
游戏规则
棋盘上散布着 上、下、左、右 四种方向的箭头,玩家用鼠标点击箭头:
- 点击后,程序沿箭头前进方向检测路径:如果箭头到棋盘边界之间 没有其他箭头阻挡,箭头就飞出棋盘、被消除;
- 如果路径上存在其他箭头,箭头 不能消除,并通过「晃动 + 红色闪烁 + 飘字提示」给出明显的碰撞反馈,同时扣除 1 次失误机会;
- 每关有 3 次失误机会;清空全部箭头即通关并进入下一关;失误耗尽则本关失败,可重新开始。
界面设计
游戏包含 4 个场景:开始界面、游戏界面、通关界面、失败界面(通关/失败以半透明面板叠加在棋盘上)。游戏界面顶部 HUD 显示当前关卡、剩余箭头数、剩余失误次数(爱心图标),底部提供「重新开始」「回到主页」按钮,鼠标悬停在箭头上时会有高亮边框提示。
主要功能与特色
- 四方向路径判定,边界越界安全;
- 飞出(滑出+淡出)、碰撞(晃动+变红+飘字)两套动画反馈;
- 4 个手工设计关卡,并用"可解性求解器"逐一验证存在通关节奏;
- 关卡选择(开始界面可直接跳转任意一关)、重新开始、回主页;
- 自动匹配系统中文字体(微软雅黑/黑体/思源黑体等),跨平台可跑;
- 单独的自测脚本
selftest.py,无窗口即可完成回归测试。
三、实现思路
3.1 箭头与方向的表示
棋盘固定为 7×7 网格。用行列坐标 (r, c) 唯一标识一格,用一个字典保存所有箭头:
board = {(r, c): "方向字符"} # 例如 {(0, 3): "^", (2, 0): "<", ...}
四个方向用"方向向量 + 字符"的映射表示,方便后续自动寻路:
DIRS = {"^": (-1, 0), "v": (1, 0), "<": (0, -1), ">": (0, 1)}
# 上 下 左 右
关卡则用可读的字符网格描述,运行时再解析成上面的 board 字典:
LEVELS = [
# 第 1 关:中央一列向上箭头,从顶端开始依次消除;四角箭头同时可消除
[
"< . . ^ . . >",
". . . ^ . . .",
# ... 共 7 行
],
]
def build_board(rows):
board = {}
for r in range(len(rows)):
cells = rows[r].split()
for c in range(len(cells)):
if cells[c] in DIRS:
board[(r, c)] = cells[c]
return board
3.2 路径检测(核心逻辑)
依据规则:「箭头与棋盘边界之间,同一行/列上是否还有其他箭头」。实现时从箭头所在格出发,沿方向逐格向边界走,一旦发现箭头即被阻挡:
def is_blocked(board, r, c):
"""判断位于 (r, c) 的箭头前进方向上是否还有其他箭头。"""
dr, dc = DIRS[board[(r, c)]]
rr, cc = r + dr, c + dc
while 0 <= rr < ROWS and 0 <= cc < COLS: # 用边界判断收尾,天然避免越界
if (rr, cc) in board:
return True # 路径上发现其他箭头 → 被阻挡
rr += dr
cc += dc
return False # 走到边界仍无箭头 → 畅通
两点值得强调:
- 边界安全:循环退出条件是坐标越出棋盘,因此边缘箭头天然安全,例如最左格
"<"第一步就会因cc < 0退出循环而判定畅通,不会产生数组越界; - 是否符合题意:题目示例
→ · · ↑ ·中,右箭头因为右方第 4 格有↑而被判定为阻挡;示例↑ · · · →(箭头在最右侧)判定为畅通。这两例都在自测脚本中做了断言。
3.3 点击后的处理流程
def handle_cell_click(self, r, c):
if (r, c) not in self.board:
return
if is_blocked(self.board, r, c):
self.mistakes -= 1 # 碰撞:扣 1 次失误
self.shakes.append(ShakeAnim(r, c, self.board[(r, c)])) # 晃动+变红
self.floats.append(FloatText("被阻挡!走不了", ...))
if self.mistakes <= 0:
self.pending = {"kind": "fail", "t": 0.0} # 进入失败流程
else:
d = self.board.pop((r, c)) # 畅通:移除箭头
self.flies.append(FlyAnim(r, c, d)) # 飞出动画
if not self.board:
self.pending = {"kind": "win", "t": 0.0} # 清空 → 通关
3.4 关卡可解性(用求解器保证"能通关")
先写求解器再定关卡是本项目一个重要设计。判断规则:移除箭头只会减少障碍、不会制造新障碍,所以贪心即可判定可解——不断移除"当前畅通"的箭头,若卡住则说明该布局死锁:
def solve_plan(board):
b = dict(board); plan = []
while b:
step = [(r, c) for (r, c) in b if not is_blocked(b, r, c)]
if not step:
return None # 死锁,无法通关
r, c = step[0]; plan.append((r, c)); b.pop((r, c))
return plan
开发中用该求解器筛掉了一个"> 与 < 面对面互堵"的无法通关的关卡(见 AIGC 使用过程记录)。
四、AIGC 使用过程
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 路径检测 | opencode | 生成 is_blocked(),对上下左右四方向沿行/列向边界逐格检测是否有其他箭头;配套生成 DIRS 方向映射 |
逻辑一次正确;与题目两个示例(→· · ↑· 阻挡、最右 → 畅通)通过断言验证 |
无需改核心逻辑;人工补充了针对性测试用例做回归验证 |
| 关卡设计 | opencode | 设计 4 个 7×7 关卡字符数组,并给出"贪心求解器"脚本判断关卡是否可通关 | 前 3 关可通;第 4 关初始布局死锁(> 与 < 面对面互相阻挡,打印结果显示 可解? None) |
运行求解器定位死锁位置,让 AI 重新设计第 4 关布局,再次验证 4 关全部存在通关顺序 |
| 飞出与碰撞动画 | opencode | 生成 FlyAnim(飞出+淡出)、ShakeAnim(碰撞晃动+红色闪烁)、FloatText 飘字;用旋转贴图实现四方向箭头外观 |
箭头贴图旋转时方向符号写反,^/v 朝向错位 |
逐方向采样贴图尖端像素核对,反馈给 AI 修正旋转角度映射后正常 |
| 失败/通关结算界面 | opencode | 生成失败/通关半透明遮罩面板与「下一关 / 重新开始 / 回到主页」按钮 | 存在 bug:正常游玩时失败面板一直盖在棋盘上(遮罩在 SC_PLAY 场景也被调用绘制) | 真人试玩发现后反馈给 AI,改为只在通关/失败场景绘制遮罩,并补充回归测试("游戏进行中不绘制遮罩"一项) |
经验小结:AI 能快速生成可运行的整体骨架和测试脚本,但"能编译 ≠ 逻辑正确 ≠ 视觉正确"——本项目中 3 个问题(方向错位、关卡死锁、遮罩遮挡)全部依赖"跑测试验证"和"真人试玩"才暴露,所以坚持"AI 生成 → 自动测试 → 手动验证"的闭环。
五、测试结果
采用「自动自测脚本 + 真人试玩」两层测试。自动自测脚本 selftest.py 在无窗口环境下运行(SDL_VIDEODRIVER=dummy),覆盖关卡可解性、判定逻辑、全关卡自动通关、失误/失败/重开、各界面渲染共 30+ 项断言,全部通过。
测试结果如下:
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 箭头被移除并播放飞出动画,棋子数 -1 | ✅ 通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误次数减 1 | 棋盘布局不变、失误 3→2→1→0 逐次递减,并出现晃动/变红/飘字反馈 | ✅ 通过 |
| T03 | 点击位于边缘且朝向棋盘外的箭头 | 箭头正常消失,不发生越界错误 | 最左格 <、最右格 > 等边缘箭头均正常消除;is_blocked 的循环以边界判断收尾,未出现越界 |
✅ 通过 |
| T04 | 消除本关全部箭头 | 显示通关并进入下一关 | 每关清空后弹出「本关通过/全部通关」面板,点击「下一关」可进入下一关 | ✅ 通过 |
| T05 | 失误次数耗尽 | 显示失败并允许重新开始 | 第 3 次碰撞后进入失败界面,「重新开始」恢复本关初始布局与 3 次失误 | ✅ 通过 |
| T06 | 游戏进行中重新开始 | 箭头布局和失误次数恢复 | 「重新开始」按钮将 board 与 mistakes 复原为关卡初始值;并验证「游戏进行中不绘制失败/通关遮罩」 |
✅ 通过 |
自测运行结果摘录:
== 关卡可解性 ==
[PASS] 第1~4关可通关节奏
== 判定逻辑(题目示例)==
[PASS] 示例1 → 被右侧箭头阻挡
[PASS] 示例2 最右 → 无障碍可飞出
== 自动游玩全部关卡 ==
[PASS] 第1~4关自动通关 / 进入下一关
== 失误与失败流程 ==
[PASS] 点击被阻挡箭头扣除失误 ×3 / 失败界面出现 / 重新开始恢复初始状态
== 界面渲染 ==
[PASS] 开始/游戏/通关/失败界面渲染;游戏进行中不绘制遮罩
自测全部通过 ✅
六、PSP 表格
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1.0 | 1.5 | +0.5 |
| Python 与图形库学习 | 2.0 | 1.5 | -0.5 |
| 游戏界面实现 | 2.0 | 2.5 | +0.5 |
| 路径与碰撞逻辑实现 | 1.5 | 1.0 | -0.5 |
| 关卡设计 | 1.0 | 1.5 | +0.5 |
| AIGC 辅助开发 | 2.0 | 3.0 | +1.0 |
| 测试与修改 | 1.5 | 2.5 | +1.0 |
| README 与博客撰写 | 1.0 | 1.5 | +0.5 |
| 合计 | 12.0 | 15.0 | +3.0 |
差异主要来自:AI 生成内容后仍需人工测试/修改(多边形旋转、关卡死锁、遮罩 bug 三处返工),以及真人试玩验证占用的时间。
七、心得体会
AI 带来的帮助:作为编码智能体,它能直接从一句话需求生成可运行的整体骨架——窗口、棋盘、判定、动画一应俱全;尤其擅长"主动写验证"——自测脚本、可解性求解器都是它建议加入的,这让"关卡保证能通关"从口号变成了可执行的断言。
出现的问题:最大的坑在于"看起来对,其实错"。
- 箭头贴图旋转方向写反(视觉错位);
- 第 4 关
>与<互堵产生死锁(逻辑层面:两张"面对面"箭头互相挡路,谁都出不去); - 失败遮罩被错误地在正常游玩时也绘制(界面层 bug,试玩才暴露)。
这三处都不是编译错误,必须靠自动测试 + 真人试玩才能发现。
收获:
- 先写判定/验证工具,再做功能:可解性求解器 + 自测脚本让每个关卡、每次改动都有了"证据";
- AI 生成的内容要当成"高风险代码"来审:能编译、能跑通主线流程只过了第一关,边界与异常分支才是返工高发区;
- 单人做"游戏感"内容时,AI 是绝佳的第二双眼睛:让 AI 去写机器能验证的部分(逻辑、测试),人专注于试玩手感与界面观感,互不抢节奏。

浙公网安备 33010602011771号