软件工程第二次作业

一箭又一箭:Python + Pygame 小游戏开发实践

项目内容 软件工程第二次个人作业:一箭又一箭小游戏
这个作业属于哪个课程 2026 春季软件工程与软件工程实践(福州大学)
这个作业要求在哪里 第二次作业:一箭又一箭
这个作业的目标 使用 Python 和 AIGC 完成 "一箭又一箭" 小游戏,掌握图形界面开发、路径判断、碰撞反馈、关卡状态管理等基础能力
学号 102401119
GitHub 仓库 Lqh5 / 一箭又一箭

一、项目展示

1. 开始界面

01_start

2. 游戏过程(第 1 关)

demo

02_level1

游戏过程中,点击 "前方无阻挡" 的箭头,箭头会沿方向飞出并消失;点击 "前方有阻挡" 的箭头,箭头会变成红色并抖动,同时失误次数减 1:

05_collision

3. 通关界面

06_win

4. 失败界面

07_lose


二、项目介绍

1. 游戏规则

  • 棋盘中有若干带方向的箭头(上、下、左、右);

  • 点击箭头后,程序沿箭头方向检查其到棋盘边界的路径:

    • 若路径上没有其他箭头 → 箭头飞出棋盘并被消除;

    • 若路径上存在其他箭头 → 箭头不能消除,产生红色高亮 + 抖动的碰撞反馈,并消耗 1 次失误机会;

  • 消除本关全部箭头 → 通关,进入下一关;

  • 失误次数耗尽 → 本关失败,可重新开始。

2. 界面设计

界面采用深色简约风格,共四种场景(状态机管理):

场景 内容
开始界面 游戏标题、玩法说明、"开始游戏" 按钮
游戏界面 顶部状态栏(关卡、剩余箭头、失误次数、重新开始按钮)+ 6×6 棋盘
通关界面 "恭喜通关" 提示 + "进入下一关" 按钮
失败界面 "挑战失败" 提示 + "重新挑战" 按钮

3. 主要功能

  • 鼠标点击选择箭头;

  • 四个方向箭头显示与路径检测;

  • 箭头飞出动画、碰撞抖动反馈;

  • 失误次数统计与显示;

  • 关卡切换(3 个难度递增的关卡,每局随机生成新布局);

  • 重新开始功能;

  • 启动时自动校验所有关卡可解。


三、实现思路

1. 关卡与箭头的表示

棋盘是一个 6×6 的二维数组 grid[r][c]:None 表示空格,0/1/2/3 表示上 / 右 / 下 / 左四个方向的箭头。关卡不写死,每局开始时由 random_grid(箭头数) 随机生成,并保证一定有解:

def random_grid(num_arrows):
    # 反推法:从空棋盘逐个放箭头
    grid = [[None] * 6 for _ in range(6)]
    placed = []
    for _ in range(num_arrows):
        # 找出所有"前方无任何已放箭头"的合法位置+方向
        # (这样新箭头按放置倒序消除时一定能飞)
        ...
        # 优先选能挡住某个旧箭头的位置,制造解谜依赖
        grid[r][c] = d
        placed.append((r, c, d))
    return grid

三关的难度通过箭头数量区分(4 / 6 / 8 个),每局重开或进下一关都会重新随机。

方向用数字 0-3 表示,并映射为行列增量(注意 grid[r][c] 中 r 是行、c 是列):

DIRECTIONS = {
    0: (-1, 0),   # 上:行减 1
    1: (0, 1),    # 右:列加 1
    2: (1, 0),    # 下:行加 1
    3: (0, -1),   # 左:列减 1
}

2. 路径检测(核心算法)

点击 (r, c) 处的箭头后,从下一格开始沿箭头方向逐格前进:遇到其他箭头说明被阻挡,返回 False;到达棋盘边界返回 True:

def path_clear(self, r, c):
    direction = self.grid[r][c]
    if direction is None:
        return False
    dr, dc = DIRECTIONS[direction]
    nr, nc = r + dr, c + dc
    while 0 <= nr < GRID_ROWS and 0 <= nc < GRID_COLS:
        if self.grid[nr][nc] is not None:
            return False  # 被其他箭头阻挡
        nr += dr
        nc += dc
    return True  # 畅通到边界

3. 点击处理与状态切换

def click(self, r, c):
    if self.status != "playing":
        return "invalid"
    if self.grid[r][c] is None:
        return "empty"
    if self.path_clear(r, c):
        self.grid[r][c] = None          # 消除
        if self.remaining_arrows() == 0:
            self.status = "win"         # 通关
        return "fly"
    else:
        self.mistakes_left -= 1         # 碰撞:失误 -1
        if self.mistakes_left <= 0:
            self.status = "lose"        # 失败
        return "collision"

