2026 秋软件工程个人作业(第二次)
2026 秋软件工程个人作业(第二次)
| 这个作业属于哪个课程 | H202601软件工程与软件工程实践 |
|---|---|
| 这个作业要求在哪里 | 2026秋软件工程个人作业(第二次) |
| 这个作业的目标 | 使用 Python 和 AIGC 完成"一箭又一箭"小游戏 |
| 学号 | 102401425 |
| GitHub 仓库 | https://github.com/SuCylinder/arrow_game |
一、项目展示
视频演示
项目截图
开始界面

游玩界面

AI演示功能

关卡选择

游戏失败界面

通关界面

二、项目介绍
- 玩法:5x5 棋盘上放若干单格箭头(上 / 下 / 左 / 右),点击某支箭头后检查它前进方向到边界之间是否还有别的箭头;无阻挡则飞出消失,有阻挡则碰撞,扣 1 次失误。
- 流程:开始界面 → 关卡选择 → 游戏界面 → 通关 / 失败界面;共 6 关,箭头数 6 / 8 / 10 / 12 / 14 / 16,每关失误次数 3 次。
- 界面:HUD 显示当前关卡、剩余箭头数、剩余失误次数(圆点)、重开按钮;支持 Esc / R 快捷键。
- 扩展功能(7 项):
- 关卡选择(含锁定 / 解锁、星级与最好成绩)
- 计时得分与三星评价
- 提示(H 键)
- 撤销上一步成功飞出(Z 键)
- AI 演示自动通关
- 随机挑战
- 进度存档到 save.json
- 环境:Python 3.11 + pygame(见 game/requirements.txt),入口
game/main.py。
三、实现思路
1. 数据表示
- 关卡定义:每关是一个列表,元素为(方向, 行, 列),方向用常量 UP/DOWN/LEFT/RIGHT(1~4),行列 0~4 对应 5×5 棋盘,见
arrow/levels/level1.py。 - 运行时展开:
build_arrows(defs)生成board[5][5](存箭头 id 或 EMPTY=0)和arrows列表、arrow_by_id字典;单格箭头用 Arrow 类记录 id、方向、head 坐标和 alive 状态,方向非法、越界或重叠抛 ValueError。
2. 路径检测(核心规则)
can_fly(board, arrow_by_id, r, c)按作业要求“同一行或同一列、箭头与边界之间是否还有别的箭头”逐格判断:取方向向量DIRS = {UP:(-1,0), DOWN:(1,0), LEFT:(0,-1), RIGHT:(0,1)},从起点下一格r+dr, c+dc开始while 0<=nr<N扫描,遇到非 EMPTY 立即返回 False,一路扫到越界返回 True。先判边界再索引,不会越界。- 触发:点击时把鼠标坐标换算为格子,空位忽略;可飞则启动 FlyAnim 滑出,不可飞则启动 ShakeAnim 晃动变红并
mistakes_left -= 1。
关键代码(arrow/core/logic.py):
def can_fly(board, arrow_by_id, r, c):
value = board[r][c]
if value == EMPTY:
return False
arrow = arrow_by_id[value]
dr, dc = DIRS[arrow.direction] # UP:(-1,0) DOWN:(1,0) LEFT:(0,-1) RIGHT:(0,1)
nr, nc = r + dr, c + dc
while 0 <= nr < N and 0 <= nc < N: # 先判边界,再索引,不会越界
if board[nr][nc] != EMPTY: # 路径上遇到别的箭头 → 碰撞
return False
nr += dr
nc += dc
return True # 一路扫到出界 → 可飞
3. 状态机与交互
- 五状态:
STATE_START开始界面、STATE_SELECT关卡选择(锁定/解锁、星级与最好成绩)、STATE_PLAY游戏中、STATE_WIN/STATE_LOSE结果界面,见arrow/config/rules.py。 - 胜负:
count_arrows为 0 进 WIN;失误归零后锁定点击(防止抢点)、进 LOSE;两状态均可重开本关(按钮/R 键)或返回。 - 输入:鼠标点棋盘与 HUD 按钮;键盘 Esc 返回、R 重开、H 提示可飞箭头、Z 撤销上一步成功飞出。
关键代码(arrow/config/rules.py 状态常量):
STATE_START = "start"
STATE_SELECT = "select"
STATE_PLAY = "play"
STATE_WIN = "win"
STATE_LOSE = "lose"
关键代码(arrow/game.py 点击与胜负,节选):
def try_click(self, r, c):
value = self.board[r][c]
if value == EMPTY:
return
arrow = self.arrow_by_id[value]
if self.mistakes_left <= 0:
return # 失误用完:锁定,等动画结束进失败界面
if self.can_fly(r, c):
self.anims.append(FlyAnim(arrow)) # 可飞 → 滑出动画
else:
self.anims.append(ShakeAnim(arrow)) # 被挡 → 晃动变红
self.mistakes_left -= 1
# 胜负切换(节选)
self.state = STATE_WIN # count_arrows() == 0 时
self.state = STATE_LOSE # 失误归零且动画播完时
4. 动画与渲染
- 飞出:按方向用
fly_distance计算完整滑出距离(头中心到边界 0.5 格 + 三角形底边 + 安全余量 FLY_MARGIN),插值平移至完全出界后清盘面数据。 - 碰撞:原地左右晃动 + 变红,结束后恢复原色。
- 绘制:view 层负责渐变背景、HUD 圆角面板(关卡/剩余/失误圆点)、消息胶囊、棋盘外框、箭头三角形(gfxdraw 抗锯齿)与按钮投影描边,
arrow/game.py只做薄封装。
5. 关卡与可通关保证
- 规模:共 6 关,箭头数 6/8/10/12/14/16,每关失误 3 次(
MISTAKES_PER_LEVEL)。 - 生成:用“倒序摆盘”生成器反向构造——从空盘开始逆向放置箭头,保证每步都有可飞的,再反向得到正向参考解(如第 1 关 (4,3)→(2,3)→(2,0)→(1,3)→(1,1)→(1,2)),不合格盘面自动丢弃并用参考解复验逐关清空、不扣失误。
6. 项目结构
core零 pygame(logic/generator/solver/scoring/save,可单测)、view负责表现、config集中常量、levels每关一文件聚合为 LEVELS,Game在arrow/game.py串联状态、计时得分三星与存档save.json。
四、AIGC 使用过程
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 需求分析 | Hermes Agent (muse spark 1.3) | 梳理作业要求,拆出规则判定、状态机、关卡、测试博客等功能点,定单文件 Tkinter/Pygame 方案 | 拆解可用,但关卡可解性没考虑到 | 定 Pygame 方案,补上倒序摆盘保证可通关的要求 |
| 路径检测 can_fly | opencode (deepseek v4.1flash) | 四个方向逐格扫描 + 边界判断 | 初版方向向量行列写反,自测即暴露 | 按反馈修正为 UP:(-1,0)、LEFT:(0,-1) 等,并补下一关越界保护 |
| 关卡设计 | opencode (deepseek v4.1flash) | “倒序摆盘”生成器,保证每关必有通关顺序 | 首版有解但偏简单 | 定筛选标准并人工筛选盘面,参考解逐关复验 |
| 回归单格 | opencode (deepseek v4.1flash) | 对照作业原文,把逻辑改为沿方向扫到边界,关卡改为 6/8/10 支 | 验证通过,代码精简 | 决定回归单格方案,定规模与失误 3 次 |
| 扩展功能 | opencode (deepseek v4.1flash) | 关卡扩到 6 个,加得分计时三星、提示撤销、存档随机挑战、AI 演示 | 评分公式与 AI 演示两处缺陷,修复后通过 | 选定 7 项,定撤销提示存档规则 |
| 飞出 / 碰撞动画 | opencode (deepseek v4.1flash) | 滑出插值 + 晃动变色动画 | 结尾空转、失败判定有抢点窗口 | 按几何精算滑出距离,失误归零后锁定操作 |
| 界面美化 | opencode (deepseek v4.1flash) | 渐变背景、HUD 圆角面板、消息胶囊、结果徽章 | 两处撞色(按钮描边、徽章与箭头色) | 选定克制专业风,改深色描边 |
对话记录截图
1. hermes需求拆解

