这个作业属于哪个课程 Click
这个作业要求在哪里 Click
这个作业的目标 使用 Python 和 AIGC 完成“一箭又一箭”小游戏
学号 112400810
GitHub仓库 Click

一、项目展示:汪汪救箭队

🐕 一款基于 Python + Pygame 的像素风休闲解谜小游戏
🐶 技术栈:Python 3.12、Pygame 2.6

[这里将放一个视频]蒽…

  • 主菜单界面:玩家“钓鱼”选择模式

GIF_20260920185937493

  • 游戏进行过程:

一些小细节
gif太短太糊没音效我一直在哭吧😭

  1. 悬停鼠标时宝箱/柯基跳动更活跃
  2. 倒计时进度条三档变色(剩余时间/总时长):大于 50% 为绿色,25%~50% 为黄色,小于等于 25% 为红色。
  3. 通关星级先按进度条定基础档(🟩 三星、🟨 二星、🟥 一星),每累计两次失误降 1 星,最低 1 星。
    下图演示为基础模式:
    GIF_20260920191327254
无尽模式 迷雾模式
无尽模式 迷雾模式
随机生成,网格与密度逐关递增,可无限通关 仅外圈箭头可见,外圈飞走后内圈才点亮

  • 两种失败界面:时长耗尽 or 失误超五次

GIF_20260920191902902

二、项目介绍

(一)游戏规则

每支箭只会沿自己的朝向移动。玩家点击箭头后,程序会检查这支箭从当前位置到棋盘边界之间是否还有其他箭头:

  • 如果前方没有箭头,它会飞出屏幕并从本关中消失;
  • 如果前方存在箭头,它会原地晃动,剩余失误次数减 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']
    ],
    # 其余关卡省略
]

其中 UDLR 分别表示上、下、左、右,. 表示空格。读取关卡时,程序把每一个方向字符转换成一个 Arrow 对象。

一支箭头最重要的信息有:

  • rowcol:在网格中的行列位置,用于路径判断;
  • direction:移动方向;
  • xy:屏幕像素坐标,用于绘制和动画;
  • state:当前状态,包括 idleshakingwindupflyingdead
  • dirxdiryfly_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

以向上的箭头为例,只要另一支箭满足“列相同、行号更小”,路径就被阻挡;其余三个方向同理。已经飞出或死亡的箭头不再参与阻挡。

(三)箭头如何飞出去

路径畅通后,程序把箭头切换为 windupflying 状态,并根据方向设置单位向量。每一帧逐步提高速度并更新像素坐标,越过窗口边缘后才标记为 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 先给出了 ArrowGame 两个类,以及事件循环、点击检测、关卡切换的基本结构。第一版已经可以运行,但路径判断的描述还比较口语化。我继续要求把“前方”落到行列坐标上,最后形成了现在的四组判断条件。

这一轮 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 帮我整理了测试驱动脚本,我在无窗口模式下调用真实的 GameArrowhandle_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 点击最外圈且前方无阻挡的箭头 蓄力后沿朝向加速飞出并消失 状态由 idlewindupflyingdead,飞行过程带拖尾
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 也很适合处理“我已经知道哪里不对,但懒得重复写样板代码”的情况。

🌱 回头看,最初版本只有一个灰白棋盘和几支箭,现在它至少已经像一款有头有尾的小游戏了。至于代码为什么从三百多行长到一千七百多行——只能说每一行都有它出现时看似充分的理由。下次一定先拆文件。嗯,下次一定。
1040g3k031uog85r12a005pekca85rvg3t02qr40nd_dft_wlteh_jpg_3

posted on 2026-09-21 00:21  钢筋混你土  阅读(8)  评论(0)    收藏  举报