软件工程第二次个人作业:“一箭又一箭”

软件工程第二次个人作业:“一箭又一箭”

项目 内容
这个作业属于哪个课程 202601 软件工程
这个作业要求在哪里 2026秋软件工程个人作业(第二次)
这个作业的目标 使用 Python 和 AIGC 完成“一箭又一箭”小游戏,实践需求分析、程序设计、测试与迭代改进
学号 102401411
GitHub 仓库 bookiii98/one-more-arrow

一、项目展示

“一箭又一箭”是一款使用 Python 和 Pygame 开发的点击式解谜游戏。玩家需要观察箭头方向和相互阻挡关系,按合适的顺序将箭头移出棋盘。

1. 开始界面

开始界面提供选关入口。有存档时,可以点击“继续游戏”恢复上次的棋盘。右上角可以开关音效,游戏中也可以按 M 键静音。

image

2. 关卡选择界面

游戏共有十二个固定关卡,默认窗口下按四列三行排列。每张卡片显示关卡名称、星级或解锁状态,缩小窗口后可以上下滚动查看。

选关页最初分成了四页,但我觉得来回翻页不够方便,后来改成了现在的同页展示。

image

3. 游戏界面

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

image

4. 通关界面

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

image

5. 失败界面

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

image

二、游戏规则与主要功能

1. 游戏规则

  1. 点击箭头后,程序检查它前进方向上的路径。
  2. 前方没有其他箭头时,该箭头飞出棋盘并消失。
  3. 前方存在阻挡时,箭头变红、向前移动后返回,剩余失误次数减一。
  4. 每关初始有三次失误机会,清空棋盘即通关,失误次数耗尽则失败。
  5. 点击“重新开始”,恢复当前关卡的初始布局和失误次数。

前三关全部使用单格箭头,对应作业要求的基础玩法。 第四关起加入占用 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:

bookiii98/one-more-arrow

开发过程中保留了多次提交,部分记录如下:

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 中,功能越加越难改。拆分以后,改关卡、调界面、加存档都有明确的位置,后续修改更容易定位。

这次也暴露了我的过程记录不足:耗时没有及时登记,后期功能集中整理提交,文档工作开始得较晚。下一次我会在开发前列出任务和验收条件,每完成一项就记录时间、测试结果并提交代码,让开发过程和最终成果都更清楚。

posted @ 2026-09-21 16:45  boki233  阅读(17)  评论(0)    收藏  举报