2. 修复存档功能

五、测试结果
按 T01~T09 逐项手工实测后填写:
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 与预期一致,箭头滑出消失 | 通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误次数减 1 | 与预期一致,箭头晃动变红、失误次数减 1 | 通过 |
| T03 | 点击位于边缘且朝向棋盘外的箭头 | 箭头正常消失,不发生越界错误 | 与预期一致,边缘箭头正常飞出 | 通过 |
| T04 | 消除本关全部箭头 | 显示通关并进入下一关 | 与预期一致,通关后进入下一关 | 通过 |
| T05 | 失误次数耗尽 | 显示失败并允许重新开始 | 与预期一致,显示失败界面可重开 | 通过 |
| T06 | 游戏进行中重新开始 | 箭头布局和失误次数恢复 | 与预期一致,布局与失误次数均恢复 | 通过 |
| T07 | 按 H 提示可飞箭头 | 高亮显示一支当前可飞的箭头 | 与预期一致,高亮一支且不影响计分 | 通过 |
| T08 | 按 Z 撤销上一步 | 撤销上一次成功飞出的箭头 | 与预期一致,箭头与得分/步数回退 | 通过 |
| T09 | 关卡选择与存档 | 通关解锁下一关、记录最好成绩与星级 | 与预期一致,解锁与存档正常 | 通过 |
另外,AI 还帮我写了个自动化测试脚本,放在 game/test_smoke.py,在 game/ 目录下跑 uv run python test_smoke.py 就行。它是无头跑的(SDL_VIDEODRIVER=dummy),不用弹窗口,覆盖了通关流程、路径检测、飞出/碰撞动画、渲染、关卡数据、失败窗口、扩展功能、模糊测试和真实入口主循环,刚才跑了一遍是 SMOKE OK:331 项检查全部通过,改完逻辑跑一遍,有 FAIL 直接看那行就行。
六、PSP 表格
〈待填:按自己实际投入填写,开发时顺手计时〉
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 分析和需求拆解 | 0.5 | 0.5 | 0 |
| Python 与图形库学习 | 0.5 | 0.6 | +0.1 |
| AI 辅助开发 | 1.5 | 1.7 | +0.2 |
| 测试与修改 | 0.5 | 0.6 | +0.1 |
| 文档撰写(README/博客等) | 0.5 | 0.6 | +0.1 |
| 合计 | 3.5 | 4 | +0.5 |
七、心得体会
这次作业 AI 确实帮我省了不少时间,但也不是全程顺利。搭框架和写 can_fly 这种规则代码时 AI 很快,方向向量写反这种低级 bug 也是跑测试时当场暴露、当场改好的。关卡生成那块我一开始没想到可解性问题,是 AI 提醒要用"倒序摆盘"反向构造,这个思路我觉得挺巧的,后来每关能不能通关全靠它保证。
AI 帮我写的自动化脚本 game/test_smoke.py 也很好用,在 game 目录下跑一下就行,不用开窗口,331 项全过,改完跑一遍很放心。
但 AI 也挖过坑。印象最深的是中间有一版多格蛇形箭头,看着很炫,动画还专门做了贪吃蛇式跟随,结果回头对照作业要求才发现基础版只要单格,等于白做了,只能回退。还有评分公式里时间奖励写反了,超时反而分更高,界面配色也有两处撞色,都是跑测试测出来的。所以我现在的感觉是,AI 写出来的东西一定得跑测试验一遍,不能直接信。
自己学到的东西,一是路径检测这种"沿方向扫到边界"的写法,看着简单但边界条件很容易写错;二是状态机加无头测试这套流程,游戏逻辑和 pygame 渲染分开之后,core 层不用开窗口就能测,改起来心里有底。以后写稍大一点的程序,我应该也会先把能单测的部分拆出来。
浙公网安备 33010602011771号