实验报告:用 Python + AIGC 开发「一箭又一箭」风格小游戏
作业基本信息
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/fzu/202601SofwareEngineering |
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/fzu/202601SofwareEngineering/homework/16717 |
| 这个作业的目标 | 使用 Python 和 AIGC 完成「一箭又一箭」小游戏 |
| 学号 | 102401404 |
| GitHub 仓库 | https://github.com/Meng-tao123/sodaworld-arrow_puzzle- |
说明:本次作业的核心玩法完全按照「一箭又一箭」基础版实现(单格方向、同行同列路径检测、失误机制、关卡流程)。
美术上把箭头造型换成了汽水瓶,并把「箭头方向」表达为瓶口朝向,规则与判定与箭头版完全一致;
这一改动只影响BottleArt的绘制部分,路径检测、状态机、关卡数据都不受影响。
我认为箭头看起来有些人机,然后参考了以前玩过的游戏——苏打世界,把箭头改成汽水瓶,感觉更有趣一些 OvO
一、项目展示
1. 运行演示(GIF)

动图内容依次为:标题界面 → 点击一个被阻挡的瓶子(瓶身晃动闪红、红色虚线标出阻挡者、失误 5→4)→ 按正确顺序点击瓶子飞出(尾部拖出苏打气泡)→ 清空后进入「关卡通过」→ 点击「下一关」进入第 2 关。
2. 开始界面

3. 游戏界面

顶部依次是「关卡 1/4」「剩余汽水瓶 6」「剩余失误 5/5」,中间是居中排布的棋盘网格,底部是「重新开始」按钮与操作提示。
4. 飞出与碰撞反馈


左图:瓶子沿瓶口方向飞出棋盘,尾部持续生成白色半透明气泡拖尾;
右图:点到被阻挡的瓶子时瓶身晃动并变红,同时用红色虚线 + 叉号指出是哪一瓶挡住了它。
5. 通关 / 失败界面
通关界面显示「关卡通过」与「下一关」按钮;失误耗尽时显示「挑战失败」与「重新开始」按钮;打完最后一关显示「全部通关」与「再玩一次」。

