软件工程第二次作业 ——“一箭又一箭”

博客草稿:一箭又一箭 · 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

一、项目展示

开始界面

start

游戏中(第 1 关)

playing

通关界面

level_complete

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

playing_level3

失败界面

failed


二、项目介绍

游戏规则

"一箭又一箭"是一个网格消除游戏。棋盘上分布着朝上、下、左、右四种方向的箭头。玩家点击一个箭头后:

  • 若该箭头前进方向上、到棋盘边界之间没有其他箭头阻挡,箭头飞出棋盘并消失,得分 +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 带来的帮助

  1. 结构化代码生成高效:Board/Arrow/Direction 的数据结构、路径检测、求解器这类结构明确的代码,AI 一次生成即可用,节省了大量样板代码编写时间。
  2. 算法思路启发:随机关卡的"反向构造"思路是我在描述需求时给 AI 的提示,但 AI 把它实现成完整函数并处理了边界情况,比我手写快很多。
  3. Bug 定位准确:draw_text 的参数错误从报错信息就能定位,AI 直接指出了"两个坐标被当成两个位置参数"的问题。

出现的问题

  1. 关卡设计缺乏"试玩直觉":AI 生成的关卡存在对向死锁,它理解规则但不理解"玩起来不能卡死"。这类任务必须配合求解器验证,不能盲信。
  2. 游戏状态语义需人类给规则:_is_last_level 对随机关卡的判断属于业务规则,AI 不知道"随机关卡通关后应回菜单而非显示全部通关",需要我明确提示。
  3. 动画参数需人工调优:AI 给的动画持续时间偏长,"看起来对"但"玩起来拖沓",这类主观体验 AI 难以把握。

自己的收获

  1. 理解了"AI 生成 + 人类验证"的工作流:AI 擅长生成结构化代码和定位明确 Bug,但涉及业务语义、用户体验、可玩性时必须人类把关。本次开发中"关卡可通关性验证"这一环最关键——求解器既是开发工具也是质量守卫。
  2. 体会了纯逻辑与界面分离的价值:把 core/ 做成不依赖图形库的纯逻辑层,使得 T01–T06 可以用 unittest 直接测试,无需启动 GUI。这个架构是 AI 建议的,实践证明非常正确。
  3. 学会了反向构造保证有解:随机关卡生成器用"放置顺序的逆序即消除顺序"保证有解,这个思路可以推广到其他消除类游戏。
  4. 踩了 Python 3.14 兼容性的坑:官方 pygame 在 3.14 上无预编译 wheel,改用 pygame-ce 才解决。这类环境问题 AI 也能帮忙排查,但最终还是要自己读报错、查文档。
posted @ 2026-09-21 17:13  远航星的告别  阅读(10)  评论(0)    收藏  举报