Loading

2026 秋软件工程个人作业(第二次)

这个作业属于哪个课程 H202601软件工程与软件工程实践
这个作业要求在哪里 2026秋软件工程个人作业(第二次)
这个作业的目标 使用 Python 和 AIGC 完成「一箭又一箭」小游戏
学号 102401339
GitHub 仓库 homework2/ at renbaoshuo/202601-software-engineering-homework

项目展示

游戏名叫“一箭又一箭”,使用 Python 和 pygame-ce 编写。

视频:https://pan.baidu.com/s/1zt_TC95Yu3E1bh7VoZPnCw?pwd=fzdx

开始界面列出规则,点击“开始游戏”进入第一关。

image

游戏中,棋盘位于中间,上方显示当前关卡、剩余箭头和剩余失误次数,右上角可以重开本关。下图是第三关的初始布局。

image

点击被挡住的箭头时,箭头变色并晃动,下方显示阻挡提示,失误次数减一。

image

清空最后一关后显示全部通关,可以从第一关重玩,也可以返回首页。失误次数用完则显示失败,可以重开当前关卡。

image

image

项目介绍

游戏规则

棋盘由网格组成,每格为空,或放置一个朝上、下、左、右的箭头。玩家用鼠标左键点击箭头所在的格子,让箭头沿自身方向离开棋盘。箭头不能旋转,也不能移动到别的格子。

能否飞出,要看箭头前方到棋盘边界之间有没有其他箭头。中间可以隔着空格,只要路径上还有箭头,就不能消除。阻挡箭头朝向哪里不影响判断。

路径畅通时,箭头飞出并消失。路径被挡住时,箭头留在原位,扣除一次失误机会。每关有三次机会,清空棋盘就通关,用完机会就失败。清除一个箭头后,原先被它挡住的箭头可能就有了出路。

界面和功能

界面使用米白色背景、浅绿色格子和深绿色箭头,橙色用于提示,红色用于碰撞和失误提醒。方向由箭头形状表示。中文字体随项目分发,箭头、按钮和棋盘由程序绘制。

开始页说明规则,游戏页显示棋盘和计数,结果页提供下一关、重玩或返回首页的操作。结果页出现后,底层棋盘不再响应点击。

飞出和碰撞都有动画。动画期间,棋盘忽略后续点击,重开和关闭窗口仍然有效。点击空格、棋盘外区域和已消除的位置不会扣次数,右键和长按也不会触发连续点选。

项目内置三个固定关卡:

关卡 棋盘大小 箭头数量 设计内容
认识阻挡 4 × 4 8 观察边界和箭头之间的阻挡顺序
交叉观察 5 × 5 12 加入隔着空格的阻挡和跨行列的依赖
连续解锁 6 × 6 20 连续解除多组阻挡,增加需要观察的箭头数量

游戏在本地运行,不需要联网。

实现思路

箭头、方向和关卡的表示

规则代码放在 game.py,关卡放在 levels.py,绘制和鼠标事件放在 ui.py。路径判断不依赖图形库,可以单独运行测试。

棋盘坐标使用 (row, col),从零开始。左上角是 (0, 0),向下增加行号,向右增加列号。每个格子用一个字符表示:UDLR 分别表示上、下、左、右,. 表示空格。

第一关的模板如下:

Level(1, "认识阻挡", ("..RD", "U..D", ".R.D", "L..R"))

Level 保存编号、名称、布局和初始失误次数。模板中的布局是字符串元组,开始游戏和重开时再复制成二维列表:

board = [list(row) for row in level.rows]

消除箭头只修改运行中的棋盘,不修改模板。重开时重新复制,就能恢复初始布局。剩余箭头数直接统计棋盘中非空格子的数量,避免棋盘已经改变、计数却忘记更新。

方向转换为行列增量后,四个方向可以共用一段检测逻辑:

方向 编码 行列增量 (dr, dc)
U (-1, 0)
D (1, 0)
L (0, -1)
R (0, 1)

路径检测

路径检测从箭头前方的相邻格开始,沿方向逐格检查到棋盘边界。遇到非空格就返回 False,走出边界仍未遇到箭头就返回 True

