软件工程第二次个人作业
| 这个作业属于哪个课程 | 2026软件工程班级 |
|---|---|
| 这个作业要求在哪里 | 软件工程第二次个人作业 |
| 这个作业的目标 | 借助 AIGC 工具完成小游戏开发,同时通过实际运行和测试,对 AI 给出的代码进行判断、修改和完善 |
| 学号 | 102401406 |
| GitHub仓库 | https://github.com/kkj11111/Arrow--Game |
一箭又一箭小游戏
一、 项目展示
开始界面:

游戏对局界面

碰撞反馈效果(箭头显示红色晃动 + 失误 +1)

提示功能(金色光圈高亮可飞出箭头)

选择关卡

通关弹窗

失败弹窗

全部关卡通过界面

二、项目介绍
1.游戏规则
棋盘内存在上、下、左、右四个方向的箭头。玩家点击箭头,程序沿着箭头方向做路径检测:
- 如果箭头到棋盘边界之间没有其他箭头,箭头飞出棋盘并消除;
- 如果路径上存在其他箭头,箭头不能消除,触发晃动并变红提示,同时消耗一次失误;
- 失误次数耗尽,本关挑战失败,可以重新开始;
- 清除棋盘全部箭头,即为通关,自动进入下一关卡;
- 6个关卡:从 4×4 入门到 6×6 最终关,难度递进
2.主要功能
- 完整三界面:开始界面、游戏界面、通关/失败弹窗;
- 基础玩法:鼠标点击箭头、路径阻挡判断、飞出动画、碰撞晃动、失误计数;
- 扩展功能:提示、撤销上一步、自动求解、本局计时、程序合成音效;
- UI特点:四方向独立配色(上 = 绿、右 = 橙、下 = 紫、左 = 青);渐变背景;卡片圆角布局,按钮悬浮高亮。
三、实现思路
箭头、方向与关卡的表示
方向用枚举表示,值为 (行增量, 列增量):
class Direction(Enum):
UP = (-1, 0)
DOWN = ( 1, 0)
LEFT = ( 0, -1)
RIGHT = ( 0, 1)
棋盘用二维列表 grid[row][col],元素要么是 Arrow 对象,要么是 None(空格)。关卡用字符数组描述,方便调试:
LEVEL_1 = [
["↑", "·", "·", "·"],
["·", "→", "→", "·"],
["·", "·", "·", "·"],
["·", "←", "·", "·"],
]
核心路径检测逻辑
拿到箭头对应的方向向量,从箭头相邻格子开始,沿着箭头前进方向逐格遍历棋盘。
- 如果遍历途中遇到其他箭头,判定存在阻挡,箭头无法飞出;
- 如果一直走到棋盘边界都没有遇到箭头,判定无阻挡,可以飞出消除。
关键代码片段:
def is_path_clear(self, arrow: Arrow) -> bool:
dr, dc = arrow.direction.value
r, c = arrow.row + dr, arrow.col + dc
while self.in_bounds(r, c):
if self.grid[r][c] is not None:
return False
r += dr
c += dc
return True
一开始向上 / 向左检测时,AI 生成的代码写成了 while r >= 0 and c >= 0 但没同时判断上界,导致在边缘箭头点击时报 IndexError。修复方式是统一用 in_bounds(r, c) 封装四个方向的边界判断,循环条件收敛成一行。
游戏状态管理
新建GameState类统一管理全部游戏状态:
- 场景状态:
start开始界面 /game游戏中 /win通关 /lose失败/select_level选关界面; - 关卡信息:当前关卡下标、失误次数、最大失误限制;
- 动画状态:晃动计时器、飞行动画计时器;
- 历史栈:用于撤销功能,保存每一步箭头布局快照。
关卡可解性自检与 AI 求解
为什么要自检?因为手工摆关卡很容易摆出 "死锁"—— 没有任何箭头能飞出去但棋盘还没清空。利用一个性质:消除箭头只会减少阻挡,绝不会产生新的阻挡,所以只要反复找出所有当前可飞的箭头并消除,能清空就是可解的,否则不可解。
def is_solvable(grid):
board = Board(grid)
while board.remaining() > 0:
movable = [a for row in board.grid for a in row
if a is not None and board.is_path_clear(a)]
if not movable:
return False
for a in movable:
board.remove(a)
return True
levels.solve() 在同样的模拟过程中记录消除顺序,就得到了一条通关点击序列 —— 这也是 "提示" 和 "自动求解" 的底层逻辑。随机关卡生成时,随机摆箭头后用 is_solvable() 过滤,最多重试若干次,保证给玩家的关一定能过。
计时修复说明
开发过程发现一个Bug:通关弹窗打开后得分会持续像倒计时一样变化。
原因:用时是实时调用time.time()计算,通关之后没有冻结结束时间,弹窗打开时钟继续走动,导致得分持续变动。
解决方案:通关或者失败时记录finish_time,胜负已定之后,读取用时直接使用已经冻结的时间,不再实时计算。
四、 AIGC 使用过程记录
| 子任务 | 借助何种AIGC技术 | AI实现或提供了什么 | 效果 | 人工修改 |
|---|---|---|---|---|
| 路径阻挡检测逻辑 | DeepSeek | 生成四方向遍历检测代码,判断前方是否有箭头 | 基础逻辑正确,向上检测存在数组越界风险 | 修改边界循环条件,增加棋盘范围判断,修复越界问题 |
| UI界面升级(无尾翼箭头+卡片布局) | Kimi | 生成绘制箭头、圆角卡片、渐变背景、彩色按钮的pygame绘图代码 | 绘图框架可用,箭头带有多余尾翼 | 删掉尾翼绘制代码,调整四方向配色,增加信息徽章,优化卡片阴影 |
| 碰撞晃动与飞出动画 | GitHub Copilot | 补全箭头晃动、飞出位移动画框架,动画计时器 | 动画可运行,晃动频率过快 | 调整动画帧计数,降低晃动幅度,增加飞出平滑插值;后续自己修复通关计时不冻结的bug |
| 实现撤销功能历史栈 | DeepSeek | 提出使用栈保存棋盘快照的方案,给出深拷贝保存箭头状态的示例代码 | 快照保存逻辑可行,但最初只保存棋盘箭头,没有同步保存失误次数,撤销失误操作时失误计数不会回退 | 在每一次快照中同时记录棋盘布局、当前失误数;区分普通飞出操作和碰撞失误操作,撤销时完整恢复对局全部状态 |
总结:AIGC可以快速搭建代码框架,但是边界条件、业务细节需要人工阅读、调试、修改,不能直接复制就交付。
- 通关后计时持续跳动 Bug:通关弹窗弹出用时、得分持续变化。修复:游戏结束冻结
finish_time时间戳。 - 路径检测数组越界闪退:边缘箭头点击发生索引报错。修复:封装棋盘边界判断函数。
- UI 文字重叠、弹窗排版错乱:新增按钮后元素互相压住,通关 / 失败弹窗高低不一致。修复:重新规划全部控件 Y 坐标,统一弹窗布局参数。
- 碰撞动画抖动严重:AI 生成动画参数导致箭头晃动抽搐。修复:调小位移,增加动画帧数。
- 关卡解锁存档失效:通关一关,下一关依旧锁定。修复:通关成功立刻持久化写入存档。
- 动画期间可以重复点击:动画播放中点击箭头造成游戏状态错乱。修复:增加忙碌标记,动画运行时屏蔽鼠标交互。
五、 测试结果
手工完成全部作业规定的测试用例:
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 箭头平滑飞出,成功移除 | 通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误次数减 1 | 箭头晃动,失误计数加一 | 通过 |
| T03 | 点击位于边缘且朝向棋盘外的箭头 | 箭头正常消失,不发生越界错误 | 边缘箭头正常消除,程序无崩溃报错 | 通过 |
| T04 | 消除本关全部箭头 | 显示通关结果并进入下一关 | 全部箭头消除后弹出通关弹窗,可以跳转下一关 | 通过 |
| T05 | 失误次数耗尽 | 显示失败结果,允许重新挑战 | 失误达到上限弹出失败弹窗,支持重置关卡 | 通过 |
| T06 | 游戏进行中重新开始 | 箭头布局和失误次数恢复初始状态 | 棋盘箭头、失误计数全部重置为本关初始值 | 通过 |
| T07 | 使用撤销功能 | 棋盘和失误计数回退到上一步 | 布局、失误数正确恢复 | 通过 |
| T08 | 使用提示功能 | 高亮可以飞出的箭头 | 金色光圈正确显示可飞箭 | 通过 |
六、PSP表格
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1 | 1 | 0 |
| Python 与图形库学习 | 1 | 1 | 0 |
| 游戏界面实现 | 2 | 2.5 | +0.5 |
| 路径与碰撞逻辑实现 | 1.5 | 1.5 | 0 |
| 关卡设计 | 1 | 1 | 0 |
| AIGC 辅助开发 | 1 | 1 | 0 |
| 测试与修改 | 1 | 1.5 | +0.5 |
| README 与博客撰写 | 1 | 1 | 0 |
| 合计 | 9.5 | 10.5 | 1 |
七、 心得体会
本次作业使用Pygame完成“一箭又一箭”小游戏,借助多种AIGC编程工具辅助开发。AI可以快速生成基础代码,节省大量重复编码时间,但是AI输出的代码经常忽略边界情况,会出现数组越界、状态没有冻结、UI不符合设计需求等问题,需要仔细阅读代码、调试修复。
通过本次项目,我理解了网格类小游戏的开发流程:数据定义、核心算法实现、状态管理、界面渲染、动画处理。重点掌握了射线式路径检测的实现思路,明白了游戏中动画计时器的用法。
同时我也体会到,AIGC只是辅助工具,开发者需要能够读懂每一段关键代码,并且做好充分测试,才能够保证程序逻辑正确。本次作业多次提交Git commit,模拟真实软件开发迭代过程,让我对版本控制也有更深的理解。在开发过程中很多 Bug 不是逻辑完全错误,而是边界场景没有考虑到,这也提醒我写程序的时候要多关注边缘情况,做好测试。
浙公网安备 33010602011771号