2026秋软件工程个人作业(第二次):一箭又一箭

一箭又一箭:用 Python + AIGC 完成"箭头消除"小游戏

作业信息

项目 内容
这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzu/202601SofwareEngineering
这个作业要求在哪里 https://edu.cnblogs.com/campus/fzu/202601SofwareEngineering/homework/16717
这个作业的目标 使用 Python 和 AIGC 完成"一箭又一箭"小游戏
学号 052401304
GitHub 仓库 https://github.com/147lsq/arrow-game

一、项目展示

演示视频(GIF):

PixPin_2026-09-20_22-16-55

开始界面:

开始界面

第 1 关 / 第 2 关:

第 1 关
第 2 关

通关 / 失败界面:

image

image


二、项目介绍

游戏规则

棋盘上散布着 上、下、左、右 四种方向的箭头,玩家用鼠标点击箭头:

  • 点击后,程序沿箭头前进方向检测路径:如果箭头到棋盘边界之间 没有其他箭头阻挡,箭头就飞出棋盘、被消除;
  • 如果路径上存在其他箭头,箭头 不能消除,并通过「晃动 + 红色闪烁 + 飘字提示」给出明显的碰撞反馈,同时扣除 1 次失误机会;
  • 每关有 3 次失误机会;清空全部箭头即通关并进入下一关;失误耗尽则本关失败,可重新开始。

界面设计

游戏包含 4 个场景:开始界面、游戏界面、通关界面、失败界面(通关/失败以半透明面板叠加在棋盘上)。游戏界面顶部 HUD 显示当前关卡、剩余箭头数、剩余失误次数(爱心图标),底部提供「重新开始」「回到主页」按钮,鼠标悬停在箭头上时会有高亮边框提示。

主要功能与特色

  • 四方向路径判定,边界越界安全;
  • 飞出(滑出+淡出)、碰撞(晃动+变红+飘字)两套动画反馈;
  • 4 个手工设计关卡,并用"可解性求解器"逐一验证存在通关节奏;
  • 关卡选择(开始界面可直接跳转任意一关)、重新开始、回主页;
  • 自动匹配系统中文字体(微软雅黑/黑体/思源黑体等),跨平台可跑;
  • 单独的自测脚本 selftest.py,无窗口即可完成回归测试。

三、实现思路

3.1 箭头与方向的表示

棋盘固定为 7×7 网格。用行列坐标 (r, c) 唯一标识一格,用一个字典保存所有箭头:

board = {(r, c): "方向字符"}   # 例如 {(0, 3): "^", (2, 0): "<", ...}

四个方向用"方向向量 + 字符"的映射表示,方便后续自动寻路:

DIRS = {"^": (-1, 0), "v": (1, 0), "<": (0, -1), ">": (0, 1)}
#        上        下          左          右

关卡则用可读的字符网格描述,运行时再解析成上面的 board 字典:

LEVELS = [
    # 第 1 关:中央一列向上箭头,从顶端开始依次消除;四角箭头同时可消除
    [
        "<  .  .  ^  .  .  >",
        ".  .  .  ^  .  .  .",
        # ... 共 7 行
    ],
]

def build_board(rows):
    board = {}
    for r in range(len(rows)):
        cells = rows[r].split()
        for c in range(len(cells)):
            if cells[c] in DIRS:
                board[(r, c)] = cells[c]
    return board

3.2 路径检测(核心逻辑)

依据规则:「箭头与棋盘边界之间,同一行/列上是否还有其他箭头」。实现时从箭头所在格出发,沿方向逐格向边界走,一旦发现箭头即被阻挡:

def is_blocked(board, r, c):
    """判断位于 (r, c) 的箭头前进方向上是否还有其他箭头。"""
    dr, dc = DIRS[board[(r, c)]]
    rr, cc = r + dr, c + dc
    while 0 <= rr < ROWS and 0 <= cc < COLS:   # 用边界判断收尾,天然避免越界
        if (rr, cc) in board:
            return True                         # 路径上发现其他箭头 → 被阻挡
        rr += dr
        cc += dc
    return False                                # 走到边界仍无箭头 → 畅通