项目中的实现如下:

DIRECTIONS = {"U": (-1, 0), "D": (1, 0), "L": (0, -1), "R": (0, 1)}


def can_exit(board: Sequence[Sequence[str]], row: int, col: int) -> bool:
    """从相邻格检查到边界;无效坐标或空格返回 False。"""
    if not board or not 0 <= row < len(board) or not 0 <= col < len(board[0]):
        return False
    direction = board[row][col]
    if direction not in DIRECTIONS:
        return False
    dr, dc = DIRECTIONS[direction]
    row, col = row + dr, col + dc
    while 0 <= row < len(board) and 0 <= col < len(board[0]):
        if board[row][col] != ".":
            return False
        row, col = row + dr, col + dc
    return True

例如,某一行是 R..U。点击最左侧的 R 时,检测会经过两个空格,再遇到 U,因此判定为阻挡。这里的 U 虽然朝上,但仍占据了向右离开的路径。等它被消除,这一行变成 R...,再次点击 R 就可以飞出。

检测不能只看相邻格,也不能从箭头自身开始,否则会漏掉远处的阻挡,或把自己当成阻挡。另一处要注意的是先检查边界,再读取格子。Python 允许负索引,如果先读取 board[-1],上边界就可能被误读成最后一行。

这个问题只需要沿一条直线检查,不涉及转弯和寻找其他路线。一次检测的时间复杂度为 \(O(\text{max(行数, 列数)})\),额外空间复杂度为 \(O(1)\)

关卡可解性

验证关卡时,程序复制一份棋盘,找出当前可以飞出的箭头并删除,再继续查找。能清空就说明可解;如果棋盘还有箭头,却找不到任何可以飞出的箭头,就说明存在无法解除的阻挡。

这里可以采用贪心检查,因为删除箭头只会减少阻挡,不会制造新的阻挡。一个原本可以飞出的箭头,不会因为别的箭头被删除而变得不能飞出。因此不需要枚举所有点击顺序。

三个内置关卡都通过了这个检查,PRD 中给出的三组通关顺序也通过了独立测试。这个求解函数用于开发验证,游戏界面没有提供自动求解按钮。

动画和状态切换

程序区分开始、游戏中、动画中、单关通关、失败和全部通关六种状态。同一时刻只处理一个棋盘动作。

成功点击后,程序先播放飞出动画,结束时才把原格子改成 .。如果这时剩余箭头为零,再进入通关状态。碰撞则在点击时扣一次机会,反馈动画结束后判断是否失败。这样可以让计数和结果页面跟随动作变化。

动画按经过的时间更新。飞出动作持续 0.32 秒,碰撞反馈持续 0.26 秒,播放期间不使用休眠阻塞事件循环。重开会丢弃当前动作,再加载本关模板,旧动作不会继续修改新棋盘。

开发时还修复过一个事件顺序问题:动画即将结束时,队列里可能已经有重复点击。如果主循环先结束动画,再处理点击,旧点击就会被当成新操作。现在每帧先按当前状态处理事件,再推进仍然有效的旧动作。遇到重开或新动作时,也不把上一帧的耗时加到新动作上。

AIGC 使用过程

第一次:把作业要求整理成 PRD

我提供作业页面,要求 Codex 读取题目,把用于实现的 PRD 写入 homework2/PRD.md

Codex 整理了游戏规则、界面、状态、数据结构、关卡和测试要求,并把题目规定与补充约定分开。例如,每关三次失误、动画期间忽略点击、最后一关结束后的按钮行为,都在文档中写明。它还给出三份关卡布局,并用脚本检查通关顺序。

这一轮的产出是一份可以直接交给实现任务的需求文档。关卡解序验证只能说明布局可解,当时还没有游戏程序。

第二次:让 Agent 按 PRD 实现和测试

我的要求是按照 PRD 实现游戏。

主 Agent 负责图形界面、资源和集成,子 Agent 负责关卡、路径检测、状态机和规则测试。之后又有子 Agent 补充鼠标事件和界面流程测试。各部分使用约定好的接口,例如用秒推进动作、动画结束后删除箭头、动画期间忽略棋盘点选。

