软件工程第二次作业 ——“一箭又一箭”
博客草稿:一箭又一箭 · Python + AIGC 小游戏开发
以下为博客正文草稿,提交时请填入真实课程链接、作业链接、学号、GitHub 仓库链接,并补充演示 GIF/视频。
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/fzu/2026-01SoftwareEngineeringandSoftwareEngineeringPractice |
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/fzu/2026-01SoftwareEngineeringandSoftwareEngineeringPractice/homework/16718 |
| 这个作业的目标 | 使用 Python 和 AIGC 完成"一箭又一箭"小游戏 |
| 学号 | 102402126 |
| GitHub 仓库 | https://github.com/X-runner354/Arrow.git |
一、项目展示
开始界面

游戏中(第 1 关)

通关界面

游戏中(第 3 关,更大棋盘)

失败界面

二、项目介绍
游戏规则
"一箭又一箭"是一个网格消除游戏。棋盘上分布着朝上、下、左、右四种方向的箭头。玩家点击一个箭头后:
- 若该箭头前进方向上、到棋盘边界之间没有其他箭头阻挡,箭头飞出棋盘并消失,得分 +10。
- 若路径上存在其他箭头,箭头不消失,发生晃动 + 变红碰撞反馈,失误次数 −1。
- 清空本关全部箭头 → 进入下一关。
- 失误次数耗尽 → 本关失败,可重新开始。
界面设计
游戏包含四个界面:开始界面、游戏界面、通关界面、失败界面。
游戏界面显示:当前关卡名、棋盘、剩余箭头数、剩余失误数、得分、计时,以及"重新开始 / 提示 / 撤销 / 菜单"按钮。
主要功能与特色
- 6 个手工设计、已验证可通关的关卡(由易到难)
- 飞出滑动淡出动画 + 碰撞晃动变红动画
- 程序内合成音效(无需外部音频文件)
- 扩展功能:得分系统、计时、提示、撤销、随机可通关关卡、AI 自动求解
三、实现思路
1. 箭头、方向和关卡的表示
- 方向
Direction:枚举类型(UP/DOWN/LEFT/RIGHT),每个方向带(d_row, d_col)单位向量,方便路径遍历。 - 箭头
Arrow:不可变dataclass,含(row, col, direction)。 - 棋盘
Board:内部用dict[(row, col)] -> Arrow存储,查询/删除均为 O(1)。 - 关卡:用字符串地图直观书写,
.表示空格,^v<>表示四个方向的箭头,由parse_map解析。
@dataclass(frozen=True)
class Arrow:
row: int
col: int
direction: Direction
class Board:
def __init__(self, rows, cols, arrows=()):
self.arrows = {a.pos: a for a in arrows} # O(1) 查询
2. 路径检测方法(重点)
Board.path_cells(arrow) 返回箭头前进方向上、到边界为止的所有格子(不含自身);path_blocked 判断这些格子中是否存在其他箭头。
以朝右为例,只需遍历同行右侧所有列:
def path_cells(self, arrow):
r, c = arrow.row, arrow.col
if arrow.direction == Direction.RIGHT:
return [(r, cc) for cc in range(c + 1, self.cols)]
# UP / DOWN / LEFT 类似
def path_blocked(self, arrow):
return any(cell in self.arrows for cell in self.path_cells(arrow))
由于用 range 生成路径,边界自动由 range 的终止值保证,不会发生越界错误(对应测试 T03)。
3. 可通关性保证
一个关键性质:删除箭头只会解除阻挡,不会让原本可飞出的箭头变得被阻挡。因此只要初始棋盘可通关,任意合法消除序列后剩余棋盘仍可通关——玩家不会陷入"有箭头却谁都飞不出去"的死局。
基于此,我用回溯 + 状态记忆化求解器 is_solvable 在加载时校验每个关卡,并在 levels.py 导入时自动执行校验:
_bad = validate_levels()
if _bad:
raise RuntimeError(f"以下关卡不可通关: {names}")
4. 随机关卡反向构造
random_solvable_level 用反向构造生成保证可通关的随机关卡:从空棋盘起不断放入"相对当前已放箭头可飞出"的箭头,放置顺序的逆序即合法消除顺序。
5. 界面与动画
ui.py提供绘制工具函数(颜色、字体、箭头、棋盘、HUD、按钮)。main.py负责主循环、状态切换、输入处理、动画队列。- 飞出动画:箭头沿方向滑出边界并淡出(0.35s)。
- 碰撞动画:原位正弦晃动 + 变红闪烁(0.4s)。
- 音效:用 NumPy 合成正弦/衰减波,音频设备不可用时自动静音。
四、AIGC 使用过程
开发过程中使用了 AIGC 工具 CodeArts(GLM-5.2) 辅助开发。以下记录 5 次代表性协作过程(详见 AIGC_LOG.md):
协作 1:路径检测
| 子任务 | 借助何种 AIGC | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 路径检测 | CodeArts (GLM-5.2) | 生成 Board/Arrow/Direction 及四向 path_cells/path_blocked |
一次生成结构清晰、逻辑正确 | 加同格校验、求解器记忆化、from_str 中文兼容 |
协作 2:关卡设计
| 子任务 | 借助何种 AIGC | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 关卡设计 | CodeArts (GLM-5.2) | 生成 6 关字符串地图 + validate_levels 校验函数 |
第 4、6 关存在对向死锁不可通关 | 重设计两关为"单向列车"布局,逐关试玩验证 |
协作 3:碰撞动画
| 子任务 | 借助何种 AIGC | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 飞出/碰撞动画 | CodeArts (GLM-5.2) | FlyOutAnim(滑动淡出)、CollisionAnim(正弦晃动+变红) |
基本可用 | 调短持续时间、加红色圆圈、加动画期间点击守卫 |
协作 4:随机关卡生成
| 子任务 | 借助何种 AIGC | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 随机关卡 | CodeArts (GLM-5.2) | random_solvable_level 反向构造算法 |
生成的关卡确实可通关 | 加尝试上限防死循环、加 seed 参数方便测试 |
协作 5:Bug 修复
| 子任务 | 借助何种 AIGC | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| Bug 定位 | CodeArts (GLM-5.2) | 定位 _is_last_level 对随机关卡误判 + draw_text 参数传错 |
两处 Bug 准确定位 | 按建议修改,修复后截图脚本一次成功 |
五、测试结果
共编写 11 个单元测试,覆盖作业测试表 T01–T06 及路径检测、求解器、关卡可通关性。
运行命令:py -m unittest tests.test_core -v
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 箭头消失,arrows_left 减 1 |
✅ 通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误次数减 1 | 箭头仍在,mistakes_left 减 1 |
✅ 通过 |
| T03 | 点击位于边缘且朝向棋盘外的箭头 | 正常消失,不发生越界错误 | 6 个边缘箭头全部飞出,无异常 | ✅ 通过 |
| T04 | 消除本关全部箭头 | 显示通关并进入下一关 | 状态 → LEVEL_COMPLETE,next_level 后进入第 2 关 |
✅ 通过 |
| T05 | 失误次数耗尽 | 显示失败并允许重新开始 | 状态 → FAILED,restart_level 后恢复 |
✅ 通过 |
| T06 | 游戏中重新开始 | 箭头布局和失误次数恢复 | 箭头数与失误数均恢复初始值 | ✅ 通过 |
| — | 四向路径检测 | 各方向阻挡判断正确 | 4 个独立棋盘全部正确 | ✅ 通过 |
| — | 全部手工关卡可通关 | 6 关均存在合法消除顺序 | is_solvable 全 True |
✅ 通过 |
| — | 随机关卡可通关 | 10 个种子生成的关卡均可通关 | 全部通过 | ✅ 通过 |
Ran 11 tests in 0.002s
OK
此外还进行了 GUI 层的冒烟测试(无头模式创建窗口、渲染帧、模拟点击、运行动画、测试重启),确认界面层与逻辑层配合正常。
六、PSP 表格
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1.0 | 1.2 | +0.2 |
| Python 与图形库学习 | 1.5 | 2.0 | +0.5 |
| 游戏界面实现 | 3.0 | 3.5 | +0.5 |
| 路径与碰撞逻辑实现 | 2.0 | 1.8 | −0.2 |
| 关卡设计 | 1.5 | 2.3 | +0.8 |
| AIGC 辅助开发 | 2.0 | 2.5 | +0.5 |
| 测试与修改 | 1.5 | 1.7 | +0.2 |
| README 与博客撰写 | 1.5 | 1.8 | +0.3 |
| 合计 | 14.0 | 16.8 | +2.8 |
差异分析:实际耗时主要超在关卡设计(AI 生成的两关不可通关,需重设计并反复试玩)和图形库学习(pygame-ce 在 Python 3.14 上的安装踩了坑,官方 pygame 无预编译 wheel)。
七、心得体会
AI 带来的帮助
- 结构化代码生成高效:
Board/Arrow/Direction的数据结构、路径检测、求解器这类结构明确的代码,AI 一次生成即可用,节省了大量样板代码编写时间。 - 算法思路启发:随机关卡的"反向构造"思路是我在描述需求时给 AI 的提示,但 AI 把它实现成完整函数并处理了边界情况,比我手写快很多。
- Bug 定位准确:
draw_text的参数错误从报错信息就能定位,AI 直接指出了"两个坐标被当成两个位置参数"的问题。
出现的问题
- 关卡设计缺乏"试玩直觉":AI 生成的关卡存在对向死锁,它理解规则但不理解"玩起来不能卡死"。这类任务必须配合求解器验证,不能盲信。
- 游戏状态语义需人类给规则:
_is_last_level对随机关卡的判断属于业务规则,AI 不知道"随机关卡通关后应回菜单而非显示全部通关",需要我明确提示。 - 动画参数需人工调优:AI 给的动画持续时间偏长,"看起来对"但"玩起来拖沓",这类主观体验 AI 难以把握。
自己的收获
- 理解了"AI 生成 + 人类验证"的工作流:AI 擅长生成结构化代码和定位明确 Bug,但涉及业务语义、用户体验、可玩性时必须人类把关。本次开发中"关卡可通关性验证"这一环最关键——求解器既是开发工具也是质量守卫。
- 体会了纯逻辑与界面分离的价值:把
core/做成不依赖图形库的纯逻辑层,使得 T01–T06 可以用unittest直接测试,无需启动 GUI。这个架构是 AI 建议的,实践证明非常正确。 - 学会了反向构造保证有解:随机关卡生成器用"放置顺序的逆序即消除顺序"保证有解,这个思路可以推广到其他消除类游戏。
- 踩了 Python 3.14 兼容性的坑:官方 pygame 在 3.14 上无预编译 wheel,改用 pygame-ce 才解决。这类环境问题 AI 也能帮忙排查,但最终还是要自己读报错、查文档。

浙公网安备 33010602011771号