两点值得强调:

  1. 边界安全:循环退出条件是坐标越出棋盘,因此边缘箭头天然安全,例如最左格 "<" 第一步就会因 cc < 0 退出循环而判定畅通,不会产生数组越界;
  2. 是否符合题意:题目示例 → · · ↑ · 中,右箭头因为右方第 4 格有 而被判定为阻挡;示例 ↑ · · · →(箭头在最右侧)判定为畅通。这两例都在自测脚本中做了断言。

3.3 点击后的处理流程

def handle_cell_click(self, r, c):
    if (r, c) not in self.board:
        return
    if is_blocked(self.board, r, c):
        self.mistakes -= 1              # 碰撞:扣 1 次失误
        self.shakes.append(ShakeAnim(r, c, self.board[(r, c)]))  # 晃动+变红
        self.floats.append(FloatText("被阻挡!走不了", ...))
        if self.mistakes <= 0:
            self.pending = {"kind": "fail", "t": 0.0}   # 进入失败流程
    else:
        d = self.board.pop((r, c))      # 畅通:移除箭头
        self.flies.append(FlyAnim(r, c, d))             # 飞出动画
        if not self.board:
            self.pending = {"kind": "win", "t": 0.0}    # 清空 → 通关

3.4 关卡可解性(用求解器保证"能通关")

先写求解器再定关卡是本项目一个重要设计。判断规则:移除箭头只会减少障碍、不会制造新障碍,所以贪心即可判定可解——不断移除"当前畅通"的箭头,若卡住则说明该布局死锁:

def solve_plan(board):
    b = dict(board); plan = []
    while b:
        step = [(r, c) for (r, c) in b if not is_blocked(b, r, c)]
        if not step:
            return None          # 死锁,无法通关
        r, c = step[0]; plan.append((r, c)); b.pop((r, c))
    return plan

开发中用该求解器筛掉了一个">< 面对面互堵"的无法通关的关卡(见 AIGC 使用过程记录)。


四、AIGC 使用过程

子任务 借助何种 AIGC 技术 AI 实现或提供了什么 效果如何 人工修改
路径检测 opencode 生成 is_blocked(),对上下左右四方向沿行/列向边界逐格检测是否有其他箭头;配套生成 DIRS 方向映射 逻辑一次正确;与题目两个示例(→· · ↑· 阻挡、最右 畅通)通过断言验证 无需改核心逻辑;人工补充了针对性测试用例做回归验证
关卡设计 opencode 设计 4 个 7×7 关卡字符数组,并给出"贪心求解器"脚本判断关卡是否可通关 前 3 关可通;第 4 关初始布局死锁>< 面对面互相阻挡,打印结果显示 可解? None 运行求解器定位死锁位置,让 AI 重新设计第 4 关布局,再次验证 4 关全部存在通关顺序
飞出与碰撞动画 opencode 生成 FlyAnim(飞出+淡出)、ShakeAnim(碰撞晃动+红色闪烁)、FloatText 飘字;用旋转贴图实现四方向箭头外观 箭头贴图旋转时方向符号写反,^/v 朝向错位 逐方向采样贴图尖端像素核对,反馈给 AI 修正旋转角度映射后正常
失败/通关结算界面 opencode 生成失败/通关半透明遮罩面板与「下一关 / 重新开始 / 回到主页」按钮 存在 bug:正常游玩时失败面板一直盖在棋盘上(遮罩在 SC_PLAY 场景也被调用绘制) 真人试玩发现后反馈给 AI,改为只在通关/失败场景绘制遮罩,并补充回归测试("游戏进行中不绘制遮罩"一项)

经验小结:AI 能快速生成可运行的整体骨架和测试脚本,但"能编译 ≠ 逻辑正确 ≠ 视觉正确"——本项目中 3 个问题(方向错位、关卡死锁、遮罩遮挡)全部依赖"跑测试验证"和"真人试玩"才暴露,所以坚持"AI 生成 → 自动测试 → 手动验证"的闭环。


五、测试结果

采用「自动自测脚本 + 真人试玩」两层测试。自动自测脚本 selftest.py 在无窗口环境下运行(SDL_VIDEODRIVER=dummy),覆盖关卡可解性、判定逻辑、全关卡自动通关、失误/失败/重开、各界面渲染共 30+ 项断言,全部通过。

