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

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

项目 内容
这个作业属于哪个课程 H202601 软件工程与软件工程实践(福州大学·计算机与大数据学院)
这个作业要求在哪里 2026 秋软件工程个人作业(第二次)
这个作业的目标 使用 Python 和 AIGC 完成"一箭又一箭"小游戏
学号 032401135
GitHub 仓库 https://github.com/otonash1/arrow-game

一、项目展示

开始界面(游戏标题 + 玩法说明 + 开始按钮):

开始界面

第 1 关(顶部显示关卡名、剩余箭头数、剩余失误次数,底部是"重新开始"按钮):

第 1 关

点击前方被挡住的箭头:箭头变红并抖动,顶部提示"前方有箭头阻挡,不能飞出(失误 +1)",失误次数扣除一次:

点击被挡住的箭头

前方畅通时,箭头沿自己的方向平滑地飞出棋盘,同时逐渐变淡:

箭头飞出的动画

第 2 关(十字形布局,9 支箭头):

第 2 关

第 3 关(6×6 棋盘、20 支箭头,失误上限 4 次):

第 3 关

过关界面(点"下一关"继续):

过关界面

失败界面(失误次数耗尽,可点"重新开始"重试本关):

失败界面

全部通关界面:

全部通关

二、项目介绍

本项目用 Python + pygame 实现了一个"一箭又一箭"风格的点击式箭头解谜小游戏。全部图形都由代码直接绘制,不依赖任何外部图片素材。

运行方式(也可以从 Releases 下载免安装的 ArrowGame.exe,双击直接运行):

pip install -r requirements.txt
python main.py

2.1 游戏规则

  • 棋盘上分布着若干带方向的箭头,方向分为上、下、左、右四种;
  • 点击一个箭头,程序检查它前进方向到棋盘边界之间是否还有其他箭头;
  • 前方无阻挡 → 箭头沿该方向飞出棋盘并被消除;
  • 前方有阻挡 → 箭头不能飞出,变红抖动并弹出提示,失误次数 +1
  • 清空当前关卡的箭头 → 过关并进入下一关;失误次数耗尽 → 本关失败,可以重新开始;
  • 重新开始会把当前关卡恢复成初始状态:飞出的箭头回来、失误次数清零。

2.2 界面设计

游戏共五个界面状态,由一个状态机统一切换:

界面 内容
开始界面 游戏标题、玩法说明、"开始游戏"按钮,ESC 退出
游戏界面 顶部显示当前关卡名、剩余箭头数、剩余失误次数;中间是棋盘;底部是"重新开始"按钮
过关界面 半透明遮罩 + "过关!"提示 + "下一关"按钮
失败界面 "本关失败"提示 + "重新开始"按钮
全部通关界面 完成所有关卡后的提示 + "返回主菜单"按钮

2.3 主要功能与特色

  • 3 个手工设计、逐一验证可通关的关卡,难度渐进:第 1 关 5×5 共 5 支箭头 → 第 2 关 5×5 共 9 支箭头 → 第 3 关 6×6 共 20 支箭头;
  • 飞出动画:箭头沿自身方向平滑滑出棋盘,越飞越淡,动画正好在"刚出界"时结束;
  • 碰撞反馈:被挡住的箭头朝自己的方向抖动一下、变红并套上红色圆环,顶部提示文字停留一段时间后淡出;
  • 纯代码绘制,无外部素材:箭头是代码画的多边形(以朝右为基准形状,按方向旋转得到其余三个方向),中文使用系统自带的微软雅黑字体渲染;
  • 逻辑与界面分离board.py 只负责数据和规则、不导入 pygame,可以脱离窗口单独测试;
  • 棋盘几何自适应:格子大小按关卡尺寸自动缩放,5×5 和 6×6 都能完整显示;
  • 免安装可执行文件:提供打包好的 ArrowGame.exe(GitHub Releases 下载),Windows 下双击即可运行,不依赖 Python 环境。

三、实现思路

3.1 项目结构

main.py       程序入口:初始化 pygame,进入主循环
config.py     全局配置:窗口尺寸、配色、字体、动画参数等常量
levels.py     关卡数据(字符矩阵)
board.py      棋盘与规则逻辑(纯逻辑,不依赖 pygame)
game.py       游戏状态机:场景切换、事件分发、动画推进
renderer.py   绘制层:棋盘、箭头、按钮和各界面
tests.py      T01–T06 自动化测试

模块之间是单向依赖:game.py 调用 board.py 的规则判定,renderer.py 只负责"把数据画出来"、不做任何规则判断。

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

四个方向用枚举表示,枚举值同时就是关卡文件里使用的字符;每个方向自带一个位移 (行增量, 列增量),四个方向就能共用同一套扫描逻辑:

class Direction(Enum):
    UP = "^"
    DOWN = "v"
    LEFT = "<"
    RIGHT = ">"

    @property
    def delta(self):
        return {
            Direction.UP: (-1, 0),
            Direction.DOWN: (1, 0),
            Direction.LEFT: (0, -1),
            Direction.RIGHT: (0, 1),
        }[self]

