软件工程第二次作业
软件工程课程第二次个人作业
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | 202601 软件工程 |
| 这个作业要求在哪里 | 软件工程第二次个人作业要求 |
| 这个作业的目标 | 使用 Python 和 AIGC 完成"一箭又一箭"小游戏 |
| 学号 | 102401328 |
| GitHub 仓库 | https://github.com/majc888888/arrow-puzzle |
一、项目展示
开始界面

游戏界面

通关界面

失败界面

演示GIF

二、项目介绍
游戏规则:棋盘上分布着上、下、左、右四种方向的箭头。点击某个箭头时,程序检查它前进方向到棋盘边界之间的路径——
- 路径上没有其他箭头:箭头沿方向飞出棋盘并消除(绿色飞出动画)
- 路径上有其他箭头:箭头晃动变红提示碰撞,并消耗 1 次失误机会
- 清空本关全部箭头 → 通关,进入下一关;失误次数耗尽 → 本关失败
主要功能:
| 功能 | 说明 |
|---|---|
| 图形界面 | 开始 / 选关 / 游戏 / 通关 / 失败 / 全部通关六种界面,状态机管理 |
| 四方向路径检测 | 沿箭头方向逐格扫描至边界,判断是否有阻挡 |
| 飞出与碰撞反馈 | 飞出为绿色平移+淡出动画;碰撞为红色晃动+顶部文字提示 |
| 失误机制 | 每关 3 次失误,界面实时显示剩余次数 |
| 关卡系统 | 8 个难度递增的关卡,均经求解器验证可通关 |
| 重新开始 | 一键恢复当前关卡初始布局与失误次数 |
| 撤销上一步 | 记录点击前快照,点错可回退,恢复布局与失误次数 |
| 提示功能 | 每关 3 次,高亮当前可安全飞出的箭头 |
| 计时与星级 | 实时显示用时,通关按剩余失误数评 1~3 星 |
| 关卡选择 | 选关界面 + 通关解锁机制 |
特色:
- 关卡内置求解器(回溯搜索),开发阶段即验证每一关确实存在通关顺序,避免出现无解关卡;
三、实现思路
1. 数据表示
箭头用数据类表示,只有三个属性——行、列、方向:
@dataclass(frozen=True)
class Arrow:
row: int
col: int
direction: Tuple[int, int] # (dr, dc),如 UP=(-1,0)
方向用行/列增量表示,天然与二维坐标对应:UP=(-1,0)、DOWN=(1,0)、LEFT=(0,-1)、RIGHT=(0,1)。
关卡用字符串数组表示,一个字符就是一个格子,直观且易改:
"U...R", # 第 0 行
".....",
"..D..", # 第 2 行
".....",
"U...L", # 第 4 行
U/D/L/R 表示箭头,. 表示空位。加载时逐字符扫描即可还原出箭头列表。
2. 路径检测(核心算法)
这是本游戏最核心的逻辑:判断被点击箭头的前进方向上、到边界之间是否还有其他箭头。
思路:从箭头的下一格开始,沿方向向量逐格推进,直到越界;途中只要遇到任何一个仍在本场的箭头位置,即为被阻挡:
def is_blocked(arrow, occupied, rows, cols):
dr, dc = arrow.direction
r, c = arrow.row + dr, arrow.col + dc # 从下一格出发,跳过自身
while 0 <= r < rows and 0 <= c < cols: # 循环条件同时完成边界判断
if (r, c) in occupied:
return True # 途中遇到其他箭头 → 阻挡
r += dr
c += dc
return False # 顺利走到边界 → 可飞出
三个关键点:
- 起点是下一格而非当前位置,天然排除自身
- 边界判断写在 while 条件里,越界前循环就终止,从根源上避免数组越界
- occupied 是当前仍在场箭头的位置集合(set),查询 O(1)。整个检测最坏 O(行/列长度),非常轻量
3. 状态机与动画
游戏用五个状态管理流程:开始 → 游戏中 → 单关通关 / 失败 → 全部通关。
点击箭头后的处理分两条线:
- 可飞出:立即从场上箭头列表移除(不再参与阻挡判定),加入"飞出动画"队列;动画按 进度 = t / 时长 沿方向平移并淡出,动画结束即彻底消失;
- 被阻挡:箭头留在场上(仍参与阻挡),加入"晃动动画"队列,绘制时用 sin(t*40) 产生水平抖动并改用红色;同时失误次数减 1,减到 0 进入失败状态。
通关判定放在每帧更新中:场上无箭头且飞出动画全部结束,才弹出通关面板——保证玩家能看到最后一支箭头完整飞出的过程。
4. 关卡可解性验证
实现了一个带回溯的求解器 find_solution:每一步在所有"当前可飞出"的箭头中任选一个消除并递归,能清空即存在通关顺序。开发时用它批量验证关卡,也顺带用于自动化测试。
设计关卡时还总结出一个死锁陷阱:同一行/列上两个箭头若"面对面"(如 → 在左、← 在右且中间无遮挡),则互相阻挡、永远无解。第一版关卡数据里就出现过这种布局,靠求解器验证发现后修正。
5. 扩展功能实现
撤销上一步:点击箭头前把 (箭头列表快照, 失误数) 压入历史栈;点「撤销」弹栈恢复。由于 Arrow 是不可变对象(frozen=True),浅拷贝列表即可安全保存状态:
# 点击箭头时
self.history.append((list(self.arrows), self.mistakes))
# 撤销时
arrows, mistakes = self.history.pop()
self.arrows, self.mistakes = arrows, mistakes
提示功能:新增 find_next_move——遍历场上箭头,返回第一个前方无阻挡者。它相当于求解器的"单步版":由于"可飞出"的箭头点击后必然消除且不扣失误,提示任意一个可飞箭头都是安全的一步。点击提示后用橙色光圈高亮目标箭头,每关限 3 次。
计时与星级:load_level 时记录起点时间,游戏进行中每帧更新已用时;通关瞬间冻结用时并计算星级——星级 = 剩余失误数(无失误 3 星、1 失误 2 星、2 失误 1 星),用 ★☆ 字符渲染在通关面板上。
关卡选择:新增选关状态与 8 宫格按钮网格;用 unlocked 变量记录已解锁进度,通关第 N 关后解锁第 N+1 关,未解锁按钮呈灰色不可点。
四、AIGC 使用过程
本项目全程与 AIGC 工具 DeepSeek 协作开发,以下记录 3 次有代表性的真实协作过程:
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 核心路径检测算法 | DeepSeek | 根据作业规则生成 is_blocked 函数:沿方向逐格扫描、while 条件内做边界判断,并附带四方向单元测试 | 一次通过,四方向与边缘用例全部正确 | 无需修改;人工阅读代码确认"从下一格出发"排除了自身干扰 |
| 调试 Windows 下中文字体崩溃 | DeepSeek | 初版用 pygame.font.match_font 按名称查找中文字体 | 实际运行报 TypeError: expected str, bytes or os.PathLike object, not int —— pygame 2.6.1 扫描 Windows 注册表字体时对某些条目处理异常 | AI 定位原因后改为直接使用系统字体文件路径(msyh.ttc / simhei.ttf 等),绕开注册表扫描,问题解决 |
| 关卡设计与可解性验证 | DeepSeek | 设计 4 个难度递增的字符串关卡,并实现回溯求解器 find_solution 自动验证每关可通关 | 第一版部分关卡(同一直线上两箭头面对面互相阻挡)被求解器判定死锁 | 分析死锁原因后重新设计关卡布局,再次用求解器验证全部通过 |
五、测试结果
自动化测试基于 unittest 编写(python -m unittest discover -s tests -v),共 21 个用例全部通过;同时进行了手工试玩验证。
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 箭头移出场,播放绿色飞出动画后消失 | 通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误次数减 1 | 箭头红色晃动,失误 3→2,且仍参与阻挡 | 通过 |
| T03 | 点击位于边缘且朝向棋盘外的箭头 | 正常消失,不发生越界错误 | 正常飞出,无异常(边界判断在 while 条件中完成) | 通过 |
| T04 | 消除本关全部箭头 | 显示通关并进入下一关 | 弹出通关面板,点击后正确载入下一关 | 通过 |
| T05 | 失误次数耗尽 | 显示失败并允许重新开始 | 第 3 次碰撞后弹出失败面板,可重开 | 通过 |
| T06 | 游戏进行中重新开始 | 箭头布局和失误次数恢复 | 布局与失误次数均恢复到本关初始状态 | 通过 |
调试过程中曾发现并修复的问题:
- 测试初次运行 3 个用例失败——原因是测试辅助函数调用 load_level 后未把状态置为"游戏中",update() 因状态不符直接返回(属于测试代码问题,游戏逻辑无错误);
- 开始界面按钮位置错误与图例文字重叠。
六、PSP 表格
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1.0 | 1.5 | +0.5 |
| Python 与图形库学习 | 1.0 | 1.0 | 0 |
| 游戏界面实现 | 2.5 | 2.0 | -0.5 |
| 路径与碰撞逻辑实现 | 2.0 | 1.5 | -0.5 |
| 关卡设计 | 2.0 | 1.5 | -0.5 |
| AIGC 辅助开发 | 2.0 | 2.0 | 0 |
| 测试与修改 | 2.0 | 1.5 | -0.5 |
| README 与博客撰写 | 2.0 | 2.5 | +0.5 |
| 合计 | 14.5 | 13.5 | -1.0 |
七、心得体会
这次的作业是我第一次利用 AIGC 来协作开发一个小游戏,其中有许多的感悟:
-
AI 改变的是我们写代码的比重,不是做软件的流程。需求拆解、结构设计、验证测试这些环节依然必须由人主导。本次开发中真正花时间的不是写代码,而是:验证关卡可解、运行程序、看截图、修 Bug——恰好都是 AI 替代不了的部分。
-
AI 生成的代码必须要经过本地运行 + 验证,不能直接相信它。 两次典型经历:字体加载代码在 AI 的逻辑里完全正确,但一到 Windows 实机就因注册表问题崩溃;AI 设计的第一版关卡里存在"面对面死锁",肉眼难以察觉,靠求解器才暴露。要通过实际运行来验证AI说的到底对不对。
-
测试先行回报极高。先把作业的 T01~T06 转成 11 个自动化用例,之后每次改动(修复字体、调整布局)都能一键回归。

浙公网安备 33010602011771号