开发先通过了 12 项关卡和路径测试,接入状态机后有 33 项规则测试,加入第一批界面测试后达到 52 项。代码按计划、路径、状态、界面等阶段提交。

这一轮中,我提供了实现范围、测试要求和提交要求,Agent 负责代码编写、运行命令和集成。

第三次:检查画面并修复遮挡

实际使用过程中,发现第二关和第三关的棋盘会遮住上方的剩余箭头数字。

Agent 调整了状态区,把数字改为横向排列,又加入回归测试:渲染各关的状态文本,检查文字矩形是否与棋盘相交。

发现原有测试能够验证程序绘制时不报错,却没有检查文字是否被遮住。于是让 Agent 补上这项检查,同类布局问题也有了自动化约束,不会再次发生。

第四次:审查动画结束帧的重复点击

使用过程中发现,碰撞动画接近结束时,如果队列里还有重复点击,主循环可能先结束动画,再把点击当作新动作,导致失误次数从 2 变成 1。于是让 Agent 修复。

Agent 先通过真实 run() 事件循环编写回归测试,在修复前复现问题。随后将主循环和截图脚本统一接入 step(events, dt),先处理输入,再推进仍然有效的旧动作。

修复后,飞出和碰撞两类结束帧测试都通过,并补充了首次点击、重开后新动作和退出后的计时检查。最终共有 57 项测试通过。

测试结果

运行环境是 macOS 15.7.9(Apple Silicon)、CPython 3.14.7、pygame-ce 2.5.8 和 SDL 2.32.10。

homework2/ 目录下执行:

python -m unittest discover -s tests -v

开发验收时共通过 57 项测试,其中规则与关卡测试 33 项,界面与事件循环测试 24 项。本次整理文章时再次运行,57 项全部通过。下面按 PRD 的测试项目归纳,同一项目可能对应多个测试方法。

编号 测试项目 预期结果 实际结果 是否通过
T01 点击前方无阻挡的箭头 飞出后消除,失误次数不变 动画结束前保留箭头,结束后只删除一次,数量减一 通过
T02 点击被阻挡的箭头 箭头保留,反馈碰撞,扣一次机会 箭头变色、晃动,位置不变,失误次数减一 通过
T03 点击四条边朝外的箭头 正常飞出,不越界 四个方向均能消除,没有负索引误读 通过
T04 清空非末关并进入下一关 显示通关,加载下一关初始状态 布局、箭头数和失误次数正确 通过
T05 点击阻挡箭头直到机会用完 反馈结束后失败,锁定棋盘,可重开 次数降为零后显示失败,重开仍为当前关 通过
T06 消除箭头、扣次后重开 恢复本关布局和计数,清除动作 布局、计数和动作均恢复 通过
T07 四方向的相邻阻挡和隔空格阻挡 路径上任何箭头都构成阻挡 没有漏查远处箭头,阻挡箭头朝向不影响结果 通过
T08 在身后或其他行列放置箭头 不构成阻挡 均未误判 通过
T09 点击空格、外部、已消除格,或使用右键、长按 不改变棋盘和次数 无无效扣次或连续动作 通过
T10 动画中及结束帧处理重复点击 只执行首次有效动作 修复事件顺序后,没有重复删除或扣次 通过
T11 两种动画中重开,再点击新局箭头 旧动作及旧帧耗时不影响新局 新局布局完整,新动作计时正确 通过
T12 消除阻挡后再点击原箭头 按当前棋盘重新判断 路径畅通后能够飞出 通过
T13 验证三关解序、自动通关及本人试玩 三关可清空,并完成本人试玩 解序、求解检查和窗口自动通关通过;本人试玩也通过 自动化通过,本人试玩也通过
T14 最后一关通关后重玩或回首页 不访问不存在的下一关 两个按钮分支均正常 通过
T15 点击棋盘边界和格子分界线 左上边界包含,右下边界不包含,每次最多命中一格 命中结果符合约定 通过
T16 重复重开和切换关卡 关卡模板不受运行过程影响 每次加载结果一致,模板未被修改 通过
T17 新环境安装、异目录启动、检查中文和布局 依赖与资源完整,文字可见 仅安装运行依赖即可启动,字形齐全,状态文字未被棋盘遮挡 通过(macOS)
T18 在各状态和动画中关闭窗口 正常退出,不等待动画 事件循环停止,显示和字体资源释放 通过