关卡数据写成字符矩阵,. 是空格,^ v < > 是四个方向的箭头。例如第 1 关:

"grid": [
    ".....",
    "..v..",
    ".<v>.",
    "..v..",
    ".....",
]

这样加关卡就是往 levels.py 里加一组字符串,不需要改动任何逻辑代码。

3.3 路径检测(核心)

整个游戏最关键的判断是"点击的箭头能不能飞出",实现在 Board.can_fly_out() 里:从箭头所在格的下一格出发,沿它的方向一格一格地往边界走;途中碰到任何箭头就返回"被挡住",一路走到棋盘外就返回"可以飞出"

def can_fly_out(self, row, col):
    """判断 (row, col) 上的箭头前方是否没有其他箭头阻挡。"""
    direction = self.arrows.get((row, col))
    if direction is None:
        return False
    delta_row, delta_col = direction.delta
    r, c = row + delta_row, col + delta_col   # 从相邻的一格开始扫,不看自己
    while 0 <= r < self.rows and 0 <= c < self.cols:
        if (r, c) in self.arrows:             # 路上碰到其他箭头 -> 被挡住
            return False
        r += delta_row
        c += delta_col
    return True                               # 一路走到棋盘外 -> 可以飞出

两个细节:

  1. 扫描从相邻的下一格开始,而不是箭头自己所在的格子,否则箭头会把自己当成阻挡物;
  2. 循环条件 0 <= r < rows and 0 <= c < cols 同时限制行和列,因为这个游戏只需要检查同一行/同一列,这个条件也顺便保证了贴着边缘、朝向棋盘外的箭头不会发生数组越界(测试 T03 专门覆盖了这种情况)。

3.4 点击处理与动画反馈

点击流程由 game.py 统一组织:先把点击坐标换算成格子坐标,交给 board.click() 判定,得到三种结果之一:

判定结果 含义 后续处理
FLY_OUT 前方畅通 箭头从棋盘移除,播放飞出动画;若已清空则进入过关
BLOCKED 前方被挡住 失误 +1,箭头变红抖动并提示;若失误耗尽则进入失败
IGNORED 点到空格或棋盘外 不产生任何影响

动画由主循环每帧传入的 dt 推进:飞出动画记录起始位置和方向,按进度沿方向平移并降低透明度;碰撞抖动用正弦函数让箭头朝自己的方向"撞一下再弹回来",配上红色圆环,提示文字停留 1.6 秒后消失。所有时长、幅度参数都放在 config.py 里,方便调整手感。

3.5 关卡设计与可解性验证

设计关卡时遵循两个原则:

  1. 同一行/列上排成一串、方向相同的箭头,总能按"最靠近边界的那支先飞走"的顺序清完;
  2. 要避免两支箭头互相指向(比如同行左边朝右、右边朝左),那样谁也飞不出去,会形成死局。

以第 3 关(6×6、20 支箭头)为例,中间是两条三连的竖链(上半朝上、下半朝下),两侧各接一条横链,相邻箭头全部"背对背",存在多种清空顺序:

..^^..
<<^^>>
..^^..
..vv..
<<vv>>
..vv..

三关的总箭头数、失误上限都不同,但每关都实际验证过存在能把所有箭头清空的点击顺序(该验证后来固化进了自动化测试 T04:对三个关卡都按"能飞就飞"的顺序模拟点击,确认可以全部清空)。

四、AIGC 使用过程

开发全程使用 Trae(内置 Coding Agent 的 AI 编程工具)辅助。下面记录 6 次有代表性的协作过程:

子任务 借助何种 AIGC 技术 AI 实现或提供了什么 效果如何 人工修改
项目骨架与模块划分 Trae 把项目拆成 6 个文件(配置/关卡/棋盘/状态机/绘制/入口),并用 Scene 枚举管理五个场景 骨架一次跑通,开始界面和棋盘正常显示 检查并确认 board.py 不导入 pygame,保证规则逻辑可以脱离窗口单独测试
路径检测 Trae 生成沿方向逐格扫描的阻挡检测代码 四个方向的判定一次通过 确认扫描必须从"相邻的下一格"开始(不能把箭头自己算成阻挡),并补上防止越界的边界条件
中文字体崩溃修复 Trae 给出绕过 SysFont、直接读取系统字体文件的方案 修复崩溃,中文正常显示 把字体路径做成 config.py 里的优先级列表,换机器时可以直接调整
关卡设计 Trae 生成十字形的第 2 关和 6×6 的第 3 关布局 布局基本可用,但初版存在箭头互相指向的死局隐患 手工调整箭头方向改成"背对背"结构,并写脚本按"能飞就飞"的顺序验证每关都能清空
自动化测试 Trae 按 T01–T06 编写 unittest 测试(棋盘规则 3 项 + 完整流程 3 项) 6 项测试全部通过 确认 T04 对三个关卡都跑一遍;T06 除了棋盘层还原,还覆盖界面层点击"重新开始"按钮的路径
打包与发布 Trae 用 PyInstaller 把游戏打包成单文件免安装 exe,并发布到 GitHub Releases 生成的 ArrowGame.exe 约 14.6 MB,双击即可运行,不需要 Python 环境 把生成的 .spec 打包配置提交进仓库,同时在 README 中补充"下载 exe / 自行打包"的说明