4. 关卡可解性保障

关卡数据不是写死的。我用了随机生成器 random_grid():每局开始都从空棋盘逐个放箭头。为了保证生成的棋盘一定有解,我用 "反推法"—— 放新箭头时要求它自己前方没有任何已放箭头,这样 "按放置倒着消除" 就一定能通关;同时优先让新箭头挡住某个旧箭头,制造 "必须先消新箭头" 的解谜依赖。程序启动时还会用 DFS 求解器对生成器做抽检:

def solve_level(grid, max_depth=20):
    """DFS 回溯:枚举所有当前可飞的箭头,逐个尝试消除。
    返回 (是否可解, 消除顺序)。"""
    g = [row[:] for row in grid]

    def dfs(current, order):
        if all(cell is None for row in current for cell in row):
            return True, order
        if len(order) > max_depth:
            return False, order
        candidates = [(r, c) for r in range(6) for c in range(6)
                      if current[r][c] is not None and can_fly(current, r, c)]
        for r, c in candidates:
            saved = current[r][c]
            current[r][c] = None
            ok, result = dfs(current, order + [(r, c)])
            if ok:
                return True, result
            current[r][c] = saved      # 回溯
        return False, order

    return dfs(g, [])

5. 界面绘制

  • 棋盘:双层矩形绘制(背景色 + 边框),鼠标悬停时高亮;

  • 箭头:ArrowButton.points() 根据方向计算三角形顶点,pygame.draw.polygon 绘制;

  • 碰撞反馈:碰撞的箭头在 30 帧内显示红色 + 随机抖动偏移;

  • 飞出动画:箭头沿方向逐步移动并淡出。


四、AIGC 使用过程

本次开发使用 豆包(Doubao) 作为 AIGC / Coding Agent 工具辅助开发,共记录了 5 次具有代表性的协作过程:

# 子任务 借助何种 AIGC AI 实现或提供了什么 效果如何 人工修改
1 路径检测 豆包 生成四个方向的路径检测代码(path_clear) 运行时发现方向判断错误:"上" 的方向增量写反,导致箭头方向与实际行为不符 人工逐格打印网格定位根因,将方向向量从 (0,-1) 修正为 (-1,0)(行 / 列索引搞混),并补充注释
2 关卡设计 豆包 生成随机关卡的算法(反推法 + 阻挡关系) 初版随机生成器能保证可解,但偶尔产出"所有箭头都能直接飞"的无聊局 人工改进生成器:放新箭头时优先选"能挡住某个旧箭头"的位置,制造顺序依赖;并用 DFS 对每关抽检 20 次验证可解
3 环境搭建 豆包 建议安装 pygame 并给出命令 Python 3.14 无 pygame 预编译包,源码编译时下载 SDL 依赖超时 人工检查系统 Python 版本,改用 Python 3.13 + 清华镜像成功安装 pygame 2.6.1
4 界面与反馈 豆包 生成箭头多边形绘制与碰撞抖动动画 界面能显示,但状态栏爱心符号 ❤ 无法渲染成方框 人工改用中文字体(微软雅黑)加载,并将失误次数改为数字显示 3 / 3
5 界面优化与加难度 豆包 生成深蓝渐变背景、箭头描边、按钮 hover 高亮、圆角结果卡片 视觉明显提升,但按钮上的 ↻、→ 符号又渲染成方框 人工把装饰符号全部换成纯文字("重开""进入下一关"),并把关卡改成每局随机生成(3 关、4/6/8 个箭头难度递增)

代表性过程 1:路径检测的方向 Bug

我向豆包描述需求:" 棋盘网格用 grid[r][c] 表示,请实现 path_clear(r, c) 判断箭头前方是否畅通。"AI 给出的代码结构基本正确(沿方向逐格前进、遇箭头返回 False、到边界返回 True),但方向映射表定义反了:

\# AI 最初生成的(错误):

DIRECTIONS = {0: (0, -1), 1: (1, 0), ...}   # "上"变成了"列减1"

\# 人工修正后:

DIRECTIONS = {0: (-1, 0), 1: (0, 1), ...}   # 上=行减1,右=列加1

这个 bug 很隐蔽:grid 是 "先行后列",而 AI 按 "先列后行" 的习惯定义了增量。我通过打印解析后的网格逐格对照,才发现是方向增量错误,而不是解析错误。这提醒我:AI 生成代码不能直接信任,尤其是二维坐标、索引这类细节,必须用测试验证。

代表性过程 2:不可解的关卡