测试结果如下:

编号 测试内容 预期结果 实际结果 是否通过
T01 点击前方无阻挡的箭头 箭头飞出棋盘并消失 箭头被移除并播放飞出动画,棋子数 -1 ✅ 通过
T02 点击前方有阻挡的箭头 箭头不消失,失误次数减 1 棋盘布局不变、失误 3→2→1→0 逐次递减,并出现晃动/变红/飘字反馈 ✅ 通过
T03 点击位于边缘且朝向棋盘外的箭头 箭头正常消失,不发生越界错误 最左格 <、最右格 > 等边缘箭头均正常消除;is_blocked 的循环以边界判断收尾,未出现越界 ✅ 通过
T04 消除本关全部箭头 显示通关并进入下一关 每关清空后弹出「本关通过/全部通关」面板,点击「下一关」可进入下一关 ✅ 通过
T05 失误次数耗尽 显示失败并允许重新开始 第 3 次碰撞后进入失败界面,「重新开始」恢复本关初始布局与 3 次失误 ✅ 通过
T06 游戏进行中重新开始 箭头布局和失误次数恢复 「重新开始」按钮将 boardmistakes 复原为关卡初始值;并验证「游戏进行中不绘制失败/通关遮罩」 ✅ 通过

自测运行结果摘录:

== 关卡可解性 ==
[PASS] 第1~4关可通关节奏
== 判定逻辑(题目示例)==
[PASS] 示例1 → 被右侧箭头阻挡
[PASS] 示例2 最右 → 无障碍可飞出
== 自动游玩全部关卡 ==
[PASS] 第1~4关自动通关 / 进入下一关
== 失误与失败流程 ==
[PASS] 点击被阻挡箭头扣除失误 ×3 / 失败界面出现 / 重新开始恢复初始状态
== 界面渲染 ==
[PASS] 开始/游戏/通关/失败界面渲染;游戏进行中不绘制遮罩
自测全部通过 ✅

六、PSP 表格

任务 预估耗时(小时) 实际耗时(小时) 差异(小时)
需求分析与游戏设计 1.0 1.5 +0.5
Python 与图形库学习 2.0 1.5 -0.5
游戏界面实现 2.0 2.5 +0.5
路径与碰撞逻辑实现 1.5 1.0 -0.5
关卡设计 1.0 1.5 +0.5
AIGC 辅助开发 2.0 3.0 +1.0
测试与修改 1.5 2.5 +1.0
README 与博客撰写 1.0 1.5 +0.5
合计 12.0 15.0 +3.0

差异主要来自:AI 生成内容后仍需人工测试/修改(多边形旋转、关卡死锁、遮罩 bug 三处返工),以及真人试玩验证占用的时间。


七、心得体会

AI 带来的帮助:作为编码智能体,它能直接从一句话需求生成可运行的整体骨架——窗口、棋盘、判定、动画一应俱全;尤其擅长"主动写验证"——自测脚本、可解性求解器都是它建议加入的,这让"关卡保证能通关"从口号变成了可执行的断言。

出现的问题:最大的坑在于"看起来对,其实错"。

  • 箭头贴图旋转方向写反(视觉错位);
  • 第 4 关 >< 互堵产生死锁(逻辑层面:两张"面对面"箭头互相挡路,谁都出不去);
  • 失败遮罩被错误地在正常游玩时也绘制(界面层 bug,试玩才暴露)。
    这三处都不是编译错误,必须靠自动测试 + 真人试玩才能发现。

收获

  1. 先写判定/验证工具,再做功能:可解性求解器 + 自测脚本让每个关卡、每次改动都有了"证据";
  2. AI 生成的内容要当成"高风险代码"来审:能编译、能跑通主线流程只过了第一关,边界与异常分支才是返工高发区;
  3. 单人做"游戏感"内容时,AI 是绝佳的第二双眼睛:让 AI 去写机器能验证的部分(逻辑、测试),人专注于试玩手感与界面观感,互不抢节奏。

posted @ 2026-09-20 22:52  147lsq123  阅读(10)  评论(0)    收藏  举报