二、项目介绍
1. 游戏规则
- 棋盘是网格,每格最多放一个汽水瓶,瓶口只有上、下、左、右四个朝向,瓶口朝向 = 飞行方向。
- 点击某个瓶子时,程序沿瓶口方向在同一行或同一列一直检查到棋盘边界:
- 一路没有任何瓶子 → 瓶子沿该方向飞出棋盘并消失;
- 路上还有别的瓶子 → 瓶子不能消失,给出碰撞反馈,并消耗 1 次失误机会。
- 清空本关全部瓶子 → 通关,点击「下一关」继续。
- 失误次数耗尽 → 本关失败,可以重新开始。
2. 界面设计
| 界面 | 内容 |
|---|---|
| 开始界面 | 游戏标题、副标题、四个朝向的瓶子示意、玩法提示、「开始游戏」按钮 |
| 游戏界面 | 顶部三块信息面板(关卡 / 剩余瓶子 / 剩余失误)、中间棋盘、底部「重新开始」按钮与提示条 |
| 结算界面 | 半透明遮罩 + 居中面板:「关卡通过」+「下一关」/「挑战失败」+「重新开始」/「全部通关」+「再玩一次」 |
3. 主要功能
- 四种方向齐全,鼠标点击选择瓶子;路径检测与边界处理正确;
- 飞出:加速飞离棋盘 + 苏打气泡拖尾(气泡逐渐变大、变淡、上浮飘散,出屏回收);
- 碰撞:瓶身沿垂直方向晃动、短暂变红,并用红色虚线 + 叉号标出阻挡者;
- 失误机制:界面实时显示剩余失误,耗尽后进入失败界面;
- 关卡流程:4 个关卡,通关自动进入下一关,失败可重开,全部通关有最终提示;
- 重新开始:底部按钮或
R键都能让当前关卡布局与失误次数完全复原; - 音效:开瓶「噗」声、碰撞「咚」声、通关三音,全部由代码合成,也支持替换成音频文件。
4. 项目特色
- 零素材依赖:所有美术(汽水瓶、瓶盖、标签、气泡、界面)都用 Pygame 基础图形绘制,音效由代码合成,仓库里不需要任何图片 / 音频文件就能运行。
- 关卡可解性有保证:4 个关卡都通过内置求解器校验,
python soda_bottle_puzzle.py --selftest可以随时复检并打印一条参考解法。 - 自带诊断能力:
--check可以一键打印 Python / Pygame / 显示驱动 / 字体 / 音效状态,并开窗 6 秒统计实际帧数与鼠标点击数,方便排查「窗口没出现」「点击没反应」这类环境问题。 - 动画与音效的工程化处理:旋转贴图按 (尺寸, 方向, 配色) 缓存、气泡贴图按半径缓存、渐变背景只生成一次、粒子数量设上限,保证低开销稳定 60 FPS。
三、实现思路
1. 方向与箭头的表示
方向用四个字符表示,并配两套向量常量——这是本项目一个关键设计点:
DIR_UP, DIR_RIGHT, DIR_DOWN, DIR_LEFT = "^", ">", "v", "<"
# 行列空间:(行增量, 列增量),只用于棋盘逻辑
DIR_VECTORS = {DIR_UP: (-1, 0), DIR_RIGHT: (0, 1), DIR_DOWN: (1, 0), DIR_LEFT: (0, -1)}
# 像素空间:(x 增量, y 增量),只用于飞行动画与气泡
DIR_PIXELS = {DIR_UP: (0, -1), DIR_RIGHT: (1, 0), DIR_DOWN: (0, 1), DIR_LEFT: (-1, 0)}
棋盘数据用二维数组存方向字符,空格用 None(布局文本里写 .):
self.grid[row][col] = "^" | "v" | "<" | ">" | None
为什么两套向量必须分开:行列坐标是「(行, 列)」,像素坐标是「(x, y)」,两者正好差 90°。
开发中真的踩到了这个坑——动画层直接用了行列向量,结果瓶子朝右侧飞时却是向下飞(详见第四节 AIGC 记录 3)。
2. 关卡的表示
每个关卡是一个字典,布局用字符串列表书写,可读性远好于纯数字数组:
{
"name": "第 1 关",
"cols": 5,
"lives": 5, # 失误次数上限
"layout": [
"...<.",
"^...v",
"...^.",
"..v..",
".>...",
],
}
字符含义:^ 上、v 下、< 左、> 右、. 空格。行数由字符串个数决定,列数由 cols 声明并在加载时校验。
3. 路径检测(核心)
这是整个作业最核心的算法。做法是:从被点击格子的相邻格开始,沿瓶口方向一格一格前进,直到走出棋盘;只要途中遇到任何瓶子就算被阻挡。
def scan_path(self, row, col):
"""返回 (是否畅通, 第一个阻挡瓶子坐标或 None)"""
direction = self.bottle_at(row, col)
if direction is None:
return False, None
dr, dc = DIR_VECTORS[direction]
r, c = row + dr, col + dc # 从相邻格开始,起点自身不算阻挡
while 0 <= r < self.rows and 0 <= c < self.cols:
if self.grid[r][c] is not None:
return False, (r, c) # 被这个瓶子挡住
r += dr
c += dc
return True, None # 一路走到边缘,可以飞出
这段代码同时解决了作业里特别强调的两件事:
- 边界处理:
while 0 <= r < rows and 0 <= c < cols本身就是边界判断。循环因为越界而结束时,正说明路径已经走到棋盘边缘,直接判定为“畅通”;不需要为四个方向分别写四套边界条件,也不可能出现数组越界。 - 只查同行同列:每一步只沿
(dr, dc)前进,向上/向下时列不变、向左/向右时行不变,天然满足“只判断同一行或同一列”。
函数还顺手返回第一个阻挡者的坐标,界面据此画出红色虚线与叉号,玩家能立刻明白“为什么飞不出去”。
4. 碰撞与飞出动画
点击后分两条分支:
clear, blocker = self.board.scan_path(row, col)
if clear:
self.launch_bottle(row, col, direction) # 从棋盘移除 + 生成飞出动画 + 播开瓶音效
else:
self.blocked_feedback(row, col, direction, blocker) # 扣失误 + 晃动闪红 + 标出阻挡者
- 飞出:逻辑上立即把瓶子从
grid移除,同时创建一个FlyingBottle对象,速度从 320 px/s 按 1500 px/s² 加速到 2100 px/s 上限飞出屏幕;每 10 ms 在瓶底位置喷出气泡,气泡带反向初速度、随机横向漂散、向上浮力,半径持续变大、透明度按寿命衰减,出屏后自动回收(粒子总数设了上限,长时间游玩不会卡顿)。 - 碰撞:加入一个
ShakeAnim,位移方向垂直于瓶口方向(也就是瓶身的左右方向),振幅随时间衰减,并在动画前 75% 的时间把配色切成偏红的"hit"调色板,实现“晃动 + 闪红”;同时用BlockedMarker画红色虚线与叉号。 - 结算延迟:清空棋盘或失误耗尽时,不立刻弹结算面板,而是设置 0.55 s / 0.8 s 的延迟,让玩家先看完飞行动画或晃动反馈,再切到结算界面。
5. 汽水瓶的绘制与旋转
瓶子只按「瓶口朝上」画一次(矩形圆角瓶身 + 多边形肩部 + 瓶颈 + 瓶盖 + 液面 + 标签 + 高光),再用 pygame.transform.rotate 旋转到目标方向,并按 (尺寸, 方向, 配色) 缓存:
DIR_ANGLES = {"^": 0, ">": -90, "v": 180, "<": 90}
base = self._render_up(size, palette) # 只画一次朝上的瓶子
image = base if angle == 0 else pygame.transform.rotate(base, angle)
self._cache[(size, direction, palette_name)] = image
90° 的整数倍旋转不会改变 Surface 尺寸,所以瓶子永远精确居中;缓存之后每帧只做一次 blit,几乎零开销。
6. 游戏状态管理
用状态机串起整个流程:
START(标题) → PLAYING(进行中) → CLEAR(本关通过) → 下一关 / ALL_CLEAR(全部通关)
└→ FAIL(失误耗尽) → 重新开始本关
主循环固定 60 FPS,每帧计算 dt 并限制在 50 ms 以内(防止拖动窗口后动画“跳帧”),依次执行:处理事件 → 更新动画与粒子 → 绘制 → flip()。
7. 关卡为什么一定可以通关
本项目所有关卡都不是“随手摆”的,而是先由程序逆向构造再由求解器校验:按“消失顺序的逆序”往空棋盘上放瓶子,每次放入时都要求该方向的路径是空的,这样天然保证存在通关顺序。
另外还有一个很好的性质:移除瓶子只会让其他瓶子的路径变得更空,不会制造新的阻挡。所以只要关卡存在一条清空顺序,玩家每次点击“当前能飞的瓶子”都不会走进死局——这也解释了为什么“看清瓶口方向、只点能飞的瓶子”一定能赢,失误只来自点错。
四、AIGC 使用过程
本次开发全程使用 Codex(OpenAI 的编码智能体) 作为主要 AIGC 工具:由我提出需求与验收标准,AI 负责查资料、写代码、生成测试脚本与排查 Bug,我再逐项运行验证并做修改。
4.1 AIGC 使用一览
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果 | 如何人工修改 |
|---|---|---|---|---|
| 关卡设计 | Codex | 生成逆向构造算法 + 求解器校验脚本 | 一开始手工设计的关卡实际不可通关,改用程序生成后 4 关全部可解 | 手工挑选布局、设置每关失误上限、用 --selftest 复检 |
| 路径检测 | Codex | 生成 scan_path() 四方向检测与边界处理代码 |
一次通过,未出现越界 | 逐条核对边界条件,补充注释说明 while 条件即边界判断 |
| 飞行动画 | Codex | 生成加速飞行与气泡拖尾代码 | 瓶子却朝侧面飞出(偏 90°) | 定位到行列向量/像素向量混用,拆成 DIR_VECTORS 与 DIR_PIXELS |
| 汽水瓶绘制 | Codex | 用基础图形绘制瓶子 + rotate 旋转 + 缓存 |
四方向朝向正确、帧率稳定 | 建立像素级校验脚本逐格验证 46 个瓶子;测试脚本自身的坐标轴写法也修正了一次 |
| 音效 | Codex | 纯 Python 合成开瓶/碰撞/通关音 + 外部文件优先加载 | 无需素材即可出声,可替换为 wav | 调整音量与包络、增加无声卡静音降级与 UTF-8 日志 |
| 环境排查 | Codex | 增加 --check 运行环境自检模式、一键启动脚本 |
定位到“命令被转义”“Ctrl+C 误报”两类问题 | 重写启动脚本的退出码判断,并强制日志 UTF-8 |
4.2 记录一:关卡设计——AI 生成的布局,真的可能无法通关
我的要求:先帮我设计 3~4 个逐步变难的关卡,并确保它们都能通关。
AI 做了什么:先给了两版“看起来很像原游戏”的手工布局。我没有直接采用,而是让 AI 写了一个求解器:用深度优先搜索枚举所有“能飞的瓶子”的点击顺序,看能否把棋盘清空。
实际效果:求解器一眼判定——第 1 关无法通关(初始状态可点击的瓶子数为 0,也就是所有瓶子互相挡死),第 2 关也只有 1 个可点瓶子且会走进死局。如果不做这步校验,这两关会直接踩中作业里“关卡实际上无法通关”的扣分项。
人工修改:改为程序化生成——按“消失顺序的逆序”往空棋盘上放瓶子,每次放入时要求该方向路径为空,这样天然保证可解;然后从大量候选中按“初始可点击数量、全程平均可点击数量、布局分散度、四个方向是否齐全”挑选,最后人工确定 4 关:
| 关卡 | 棋盘 | 瓶子 | 失误上限 | 初始可点击 |
|---|---|---|---|---|
| 第 1 关 | 5×5 | 6 | 5 | 5 |
| 第 2 关 | 6×6 | 10 | 4 | 5 |
| 第 3 关 | 6×6 | 14 | 3 | 5 |
| 第 4 关 | 7×7 | 16 | 3 | 7 |
同时把校验能力做进程序:--selftest 会逐关验证并打印一条参考解法,方便我每次改关卡后立刻复检。
4.3 记录二:路径检测——一次写对边界,但要自己核验
我的要求:按“同行或同列、从当前位置到棋盘边界之间是否有其他瓶子”实现检测,注意四个方向的边界,不许出现数组越界。
AI 做了什么:给出 scan_path()(见第三节代码),核心是从相邻格开始扫描,并用一个 while 0 <= r < rows and 0 <= c < cols 作为唯一循环条件;返回布尔值的同时返回第一个阻挡者坐标,供界面画提示线。
实际效果:一次运行通过,没有出现越界;四个方向的判定都正确。
人工修改与核验:我逐条推演了四个方向的边界情况(例如最上一行朝上、最右一列朝右时应立即判定畅通),并确认“起点自身不算阻挡”这一点是从 row + dr 开始的关键原因;另外补了注释,把这个技巧写清楚便于答辩讲解。为了可验证,我还写了一个像素级脚本:渲染每个瓶子后统计红色瓶盖的质心相对瓶身中心的偏移,逐一比对布局数据,46 个瓶子全部吻合。
4.4 记录三:一个真 Bug——瓶子朝右飞,却飞向了下方
我的要求:让瓶子沿瓶口方向飞出,并在尾部拖出气泡。
AI 做了什么:实现了 FlyingBottle(加速飞行、尾部定时喷气泡)与气泡粒子系统,代码看起来完全正常,画面截图也看不出问题。
实际效果:我让 AI 打印瓶子每 1/60 秒的位置,结果发现方向不对——朝右飞时 y 在增加(向下飞),也就是说飞行动画整体偏了 90°。
原因与人工修改:棋盘逻辑用的是「行列向量」(行增量, 列增量),而动画层把它直接当成了像素向量 (x, y),两者恰好差 90°。修复方案是拆成两套常量——DIR_VECTORS(行列,供棋盘逻辑)与 DIR_PIXELS(像素,供动画与气泡),并在文件头和 README 里写明“两者不可混用”。随后补了一条回归测试:对四个方向分别点击并累积位移,断言“沿瓶口方向的位移为正、垂直方向的偏移接近 0”,四个方向全部通过。
这个 Bug 也说明:AI 生成的代码“能跑”不等于“对了”,必须设计能暴露错误的验证手段(这里是数值断言,而不是肉眼看截图)。
4.5 记录四:汽水瓶绘制与旋转(连测试脚本本身也修了一次)
我的要求:用 Pygame 基础图形画一个简约汽水瓶,支持四个方向旋转,不要用外部图片,并且性能要好。
AI 做了什么:把瓶子按“瓶口朝上”画在一张透明 Surface 上(圆角瓶身、多边形肩部、瓶颈、瓶盖、液面、标签、高光),再用 pygame.transform.rotate 按 0 / -90 / 180 / 90 度旋转,结果按 (尺寸, 方向, 配色) 缓存,并额外维护一套偏红的 "hit" 调色板用于碰撞闪红。
实际效果:画面正常、60 FPS 稳定;但第一次跑像素级校验时,脚本报告“四个方向全部不符”。
人工修改:排查后发现问题在我的校验脚本——pygame.surfarray.array3d 返回的数组索引是 [x][y],我却按 [y][x] 取了坐标,等于把 x/y 轴写反了。修正脚本后,四个方向的瓶盖朝向全部正确。这件事提醒我:自动化测试报错时,先确认测试本身是否正确,否则会把好的代码改坏。
4.6 记录五:音效——没有素材也要能出声
我的要求:飞出播放“开汽水瓶”的音效、碰撞播放短促提示音;可以代码生成,也可以预留音频文件接口。
AI 做了什么:用标准库 array 手工合成 16bit 双声道 PCM 波形,交给 pygame.mixer.Sound(buffer=...) 播放:开瓶声是“高频噪声爆点 + 音高快速下滑的阻尼音调”,碰撞声是低频短促“咚”,通关是三音上行小叮咚;同时约定 assets/sounds/ 下同名 wav 文件优先加载,找不到才用合成音,并在没有音频设备时自动静音降级(不抛异常、不影响玩法)。
实际效果:实测三段音效时长分别为 0.24 / 0.14 / 0.60 秒,峰值约 0.6 满量程、无削波;开瓶音前 20% 的平均能量明显高于后段(说明“噗”的瞬态存在);把一段 0.5 秒的测试音频放到约定路径后,程序确实改为加载外部文件。
人工修改:我调整了音量系数与包络参数,让声音更接近“开瓶”的听感;并补充了“音频设备不可用则静音”的兜底分支。
4.7 记录六:排查“运行没反应”——把经验固化成工具
我的要求:程序要能在同学的电脑上按 README 直接跑起来。
遇到的实际问题:一次运行中,终端显示 [ERROR] The game exited with an error,同时出现 Terminate batch job (Y/N)?。
AI 做了什么:分析后确认这是在命令行按了 Ctrl+C 造成的——Ctrl+C 的退出码不是 0,被启动脚本误判成“程序崩溃”;另外还发现从聊天窗口复制命令时,路径里的下划线被转义成了 \_,导致文件名错误、命令根本没执行。
人工修改:把这两条经验固化进项目——给启动脚本加退出码判断(Ctrl+C 提示“Interrupted by Ctrl+C”,不再误报为崩溃)、把运行日志强制为 UTF-8(记事本打开不乱码);再给游戏加一个 --check 自检模式,一条命令就能打印 Python / Pygame / 显示驱动 / 窗口尺寸 / 字体 / 音效,并开窗 6 秒统计真实渲染帧数与鼠标点击数。实测在这台机器上输出为:视频驱动 windows、窗口 800×600、约 60 FPS、字体 msyh.ttc、音效已启用。
五、测试结果
5.1 作业要求的六项测试
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 瓶子沿瓶口方向加速飞出屏幕,剩余数量减 1,尾部有气泡拖尾 | 通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误次数减 1 | 瓶子留在原地并晃动闪红,红色虚线与叉号标出阻挡者,失误 5→4 | 通过 |
| T03 | 点击位于边缘且朝向棋盘外的箭头 | 正常消失,不发生越界错误 | 上/下/左/右四个方向分别测试,均立即飞出且无越界异常 | 通过 |
| T04 | 消除本关全部箭头 | 显示通关并进入下一关 | 清空后 0.55 秒显示「关卡通过」,点「下一关」进入下一关;4 关用求解器给出的顺序全部走通 | 通过 |
| T05 | 失误次数耗尽 | 显示失败并允许重新开始 | 第 5 次误点后显示「挑战失败」,点「重新开始」回到初始布局 | 通过 |
| T06 | 游戏进行中重新开始 | 箭头布局和失误次数恢复 | 按钮与 R 键均可:瓶子数回到 6、失误回到 0/5 |
通过 |
5.2 补充的自动化测试
| 编号 | 测试内容 | 方法 | 结果 |
|---|---|---|---|
| A01 | 关卡可解性 | python soda_bottle_puzzle.py --selftest(内置求解器) |
4 关全部「可通关 OK」,并给出参考解法 |
| A02 | 朝向渲染正确性 | 渲染每个瓶子后统计红色瓶盖质心相对瓶身中心的偏移 | 4 个方向正确;4 关共 46 个瓶子与布局数据逐一吻合 |
| A03 | 飞行动画方向 | 四方向各点击一次,累积位移做断言 | 沿瓶口方向位移为正、垂直偏移 ≈ 0;气泡拖尾位于瓶身后方;出屏后回收 |
| A04 | 音效 | 读取波形统计时长 / 峰值 / 前后段能量;替换外部文件后再测 | 时长 0.24/0.14/0.60 s,无削波,瞬态明显,外部文件优先加载生效 |
| A05 | 输入链路 | 向 pygame 事件队列投递真实 MOUSEBUTTONDOWN / KEYDOWN |
按钮、瓶子、棋盘外点击、R/ESC 快捷键全部符合预期 |
| A06 | 状态机健壮性 | 6000 次随机乱点(含棋盘外、结算面板、快捷键) | 0 异常:状态始终合法、失误不超上限、棋盘清空必定进入结算流程 |
| A07 | 完整流程 | 自动走完「标题 → 逐关通关 → 全部通关 → 再玩一次」 | 76 项断言全部通过 |
| A08 | 真机冒烟 | 真实显示驱动 + 真实音频设备运行 | 视频驱动 windows,窗口 800×600,约 60 FPS,音频初始化成功 |
5.3 测试中发现并修复的问题
| 问题 | 现象 | 原因 | 修复 |
|---|---|---|---|
| 关卡不可通关 | 第 1 关初始可点瓶子数为 0 | 手工布局互相挡死 | 改为逆向构造 + 求解器校验 |
| 飞行动画偏 90° | 朝右的瓶子向下飞 | 行列向量被当作像素向量使用 | 拆分 DIR_VECTORS / DIR_PIXELS 并加回归测试 |
| 校验脚本误报 | 四个方向全部“朝向不符” | 测试里 surfarray 的 x/y 索引写反 |
修正测试脚本后全部通过 |
| 启动器误报崩溃 | Ctrl+C 中断被当成程序出错 |
只看“退出码是否为 0” | 单独识别中断退出码,并输出 UTF-8 日志 |
| 复制命令无法执行 | 路径下划线被转义成 \_ |
从聊天窗口复制带入转义 | 文档改用 python soda*.py 通配符写法 |
六、PSP 表格
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1.5 | 1.0 | -0.5 |
| Python 与图形库学习 | 2.0 | 1.0 | -1.0 |
| 游戏界面实现 | 3.0 | 2.5 | -0.5 |
| 路径与碰撞逻辑实现 | 3.0 | 2.0 | -1.0 |
| 关卡设计 | 2.0 | 2.5 | +0.5 |
| AIGC 辅助开发 | 2.0 | 3.0 | +1.0 |
| 测试与修改 | 2.5 | 3.0 | +0.5 |
| README 与博客撰写 | 2.0 | 2.0 | 0 |
| 合计 | 18.0 | 17.0 | -1.0 |
差异分析:图形库学习与界面实现比预期顺利(AI 直接给出了可运行的骨架);而关卡设计与测试修改都超时——关卡要保证可通关、动画要逐方向验证、音效与输入链路都要实测,这些“看不见的工作”占用了更多时间。
七、心得体会
1. AI 让“从零到能跑”变得非常快,但不会自动保证正确。
这次最典型的两个例子:AI 给出的手工关卡布局看起来完全合理,却根本无解;AI 写的飞行动画代码能跑、截图也看不出问题,实际却偏了 90°。它们都是靠“求解器校验”和“打印坐标做断言”才暴露出来的。所以我把这次作业的关键经验总结为一句话:让 AI 写代码的同时,也让它写能证伪这段代码的检查。
2. 需求描述越具体,AI 的产出越可用。
例如把“路径检测要正确处理边界”具体成“只检查同行同列、从当前位置到棋盘边界、不能越界”,AI 给出的 scan_path() 就一次写对了;而只说“做个好玩的小游戏”时,产出的东西往往需要大量返工。
3. 自动化测试救了我。
项目里最终留下了 4 类可重复运行的检查:关卡可解性自检(--selftest)、朝向像素级校验、飞行动画方向回归测试、6000 次随机乱点压力测试。每次改动画参数或换关卡,跑一遍就能确认没有把已经好的功能改坏——这也是我能放心重构动画层的前提。
4. 对“边界处理”的理解更扎实了。
以前写这类四方向扫描,第一反应是给四个方向各写一套边界判断。这次学到一个更简洁的写法:用统一的方向向量配合 while 0 <= r < rows and 0 <= c < cols,循环因越界而结束本身就代表“到达棋盘边缘”。代码短、不易漏分支,也好讲清楚。
5. 关于“额外功能”的取舍。
作业强调基础功能的完整与正确优先。所以我把精力放在“规则一定正确、关卡一定能通关、异常情况一定有反馈”上;汽水瓶造型、气泡拖尾、合成音效这些属于美化与体验,但都建立在核心玩法正确的基础上。若继续迭代,我会考虑:关卡选择与星级评价、提示功能、撤销上一步、保存进度,以及用 PyInstaller 打包成 exe 方便同学直接试玩。
6. 对“AI 生成代码”的态度。
AI 写出的代码进入项目后由我负责,所以我坚持两条:一是每一处关键逻辑我都能讲清楚(路径检测、向量拆分、状态机、可解性原理都写在注释和 README 里);二是不把没验证过的代码当成完成品,宁可多写几个测试脚本。这样即使在答辩时被追问细节,也能解释清楚。
posted on
浙公网安备 33010602011771号