第二次软件工程作业

项目 内容
这个作业属于哪个课程 202601 软件工程
这个作业要求在哪里 软件工程第二次作业
这个作业的目标 使用 Python 和 AIGC 完成“一箭又一箭”小游戏
学号 102401317
GitHub 仓库 仓库链接

一、项目展示

游戏一共四个界面,共 4 个关卡(前两关 5×5、后两关 6×6),每个关卡都经过程序求解验证,保证存在合理的通关顺序。

开始界面 —— 启动后停在这里,显示标题、玩法说明和「开始游戏」按钮。

![开始界面]

screen_start

游戏界面 —— 顶部信息栏显示当前关卡、剩余箭头、剩余失误和「重新开始」按钮,下方是棋盘。四种箭头用颜色区分:绿=上、红=下、蓝=左、橙=右。

![游戏界面]
screen_game

通关界面 —— 清空本关全部箭头后弹出「本关通过!」,点「下一关」继续;最后一关显示「全部通关!」。

![通关界面]

screen_clear

失败界面 —— 失误耗尽后弹出「失误耗尽」,点「重新开始」恢复到本关初始状态。

![失败界面]

screen_fail


二、项目介绍

2.1 游戏规则

棋盘上分布着上、下、左、右四个方向的箭头,玩家需要观察箭头方向以及它们之间的相互阻挡关系,按合适的顺序点击,让所有箭头依次飞出棋盘:

  • 点击某个箭头,程序沿它所指方向、从前方一格扫描到棋盘边界;
  • 前方无其它箭头阻挡 → 箭头飞出棋盘并被消除;
  • 前方有其它箭头阻挡 → 箭头无法飞出,触发碰撞反馈(原地晃动 + 红色闪烁),并消耗一次失误机会;
  • 清空本关全部箭头 → 进入下一关;
  • 失误次数耗尽 → 本关失败,可重新开始。

每关有 3 次失误机会。共 4 关,前两关 5×5、后两关 6×6,难度随箭头数量与布局复杂度递增。

2.2 界面设计

  • 窗口:520×720,60 FPS 刷新;
  • 布局:顶部信息栏(HUD)固定显示当前关卡、剩余箭头数、剩余失误次数与「重新开始」按钮,下方为棋盘区域;
  • 配色:四种箭头用四种颜色区分(绿=上、红=下、蓝=左、橙=右),碰撞反馈统一用红色,通关用绿色,视觉上「安全 / 警告」一目了然;
  • 字体:直接加载系统自带微软雅黑 msyh.ttc,保证中文正常显示且不依赖外部素材。

2.3 主要功能与特色

  • 四个方向箭头的路径阻挡检测与飞出消除;
  • 飞出动画(沿方向移动 + 淡出)、碰撞动画(晃动 + 闪烁);
  • 4 个手工设计的关卡,每关都经过程序求解验证可通关;
  • 完整的五状态机:开始 / 游戏中 / 通关 / 全部通关 / 失败;
  • 自动化测试脚本,覆盖可解性、通关流程与碰撞逻辑;
  • 零外部素材:箭头、界面全部用 Pygame 图形原语绘制,字体用系统字体。

三、实现思路

代码集中在单文件 arrow_game.py 中,结构分为:常量与配色 → 关卡数据 → 工具函数 → Game 类(核心逻辑)→ 主程序。

3.1 箭头、方向与关卡的表示

箭头方向用一个「行增量、列增量」的字典表示(行向下增长、列向右增长):

DIRS = {
    'U': (-1, 0),   # 上
    'D': (1, 0),    # 下
    'L': (0, -1),   # 左
    'R': (0, 1),    # 右
}

箭头图标统一预渲染成「朝右」,再用 pygame.transform.rotate 旋转出其它方向(R:0° / U:90° / L:180° / D:270°),省去为每个方向单独画图的麻烦。

关卡用字符串数组表示:. 是空格,U/D/L/R 是箭头。例如第 1 关:

