软件工程第二次作业
软件工程第二次个人作业:一箭又一箭小游戏
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | 202601 软件工程班级博客 |
| 这个作业要求在哪里 | 第二次个人作业要求 |
| 这个作业的目标 | 使用 Python 和 AIGC 完成 "一箭又一箭" 小游戏 |
| 学号 | 102401405 |
| GitHub 仓库 | https://github.com/wiyiti/one-arrow-game |
一、项目展示
1.1 开始界面
游戏启动后显示标题 "一箭又一箭",包含 "开始游戏" 和 "选择关卡" 两个按钮。
1.2 选关界面
以网格展示 7 个关卡,通关后自动解锁下一关,已通关的关卡显示金色星星。
1.3 游戏界面
顶部 HUD 显示当前关卡、剩余箭头数、失误次数、实时星级和倒计时。中部为游戏棋盘,四种方向的箭头分别用不同颜色表示(上 = 绿、下 = 蓝、左 = 橙、右 = 紫)。底部有撤销、提示、自动求解三个功能按钮。
1.4 碰撞 / 失误反馈
点击被挡住的箭头后,箭头沿方向飞出一段距离再弹回,同时变红,失误次数减 1,失去一颗星。
1.5 提示功能
点击 "提示" 按钮,程序会找到一个当前可以消除的箭头,用金色闪烁框高亮显示 2.5 秒。
1.6 失败界面
失误次数用完或时间耗尽时,显示失败原因,点击任意处重新开始。
1.7 第一关演示 GIF
1.8 可解性测试
运行 python ``test_game.py 可自动验证 T01-T06 用例,并输出每关的棋盘尺寸、箭头数和通关步数:
二、项目介绍
2.1 游戏规则
棋盘中分布着上、下、左、右四个方向的箭头。玩家点击箭头后:
-
如果箭头到棋盘边界之间没有其他箭头阻挡,箭头飞出棋盘并被消除;
-
如果路径上存在其他箭头,箭头不能飞出,会沿方向弹出一段再弹回并变红,同时消耗 1 次失误;
-
每关限失误 3 次、限时 75 秒,任一耗尽则本关失败;
-
清空棋盘全部箭头即通关,按剩余失误评 1-3 颗星;
-
共 7 个关卡,按顺序解锁,进度自动保存。
2.2 界面设计
游戏采用暖色调简约风格,背景为米白色(RGB 245,240,230),棋盘为浅棕色圆角矩形,整体视觉柔和不刺眼。HUD 区域分为三行排列:
- 第一行:返回按钮、当前关卡、重开本关按钮
- 第二行:剩余箭头数、失误次数、实时星级、倒计时
- 第三行:撤销、提示、自动求解三个功能按钮
四种方向的箭头各有专属配色:上=绿色、下=蓝色、左=橙色、右=紫色,配合带箭杆的实心箭头造型,玩家一眼就能分辨方向。箭头使用 2 倍超采样离屏渲染后平滑缩放,边缘无锯齿。
2.3 主要功能
基础功能:
-
完整三界面:开始界面、游戏界面、通关 / 失败界面
-
四方向箭头,鼠标点击操作
-
同行 / 同列路径检测,正确判断阻挡
-
箭头飞出动画、碰撞弹回动画
-
失误计数、重新开始、关卡切换
附加功能:
-
75 秒倒计时,最后 10 秒变红警告
-
三星评价,实时显示在 HUD 上
-
选关界面,按进度解锁
-
提示功能(金色闪烁高亮可消除箭头)
-
撤销上一步(恢复上一个被消除的箭头)
-
AI 自动求解(回溯算法自动演示通关)
-
本地存档(progress.json 记录解锁进度和星级)
-
抗锯齿箭头渲染(2 倍超采样)
2.4 游戏特色
- 碰撞反馈生动:被挡住的箭头不是简单晃动,而是沿方向弹出一段距离再弹回原位,同时变红,直观展示"撞到东西了"
- 实时星级评价:HUD 上始终显示当前星级,每失误一次金色星星变灰一颗,不用等到通关才知道评价
- 智能提示:卡住时点"提示",金色闪烁框标出一个当前可以消除的箭头,帮助玩家理清思路
- 撤销反悔:点错了不用重开,撤销按钮恢复上一个被消除的箭头
- AI 自动求解:回溯算法自动寻找通关路径并逐步演示,既能看完整解法,也能验证关卡可解性
- 难度递进:7 个关卡按箭头数量从 5 个逐步增加到 14 个,棋盘从 3×3 扩展到 5×5,难度平缓上升
- 本地存档:通关进度和星级自动保存到 progress.json,下次打开继续玩
三、实现思路
3.1 箭头、方向与关卡的表示
方向用字典映射为方向向量:
DIRS = {'U': (-1, 0), 'D': (1, 0), 'L': (0, -1), 'R': (0, 1)}
棋盘用二维列表表示,单元格为 None(空格)或 'U'/'D'/'L'/'R':
grid = [
['R', None, 'D'],
[None, 'U', None],
['U', None, 'R'],
]
每个关卡就是一个这样的二维数组,7 个关卡按箭头数量从少到多排列(5→6→8→10→11→12→14)。
3.2 路径检测
这是游戏的核心逻辑。点击一个箭头后,从箭头相邻格子开始,沿方向向量逐格遍历:
def is_blocked(self, row, col):
d = self.grid[row][col]
dr, dc = DIRS[d]
r, c = row + dr, col + dc
while 0 <= r < self.rows and 0 <= c < self.cols:
if self.grid[r][c] is not None:
return True # 遇到其他箭头,被挡住
r += dr
c += dc
return False # 走到边界都没遇到箭头,可以飞出
边界条件 0 <= r < self.rows and 0 <= c < self.cols 同时判断上下左右四个方向,避免数组越界。
3.3 动画系统
飞出动画和碰撞动画都用 time.time() 计算进度:
-
飞出动画:0.4 秒内箭头沿方向移动 200px,然后消失
-
碰撞动画:前 60% 时间沿方向弹出 30px,后 40% 弹回原位,期间变红
3.4 撤销功能
用一个栈记录每步被消除的箭头坐标和方向:
self.history.append((row, col, d)) # 消除时入栈
r, c, d = self.history.pop() # 撤销时出栈
self.grid[r][c] = d # 恢复箭头
3.5 自动求解
用回溯搜索找到一条通关顺序:
def _solve(self, grid, rows, cols, path):
for r in range(rows):
for c in range(cols):
if grid[r][c] is not None and not blocked(r, c):
grid[r][c] = None
path.append((r, c))
if self._solve(grid, rows, cols, path):
return True
path.pop()
grid[r][c] = d
return False
return all(grid[r][c] is None for r in range(rows) for c in range(cols)) # 棋盘空了即成功
找到路径后,每隔 0.6 秒自动 "点击" 一个箭头,播放飞出动画。
3.6 存档
游戏使用 JSON 文件 progress.json 来保存玩家的通关进度。每次通关后,程序会将当前已解锁到第几关、以及每关获得的最高星数写入这个文件。下次启动游戏时,先读取该文件恢复进度,未通关的关卡保持锁定状态。这样玩家关闭游戏后再打开,可以从上次的进度继续玩,而不需要从头开始。
四、AIGC 使用过程记录
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 路径检测逻辑 | DeepSeek | 生成四个方向的遍历检测代码,从箭头沿方向走到边界判断是否有阻挡 | 基础逻辑正确,但最初向上 / 向左检测时只写了 r >= 0 没加上界判断,边缘箭头会数组越界 |
修改 while 条件为 0 <= r < rows and 0 <= c < cols,统一四个方向的边界判断 |
| 碰撞动画 | Codex | 生成箭头被挡住后左右晃动再恢复的动画代码 | 晃动方向不对(应该沿箭头方向弹出再弹回,而不是左右晃),且动画速度太快 | 改为沿方向向量做三角形位移:前 60% 时间弹出 30px,后 40% 弹回,调整动画时长从 0.3s 到 0.5s |
| 关卡可解性验证 | Codex | 生成回溯搜索代码,自动验证每个关卡是否存在通关路径 | 能正确检测死锁布局,但最初的算法在找到第一个可消箭头后直接 return False,逻辑有误 | 修改回溯逻辑:消除箭头后递归,成功才返回 True,失败则恢复箭头继续尝试其他位置 |
| 箭头绘制优化 | DeepSeek | 生成带箭杆和箭头的绘制代码,并建议用 2 倍超采样抗锯齿 | 基本可用,但直接画在屏幕上仍有锯齿 | 按建议实现离屏渲染:在 2 倍大小的透明 surface 上画箭头,再用 smoothscale 缩小回原尺寸,边缘明显平滑 |
总结:AIGC 能快速生成代码框架,但边界条件、动画细节和算法逻辑都需要人工调试和修改。尤其是路径检测的越界问题和回溯算法的正确性,AI 给出的初版都有 bug,必须自己运行测试才能发现。
五、测试结果
自动化测试运行 python test_game.py,覆盖以下用例:
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 箭头飞出,格子变为 None | 通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误次数减 1 | 箭头弹回,grid 不变,misses_left -1 | 通过 |
| T03 | 点击位于边缘且朝向棋盘外的箭头 | 箭头正常消失,不发生越界错误 | 正常消除,无 IndexError | 通过 |
| T04 | 消除本关全部箭头 | 显示通关并进入下一关 | 通关后 level_idx +1,load_level | 通过 |
| T05 | 失误次数耗尽 | 显示失败并允许重新开始 | state 变为 LOSE,可重开 | 通过 |
| T06 | 游戏进行中重新开始 | 箭头布局和失误次数恢复 | load_level 重置 grid 和 misses_left | 通过 |
附加关卡可解性验证:
| 关卡 | 棋盘 | 箭头数 | 通关步数 | 结果 |
|---|---|---|---|---|
| 第 1 关 | 3×3 | 5 | 5 | 可通关 |
| 第 2 关 | 4×4 | 6 | 6 | 可通关 |
| 第 3 关 | 4×4 | 8 | 8 | 可通关 |
| 第 4 关 | 5×4 | 10 | 10 | 可通关 |
| 第 5 关 | 5×5 | 11 | 11 | 可通关 |
| 第 6 关 | 5×5 | 12 | 12 | 可通关 |
| 第 7 关 | 5×5 | 14 | 14 | 可通关 |
六、PSP 表格
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1 | 1 | 0 |
| Python 与图形库学习 | 1.5 | 1 | -0.5 |
| 游戏界面实现 | 2 | 2.5 | +0.5 |
| 路径与碰撞逻辑实现 | 1.5 | 1.5 | 0 |
| 关卡设计 | 1 | 1 | 0 |
| AIGC 辅助开发 | 1 | 1.5 | +0.5 |
| 测试与修改 | 1 | 1.5 | +0.5 |
| README 与博客撰写 | 1.5 | 1.5 | 0 |
| 合计 | 10.5 | 11.5 | +1 |
七、心得体会
AI 带来的帮助
这次作业从始至终都借助了 AIGC 工具(DeepSeek、Codex等)来辅助开发,AI 在以下几个方面帮了很大忙:
- 快速搭建代码框架:Pygame 的初始化、事件循环、界面绘制这些模板代码,AI 几秒钟就能生成,省去了查文档的时间
- 核心算法实现:路径检测和回溯求解的逻辑,AI 给出了初版代码,我只需要在其基础上调整边界条件和参数
- 动画和 UI:飞出动画、碰撞弹回、按钮样式等,AI 生成的代码基本可用,微调后效果不错
- Bug 定位:遇到数组越界、界面重叠等问题时,把报错信息贴给 AI,能快速定位原因
出现的问题
但 AI 生成的代码也不是拿来就能用,实际开发中遇到了不少问题:
- 边界条件遗漏:路径检测最初只判断了
r >= 0,没有加上界,导致边缘箭头点击时数组越界崩溃 - 动画效果不对:AI 生成的碰撞动画是左右晃动,但实际应该沿箭头方向弹出再弹回,效果不好
- 回溯算法逻辑错误:最初的求解器在找到第一个可消箭头后直接 return False,导致所有关卡都被判为无解
- 界面元素重叠:加了倒计时、撤销、提示、自动求解等按钮后,HUD 上的元素互相遮挡,需要重新排版
- 字体不支持 emoji:直接在代码里写 ⏱ 和 🔒,中文系统上显示成方框,只能换成文字
这些问题如果不自己运行测试,光看代码是发现不了的。AI 给的代码"看起来对"和"实际能跑"是两回事。
自己的收获
- 理解了游戏开发的基本流程:从需求分析、数据结构设计、核心逻辑实现、界面绘制到测试调试,走完了一个完整的小型软件开发流程
- 掌握了路径检测的思路:从一个格子沿方向向量逐格扫描,遇到箭头就返回被挡,走到边界就返回可飞,这个思路不仅适用于这个游戏,也是很多网格类问题的基础
- 学会了状态管理:游戏有开始、选关、游戏中、通关、失败等多个状态,用一个
self.state变量切换,每个状态对应不同的绘制和事件处理,代码结构清晰了很多 - 体会到了版本控制的重要性:每完成一个小功能就 commit 一次,出了问题可以回退,也能清楚看到开发进度,这和课堂上学的 Git 工作流是一致的
- 明白了 AIGC 是辅助工具而不是替代品:AI 能快速生成代码框架,但边界情况、业务逻辑、调试修改都需要自己来。提交的每一行代码,自己都要能解释清楚

浙公网安备 33010602011771号