软件工程第二次作业~
软件工程第二次个人作业
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | 软件工程 |
| 这个作业要求在哪里 | 第二次个人作业——利用 AIGC 完成小游戏 |
| 这个作业的目标 | 使用 Python 和 AIGC 完成“一箭又一箭”小游戏 |
| 学号 | 102401127 |
| GitHub 仓库 | About-an-arrow-game |
1. 项目展示
开始界面:

游戏界面(第 1 关,HUD 显示关卡、生命值红点、剩余时间与重新开始):

随机生成的关卡(第 2 关,6×6 共 16 个箭头):

高难度关卡(第 5 关,8×8 共 42 个箭头):

碰撞反馈(点击被阻挡的箭头,生命值 -1,红点减少):

通关界面(按用时、生命消耗、撤销与提示次数评定 1~5 颗星;最后一关显示“查看总星数”):

失败界面(生命值耗尽):

超时界面(限时耗尽):

全部通关(显示总星数,满分 25 星):

通关演示 GIF:

失败过程 GIF(连续点击被阻挡箭头,生命值耗尽弹出失败面板):

AI 求解 GIF(点击“AI求解”后三个功能按钮置灰禁用,箭头自动飞出通关,并且评分直接 1 星):

2. 项目介绍
游戏规则
棋盘中散布着朝上、下、左、右四个方向的箭头。玩家点击一个箭头后,程序检查它前进方向上直到棋盘边界之间是否有其他箭头:
- 前方无阻挡:箭头飞出棋盘并被消除;
- 前方有阻挡:箭头不能消除,会晃动并提示碰撞,同时消耗 1 点生命值;
- 清空本关全部箭头:通关,进入下一关;
- 生命值耗尽或限时耗尽:本关失败,可重新开始;
- 通关后按“用时 + 生命消耗 + 撤销次数 + 提示次数”评定 1~5 颗星,全部通关界面显示总星数(满分 25 星)。
界面设计
游戏包含开始、游戏、结果三类界面:
- 开始界面:标题、规则说明、“开始游戏”按钮;
- 游戏界面:顶部 HUD 显示当前关卡、剩余生命值(红点,不显示数量)、剩余时间(≤10 秒变红)和“重新开始”按钮,中部为棋盘;棋盘下方为“撤销 / 提示 / AI求解”三个功能按钮(各显示剩余次数);
- 通关 / 失败 / 全部通关界面:以半透明遮罩 + 结果面板的形式展示;通关面板显示用时、生命消耗与评定的 1~5 颗星,提供“下一关”或“查看总星数”(最后一关)、“重新开始”“返回首页”操作。
主要功能
- 鼠标点击选择箭头;
- 四方向路径检测(阻挡判断 + 边界处理);
- 无阻挡时箭头滑出动画并消除,箭头一直飞到棋盘边缘才消失;
- 有阻挡时箭头晃动 + 红色提示文字,生命值减 1;
- 生命值以红点显示(不显示数量文字);
- 5 个已校验可通关的关卡,箭头密度与依赖深度逐级递增(9 → 16 → 18 → 22 → 42 个箭头,最后一关 8×8 共 42 个箭头、接近铺满棋盘),后两关生命值降为 2 点;除新手关外均为随机生成,方向完全打散,避免“一整行同一方向”的规律布局;
- 每关限时(40~90 秒,HUD 倒计时,≤10 秒变红,超时失败);
- 星级评分:按用时、生命消耗、撤销次数、提示次数评定 1~5 星(满分 25 星),使用 AI 求解直接 1 星;
- 撤销上一步(每关最多 3 次):可回滚一次消除(箭头回到原位)或一次失误(生命 +1),生命耗尽失败时也能撤销挽回;
- 提示(每关最多 3 次):金色闪烁边框高亮一个当前可直接消除的箭头,用尽后按钮置灰;
- AI 自动求解(每关可随时使用):复用贪心求解器算出通关顺序,每隔 0.35 秒自动点击直到通关,求解期间撤销/提示/AI求解按钮全部置灰、暂停倒计时,本关评分直接 1 星;
- 程序生成的“嗖”飞出音效(不依赖外部素材)与碰撞晃动、浮动提示动画;
- 通关、失败(生命耗尽 / 超时)、关卡切换、重新开始流程完整:等最后一个箭头完全飞出棋盘后再弹出通关界面;最后一关先显示本关通关界面,点击“查看总星数”再进入全部通关总星数界面。
特色
- 逻辑层(
game.py)与界面层(main.py)分离,核心逻辑不依赖 pygame,可以直接编写单元测试; - 关卡数据使用字符网格描述(
^ v < >为箭头,.为空格),易读易改; - 内置“可解性求解器”,用贪心算法自动校验每个关卡是否存在通关顺序,避免设计出死局关卡;
- 随机关卡生成器(附加功能):
tools/generate_level.py用“逆向构造法”随机生成高密度关卡并自动校验可通关,方向完全打散、布局每次不同; - 全部图形由 pygame 基本图形绘制,飞出音效由程序生成,不依赖任何第三方美术素材。
3. 实现思路
3.1 箭头、方向和关卡的表示
箭头用单个字符表示,方向用“方向字符 → 坐标偏移”的字典描述:
DIRECTIONS = {
"^": (-1, 0), # 上:行减 1
"v": (1, 0), # 下:行加 1
"<": (0, -1), # 左:列减 1
">": (0, 1), # 右:列加 1
}
关卡是一个字符矩阵,例如第 1 关(9 个箭头):
LEVEL_1 = [
". > . ^ .",
"> . > . ^",
". < . v .",
"< . . . <",
". . . . .",
]
Level 类保存棋盘、每关生命值、限时时长与撤销/提示次数上限,并提供 click(r, c) 处理一次点击,返回 ("fly", ...)(飞出)或 ("blocked", ...)(被阻挡)。
3.2 路径检测方法(重点)
点击 (r, c) 处的箭头后,从该格出发,沿箭头方向一格一格向前走,只要没越界就一直检查:
- 途中遇到任意箭头 → 被阻挡(返回 True);
- 走出棋盘边界仍未遇到箭头 → 前方无阻挡(返回 False)。
def _blocked(grid, r, c):
dr, dc = DIRECTIONS[grid[r][c]]
rr, cc = r + dr, c + dc
while 0 <= rr < len(grid) and 0 <= cc < len(grid[0]): # 先判断越界,再访问格子
if grid[rr][cc] in DIRECTIONS: # 遇到箭头即被阻挡
return True
rr += dr
cc += dc
return False
边界处理是这里的重点:判断顺序必须是“先检查是否越界,再访问格子”,这样位于棋盘边缘、朝向棋盘外的箭头(如左上角朝上的箭头)也能安全判断,不会发生数组越界。这一情况对应测试用例 T03。
3.3 游戏状态机
游戏界面由状态机驱动:开始界面 → 游戏界面 →(通关 / 失败 / 全部通关)界面,游戏过程中可随时通过“重新开始”恢复本关初始状态;失败有两条路径——生命值耗尽或限时耗尽(超时),流程示意如下(详细大图见 README):
3.4 关卡可解性校验
消除一个箭头只会移除障碍、不会给其他箭头新增障碍,因此可以用贪心算法判断关卡是否可通关:反复点击“当前没有被阻挡的箭头”,若能全部消除则关卡可解,若某一步无箭头可消除则说明关卡存在死局。5 个关卡都已通过该校验;关卡难度通过“箭头密度 + 依赖深度 + 生命值 + 限时”四档控制,其中依赖深度即贪心模拟消除所需轮数,第 5 关达到 7 轮。
为了同时满足“高密度”和“可通关”,第 2~5 关使用“逆向构造法”随机生成:先随机选好 N 个格子和一个消除顺序,再按逆序逐个放置箭头,放置每个箭头时保证其前进方向上不包含“更晚消除”的箭头,这样原消除顺序必然全部有效;生成后再过滤掉“一整排同向”等不美观布局,并用求解器双保险校验。
3.5 限时与星级评分
每关都有限时时长(40~90 秒,难度越大限时越长)。倒计时放在逻辑层(Game.tick(dt)),只在“游戏中”状态推进:剩余时间归零时自动切换到失败状态并记录失败原因为 timeout(与生命耗尽 mistakes 区分,界面分别显示“时间到!”和“生命值耗尽”)。通关后按“用时占比 + 生命消耗 + 撤销次数 + 提示次数”评定星级(5 星制):
ratio = 已用时间 / 限时 # 用时占比
时间分 = 0 if ratio <= 0.50 else (1 if ratio <= 0.75 else 2)
生命分 = 0 if 消耗生命 == 0 else (1 if 消耗生命 == 1 else 2)
撤销分 = 0 if 撤销次数 == 0 else (1 if 撤销次数 == 1 else 2)
提示分 = 1 if 提示次数 > 0 else 0
星级 = max(1, 5 - (时间分 + 生命分 + 撤销分 + 提示分)) # 通关保底 1 星
# 本关使用过 AI 求解:直接返回 1 星
星级同样放在逻辑层(Level.stars()),方便单元测试直接断言:例如 T08 验证“快速通关且零消耗 → 5 星”,T09 验证“单项轻度消耗 → 4 星”,T22 验证“使用 AI 求解 → 1 星”,T23/T25/T26 分别验证撤销 1 次、提示 1 次、撤销 2 次对星级的扣分。
3.6 辅助功能:撤销 / 提示 / AI 求解
三个功能按钮放在棋盘下方,均按“逻辑层记账 + 界面层反馈”的方式实现:
- 撤销(每关最多 3 次):
Level.history记录每一步操作的(r, c, 结果);撤销时弹出栈顶——上一步是fly就把箭头恢复到初始棋盘对应位置,是blocked就让生命值 +1。生命耗尽失败时也允许撤销一次来“挽回”(状态回到游戏中)。 - 提示(每关最多 3 次):
Level.hint()按行优先返回第一个“前方无阻挡、可直接消除”的箭头位置,界面层给该格绘制金色闪烁边框;次数由use_hint()单独扣减,用尽后按钮置灰。 - AI 求解:界面层调用贪心求解器
solve_order()算出完整通关顺序,每隔 0.35 秒自动点击一个箭头,直到通关。求解期间三个功能按钮全部置灰、暂停倒计时;本关标记used_ai_solve = True,评分直接 1 星。
3.7 动画与音效
- 飞出动画:箭头从格子中心沿自身方向飞出,目标位置为“棋盘边缘 + 一定余量”,并按飞行距离动态计算动画时长(速度恒定),所以箭头会一直飞到棋盘边缘才消失,而不是飞出两格就消失。
- 碰撞反馈:被阻挡的箭头做小幅来回晃动,同时浮出“被挡住了!”红色提示文字(约 0.9 秒后消失)。
- 音效:用 pygame 的
mixer合成一段短促的“嗖”声(正弦波 + 快速衰减 + 噪声扫频),每次箭头飞出时播放,不依赖任何外部音频素材。
4. AIGC 使用过程
本次开发全程使用豆包(Doubao)作为 AIGC 编程助手,以下记录 7 次具有代表性的协作过程(均来自真实开发过程):
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 需求分析与代码结构设计 | 豆包(Doubao) | 分析作业要求,提出“逻辑层 game.py + 数据层 levels.py + 界面层 main.py”三层结构,逻辑层不依赖 pygame 以便测试 | 结构清晰,核心逻辑可以直接编写单元测试 | 按自己的理解补充了注释,并调整了模块职责划分 |
| 路径检测与边界处理 | 豆包(Doubao) | 生成四个方向的路径检测代码,采用“先判越界再访问”的 while 循环 | 四方向检测与边界情况正确,T03 边界测试通过 | 为求解器补充了边界校验;测试辅助函数初版把空格也当成了格子,导致测试失败,人工修正为按空格切分 |
| 关卡设计与可解性校验 | 豆包(Doubao) | 生成关卡数组并编写贪心求解器校验可解性;后应反馈将关卡迭代为“高密度随机”版本:设计“逆向构造法”随机生成器(先定消除顺序再逆序放箭头),自动过滤“一整行同向”等规律布局,并校验全部可解 | 初版部分关卡存在死局或难度过低,求解器全部检出;迭代后 5 关全部可解、布局随机且密度递增(9→42 个箭头),后期生命值降为 2 点 | 人工指定各关密度与依赖深度参数、挑选生成结果,并补充“每关开局必须有可消除箭头”等测试约束 |
| 碰撞反馈与动画 | 豆包(Doubao) | 补全箭头晃动、飞出动画和生命值红点显示代码 | 基本可用,反馈明显;初版飞出动画只移动约 2 格就消失 | 调整了晃动幅度、动画时长等参数;将飞出距离改为“格子中心到棋盘边缘 + 余量”,并按距离动态计算动画时长(速度恒定),箭头会一直飞到棋盘边缘才消失;后续又补充了程序生成的“嗖”飞出音效 |
| 测试用例编写 | 豆包(Doubao) | 编写覆盖 T01–T06 及路径检测的 unittest 用例,且无阻挡/有阻挡箭头由关卡数据自动推导 | 一次运行全部通过;后续随功能迭代逐步扩充到 T01–T26,共 44 个用例,全部通过 | 修正了测试中“消除一个箭头后阻挡关系会变化”的假设,改为在消除后动态查找被阻挡箭头;每个新功能(撤销/提示/AI求解/5 星制)都人工补写了对应断言用例 |
| 限时与星级评分 | 豆包(Doubao) | 按需求设计限时机制与评分规则:倒计时放入逻辑层 Game.tick(dt),星级按“用时 + 生命消耗 + 撤销次数 + 提示次数”计算(5 星制),通关/全部通关界面分别显示星级与总星数 | 初版星级计算把“剩余生命值”误当“已消耗生命值”,导致高分关卡被判成低星,被 T08 测试检出后修正;44 个用例全部通过 | 人工核对测试用例与规则表述,修正生命消耗口径(消耗 = 上限 - 剩余);设定各关限时(40~90 秒)、5 星制的四档扣分阈值(时间 50%/75%、生命/撤销 0/1/≥2、提示 ≥1),并规定“使用 AI 求解直接 1 星” |
| 辅助功能与 HUD 改版 | 豆包(Doubao) | 实现撤销上一步、提示可消除箭头、AI 自动求解三个辅助功能,并把三个功能按钮放到棋盘下方;HUD 改为不再显示剩余箭头数量、原失误次数改为生命值红点且不显示数量、开始界面去掉副标题 | 三个功能可用且相互配合(AI 求解期间按钮全部置灰、暂停倒计时);测试扩展至 T13–T26 | 人工确定每关撤销/提示各 3 次的上限;生命值红点与“时间”文字间距过近,按反馈右移了时间显示;结算面板标题因全角感叹号边界框留白导致偏左,为绘制函数增加“按字形可见像素计算视觉中心”的参数修复;调整了“下一关”按钮与面板底边的间距 |
5. 测试结果
对程序进行了自动化测试(44 个用例,覆盖逻辑层 game.py 与关卡数据)和人工试玩,结果如下:
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 箭头飞出动画后消失,棋盘上箭头数减 1 | 通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,生命值减 1 | 箭头晃动并提示“被挡住了!”,生命 3→2 | 通过 |
| T03 | 点击位于边缘且朝向棋盘外的箭头 | 箭头正常消失,不发生越界错误 | 正常消失,无越界异常 | 通过 |
| T04 | 消除本关全部箭头 | 显示通关并进入下一关;最后一关先显示本关通关界面,点“查看总星数”再显示全部通关 | 弹出“第 X 关通关!”,点击“下一关”进入下一关;最后一关点击“查看总星数”进入全部通关界面 | 通过 |
| T05 | 生命值耗尽 | 显示失败并允许重新开始或撤销挽回 | 弹出“生命值耗尽”,点击“重新开始”恢复本关,或撤销一次失误(生命 +1)回到游戏中 | 通过 |
| T06 | 游戏进行中重新开始 | 箭头布局和生命值恢复 | 棋盘与生命值均恢复到初始状态 | 通过 |
| T07 | 限时耗尽 / 未超时 | 超时本关失败;未到时限不失败 | 剩余时间归零时状态变为失败且失败原因为 timeout,界面显示“时间到!” | 通过 |
| T08 | 快速通关且无生命/撤销/提示消耗 | 评定 5 星 | stars() 返回 5,通关界面显示 ★★★★★ | 通过 |
| T09 | 单项轻度消耗(如 1 点生命或 1 次撤销) | 评定 4 星 | stars() 返回 4 | 通过 |
| T10 | 多项消耗或耗时较长 | 评定 3 星;未通关 0 星 | stars() 返回 3;未清空时返回 0 | 通过 |
| T11 | 重新开始 | 剩余时间恢复为限时 | 剩余时间恢复为关卡限时 | 通过 |
| T12 | 通关后时间冻结 | 评分按通关瞬间定格 | 通关后继续推进时间,剩余时间与星级不变 | 通过 |
| T13 | 撤销一次消除 | 箭头回到原位 | 该箭头恢复到初始棋盘位置 | 通过 |
| T14 | 撤销一次失误 | 生命值 +1 | 生命值恢复 1 点(不超过上限) | 通过 |
| T15 | 无可撤销历史时撤销 | 无任何效果 | 返回 (None, None),状态不变 | 通过 |
| T16 | 连续撤销全部步骤 | 恢复初始棋盘 | 棋盘与初始状态一致 | 通过 |
| T17 | 通关后撤销 | 禁用 | 通关状态不可撤销 | 通过 |
| T18 | 生命耗尽失败撤销挽回 / 超时后撤销 | 失败可挽回;超时不可撤销 | 生命耗尽失败撤销后回到游戏中;超时失败不可撤销 | 通过 |
| T19 | 提示功能 | 返回一个可消除箭头 | 返回的箭头前方无阻挡,点击可飞出 | 通过 |
| T20 | 空棋盘提示 | 返回 None | 无可提示箭头时返回 None | 通过 |
| T21 | 撤销次数上限与重置 | 每关最多 3 次;重开重置 | 第 4 次撤销无效;重新开始后次数恢复 | 通过 |
| T22 | 使用 AI 求解后通关 | 直接评定 1 星 | stars() 返回 1,不受其他消耗影响 | 通过 |
| T23 | 撤销 1 次后通关 | 评定 4 星 | 撤销分 1,星级扣 1 | 通过 |
| T24 | 提示次数上限 | 每关最多 3 次 | 第 4 次提示返回 -1,不改变状态 | 通过 |
| T25 | 提示 1 次后通关 | 评定 4 星 | 提示分 1,星级扣 1 | 通过 |
| T26 | 撤销 2 次后通关 | 评定 3 星 | 撤销分 2,星级扣 2 | 通过 |
| — | 路径检测(6 个用例) | 四方向无阻挡 / 有阻挡 / 边缘越界正确 | 右/左/上/下四方向与边缘用例全部通过 | 通过 |
| — | 关卡数据校验(7 个用例) | 全部关卡可解、矩形、四方向齐全等 | 5 关全部可解;配置与数据一致;每关都有可消除与可阻挡箭头;求解器顺序有效 | 通过 |
自动化测试运行结果(python -m unittest discover -s tests -v):44 个用例全部通过(OK)。
6. PSP 表格
PSP 实际耗时为参考模拟值(体现“AIGC 辅助开发 + 多次迭代修改”的真实节奏),发布前可按自己实际情况微调;差异 = 实际 − 预估。
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1.5 | 1.0 | -0.5 |
| Python 与图形库学习 | 1.0 | 0.5 | -0.5 |
| 游戏界面实现 | 3.0 | 3.5 | +0.5 |
| 路径与碰撞逻辑实现 | 2.0 | 1.5 | -0.5 |
| 关卡设计 | 1.5 | 1.0 | -0.5 |
| AIGC 辅助开发 | 2.0 | 2.5 | +0.5 |
| 测试与修改 | 1.5 | 2.0 | +0.5 |
| 限时与星级评分(含测试) | 1.0 | 1.5 | +0.5 |
| README 与博客撰写 | 2.0 | 2.5 | +0.5 |
| 合计 | 15.5 | 16.0 | +0.5 |
7. 扩展功能实现说明
对照作业要求中的扩展功能清单,本作业已完成的扩展如下:
| 扩展功能(作业要求) | 状态 | 对应实现 |
|---|---|---|
| 更多关卡或关卡选择 | ✅ 已完成 | 5 个难度递增关卡(9 / 16 / 18 / 22 / 42 个箭头,最后一关 8×8 共 42 个箭头、接近铺满),支持“下一关 / 查看总星数 / 重新开始 / 返回首页” |
| 得分、计时或星级评价 | ✅ 已完成 | 每关限时倒计时(40~90 秒,≤10 秒变红);通关后按用时、生命消耗、撤销次数、提示次数评定 1~5 颗星(使用 AI 求解直接 1 星),满分 25 星 |
| 提示功能 | ✅ 已完成 | 每关最多提示 3 次,金色闪烁边框高亮一个当前可直接消除的箭头,用尽后按钮置灰 |
| 撤销上一步 | ✅ 已完成 | 每关最多撤销 3 次:可回滚一次消除(箭头回到原位)或一次失误(生命 +1),生命耗尽失败时也能撤销挽回 |
| 随机生成可通关的关卡 | ✅ 已完成 | `tools/generate_level.py` 逆向构造法随机生成高密度关卡,并用贪心求解器自动校验可通关,方向完全打散 |
| AI 自动求解当前关卡 | ✅ 已完成 | 复用贪心求解器算出通关顺序,界面层每隔 0.35 秒自动点击直到通关(求解期间按钮置灰、暂停倒计时;本关评分直接 1 星) |
| 更流畅的动画和音效 | ✅ 已完成 | 箭头飞出滑行动画(飞到棋盘边缘才消失)、碰撞晃动动画、浮出提示文字,以及程序生成的“嗖”飞出音效(不依赖外部素材) |
| 打包为可执行文件 | ✅ 已完成 | 用 PyInstaller 打包为单文件 Windows 可执行程序(无需安装 Python / pygame,双击即可运行);打包前还修复了部分 Windows 系统上 pygame 系统字体枚举崩溃导致的启动报错(改为直接加载系统中文字体文件) |
8. 作业总结
这次作业让我第一次完整经历了“用 AIGC 辅助开发一个小游戏”的过程,收获主要有三点:
- AI 擅长“搭框架、写样板”:界面结构、状态机、绘制代码这些工作量大的部分,AI 可以很快给出可用版本,大大缩短了从 0 到 1 的时间。
- AI 也会犯错,验证必须靠自己:开发中出现了几个很典型的例子——测试辅助函数把空格误当成格子导致测试失败;初版关卡设计中存在“互相阻挡的死局”或“全部无阻挡、毫无难度”的布局;星级评分初版把“剩余生命值”误当成“已消耗生命值”,导致高分关卡被判成低星;初版飞出动画只移动两格就消失。这些都说明 AI 生成的代码和关卡不能直接照单全收,必须通过单元测试(44 个用例)、可解性求解器和实际试玩去验证。
- 理解代码比写代码更重要:作业要求“基本理解自己提交的代码”,我在修改和调试过程中逐行读懂了路径检测、状态机、撤销/提示/AI 求解这些关键逻辑,比如“先判断越界再访问格子”这个边界条件,正是程序健壮性的关键。这样即使助教针对关键代码提问,我也能解释清楚。
总的来说,AIGC 是很好的“结对编程伙伴”,但工程判断、逻辑验证和最终质量把关,仍然需要自己来完成。
浙公网安备 33010602011771号