{
    "name": "第 1 关",
    "mistakes": 3,
    "grid": [
        ".RRR.",
        "U...D",
        "U.U.D",
        "U...D",
        ".LLL.",
    ],
}

加载关卡时把字符串数组转换成二维 board,None 表示空格、方向字符表示箭头。

3.2 路径检测(核心算法)

这游戏 90% 的逻辑都集中在「判断一个箭头能否飞出棋盘」上:

def can_fly(self, r, c):
    dr, dc = DIRS[self.board[r][c]]
    rr, cc = r + dr, c + dc
    while 0 <= rr < self.rows and 0 <= cc < self.cols:
        if self.board[rr][cc] is not None:   # 路上有箭头 → 被挡
            return False
        rr, cc = rr + dr, cc + dc
    return True                              # 走到边界都没挡 → 能飞

思路就是「沿方向一步步走,撞到边界就是能飞,撞到箭头就是被挡」。对应题目里的两个例子:→ · · ↑ · 里第一个 → 一路往右会撞上 ↑,返回 False;↑ · · · → 里最后的 → 往右一路到边界都是空的,返回 True。这个函数一写对,整个游戏的骨架就立起来了。

3.3 飞出与碰撞动画

点击箭头的处理在 click_arrow 中分成两支:

  • 能飞:把 board[r][c] 置为 None,同时往 fly_anims 里压入一条动画记录(起点坐标、方向、初始透明度、速度),在 update 中让图标沿方向以 speed=540 像素/秒移动,透明度线性衰减,完全淡出后移除;
  • 被挡:mistakes_left -= 1,往 hit_flashes 里压入碰撞记录,绘制时用 sin 函数做左右小幅晃动,并叠加一层逐渐消失的红色半透明闪烁,持续约 0.4 秒。

飞出动画的时机我特别注意过:只有箭头完全飞出窗口后才算真正消除,避免「还没飞出就从棋盘上消失」的割裂感。

3.4 关卡可解性验证

手工设计关卡最容易出的问题是「设计出一个死局」——某些箭头互相卡死,永远消不完。为此我写了一个 DFS 求解器 solve(level):反复寻找当前「能飞」的箭头、消除它、递归求解剩余棋盘,直到清空或证明无解。4 个关卡全部通过该求解器验证后才定稿。


四、AIGC 使用过程

本次开发全程使用 Claude Code 辅助。协作节奏是:我把要求丢给它,它写代码;我再让它写测试,把测出来的问题喂回去修,循环到全绿。分工上,AI 负责「写」,我负责「判」——通过实际运行和测试,对 AI 给出的代码进行判断、修改和完善。

4.1 协作过程概览

子任务 AI 提供的内容 实际效果 人工修改
学习 Pygame 用法 梳理主循环、鼠标事件、图形绘制 直接可用 无
设计代码结构 把作业要求拆成模块,设计单文件结构 清晰 无
编写核心算法 can_fly 路径检测、棋盘绘制 正确 无
设计关卡数据 设计 4 个关卡 部分关卡存在死局风险 加 DFS 求解器逐关验证
查找修改 Bug 定位并修复启动跳关、字体崩溃 基本修复 见下文三个案例
编写测试用例 自动化测试脚本 覆盖不全 补充碰撞逻辑、动画稳定性测试

4.2 案例一:游戏一启动就跳过开始界面

问题:作业明确要求有「开始界面」,但游戏一运行直接蹦进了游戏界面。

定位:AI 在构造函数里调用了关卡加载函数,而这个函数顺手把状态设成了「游戏中」——游戏刚出生就被踹进了游戏里。

修复:启动时停在开始界面,等玩家点「开始游戏」再加载第一关。

启发:这种「状态机初始状态没摆对」的 bug,不实跑一遍根本发现不了——逻辑上看每句话都没错,合起来就是错的。

4.3 案例二:字体加载直接崩溃

问题:跑起来一到画文字的地方就报错:

TypeError: expected str, bytes or os.PathLike object, not int

