| 这个作业属于哪个课程 | Click |
|---|---|
| 这个作业要求在哪里 | Click |
| 这个作业的目标 | 使用 Python 和 AIGC 完成“一箭又一箭”小游戏 |
| 学号 | 112400810 |
| GitHub仓库 | Click |
一、项目展示:汪汪救箭队
🐕 一款基于 Python + Pygame 的像素风休闲解谜小游戏
🐶 技术栈:Python 3.12、Pygame 2.6
[这里将放一个视频]蒽…
-
主菜单界面:玩家“钓鱼”选择模式

-
游戏进行过程:
一些小细节
gif太短太糊没音效我一直在哭吧😭
- 悬停鼠标时宝箱/柯基跳动更活跃
- 倒计时进度条三档变色(剩余时间/总时长):大于 50% 为绿色,25%~50% 为黄色,小于等于 25% 为红色。
- 通关星级先按进度条定基础档(🟩 三星、🟨 二星、🟥 一星),每累计两次失误降 1 星,最低 1 星。
下图演示为基础模式:

| 无尽模式 | 迷雾模式 |
|---|---|
![]() |
![]() |
| 随机生成,网格与密度逐关递增,可无限通关 | 仅外圈箭头可见,外圈飞走后内圈才点亮 |
-
两种失败界面:时长耗尽 or 失误超五次