界面自动化使用 SDL 虚拟显示器。开发验收还在 macOS 原生窗口中通过事件队列完成了三关、失败、重开和返回首页。

PSP 表格

本表沿用开发时的记录,统计 AI 协作经过的时间。预计时间在功能开发前填写,实际时间按主要活动划分并取整到分钟。主 Agent 和子 Agent 有并行工作,重叠时间只计一次。

阶段 预计耗时(分钟) 实际耗时(分钟) 差异(实际 − 预计)
需求、环境和任务拆分 5 2 -3
规则、关卡与测试;界面并行开发 20 6 -14
集成、自动化验收与画面检查 15 6 -9
README、截图和测试记录 10 3 -7
合计 50 17 -33

记录区间为 2026 年 9 月 13 日 18:50—19:07(UTC+8)。前期 PRD 编写、本人学习、审阅、试玩,以及本篇博客的整理和复测均不在这 17 分钟内。

实际耗时低于预计,主要是规则和界面同时开发,测试使用标准库,画面使用 Pygame 基本图形绘制。集成期间仍花了时间处理计数遮挡和结束帧重复点击。由于没有记录人工独立实现同一项目的用时,这组数字不能直接换算成 AI 带来的效率倍数。

心得体会

这次开发中,我主要做的是给出目标和约束。先让 Codex 根据作业生成 PRD,再补充文字规范、提交格式和测试要求,之后把实现交给 Agent。拆分任务、编写代码、运行测试、修复问题和整理记录,都由 Agent 接着执行。我不需要把每个函数拆成问题,逐段拿到代码后再复制进项目。

这就是我在这次作业中采用的 Agent 驱动开发。人给出方向、范围和完成标准,Agent 组织实现过程。对这个项目来说,我可以把主要精力放在指导上,不必亲手完成每一行代码和每一次命令操作。

指导也需要具体内容。只有「做一个箭头游戏」,很多行为仍然没有确定:动画中能不能继续点击,机会什么时候扣除,重开是否保留当前关卡,最后一关结束后去哪里。这些约定写进 PRD 后,Agent 才有依据实现,测试也有依据判断。我的后续要求同样写回文档,避免只留在某一轮对话里。

并行 Agent 在这次开发中承担了不同工作。规则和关卡可以先实现,不必等待窗口完成;界面依照接口接入;测试再检查两者如何一起运行。主 Agent 负责协调接口和集成。分阶段提交则留下了需求拆分、路径检测、状态机、界面和修复的过程,方便查看某个改动从哪里开始。

AI 生成的实现也出现了问题。大棋盘遮住计数时,已有测试仍然通过,因为它们没有检查文字与棋盘的位置关系。动画结束帧的重复点击也没有被最初的测试覆盖。后来 Agent 继续检查画面和事件循环,才找到并修复这些问题。这让我看到,给 Agent 的任务需要包含运行、检查和修复,不能只停在代码生成。

这两次修复也说明了测试该怎么补。布局问题要检查实际绘制的文字矩形;事件队列问题要经过真实主循环。发现问题后,先让测试复现,再看修复是否使它通过,比单独增加几个正常点击案例更有用。测试数量能说明做了多少检查,具体检查了什么,仍要看测试内容。

人的工作还包括决定是否接受结果。Agent 可以给出三关的解序、自动通关记录和截图,我仍需要理解路径为什么这样判断,查看功能是否符合要求,并补上本人试玩。把代码实现交给 Agent 后,这些判断没有消失。

以后做类似项目,我会继续先明确需求和验收条件,让 Agent 连同实现、测试和问题修复一起完成。反馈时给出操作步骤、预期和实际结果;能写进文档的约定就写进去,能复现的问题就留下测试。这样我可以少花时间传递代码,把时间用在说明需求和判断结果上。

posted @ 2026-09-13 23:34  宝硕  阅读(79)  评论(0)    收藏  举报