定位:pygame.font.match_font() 遍历系统字体注册表时,撞上了一个 DWORD 类型的注册表项直接炸了(Pygame 2.6.1 在部分 Windows 上的已知坑)。

修复:不再查询系统字体,改为按文件路径直接加载微软雅黑 msyh.ttc,并做多字体回退(雅黑 → 黑体 → 宋体 → 默认)。

启发:具体到报错堆栈的描述,比「字体显示不出来」这种笼统说法有用得多。

4.4 案例三:控制台编码的坑

问题:测试脚本往 GBK 编码的 Windows 控制台里打印 emoji,直接 UnicodeEncodeError。

修复:把标准输出重定向成 UTF-8(sys.stdout.reconfigure(encoding="utf-8")),并少用控制台打不了的字符。

三个坑,一个是逻辑 bug,两个是环境兼容问题。它们有个共同点:都是 AI 初次生成的代码里没有的,只有真正跑起来才暴露。


五、测试结果

通过 python test_arrow_game.py 运行自动化测试(使用虚拟显示器,无需真实窗口),测试结果如下:

编号 测试内容 预期结果 实际结果 是否通过
T01 4 个关卡可解性 每关均能找出完整通关顺序 4 关全部可解 通过
T02 开始界面 → 游戏界面 点击「开始游戏」进入 PLAYING 状态正确切换 通过
T03 按正确顺序消除 剩余箭头为 0,且不消耗失误 全部通关 通过
T04 通关进入下一关 / 全部通关 CLEAR → 下一关,最后一关 WIN 正确 通过
T05 碰撞扣失误 点被挡箭头消耗 1 次失误且箭头不消失 正确 通过
T06 动画与绘制 连续 60 帧更新 + 绘制无异常 无异常 通过

此外,求解器在每关求解时都校验了「消除后剩余箭头数归零」和「正确顺序不消耗失误」,保证关卡设计本身没有死锁。


六、PSP 表格

任务 预估耗时(h) 实际耗时(h) 差异
需求分析与玩法设计 1.0 1.0 0
Python / Pygame 学习 1.5 2.0 +0.5
路径检测算法实现 1.5 1.5 0
关卡设计与可解性验证 1.5 1.0 -0.5
界面与动画实现 2.0 2.5 +0.5
测试与调试 1.5 2.0 +0.5
文档撰写 1.0 1.5 +0.5
总计 10.0 11.5 +1.5

七、心得体会

这次作业让我重新理解了「AI 写代码」这件事。

AI 确实能大幅提效:搭框架、写算法、画关卡、写测试、定位 bug,样样都行,而且快得离谱。

但 AI 不是替你写代码,而是和你一起写。它第一次交出来的东西可能带着逻辑 bug(跳过开始界面),可能撞上环境兼容问题(字体崩溃、控制台编码)——这些都得靠你自己去跑、去读、去判断、去改。

最深的一点体会是:「能跑」和「能用」之间隔着一条河。AI 给出的代码逻辑上往往是对的,但真正跑起来,问题常藏在环境、编码、字体这些边边角角的地方。所以题目里那句「要基本理解自己提交的代码」,我这次是真的体会到了——不是背下来的理解,是踩过坑之后的理解。

说白了:AI 负责出草稿,你负责把草稿改对,判断力始终在自己手上。


八、资源与仓库说明

  • 图形全部用 Pygame 图形原语绘制(多边形箭头 + 旋转),字体用系统自带微软雅黑,无外部素材依赖;
  • 主程序 arrow_game.py,测试 test_arrow_game.py,游戏截图位于 screenshots/;
  • 代码与文档见 GitHub 仓库:power819/arrow-escape。
# 运行游戏
python arrow_game.py

# 运行自动化测试
python test_arrow_game.py

本文由 Claude Code 协助完成开发,由我本人完成验证与成文。 |

posted @ 2026-09-19 15:41  power1897  阅读(19)  评论(0)    收藏  举报