二、项目介绍
(一)游戏规则
每支箭只会沿自己的朝向移动。玩家点击箭头后,程序会检查这支箭从当前位置到棋盘边界之间是否还有其他箭头:
- 如果前方没有箭头,它会飞出屏幕并从本关中消失;
- 如果前方存在箭头,它会原地晃动,剩余失误次数减 1;
- 当全部箭头都被消除时,本关通关;
- 失误次数归零、超时或所有剩余箭头互相阻挡时,本关失败;
- 游戏中随时可以重新开始。
(二)三种模式
| MODE | LEVEL | TRAIT |
|---|---|---|
| 基础模式 | 固定 3 关(3×3 → 4×4 → 5×5) | 循序渐进,适合熟悉规则 |
| 无尽模式 | 随机生成,网格 3×3 起步、最大 6×6 | 箭头数随关卡递增,每张图生成后都会先检查是否可解 |
| 迷雾模式 | 共 10 关,5×5 起步 | 只有当前最外圈箭头可见,外圈飞走后内圈才"亮起" |
(三)界面与功能
整体采用柯基主题的像素画风,窗口大小为 600×750。主要功能包括:
🎣 钓鱼模式选择和规则弹窗;
💇 箭头蓄力、加速、拖尾、碰撞晃动和离场粒子;
🌟 关卡计时、失误累积、AI 提示、重新开始和返回菜单;
🥺 失败落泪小狗;
🥳 通关星级、彩屑烟花动画
...
字体来源:https://github.com/TakWolf/fusion-pixel-font
音频来源:~☕️夏日曼哈顿咖啡店 Jazz BGM
运行方式:
pip install pygame
python3 main.py
我的开发与测试环境是 Python 3.12.7、Pygame 2.6.1。当前背景音乐通过 macOS 的 afplay 循环播放,因此如果要在 Windows 或 Linux 上运行,还需要把这一部分替换成 Pygame Mixer 或其他跨平台方案。
三、实现思路
(一)箭头、方向和关卡如何表示
固定关卡使用二维列表表示,每个元素对应棋盘上的一个格子:
LEVELS = [
[
['U', 'U', 'R'],
['L', 'U', 'R'],
['L', 'D', 'D']
],
# 其余关卡省略
]
其中 U、D、L、R 分别表示上、下、左、右,. 表示空格。读取关卡时,程序把每一个方向字符转换成一个 Arrow 对象。
一支箭头最重要的信息有:
row、col:在网格中的行列位置,用于路径判断;direction:移动方向;x、y:屏幕像素坐标,用于绘制和动画;state:当前状态,包括idle、shaking、windup、flying和dead;dirx、diry、fly_speed:飞行动画的方向向量和速度。
网格坐标负责逻辑,像素坐标负责画面。把这两套坐标分开后,判断前方有没有箭头时不需要和动画位置搅在一起,棋盘缩放时也只需重新计算像素坐标。
游戏整体则由状态机控制:
MENU → FISHING_RESULT → PLAYING
↙ ↓ ↘
LEVEL_CLEAR GAME_OVER VICTORY
主循环根据 state 分发点击事件并绘制对应页面,从而避免开始页、游戏页和结算页的逻辑互相干扰。
(二)重点:路径检测
一开始我差点把问题想复杂,准备逐格扫描棋盘。后来发现规则只关心一件事:同一行或同一列上,箭头朝向的前方是否还存在未消除的箭头。
code
def is_path_blocked(self, arrow):
for other in self.arrows:
if other == arrow or other.state in ['flying', 'dead']:
continue
if arrow.direction == 'U' and other.col == arrow.col and other.row < arrow.row:
return True
if arrow.direction == 'D' and other.col == arrow.col and other.row > arrow.row:
return True
if arrow.direction == 'L' and other.row == arrow.row and other.col < arrow.col:
return True
if arrow.direction == 'R' and other.row == arrow.row and other.col > arrow.col:
return True
return False
以向上的箭头为例,只要另一支箭满足“列相同、行号更小”,路径就被阻挡;其余三个方向同理。已经飞出或死亡的箭头不再参与阻挡。
(三)箭头如何飞出去
路径畅通后,程序把箭头切换为 windup 或 flying 状态,并根据方向设置单位向量。每一帧逐步提高速度并更新像素坐标,越过窗口边缘后才标记为 dead:
code
self.fly_speed = min(30.0, self.fly_speed * 1.16 + 0.5)
self.x += self.dirx * self.fly_speed
self.y += self.diry * self.fly_speed
if self.x < -28 or self.x > WIDTH + 28 \
or self.y < -28 or self.y > HEIGHT + 28:
self.state = 'dead'
没有直接在点击后删除对象,游戏反馈会自然很多。
(四)随机关卡如何保证可解
我的处理方法是:先随机生成网格,再复制一份箭头集合进行模拟消除。后面想想直接逆向构造,倒过来放置箭头就行了🙃
迷雾模式还多了一层限制:玩家只能看见动态最外圈。因此除了普通可解检查外,我又增加了 _fog_shell_solvable(),否则可能出现“能走的箭全藏在迷雾里”的阴间关卡。
四、AIGC 使用过程
🌫️实际过程有点像结对编程:我负责提出需求、运行和判断,AI 负责给方案、补代码和陪我定位问题。下面提问是根据开发记录整理的核心内容:
0x01:从规则描述到最小可玩版本
我的需求: 用 Pygame 做一个箭头消除游戏。箭头只能沿自己的方向飞出;前方有箭头时不能消除,并扣一次失误;全部消除后进入下一关。
AI 先给出了 Arrow 和 Game 两个类,以及事件循环、点击检测、关卡切换的基本结构。第一版已经可以运行,但路径判断的描述还比较口语化。我继续要求把“前方”落到行列坐标上,最后形成了现在的四组判断条件。
这一轮 AI 最大的帮助是快速搭起最小闭环:显示棋盘 → 点击箭头 → 判断路径 → 更新状态 → 判断胜负。
结果: 形成第一个可玩的 3×3 版本,也确定了后面所有模式共用的核心规则。
0x02:像素风界面不是“换个背景色”
我的需求: 把纯色棋盘改成轻松、明亮的像素风场景,加入柯基、木框面板、草地和河流,但不能影响棋盘操作。
AI 帮我把绘制拆成背景、HUD、棋盘、按钮、角色等函数,并给出几何图形绘制和素材缩放的实现。实际跑起来后,新的问题也很快出现:棋盘会和两侧按钮重叠,6×6 关卡放不下;文字在不同字号下不居中;部分素材平滑缩放后与像素风不统一。
我根据截图继续反馈具体位置,加入动态 cell_size、棋盘安全区、像素字体回退、最近邻缩放和统一配色。
结果: 从“能玩”变成“看起来像一个完整小游戏”。其实AIGC生成图像效果一般,对于特殊风格的需求满足性不大。审美可能目前是完全无法替代人类的吧
0x03:随机不等于无尽,能生成还得能通关
我的需求: 增加无尽模式和迷雾模式。关卡可以随机,但必须保证有解;迷雾模式只能看到当前外圈,但不能把正确路线藏死。
AI 最初给出的方向是随机填充后做普通可解性检查,于是有了 _grid_solvable()。后来实测迷雾模式时发现,普通可解并不代表“在当前可见信息下可解”:安全箭头可能位于内部,外圈全被阻挡。
我把这个反例继续交给 AI,最终新增 _fog_shell_solvable():每轮先计算剩余箭头的 top / bottom / left / right,只允许从动态外圈选择安全箭头,再模拟到集合为空。生成条件也从“普通可解”升级为“普通可解并且外圈策略可解”。
结果: 无尽模式不会直接生成死局,迷雾模式也不会出现只能靠透视才能通关的地图。
0x04:让 AI 帮我测,但不让它宣判胜利
我的需求: 针对正常消除、错误点击、边界、通关、失败和重新开始设计测试,并输出可核对的实际状态。
AI 帮我整理了测试驱动脚本,我在无窗口模式下调用真实的 Game、Arrow、handle_click()、update() 和 load_level()。一开始若只检查“点击后进入 flying”并不够,因为箭头还有 windup 状态;于是测试改为持续推进动画,直到箭头真正进入 dead。T04 也不是直接把状态变量改成通关,而是按规则逐支寻找可消除箭头,实际删完第一关的 9 支箭后再检查通关条件。
结果: 六个必测场景全部通过,详细记录见下一节。AI 可以很快写测试框架,但“测到哪个状态才算真的完成”仍然需要自己定义。
五、测试结果
功能逐渐多起来之后,只靠自己从头玩到尾已经不太可靠,我把核心玩法拆成了 16 个测试场景。自动化部分使用 SDL 无窗口驱动加载真实游戏对象,调用实际的点击、动画更新、提示和关卡重载逻辑;GUI 与音频部分采用手工测试。
click(T01–T16)
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 基础模式依次进入 3×3、4×4、5×5 并通关 | 按顺序进关,每次清空后进入单关结算,第三关结算后进入总通关 | 三关尺寸与顺序正确,清空均进入 LEVEL_CLEAR,第三关继续后进入 VICTORY |
✅ |
| T02 | 点击最外圈且前方无阻挡的箭头 | 蓄力后沿朝向加速飞出并消失 | 状态由 idle→windup→flying→dead,飞行过程带拖尾 |
✅ |
| T03 | 点击前方有阻挡的箭头 | 不消失,原地晃动,记 1 次失误 | 进入 shaking,播放失误音效,失误次数 5→4 |
✅ |
| T04 | 点击边缘且朝向棋盘外的箭头 | 正常飞出,不发生越界错误 | (0,0) 的向上箭头正常飞出至 dead,无异常 |
✅ |
| T05 | 失误次数耗尽 | 进入失败界面并允许重开 | 连续 5 次失误后 mistakes=0,进入 GAME_OVER(reason=mistakes) |
✅ |
| T06 | 倒计时归零 | 超时失败 | 时间走完后进入 GAME_OVER(reason=timeout) |
✅ |
| T07 | 游戏进行中重新开始 | 布局与失误次数恢复 | 将失误次数改为 2 后调用 load_level(),布局还原、失误次数恢复为 5 |
✅ |
| T08 | 倒计时进度条三档变色 | 剩余时间 ≥20 秒为绿、10~20 秒为黄、<10 秒为红 | 随剩余秒数减少依次显示绿→黄→红 | ✅ |
| T09 | 无尽模式难度递增 | 网格逐步增大,箭头生成概率提高,可持续生成新关卡 | 第 1~2 关为 3×3、第 3~4 关为 4×4、第 5~6 关为 5×5、第 7 关起为 6×6;第 8 关起生成概率达到上限 0.90 | ✅ |
| T10 | 随机关卡可解性 | 抽样生成的每张地图都存在完整解法 | 所有样本均能由模拟消除算法清空,未出现开局死锁 | ✅ |
| T11 | 迷雾模式可见性 | 仅动态最外圈可见,外圈飞走后内圈点亮 | 固定测试棋盘中外圈亮起、内部遮挡,消除后下一层正常点亮 | ✅ |
| T12 | 迷雾模式终点 | 完成 10 关后进入总通关 | 第 10 关结算并继续后进入 VICTORY |
✅ |
| T13 | 死锁检测 | 存活箭头全部受阻且没有飞行箭头时判负 | 正确识别并进入 GAME_OVER(reason=deadlock) |
✅ |
| T14 | AI 提示宝箱 | 高亮一支当前可安全消除的可见箭头 | 金环闪烁高亮正确目标,迷雾模式中只提示已点亮箭头 | ✅ |
| T15 | 星级评定 | 按耗时比例确定基础星级,每 2 次失误再降 1 星 | <50% 为 3 星、50%≤耗时占比<75% 为 2 星、≥75% 为 1 星;3 星基础档出现 2 次失误后降为 2 星 |
✅ |
| T16 | 自定义光标、钓鱼菜单、BGM 与音效 | 柯基光标跟随鼠标、钓鱼选模式、BGM 循环且交互音效正常 | 光标、钓竿动画与模式选择正常,Jazz BGM 循环,甩竿、失误、提示和通关音效均正常(手工确认) | ✅ |
T01 PASS levels=[3x3,4x4,5x5], each clear -> LEVEL_CLEAR, final continue -> VICTORY
T02 PASS outer arrow: idle->windup->flying->dead, trailing ok, remain 9->8
T03 PASS state=shaking, sfx=error, mistakes 5->4
T04 PASS edge=(0,0), direction=U, state=dead, no exception
T05 PASS mistakes 4->3->2->1->0, GAME_OVER(reason=mistakes)
T06 PASS timeout -> GAME_OVER(reason=timeout)
T07 PASS layout_restored=True, mistakes=5
T08 PASS bar: >=20s green, 10~20s yellow, <10s red
T09 PASS endless: L1=3x3, L3=4x4, L5=5x5, L7=6x6, L8 density cap=0.90
T10 PASS all sampled boards solvable, no dead opening
T11 PASS fog: outer shell lit, inner hidden, next shell revealed after clearing
T12 PASS fog level 10 cleared, continue -> VICTORY
T13 PASS all active arrows blocked & no flying -> GAME_OVER(reason=deadlock)
T14 PASS hint selects a safe visible idle arrow; fog never hints hidden arrows
T15 PASS stars: <50%=3, 50%<=ratio<75%=2, >=75%=1; 3-star base + 2 mistakes -> 2 stars
T16 PASS cursor / fishing / BGM + SFX (GUI and audio manually checked)
----------------------------------------------------------------
15 automated checks passed; 1 manual interaction check passed; 0 failed
此外还执行了语法检查:
$ python3 -m py_compile main.py && echo "syntax OK"
syntax OK
六、PSP 表格
和一开始预期有所不同,实际耗时的超出部分主要集中在游戏界面实现上,美工还是太难了。囧
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1.0 | 1.0 | 0 |
| Python 与图形库学习 | 1.5 | 1.5 | 0 |
| 游戏界面实现(像素 UI、菜单、动画) | 3.0 | 5.5 | +2.5 |
| 路径与碰撞逻辑实现 | 2.5 | 2.0 | −0.5 |
| 关卡设计(基础 / 无尽 / 迷雾) | 2.0 | 2.5 | +0.5 |
| AIGC 辅助开发 | 3.0 | 3.5 | +0.5 |
| 测试与修改 | 2.0 | 2.5 | +0.5 |
| README 与博客撰写 | 1.8 | 3.4 | +1.6 |
| 合计 | 16.8 | 21.9 | +5.1 |
七、心得体会
🤖 AIGC 最明显的帮助是把我的想法更快地变成可以运行的东西。如果全部从空白开始查文档,时间会长很多。AI 也很适合处理“我已经知道哪里不对,但懒得重复写样板代码”的情况。
🌱 回头看,最初版本只有一个灰白棋盘和几支箭,现在它至少已经像一款有头有尾的小游戏了。至于代码为什么从三百多行长到一千七百多行——只能说每一行都有它出现时看似充分的理由。下次一定先拆文件。嗯,下次一定。



浙公网安备 33010602011771号