软件工程第二次个人作业:“一箭又一箭”
软件工程第二次个人作业:“一箭又一箭”
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | 202601 软件工程 |
| 这个作业要求在哪里 | 2026秋软件工程个人作业(第二次) |
| 这个作业的目标 | 使用 Python 和 AIGC 完成“一箭又一箭”小游戏,实践需求分析、程序设计、测试与迭代改进 |
| 学号 | 102401411 |
| GitHub 仓库 | bookiii98/one-more-arrow |
一、项目展示
“一箭又一箭”是一款使用 Python 和 Pygame 开发的点击式解谜游戏。玩家需要观察箭头方向和相互阻挡关系,按合适的顺序将箭头移出棋盘。
1. 开始界面
开始界面提供选关入口。有存档时,可以点击“继续游戏”恢复上次的棋盘。右上角可以开关音效,游戏中也可以按 M 键静音。

2. 关卡选择界面
游戏共有十二个固定关卡,默认窗口下按四列三行排列。每张卡片显示关卡名称、星级或解锁状态,缩小窗口后可以上下滚动查看。
选关页最初分成了四页,但我觉得来回翻页不够方便,后来改成了现在的同页展示。

3. 游戏界面
游戏采用 7×7 棋盘,顶部显示当前关卡、剩余失误次数和剩余箭头数量,并提供返回关卡、重新开始和撤销按钮。四个方向的箭头使用不同颜色,方便辨认。

4. 通关界面
清空棋盘后,程序显示本关星级和失误次数。点击“继续下一关”可以直接进入下一关,也可以重新挑战或返回选关界面。

5. 失败界面
失误次数耗尽后进入失败界面。玩家可以重新挑战,也可以撤销上一次错误操作后继续游戏。

