软件工程第二次个人作业——使用 AIGC 完成"一箭又一箭"小游戏
软件工程第二次个人作业——使用 AIGC 完成"一箭又一箭"小游戏
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | H202601 软件工程与软件工程实践 |
| 这个作业要求在哪里 | 2026 秋软件工程个人作业(第二次) |
| 这个作业的目标 | 使用 Python 和 AIGC 完成"一箭又一箭"小游戏 |
| 学号 | 102401526 |
| GitHub 仓库 | codeyuan1/arrow-game |
一、项目展示
本项目使用 Python + Pygame 开发,棋盘、箭头和界面装饰全部由代码实时绘制,音效由程序实时合成,没有使用任何商业游戏的代码、美术素材、音效或关卡。
游戏演示 GIF

开始界面
深蓝到深紫蓝的渐变背景,标题带发光效果,背景有缓慢上下浮动的半透明箭头装饰。
关卡选择界面
共 4 个关卡,难度递增(4×4 / 4×4 / 5×5 / 6×6),卡片上显示解锁状态和已获得的星级。
游戏界面
顶部信息栏显示当前关卡、剩余箭头数、失误次数和用时;底部为提示、撤销、重新开始、返回四个按钮。
碰撞反馈
点击被阻挡的箭头后,箭头原地左右晃动并变成红色,同时失误次数减 1、播放低沉的碰撞音效。
通关界面
清空全部箭头后弹出结算面板,显示本关星级(0 失误 3 星、1 失误 2 星、2 次以上 1 星)和通关用时。
失败界面
失误次数耗尽后弹出失败面板,可以重新挑战或返回选关。
二、项目介绍
游戏规则
棋盘上分布着上、下、左、右四种方向的箭头,每支箭头只占一个网格。玩家点击箭头后,程序检查该箭头前进方向(同一行或同一列)上、箭头与棋盘边界之间是否存在其他箭头:
- 没有阻挡:箭头沿自身方向飞出棋盘并消失;
- 存在阻挡:箭头不能消除,原地晃动变红提示碰撞,同时消耗一次失误机会;
- 清空本关全部箭头即通关,可进入下一关;
- 失误次数耗尽则本关失败,可重新开始。
界面设计
程序共包含五个场景,由一个整数状态变量 page 统一管理:开始菜单(MENU)、关卡选择(LEVEL_SELECT)、游戏中(PLAYING)、通关(WIN)、失败(LOSE)。游戏界面顶部信息栏常驻显示关卡、剩余箭头、失误次数和用时,底部常驻四个操作按钮。整体采用深蓝渐变背景 + 近白色箭头 + 青绿色点缀的配色。
主要功能与特色
基础功能:
- 四方向箭头齐全,鼠标点击操作;
- 路径逐格检测,正确处理边界(边缘朝外的箭头直接飞出,不越界);
- 飞出动画(沿方向移动 + 渐隐)与碰撞动画(正弦晃动 + 变红);
- 失误计数与失败判定;
- 4 个关卡全部经过自动化求解验证,确认存在合理通关顺序。
扩展功能(附加分):
- 关卡选择:4 关卡片式选择,通关后解锁下一关;
- 计时与星级评价:根据失误次数给出 1~3 星;
- 提示功能:绿色高亮当前所有可安全飞出的箭头,每关限 3 次;
- 撤销上一步:点击前保存棋盘快照,可逐步回退,每关限 3 次;
- 保存进度:通关解锁与星级自动写入
save.json,下次启动自动读取; - 程序化合成音效:飞出、碰撞、通关、失败四种音效全部由
math.sin生成 sine 波 PCM 数据实时合成,零外部音频文件、零版权风险。
三、实现思路
1. 项目结构
为了把游戏规则和图形界面解耦,项目拆成 5 个 Python 文件:
arrow-game/
├── main.py # 入口:页面状态机 + 鼠标事件处理 + 主循环 + 存档读写
├── core.py # 纯逻辑:方向字典、get_blocker 路径检测、GameState 状态管理
├── levels.py # 关卡数据:网格大小、失误上限、箭头坐标与方向
├── render.py # 绘制:渐变背景、发光标题、箭头、棋盘、飞出/晃动动画
├── sound.py # 程序化音效:sine 波 PCM 合成
├── tests.py # T01~T06 自动化测试(24 项断言)
└── requirements.txt
其中 core.py 不 import pygame,是纯 Python 逻辑,所以路径检测可以脱离图形界面直接跑自动化测试,tests.py 不需要打开游戏窗口就能验证规则。
2. 箭头与方向的表示
行号向下增长、列号向右增长(和屏幕像素坐标一致)。用一个字典把方向名映射成"行偏移、列偏移":
DIRS = {
'UP': (-1, 0), # 上:行 -1
'DOWN': (1, 0), # 下:行 +1
'LEFT': (0, -1), # 左:列 -1
'RIGHT': (0, 1), # 右:列 +1
}
棋盘是一个二维列表,0 表示空格,字符串表示该格箭头的方向:
board = [
[0, 0, 'RIGHT', 0],
['DOWN', 0, 0, 0],
[0, 'LEFT', 0, 0],
[0, 0, 0, 'UP']
]
3. 关卡数据的表示
每个关卡用一个字典描述,箭头用 (行, 列, 方向) 三元组列表表示。初始化 GameState 时遍历该列表,把方向字符串填入棋盘对应位置:
{
'rows': 4, 'cols': 4, 'mistakes': 3,
'arrows': [
(0, 0, 'DOWN'), (0, 1, 'RIGHT'), (0, 3, 'DOWN'),
(2, 0, 'LEFT'), (2, 3, 'RIGHT'),
(3, 1, 'UP'),
],
}
四个关卡依次为 4×4(6 支箭)、4×4(8 支箭)、5×5(10 支箭)、6×6(12 支箭,失误上限 4 次),规模和箭头数递增。
4. 路径检测核心方法
路径检测是整个游戏最核心的算法。思路是:从被点击箭头的前方一格开始,沿方向逐格前进,遇到第一支非 0 的格子就返回它的坐标;如果坐标先走出棋盘,说明一路畅通,返回 None。
def get_blocker(board, r, c, direction):
dr, dc = DIRS[direction] # 取方向对应的行列偏移
nr, nc = r + dr, c + dc # 从前方第一格开始查
rows = len(board)
cols = len(board[0]) if rows else 0
while 0 <= nr < rows and 0 <= nc < cols:
if board[nr][nc] != 0: # 遇到箭头 → 被阻挡
return (nr, nc) # 返回阻挡者坐标
nr += dr # 否则继续向前一格
nc += dc
return None # 走出边界 → 畅通,可以飞出
两个关键细节:
- 只看前方:探测从
r + dr开始,身后的箭头不影响判定。例如最右列朝右的箭头,左边有多少箭头都能飞出; - 边界天然安全:
while的条件0 <= nr < rows and 0 <= nc < cols保证边缘朝外的箭头第一次判断就跳出循环返回 None,不会发生数组越界(对应测试 T03)。
这个函数返回 None 或阻挡者坐标,而不是简单的 True/False,好处是调用方既能判断是否被挡,也能知道是被谁挡住的,方便后续做碰撞反馈。
点击逻辑在 GameState.click_cell 中:被挡则失误 +1 并启动 20 帧晃动计时,失误达到上限则置 lost = True;畅通则把该格置 0、剩余箭头数 -1,剩余为 0 时置 won = True。
5. 飞出与碰撞动画
- 飞出动画:箭头从逻辑棋盘移除的瞬间,向
fly_arrows列表加入一个动画对象,每帧progress += 0.06,位移为progress × 格宽 × 3,透明度为255 × (1 - progress),实现"沿方向飞出且逐渐淡出"的效果; - 碰撞动画:被挡箭头不消失,记录
[行, 列, 20]晃动计时器,绘制时水平偏移取sin(t × 0.8) × 6像素,并用红色绘制,20 帧(约 0.33 秒)后自动恢复。
6. 程序化合成音效
为避免使用外部音频素材的版权问题,音效全部在程序里合成:以 44100Hz 采样率,用 math.sin(2πft) 生成正弦波,乘上指数衰减包络 exp(-t × 8) 模拟短促的乐器音,再用 struct.pack 打包成 16 位 PCM 字节交给 pygame.mixer.Sound 播放。四种音效分别是:
| 音效 | 构成 |
|---|---|
| 飞出 | A5(880Hz)短音,0.10 秒 |
| 碰撞 | 220Hz 柔和低频,0.22 秒,衰减更慢 |
| 通关 | C5→E5→G5→C6 上行旋律,末音拉长 |
| 失败 | G5→E5→D5 下行旋律 |
7. 撤销、提示与存档
- 撤销:每次点击箭头前把
(棋盘深拷贝, 失误数, 剩余箭头数)压入历史栈,撤销时弹栈还原,每关最多 3 次; - 提示:遍历整个棋盘,对每支箭头调用
get_blocker,把所有返回 None 的格子用绿色高亮,每关最多 3 次; - 存档:通关时把已解锁关卡数和各关星级写入程序同目录下的
save.json,启动时自动读取;该文件已在.gitignore中排除,不会上传 GitHub。
四、AIGC 使用过程
本次开发使用 Trae(作业允许的 AIGC 编程工具)辅助完成需求分析、编码和测试。AI 生成的代码没有直接作为最终版本,都经过了运行、测试和人工修改。
| 子任务 | AIGC 工具 | AI 实现或提供了什么 | 实际效果 | 人工修改 |
|---|---|---|---|---|
| 框架与路径检测 | Trae | 5 文件分层结构、页面状态机、get_blocker 逐格检测函数 |
逻辑正确,四方向边缘测试一次通过 | 把返回值从 bool 改为阻挡者坐标元组,方便做碰撞反馈 |
| 关卡设计 | Trae | 生成 4 个不同尺寸的关卡数组 | 初版存在无法通关的布局 | 编写暴力求解器验证,人工调整箭头方向和位置后四关全部可解 |
| 碰撞与飞出场动画 | Trae | 正弦晃动、变红、渐隐飞出代码 | 基本可用 | 调整晃动幅度、帧数和飞出速度,让反馈更明显 |
| 程序化音效 | Trae | sine 波 PCM 合成方案与 4 种音效 | 可正常播放,无外部素材 | 调小音量,调整通关旋律末音时长 |
第一次:搭框架与路径检测
我把作业要求完整发给 Trae,让它先设计代码结构再实现路径检测。它建议把纯逻辑(core.py)和绘制(render.py)分开,并给出了用方向偏移字典 + while 循环的统一检测方案,没有为四个方向复制四段代码。我运行后确认逻辑正确,但把它的返回值从 True/False 改成了 None/阻挡者坐标,因为后面做碰撞反馈时需要知道是哪支箭头挡住的。这种"逻辑层不依赖 pygame"的结构也让我后面写自动化测试时完全不用打开游戏窗口。
第二次:关卡数据与可解性验证
我让 Trae 生成 4 个难度递增的关卡,它很快给出了布局数组,但我实际试玩时发现有的关卡根本无法通关——某些箭头互相指向对方,形成"环",谁都先飞不出去。AI 能画出"看起来合理"的布局,却没有能力验证布局是否有解。
于是我让 Trae 帮我写了一个暴力求解器:每一轮找出当前所有前方无阻挡的箭头,任选一支移除,不断重复;如果棋盘还没空却找不到可移除的箭头,就说明关卡死锁。用它重新验证后,我人工修改了几处箭头的方向和位置,最终四个关卡都能输出完整的清除顺序。
第三次:碰撞晃动与飞出动画
我要求 AI 实现"点错时箭头晃动变红、点对时飞出消失"的反馈。它给出了用正弦函数控制水平偏移的晃动方案和透明度递减的飞出方案。实际运行后我觉得晃动太轻微、一闪而过看不清,于是把晃动幅度调到 6 像素、持续 20 帧,飞出速度定为每帧推进 6%,视觉反馈就清楚多了。
五、测试结果
采用自动化测试,直接运行:
python tests.py
该脚本不启动图形界面,直接对 core.py 的纯逻辑做断言,共 24 项检查,覆盖作业要求的 T01~T06:
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 返回 fly,剩余数 -1,该格变 0 |
✅ 通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误次数减 1 | 返回 blocked,失误 +1,箭头保留 |
✅ 通过 |
| T03 | 点击位于边缘且朝向棋盘外的箭头 | 箭头正常消失,不发生越界错误 | 四方向边缘用例全部正常飞出 | ✅ 通过 |
| T04 | 消除本关全部箭头 | 显示通关并进入下一关 | 求解序列点完后 won=True,剩余 0 |
✅ 通过 |
| T05 | 失误次数耗尽 | 显示失败并允许重新开始 | 失误达到上限后 lost=True |
✅ 通过 |
| T06 | 游戏进行中重新开始 | 箭头布局和失误次数恢复 | 棋盘、失误数、箭头数全部复原 | ✅ 通过 |
终端输出:
此外还做了手工测试:4 个关卡逐一实机试玩,确认通关、失败、下一关解锁、撤销、提示、重新开始按钮均正常。
六、Git 提交记录
项目按开发阶段分 3 次提交到 GitHub,没有一次性上传整个项目:
| 次序 | Commit 内容 |
|---|---|
| 第 1 次 | 核心逻辑:方向字典、get_blocker 路径检测、4 个关卡数据 |
| 第 2 次 | 界面动画音效:5 页面状态机、箭头绘制、飞出/晃动动画、程序化合成音效 |
| 第 3 次 | 文档测试配置:README、T01-T06 自动化测试、依赖清单、.gitignore |
仓库地址:https://github.com/codeyuan1/arrow-game
七、PSP 表格
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1.0 | 0.8 | -0.2 |
| Python 与 Pygame 学习 | 1.5 | 1.0 | -0.5 |
| 游戏界面实现 | 2.0 | 2.5 | +0.5 |
| 路径与碰撞逻辑实现 | 1.5 | 1.5 | 0 |
| 关卡设计 | 1.0 | 2.0 | +1.0 |
| AIGC 辅助开发 | 1.5 | 2.0 | +0.5 |
| 测试与修改 | 1.0 | 1.5 | +0.5 |
| README 与博客撰写 | 1.5 | 2.0 | +0.5 |
| 合计 | 11.0 | 13.3 | +2.3 |
八、心得体会
AI 带来的帮助
最大的帮助是快速搭建项目结构和给出核心算法的可用初稿。路径检测、状态机、动画这些我自己从头写要查很多文档的部分,Trae 基本一次就能给出可运行的版本,我只需要理解后做小修改。程序化音效这个方案更是超出预期——我原本打算去网上找免费音效素材,AI 提出直接用正弦波合成,既省去了找素材的时间,又完全没有版权问题。
出现的问题
印象最深的是关卡设计。AI 生成的关卡数组"看起来"方向齐全、布局合理,但实际存在互相阻挡的死局。这让我意识到 AI 生成的内容必须经过验证才能使用:代码语法正确不代表逻辑正确,数据类内容尤其容易"看起来对、实际上错"。最后靠一个简单的暴力求解器才把问题彻底解决,这个求解器后来还直接复用到了测试里。
另外环境问题 AI 帮不上忙:Windows PowerShell 对中文路径和中文 commit message 的编码问题折腾了不少时间,最后改用 GitHub 网页端上传才解决。
个人收获
这次实践让我真正体会到"逻辑与界面分离"的好处——正因为 core.py 不依赖 pygame,我才能写 24 项自动化断言快速验证规则,而不用每次改代码都打开窗口手点。我也理解了坐标体系(行向下为正、所以"上"的行偏移是 -1)、循环边界条件、状态快照这些概念为什么重要。
最重要的收获是:AI 可以替你写代码,但理解代码、验证代码、为代码负责的人仍然是你自己。作业评分里明确提到可能针对关键代码提问,所以我把 get_blocker 的每一行都搞懂了——从哪一格开始扫、循环条件为什么这样写、返回值怎么用。能讲清楚自己提交的代码,才算真正完成了这次作业。
九、运行方法
# 1. 克隆仓库
git clone https://github.com/codeyuan1/arrow-game.git
cd arrow-game
# 2. 安装依赖
pip install pygame==2.5.2
# 3. 运行游戏
python main.py
# 4.(可选)运行自动化测试
python tests.py
开发环境:Windows + Python 3.12 + Pygame 2.5.2。










浙公网安备 33010602011771号