几个过程的补充说明:

路径检测是游戏最核心的逻辑,AI 给出的循环结构基本可直接使用,但"从哪里开始扫"这个细节很容易出错——一开始的直觉写法是从箭头自己那一格开始,结果每个箭头都会把自己当成阻挡物。改成从相邻格开始后,再用 T03(贴着边界、朝向棋盘外的箭头)确认了边界处理没有问题。

中文字体崩溃是实际开发中遇到的一个真实 bug:用 pygame.font.SysFont() 加载中文字体时,程序在部分 Windows 上会抛 TypeError(读取系统字体注册表时出错),窗口起不来。AI 建议不再走 SysFont,而是直接用 pygame.font.Font() 读取 C:\Windows\Fonts\msyh.ttc(微软雅黑)字体文件,问题解决。

关卡设计不能只靠 AI 生成就完事:AI 排出来的初始布局里有几处箭头互相指向,一旦玩家点错就会卡死。最终改成"背对背"的布局,并且每一关都用脚本按"能飞就飞"的顺序模拟清空验证过。

五、测试结果

测试采用自动化测试:用 Python 标准库 unittest 编写(tests.py),并在导入 pygame 之前设置 SDL_VIDEODRIVER=dummy,因此在没有真实显示器的环境里也能运行。在项目目录下执行:

python tests.py

覆盖作业要求中的 T01–T06 六项测试:

编号 测试内容 预期结果 实际结果 是否通过
T01 点击前方无阻挡的箭头 箭头飞出棋盘并消失 箭头从棋盘移除,剩余箭头数 -1,失误次数不变 通过
T02 点击前方有阻挡的箭头 箭头不消失,失误次数减 1 箭头留在原地,失误次数 +1,剩余失误数正确 通过
T03 点击位于边缘且朝向棋盘外的箭头 箭头正常消失,不发生越界错误 四个朝外的边缘箭头都能正常飞出 通过
T04 消除本关全部箭头 显示通关并进入下一关 三个关卡都能按"能飞就飞"的顺序清空,清空后进入过关界面 通过
T05 失误次数耗尽 显示失败并允许重新开始 失误记满后进入失败界面,按钮为"重新开始" 通过
T06 游戏进行中重新开始 箭头布局和失误次数恢复 飞出的箭头全部回来、失误清零、回到游戏界面 通过

运行结果:

Ran 6 tests in 0.360s

OK

对应的运行截图(完整输出见仓库 README):

测试运行结果

六、PSP 表格

任务 预估耗时(小时) 实际耗时(小时) 差异(小时)
需求分析与游戏设计 0.5 0.5 0
Python 与图形库学习 0.5 0.3 -0.2
游戏界面实现 1.0 1.2 +0.2
路径与碰撞逻辑实现 1.0 1.0 0
关卡设计 0.5 0.8 +0.3
AIGC 辅助开发 1.0 1.2 +0.2
测试与修改 1.0 0.7 -0.3
开发环境与 Git 配置 0.5 1.5 +1.0
README 与博客撰写 0.5 0.5 0
合计 6.5 7.7 +1.2

实际耗时超出预估的主要在"开发环境与 Git 配置":安装 Git、配置账号与代理、处理 GitHub 认证花费的时间比预想的多。

七、心得体会

这次作业是我第一次用 AIGC 工具从头到尾参与一个小项目的开发,整体感受可以概括为三句话:AI 能把"知道要做什么但写起来慢"的部分变得很快;但 AI 的产出必须自己验证;核心逻辑必须自己能讲清楚。

AIGC 带来的帮助:项目骨架的模块划分、飞出动画的绘制细节、unittest 测试的模板,这些内容 AI 都能在很短时间内给出可用的结果,省下了大量查文档的时间。比如箭头形状按方向旋转的画法、动画进度控制这些 pygame 细节,直接问 AI 比翻文档快得多。

出现的问题

  1. AI 生成的环境相关代码不一定适配自己的机器——中文字体崩溃那次,最后是换了一种字体加载方式才解决,这类问题只能实际运行才能发现;
  2. AI 生成的关卡布局存在"互相指向"的死局隐患,必须自己逐个检查、实际试玩(或者写脚本验证)才能确认可通关;
  3. 动画的手感(时长、抖动幅度)AI 给的默认值不一定合适,需要在 config.py 里反复微调。

收获:对"逻辑与界面分离"有了实际体会——board.py 不碰 pygame,所以六项测试全部可以脱离窗口、在命令行里秒级跑完,改动规则后回归测试非常方便。也因为助教可能对关键代码提问,我在写完每段 AI 生成的代码后都会自己过一遍:为什么路径检测要从相邻格开始扫、为什么循环条件能防止越界、状态机是怎么在五个界面之间切换的。这种"先理解再采纳"的习惯,大概是这次作业里最有价值的部分。

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