二、游戏规则与主要功能
1. 游戏规则
- 点击箭头后,程序检查它前进方向上的路径。
- 前方没有其他箭头时,该箭头飞出棋盘并消失。
- 前方存在阻挡时,箭头变红、向前移动后返回,剩余失误次数减一。
- 每关初始有三次失误机会,清空棋盘即通关,失误次数耗尽则失败。
- 点击“重新开始”,恢复当前关卡的初始布局和失误次数。
前三关全部使用单格箭头,对应作业要求的基础玩法。 第四关起加入占用 2~3 格的长箭头,点击其任意占用格都可以选中整支箭头。
2. 主要功能
| 类别 | 具体功能 |
|---|---|
| 基础玩法 | 四方向箭头、鼠标点击、路径判断、飞出消除、碰撞反馈 |
| 关卡流程 | 十二个固定关卡、逐关解锁、进入下一关、失败与重新挑战 |
| 界面 | 中文显示、统一按钮与状态卡尺寸、窗口缩放适配 |
| 星级评价 | 零失误三星、一次失误两星、两次失误一星,保留历史最佳成绩 |
| 撤销 | 恢复上一次有效点击前的箭头和失误次数,支持连续撤销 |
| 保存进度 | 保存当前棋盘、失误次数、解锁状态、星级和撤销记录 |
| 动画 | 鼠标悬停反馈、页面转场、箭头飞出和碰撞返回 |
| 音效 | 点击、飞出、碰撞、撤销、通关、全部通关和失败提示 |
| 音效设置 | 按钮开关、M 键静音,设置随存档保存 |
三、开发环境与运行方式
1. 开发环境
| 项目 | 使用情况 |
|---|---|
| 开发平台 | macOS |
| 编程语言 | Python |
| 图形库 | Pygame |
| 自动测试环境 | Python 3.9.6、Pygame 2.6.1 |
| AIGC 工具 | ChatGPT、Codex |
| 版本管理 | Git、GitHub |
中文字体优先使用 macOS 自带的中文字体。在其他系统上运行时,需要在 config.py 中配置可用的中文字体路径。
2. 安装与运行
下载代码后,在项目目录中执行:
python3 -m pip install pygame==2.6.1
python3 main.py
3. 操作说明
| 操作 | 作用 |
|---|---|
| 鼠标左键 | 点击箭头或界面按钮 |
| 重新开始 | 重置当前关卡 |
| 撤销一步 | 撤回本关上一次有效点击 |
| 返回关卡 | 打开选关界面 |
| 继续游戏 | 恢复保存的游戏进度 |
| 鼠标滚轮或触控板 | 在较小窗口中滚动查看关卡 |
M 键 |
切换音效开关 |
存档保存在 saves/progress.json 中,不需要手动点击保存。
四、实现思路
1. 模块划分
早期版本将关卡、逻辑、界面和动画都写在 main.py 中。功能增加后,修改按钮和状态跳转变得不方便,我便要求 AI 按职责拆分代码。
目前主要结构如下:
one-more-arrow/
├── main.py # 初始化、状态协调和事件循环
├── config.py # 窗口、颜色、字体和尺寸配置
├── levels.py # 关卡数据与名称
├── game_logic.py # 路径判断、游戏规则和撤销
├── ui.py # 界面布局、绘制和视觉反馈
├── progress.py # 存档校验、保存与恢复
├── audio.py # 音效合成与播放
├── test_game.py # 基础玩法与流程测试
├── test_basic_levels.py # 前三关单格规则与旧存档兼容测试
├── test_progress.py # 存档测试
├── test_undo.py # 撤销测试
├── test_level_selection.py # 选关测试
└── test_audio.py # 音效测试
拆分以后,调整箭头外观主要修改 ui.py,增加关卡修改 levels.py,保存进度则由 progress.py 处理,后续功能更容易定位。
2. 箭头和方向的表示
每支箭头使用字典表示:
{
"row": 2,
"col": 1,
"direction": "right",
"length": 1
}
row 和 col 表示箭尾所在格,direction 表示方向,length 表示占用格数。前三关的 length 全部为 1,扩展关卡中也会出现 2 和 3。
方向统一转换为行、列增量:
| 方向 | 行变化 dr | 列变化 dc |
|---|---|---|
| 上 | -1 | 0 |
| 下 | 1 | 0 |
| 左 | 0 | -1 |
| 右 | 0 | 1 |
这样不必为四个方向分别编写整套占格计算:
def get_arrow_cells(arrow):
dr, dc = get_direction_vector(arrow["direction"])
return [
(arrow["row"] + dr * i, arrow["col"] + dc * i)
for i in range(arrow["length"])
]
例如,箭尾位于 (2, 1)、向右且长度为 3 的箭头,占用 (2, 1)、(2, 2)、(2, 3) 三个格子。一个关卡由多支箭头组成,所有关卡保存在 LEVELS 列表中。
3. 路径与碰撞检测
路径检测分为两步:先收集其他箭头占用的格子,再从当前箭头头部前方一格开始,沿方向逐格检查。
def find_blocker(arrow, arrows):
occupied = {
cell: other
for other in arrows
if other is not arrow
for cell in get_arrow_cells(other)
}
row, col = get_arrow_head_cell(arrow)
dr, dc = get_direction_vector(arrow["direction"])
row, col, distance = row + dr, col + dc, 1
while 0 <= row < ROWS and 0 <= col < COLS:
if (row, col) in occupied:
return occupied[row, col], distance
row, col = row + dr, col + dc
distance += 1
return None, None
循环中发现占用格,就返回阻挡箭头和距离;一直走到棋盘外仍没有阻挡,则允许消除。
这段代码中,我重点理解了三个地方:
other is not arrow用于排除当前箭头自身。- 长箭头从头部前方开始检查,不能从箭尾直接扫描。
- 循环条件先限制坐标范围,边缘箭头朝外移动时可以直接结束检查。
设所有箭头总占格数为 K,路径上最多检查 L 个格子,一次检测的时间复杂度为 O(K + L)。
4. 游戏状态和页面切换
程序使用以下状态组织流程:
start 开始界面
level_select 选择关卡
playing 游戏进行中
game_over 挑战失败
level_complete 当前关卡完成
all_clear 全部关卡完成
开发时曾出现“通关后点击继续,却返回选关界面”的问题。我指出后,将通关操作调整为:
关卡完成
├── 继续下一关 → 加载下一关
├── 重新挑战 → 重置当前关卡
└── 返回关卡 → 进入选关界面
最后一关完成后,“再玩一次”从第一关开始。
5. 关卡设计
前三关分别有 9、11、13 支单格箭头,保留四个方向和阻挡关系。后九关从 14 支增加到 22 支,并加入长箭头。
新增关卡在开发阶段生成、筛选后写入数据文件,运行时使用固定布局。验证内容包括坐标是否越界、占格是否重叠,以及是否存在通关顺序。
检查可解性时,程序不断寻找没有阻挡的箭头并移除。如果最终清空棋盘,说明存在可行顺序;如果还有箭头但没有任何箭头能移动,则说明存在死局。
在自动检查之外,我也逐关完成了实际试玩,检查通关顺序和关卡切换是否正常。
6. 撤销与存档
每次有效点击前,程序保存箭头列表和剩余失误次数。撤销时恢复该记录,并清除正在进行的动画。因此,既可以恢复被消除的箭头,也可以撤回一次碰撞扣除的机会。
点击空白区域不会增加撤销记录,重新开始或进入另一关时会清空本关记录。
存档保存当前棋盘、失误次数、最佳星级、解锁状态、撤销记录和音效设置。为处理动画中途退出的情况,保存的是这次操作完成后的结果,而不是某一帧的位置。
写入时先生成临时文件,再替换正式存档,降低写入失败损坏原存档的风险。
7. 箭头绘制与音效
最初的箭头有明显锯齿,灰色硬阴影还产生了重影。修改后,程序先以四倍分辨率绘制圆头线条,再缩小到目标尺寸,同时调整线宽和箭尖比例。绘制结果会缓存,避免每帧重复生成。
音效由程序合成,使用简短音调表达点击、碰撞和通关等事件,并通过渐入渐出减少突兀感。没有可用音频设备时,游戏仍可以正常运行。
五、AIGC 使用记录
我使用 ChatGPT 讨论需求和实现方案,使用 Codex 修改项目文件、编写测试和检查结果。以下是几次比较有代表性的协作过程。
| 子任务 | 使用工具 | 我提出的要求 | AI 完成的内容 | 效果与人工调整 |
|---|---|---|---|---|
| 模块化重构 | ChatGPT、Codex | 代码过长,希望按软件工程方法整理,同时统一 UI 尺寸 | 拆分配置、关卡、逻辑和 UI,整理主循环 | 结构更清楚;我进一步指出“继续”跳转不合理,要求直接进入下一关 |
| 箭头外观 | Codex | 提供截图,指出箭头粗糙、锯齿和重影明显 | 超采样绘制、圆头线条、调整箭尖、移除硬阴影 | 我查看修改后的效果,确认采用这一版本 |
| 保存与撤销 | Codex | 分阶段增加进度保存和撤销功能 | 实现存档、继续游戏、操作记录和恢复,并补充测试 | 我检查重开和撤销操作,确认能恢复棋盘与失误次数 |
| 音效 | Codex | 增加操作和结果音效 | 七种短音效、静音开关和设置保存 | 完成自动测试后,我进行了实际试听和开关检查 |
六、测试过程与结果
1. 自动化测试
当前项目共有 22 项自动化测试,运行结果如下:
Ran 22 tests
OK
测试命令:
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy python3 -m unittest -v
自动测试使用无窗口和模拟音频模式,覆盖游戏逻辑、界面事件、存档、撤销、关卡选择及音效触发。
2. 基础功能测试
我按照作业要求检查了六项基础功能,并完成了十二关实际试玩。
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 箭头正常飞出,剩余箭头数量减少 | 通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头保留,失误次数减一 | 箭头变红、移动后返回,失误次数减一 | 通过 |
| T03 | 点击边缘且朝向棋盘外的箭头 | 正常消除,不发生越界错误 | 四个方向的边缘情况均正常 | 通过 |
| T04 | 消除本关全部箭头 | 显示通关结果,可以进入下一关 | 结果显示正常,“继续下一关”直接加载下一关 | 通过 |
| T05 | 失误次数耗尽 | 显示失败,允许重新开始 | 三次失误后显示失败,重新挑战可恢复初始状态 | 通过 |
| T06 | 游戏进行中重新开始 | 恢复初始布局与失误次数 | 布局和三次失误机会恢复,撤销记录清空 | 通过 |
3. 扩展功能测试
| 测试项目 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|
| 前三关单格规则 | 每支箭头只占一格,关卡可通关 | 三关均符合单格规则,数量为 9、11、13 支 | 通过 |
| 长箭头点击 | 点击任意占用格均可选中 | 可正确定位同一支箭头 | 通过 |
| 关卡解锁 | 未解锁关卡不能进入,通关后解锁下一关 | 解锁限制和通关后的更新正常 | 通过 |
| 单页选关 | 显示十二关,窄窗口可滚动 | 网格布局和滚动选择正常 | 通过 |
| 连续撤销 | 依次恢复此前的操作 | 箭头和失误次数正确恢复 | 通过 |
| 失败后撤销 | 恢复一次机会并继续游戏 | 能从失败状态返回游戏 | 通过 |
| 保存与继续 | 关闭重开后恢复进度 | 棋盘、星级、解锁和撤销记录正常恢复 | 通过 |
| 动画中保存 | 不重复扣次数,不丢失操作结果 | 自动测试中的恢复结果正常 | 通过 |
| 旧存档兼容 | 扩展关卡和调整前三关后仍能读取旧进度 | 星级和解锁状态保留,棋盘按新规则转换 | 通过 |
| 存档写入异常 | 写入失败不破坏原存档 | 模拟失败后原文件保持不变 | 通过 |
| 音效与静音 | 操作时播放对应音效,静音设置可保存 | 自动测试及实际试听正常 | 通过 |
| 音频设备不可用 | 游戏仍能继续运行 | 模拟设备异常时未影响游戏逻辑 | 通过 |
4. 实际运行检查
除了自动测试,我还检查了窗口缩放、鼠标悬停、动画、音效开关、重新开始和退出后继续游戏等操作,并逐关验证了通关流程。
自动测试主要用来检查规则和状态恢复,实际试玩则帮助我发现界面和交互问题。例如,箭头锯齿、按钮大小不协调,以及“继续”返回选关页,都是在查看和操作界面时发现的。
七、Git 与资源说明
项目代码已推送到 GitHub:
开发过程中保留了多次提交,部分记录如下:
| Commit | 提交内容 |
|---|---|
367b2d0 |
初始化 Pygame 窗口和棋盘 |
2ad3168 |
增加不同方向的箭头 |
31db465 |
增加鼠标点击和箭头选择 |
aeee680 |
增加三关游戏流程 |
8fd29ca |
改进界面和视觉设计 |
e3a7ab7 |
整合模块化结构、十二关、存档、撤销、音效和测试 |
本地存档、缓存文件和重构前的备份已排除在版本管理之外。
本项目没有使用原商业游戏的代码、美术、音效或关卡。界面和箭头由程序绘制,音效由程序合成,关卡在开发过程中设计、生成并验证,没有额外下载网络图片或音效素材。
八、PSP 表格
开发过程中没有完整计时,以下时间是结合任务完成情况进行的事后估算。差异按“实际耗时-预估耗时”计算。AIGC 辅助开发一项主要统计方案讨论、提示词调整和代码阅读,具体实现与测试时间计入对应阶段。
| 任务 | 预估耗时(小时) | 实际耗时(事后估算,小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 2 | 3 | +1 |
| Python 与图形库学习 | 2 | 2 | 0 |
| 游戏界面实现 | 4 | 5 | +1 |
| 路径与碰撞逻辑实现 | 3 | 4 | +1 |
| 关卡设计 | 3 | 4 | +1 |
| AIGC 辅助开发 | 2 | 3 | +1 |
| 测试与修改 | 2 | 3 | +1 |
| README 与博客撰写 | 2 | 2 | 0 |
| 合计 | 20 | 26 | +6 |
实际投入比预估多了约六小时,主要花在界面返工、关卡扩展,以及增加存档、撤销和音效后的验证上。AI 生成代码比较快,但提出准确要求、理解代码和检查结果仍然需要时间。
九、心得体会
这次作业中,我花了不少时间调整看起来很小的问题,比如箭头的线宽、按钮尺寸、选关布局和通关后的跳转。这些内容不会让程序报错,却会影响玩起来是否顺手。做到后面,我才逐渐把关注点从“功能有没有”转向“操作是否合理”。
与 AI 协作时,最有用的做法是把问题说具体。只说“界面不好看”,AI 可能会增加更多装饰;指出“标题太大”“箭头有重影”“继续按钮跳错页面”,修改目标就清楚得多。截图和实际操作反馈也比笼统描述更有效。
模块化重构是另一个明显的变化。最开始所有内容都在 main.py 中,功能越加越难改。拆分以后,改关卡、调界面、加存档都有明确的位置,后续修改更容易定位。
这次也暴露了我的过程记录不足:耗时没有及时登记,后期功能集中整理提交,文档工作开始得较晚。下一次我会在开发前列出任务和验收条件,每完成一项就记录时间、测试结果并提交代码,让开发过程和最终成果都更清楚。

浙公网安备 33010602011771号