2026 秋软件工程个人作业(第二次):一箭又一箭
2026 秋软件工程个人作业(第二次):一箭又一箭
一、作业基本信息
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | 2026 秋软件工程 |
| 这个作业要求在哪里 | 个人作业(第二次) |
| 这个作业的目标 | 使用 Python 和 AIGC 完成“一箭又一箭”小游戏 |
| GitHub 仓库 | https://github.com/TCHHPk/FZU-SoftwareEngineering-2026 |
| 项目目录 | 第02次作业 |
二、项目展示
本次作业使用 Python + Pygame 实现一个基于网格的“一箭又一箭”小游戏。
游戏的主要目标是根据箭头方向和棋盘上的阻挡关系,按照正确顺序将所有箭头移出棋盘。
2.1 开始界面
此处插入开始界面截图

开始界面包含游戏标题和 START 按钮,点击后进入第一关。
2.2 游戏界面
此处插入游戏过程截图

游戏界面主要显示:
- 当前关卡
- 当前剩余箭头数量
- 剩余错误机会
- 操作次数
- 当前用时
- 游戏棋盘
玩家可以使用鼠标直接点击箭头进行操作。
2.3 Hint 提示
游戏中可以按:
H
获取提示。
程序会自动寻找当前局面中可以安全离开棋盘的箭头,并将该箭头显示为绿色。
此处插入 Hint 截图
2.4 失败界面
如果玩家连续点击被阻挡的箭头,会减少剩余机会。
每一关初始拥有:
3 次机会
机会耗尽后进入:
GAME OVER
玩家可以选择重新挑战当前关。
此处插入 GAME OVER 截图
2.5 通关界面
清空当前关所有箭头后进入:
LEVEL COMPLETE!
点击 NEXT LEVEL 即可进入下一关。
完成全部关卡后显示:
YOU WIN!
此处插入通关截图
三、项目介绍
3.1 游戏规则
棋盘上的每个箭头具有以下四种方向之一:
↑
↓
←
→
玩家点击某个箭头后,程序沿箭头所指方向扫描。
情况一:前方没有其他箭头
如果从当前箭头到棋盘边界之间没有其他箭头:
箭头 → 飞出棋盘 → 消失
玩家可以继续处理剩余箭头。
情况二:前方存在其他箭头
如果当前箭头前进方向上存在其他箭头:
箭头 → 被阻挡
本次操作失败,同时减少一次剩余机会。
因此,游戏并不是简单地将所有箭头逐个点击,而是需要找到正确的消除顺序。
四、开发环境
本项目开发环境如下:
| 工具 | 说明 |
|---|---|
| 操作系统 | macOS |
| Python | Python 3.12 |
| 图形库 | Pygame 2.6.1 |
| 编辑方式 | Terminal + 文本编辑器 |
| AIGC | ChatGPT |
| 版本管理 | Git + GitHub |
安装 Pygame:
python -m pip install pygame
运行游戏:
python code/main.py
五、项目结构
项目目录如下:
第02次作业
├── blog.md
├── code
│ └── main.py
├── outputs
└── screenshots
其中:
code/main.py:游戏主程序screenshots/:博客使用的截图outputs/:程序运行过程中产生的其他输出blog.md:本次作业博客原稿
六、核心功能实现
6.1 游戏状态机
为了避免所有游戏逻辑全部混在同一个界面中,我使用状态变量管理游戏流程。
主要包含以下状态:
STATE_MENU = "menu"
STATE_PLAYING = "playing"
STATE_LEVEL_COMPLETE = "level_complete"
STATE_GAME_COMPLETE = "game_complete"
STATE_GAME_OVER = "game_over"
状态之间的关系大致如下:
MENU
↓
PLAYING
├── 本关完成 → LEVEL_COMPLETE
│ ↓
│ 下一关
│ ↓
│ PLAYING
│
├── 失误耗尽 → GAME_OVER
│ ↓
│ RETRY
│ ↓
│ PLAYING
│
└── 最后一关完成 → GAME_COMPLETE
使用状态机后,开始界面、游戏界面、失败界面和通关界面之间的逻辑更加清晰。
6.2 箭头数据结构
每一个箭头使用字典保存:
{
"row": 2,
"col": 1,
"direction": "right"
}
其中:
row表示箭头所在行col表示箭头所在列direction表示箭头方向
例如:
{"row": 2, "col": 1, "direction": "right"}
表示位于第 2 行、第 1 列并向右移动的箭头。
6.3 路径检测
整个游戏最重要的逻辑是:
判断一个箭头沿当前方向能否离开棋盘。
核心思路是从箭头当前位置开始,不断沿方向移动一个格子。
例如向右:
col += 1
向上:
row -= 1
如果扫描过程中先离开棋盘:
return True
说明路径畅通。
如果途中遇到其他箭头:
return False
说明箭头被阻挡。
对应的核心逻辑:
def path_is_clear(arrow):
row = arrow["row"]
col = arrow["col"]
direction = arrow["direction"]
while True:
row, col = get_next_position(
row,
col,
direction,
)
if not is_inside(row, col):
return True
if is_occupied(row, col):
return False
七、关卡可解性检测
这一部分是本次开发过程中比较重要的一次修改。
最开始设计关卡时,我主要通过人工观察箭头之间的关系判断关卡是否可解。
但是实际运行后发现,有些看起来合理的关卡实际上会形成死锁。
例如:
→ ←
左边箭头等待右边箭头消失,而右边箭头又等待左边箭头消失,因此两者都无法移动。
更复杂的情况还可能出现四个箭头互相阻挡:
→ ↓
↑ ←
形成完整的循环依赖。
因此我后来增加了一个自动关卡可解性检测器。
7.1 DFS 搜索
程序将当前所有剩余箭头看作一个状态。
对于每一个状态:
- 找到当前可以离开棋盘的箭头;
- 假设将这个箭头删除;
- 得到新的棋盘状态;
- 对新状态继续进行搜索;
- 如果最终可以删除所有箭头,则说明本关有解。
核心思想可以表示为:
当前状态
↓
寻找所有可移动箭头
↓
尝试删除其中一个
↓
得到新状态
↓
继续 DFS
↓
所有箭头被删除
↓
SOLVABLE
如果遍历所有可能操作之后仍然无法清空棋盘,则:
UNSOLVABLE
7.2 实际发现的问题
在第一次加入检测器后,运行:
python code/main.py
程序输出:
Level 1: SOLVABLE
Level 2: UNSOLVABLE
Level 3: SOLVABLE
这说明第二关存在问题。
检查后发现第二关四个箭头形成了循环阻挡:
(1,1) → 被 (1,6) 阻挡
(1,6) ↓ 被 (4,6) 阻挡
(4,6) ← 被 (4,1) 阻挡
(4,1) ↑ 被 (1,1) 阻挡
没有任何一个箭头能够成为第一步。
后来修改其中一支箭头的方向:
{"row": 1, "col": 6, "direction": "up"}
再次运行检测器:
Level 1: SOLVABLE
Level 2: SOLVABLE
Level 3: SOLVABLE
三个关卡全部通过验证。
这个检测器也避免了后续仅通过人工试玩来判断关卡是否可通关的问题。
八、Hint 提示功能
由于程序本身已经能够判断某个箭头是否能够安全离场,因此可以直接利用这一逻辑实现 Hint。
按下:
H
后,程序遍历当前剩余箭头:
def find_hint():
for i, arrow in enumerate(arrows):
if path_is_clear(arrow):
return i
return None
找到合法箭头后,将对应箭头显示为绿色。
因此提示功能并不是预先写死某个答案,而是根据当前局面实时计算得到。
九、错误次数机制
为了避免玩家可以无限点击测试,我加入了错误次数限制。
每关开始:
MAX_MISTAKES = 3
玩家点击被阻挡的箭头:
mistakes_left -= 1
当:
mistakes_left <= 0
游戏进入:
GAME OVER
玩家需要重新开始当前关卡。
十、计时与操作次数
为了记录玩家完成关卡的过程,我加入:
Moves
Time
两个统计指标。
玩家每点击一次箭头:
move_count += 1
关卡开始时记录:
level_start_time = pygame.time.get_ticks()
随后根据当前时间计算:
level_elapsed_time = (
current_time - level_start_time
) / 1000
最终可以显示:
Moves: 5
Time: 12.4s
使游戏具有更加完整的结果反馈。
十一、AIGC 使用过程
本次作业开发过程中使用 ChatGPT 辅助进行需求分析、代码生成、Debug 和功能迭代。
整个过程并不是直接要求 AI 一次生成最终程序,而是采用:
提出一个小需求
↓
生成代码
↓
实际运行
↓
发现问题
↓
反馈问题
↓
继续修改
的迭代开发方式。
11.1 第一次:建立最小 Pygame 程序
最开始先要求 AI 实现最简单的 Pygame 游戏窗口。
第一阶段只完成:
- 创建窗口;
- 游戏主循环;
- 关闭程序。
确认运行成功后,再加入棋盘。
这种方式可以避免一次生成大量代码之后难以定位错误。
11.2 第二次:加入棋盘与箭头
随后逐步加入:
棋盘
↓
单个箭头
↓
方向
↓
箭头移动
↓
多个箭头
↓
碰撞和阻挡
最初使用键盘选择箭头,后面改为更加符合游戏操作习惯的鼠标点击。
11.3 第三次:发现 AI 设计的关卡无解
开发过程中 AI 给出了一组关卡。
实际测试第一关时,我发现其中:
→ ←
两个箭头互相阻挡,因此整个关卡无法完成。
修改之后,又利用自动检测程序发现:
Level 2: UNSOLVABLE
说明仅依赖 AI 或肉眼设计关卡并不可靠。
因此,我要求继续加入自动可解性检测器。
最终利用 DFS 自动验证三个关卡:
Level 1: SOLVABLE
Level 2: SOLVABLE
Level 3: SOLVABLE
这也是本次使用 AIGC 过程中比较明显的一次:
AI 生成 → 人工发现问题 → 设计验证机制 → 再修正 AI 结果。
11.4 第四次:完善游戏流程
完成核心玩法后继续要求 AI 逐步加入:
- 开始界面
- LEVEL COMPLETE
- NEXT LEVEL
- GAME OVER
- RETRY
- PLAY AGAIN
- 剩余箭头数量
- 剩余机会
- Hint
- 操作次数
- 计时
最终将最初只有一个窗口和一个箭头的程序逐步完善成一个完整小游戏。
十二、测试
本项目主要进行了以下测试。
| 测试内容 | 操作 | 预期结果 | 结果 |
|---|---|---|---|
| 启动程序 | python code/main.py |
正常打开游戏 | 通过 |
| 开始游戏 | 点击 START | 进入第一关 | 通过 |
| 合法箭头 | 点击无遮挡箭头 | 箭头飞出棋盘 | 通过 |
| 非法箭头 | 点击被阻挡箭头 | 显示 Blocked | 通过 |
| 错误次数 | 连续错误三次 | GAME OVER | 通过 |
| 重试 | 点击 RETRY | 当前关重新开始 | 通过 |
| Hint | 按 H | 合法箭头变绿 | 通过 |
| 重开关卡 | 按 R | 当前关恢复初始状态 | 通过 |
| 清空棋盘 | 删除所有箭头 | LEVEL COMPLETE | 通过 |
| 下一关 | 点击 NEXT LEVEL | 进入下一关 | 通过 |
| 最后一关完成 | 完成 Level 3 | YOU WIN | 通过 |
| 可解性检测 | 启动游戏 | 三关均显示 SOLVABLE | 通过 |
十三、PSP 表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 20 | 20 |
| Estimate | 估计任务时间 | 10 | 10 |
| Development | 开发 | 180 | 240 |
| Analysis | 需求分析 | 20 | 30 |
| Design Spec | 游戏规则与界面设计 | 30 | 40 |
| Coding Standard | 整理代码结构 | 20 | 20 |
| Design Review | 检查游戏流程 | 20 | 30 |
| Coding | 编写程序 | 120 | 160 |
| Code Review | 检查代码 | 20 | 30 |
| Test | 游戏测试与 Debug | 50 | 80 |
| Reporting | 博客及项目整理 | 60 | 60 |
| 合计 | 550 | 720 |
十四、心得体会
这次作业与单纯完成一个 Python 程序相比,让我更明显地体会到了“软件开发”和“让代码运行起来”之间的区别。
一开始,我采用的是非常直接的方式:先让程序出现一个 Pygame 窗口,然后加入棋盘,再加入一个箭头。确认每一步能够运行以后才继续增加下一项功能。
这种逐步开发的方法虽然比一次完成所有功能慢,但出现问题时更容易判断错误来自哪里。
本次开发过程中印象最深的问题是关卡可解性。
最初无论是人工观察还是通过 AIGC 生成关卡,我都认为关卡应该能够正常完成。但在实际运行之后,却出现了箭头互相阻挡而导致完全没有第一步可以执行的情况。
在第一次发现死锁后,仅仅修改一个关卡并不能真正解决问题,因为后面设计新的关卡仍然可能出现同样的错误。
因此最后采用 DFS 搜索的方式自动验证关卡是否存在一条完整的通关路径。
从:
Level 2: UNSOLVABLE
到修正后的:
Level 1: SOLVABLE
Level 2: SOLVABLE
Level 3: SOLVABLE
这个过程让我意识到:
与其依靠“看起来应该没问题”,不如让程序自己验证条件是否真的成立。
在 AIGC 使用方面,我也发现 AI 更适合作为一个快速实现和讨论方案的工具,而不是直接把第一次生成的结果当作最终答案。
本次开发中 AI 确实大幅降低了编写 Pygame 界面和基础逻辑的时间,但 AI 也实际生成过无法通关的关卡。如果没有自己运行、验证和继续追问,这些问题很可能会直接保留到最终提交中。
因此我认为比较有效的 AIGC 使用方式是:
需求拆分
→ AI 生成
→ 自己运行
→ 找出问题
→ 明确反馈
→ 修改
→ 再测试
而不是:
描述整个作业
→ 复制 AI 输出
→ 提交
通过这次作业,我对 Pygame 的事件循环、游戏状态管理、碰撞和路径判断有了更加直观的理解,同时也第一次将 DFS 搜索用于验证一个小游戏关卡是否存在合法解。
这也是本次作业中,我认为比单纯完成游戏本身更有价值的部分。





浙公网安备 33010602011771号