我让 AI 设计关卡。最初版本是写死的 5 个固定关卡,但 AI 给的第 2 关是 "两个箭头面对面互相阻挡" 的布局——这类布局永远无法清除。后来我干脆改成随机生成:每局开始都从空棋盘放箭头。AI 给出了"反推法"思路(放新箭头时保证它前方没东西挡,倒着消一定能通关),但初版生成器有时产出"所有箭头互不挡、随便点"的无聊棋盘。我改进了生成器:放新箭头时优先选"恰好挡在某个旧箭头前方"的位置,这样既保证有解,又制造了真正的解谜顺序依赖。

心得:游戏关卡不是"摆几个箭头"就行,必须保证存在可行的消除顺序;而随机生成更要有算法兜底,光靠随机摆必然出死局。

心得:游戏关卡不是 "摆几个箭头" 就行,必须保证存在可行的消除顺序。写一个自动求解器来验证关卡可解性,比人肉试玩更可靠。

代表性过程 3:Python 3.14 装不上 pygame

AI 给的安装命令是 pip install pygame,但在我的环境(Python 3.14.7)下 pip 找不到预编译包,转而从源码编译,编译过程需要从 libsdl.org 下载 SDL 依赖,最终超时失败(WinError 10060)。我检查后发现系统还装有 Python 3.13,用 3.13 创建虚拟环境后从清华镜像直接下载到了 cp313 的 pygame 2.6.1 wheel,10 秒安装完成。

心得:AI 给出的 "标准答案" 不一定适配你的环境。遇到依赖问题时,先检查 Python 版本和镜像源,换版本 / 换源往往比硬编译更快。


五、测试结果

我编写了自动化测试脚本 test_logic.py,对核心逻辑(不依赖图形界面)进行测试,共 24 项断言全部通过。其中 T00 是对随机关卡生成器的专项测试:每关配置各随机生成 20 个棋盘,用 DFS 求解器验证全部可解,结果 60/60 全部通过。

编号 测试内容 预期结果 实际结果 是否通过
T00 随机关卡生成器:3 关各随机 20 个棋盘 全部可解(DFS 验证) 60/60 可解 ✅ 通过
T01 点击前方无阻挡的箭头 箭头飞出棋盘并消失 箭头消除,失误次数不变 ✅ 通过
T02 点击前方有阻挡的箭头 箭头不消失,失误次数减 1 箭头保留,失误减 1 ✅ 通过
T03 点击位于边缘且朝向棋盘外的箭头 箭头正常消失,不发生越界错误 正常消除,全棋盘 path_clear 遍历无越界 ✅ 通过
T04 消除本关全部箭头 显示通关并进入下一关 状态变为 win,进入第 2 关(重新随机) ✅ 通过
T05 失误次数耗尽 显示失败并允许重新开始 状态变为 lose,reset() 恢复正常 ✅ 通过
T06 游戏进行中重新开始 箭头布局和失误次数恢复 箭头数量、失误次数、状态全部恢复 ✅ 通过

运行截图:

结果: 24 通过, 0 失败

六、PSP 表格

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

说明:实际耗时普遍超出预估,主要原因是环境安装(Python 3.14 装 pygame 失败)与方向 bug 定位花掉了额外时间。这也说明 PSP 中的预估能力需要多练习。


七、心得体会

1. AI 带来的帮助

  • 写代码提速明显:路径检测、界面绘制、碰撞动画这类 "套路化" 代码,AI 一次就能给出可用版本,节省了大量查文档时间;

  • 帮助理解概念:对于 Pygame 的事件循环、surface 绘制等概念,AI 用中文解释得很清楚;

  • 关卡设计有新思路:AI 给出的关卡虽然有问题,但启发了我的设计思路。

2. 出现的问题

  • AI 代码不能直接信任:方向向量定义反了这个 bug 极具隐蔽性 —— 代码结构、逻辑流程全对,就错在一个坐标增量上。这类 bug 靠 "读代码" 很难发现,必须靠打印中间数据 + 自动化测试;

  • 环境适配比想象中麻烦:Python 版本太新反而装不上第三方库,换版本解决;

  • 关卡可解性需要验证:人眼看着 "差不多" 的关卡,实际可能是死局。

3. 收获

  • 完整走了一遍 "需求分析 → 数据结构设计 → 算法实现 → 界面开发 → 测试验证 → 文档撰写" 的软件开发流程;

  • 学会了用自动化测试验证游戏逻辑(不依赖 GUI 也能测核心规则);

  • 深刻体会到 AIGC 时代的开发模式:AI 生成初稿 + 人工理解验证 + 测试兜底,人仍然是质量的第一负责人。


posted @ 2026-09-22 21:02  LLLqh  阅读(14)  评论(0)    收藏  举报