软件工程第二次作业

软件工程第二次个人作业——“一箭又一箭”小游戏

项目 内容
这个作业属于哪个课程 2026-01 软件工程与软件工程实践
这个作业要求在哪里 第二次个人作业
这个作业的目标 使用 Python 和 AIGC 完成“一箭又一箭”小游戏
姓名、学号 102401614 林志涛
GitHub 仓库 arrow_game

一、项目展示

1. 开始界面

image

2. 游戏界面

image

3. 碰撞反馈

image

4. 通关与失败界面(游戏通关新增星级评分模块)

image

image

5.扩展提示模块

image

二、项目介绍

“一箭又一箭”是一款点击式箭头解谜游戏。棋盘中放置了朝上、下、左、右四个方向的箭头。玩家需要观察箭头的朝向和相互阻挡关系,选择正确的消除顺序,最终清空棋盘。

  • 前方没有其他箭头时,被点击的箭头会沿自身方向飞出棋盘。
  • 前方存在其他箭头时,箭头会变红并回弹,同时扣除一次失误机会。
  • 正确消除使用箭头飞出的短促“嗖”声,错误碰撞使用低沉警示音,玩家无需看文字也能区分操作结果。
  • 清空当前关卡即可通关;失误机会耗尽则挑战失败。
  • 游戏包含四个难度递增且经过求解器验证的关卡。
  • 游戏添加星级评分以及提示模块以提高可玩性。
  • 支持重新开始、返回主页以及 REsc 快捷键。

游戏使用 960×720 窗口和米白色暖色主题,所有图形由 Pygame 直接绘制,两种音效由代码合成,不依赖外部图片或音频素材;音频设备不可用时会静默降级。

三、实现思路

1. 方向与关卡的数据表示

关卡使用字符串元组保存,每个字符对应一个格子:UDLR 分别代表上、下、左、右,. 代表空格。方向枚举同时提供行列增量,四个方向可以共用同一套路径算法。

class Direction(str, Enum):
    UP = "U"
    DOWN = "D"
    LEFT = "L"
    RIGHT = "R"

    @property
    def delta(self) -> tuple[int, int]:
        return {
            Direction.UP: (-1, 0),
            Direction.DOWN: (1, 0),
            Direction.LEFT: (0, -1),
            Direction.RIGHT: (0, 1),
        }[self]
@dataclass(frozen=True)
class Level:
    name: str
    grid: tuple[str, ...]
    max_mistakes: int

例如第一关的布局如下。RU 在第一行,DL 在第三行,中间五格为空:

Level(
    name="第一关 · 初窥门径",
    grid=(
        "R.U",
        "...",
        "D.L",
    ),
    max_mistakes=3,
)

程序加载关卡时会把不可变字符串复制成二维列表,游戏过程只修改副本,因此重新开始时可以从原始模板完整恢复棋盘。

2. 路径检测算法

路径检测是游戏的核心。程序先取得箭头方向,从相邻的下一格开始,沿 (dr, dc) 一直扫描到边界。只要途中发现非空格子,就说明路径被挡住;循环自然结束则表示箭头可以飞出。

def has_clear_path(self, row: int, col: int) -> bool:
    direction = self.direction_at(row, col)
    if direction is None:
        return False

    dr, dc = direction.delta
    row += dr
    col += dc

    while self._inside(row, col):
        if self.board[row][col] != ".":
            return False
        row += dr
        col += dc
    return True

这里有三个重要细节:

  1. 检查的是直到边界的整条路径,而不只是相邻格;隔着多个空格的箭头同样会形成阻挡。
  2. 每次访问棋盘前先调用 _inside 判断坐标,因此不会出现数组越界。
  3. 位于边缘且朝向棋盘外的箭头,第一步就会离开棋盘,循环体不会执行,会直接返回 True

单次检测最多扫描一行或一列,时间复杂度为 O(max(rows, cols)),空间复杂度为 O(1)

3. 点击处理、失误和状态切换

规则层将一次点击归纳为三种结果:成功消除 REMOVED、发生碰撞 BLOCKED、无效点击 IGNORED。界面层只需要根据返回结果选择动画和音效,不需要重复实现游戏规则。

def attempt_move(self, row: int, col: int) -> MoveResult:
    if self.phase is not GamePhase.PLAYING:
        return MoveResult.IGNORED

    direction = self.direction_at(row, col)
    if direction is None:
        return MoveResult.IGNORED

    if not self.has_clear_path(row, col):
        self.mistakes_remaining -= 1
        if self.mistakes_remaining <= 0:
            self.mistakes_remaining = 0
            self._freeze_timer()
            self.phase = GamePhase.FAILED
        return MoveResult.BLOCKED

    self.board[row][col] = "."
    if self.remaining_arrows == 0:
        self._freeze_timer()
        self.phase = GamePhase.LEVEL_COMPLETE
    return MoveResult.REMOVED

成功时直接把对应格子改为 .,剩余箭头数通过扫描棋盘实时计算,避免单独维护计数器造成状态不同步。失败或通关时会冻结计时,使结果界面的时间不再继续增加。

游戏流程由状态枚举统一管理:

class GamePhase(str, Enum):
    MENU = "menu"
    PLAYING = "playing"
    LEVEL_COMPLETE = "level_complete"
    FAILED = "failed"
    ALL_COMPLETE = "all_complete"

4.动画与连续点击优化

界面层将每个视觉效果保存为独立的 Animation 对象,而不是用一个全局动画锁。成功消除后,规则层已经移除了箭头,飞出动画则使用保存的坐标和方向继续绘制,因此玩家可以立刻点击下一个箭头。每一帧只过滤已经播放完的动画,其余动画可以同时存在,飞出动画使用二次缓动 progress²,使箭头逐渐加速离开;碰撞动画使用正弦函数完成前冲与回弹,并叠加高频小幅抖动。正常箭头统一为绿色,碰撞动画中的箭头临时绘制为红色。

5.DFS 关卡求解器与难度验证

手工设计的箭头布局可能出现互相阻挡的死局,因此项目实现了 DFS 求解器。每一步枚举当前能够飞出的箭头,模拟移除后递归搜索;如果棋盘清空就返回点击序列,如果某个局面无解则记录到 failed 集合,避免重复搜索。

6.扩展模块创建

关卡完成后,根据游戏状态中保存的 stars_earned 星级结果,在结算界面绘制最多 3 颗星。界面层通过循环依次生成三个星形,并判断当前序号是否小于获得的星数:满足条件时绘制为金色实心星,否则绘制为空心星。
界面为每关设置提示功能,通过 hinted_cell 保存当前被提示的格子。玩家点击“提示”按钮后调用 use_hint(),再由游戏逻辑层的 request_hint() 返回一个当前可以正确点击的箭头位置。获得位置后,界面使用黄色矩形和周期变化的亮度对该格子进行动态高亮,使玩家能够快速识别提示目标。

四、AIGC 使用过程

子任务 使用的 AIGC 提出的要求 AI 提供的内容 当前效果 人工修改
架构设计 Codex 使用 Python 3.13 + Pygame,规则层和界面层分离 设计 GameState、关卡模型、移动结果、状态机、动画和求解器接口 便于独立测试规则 对界面层进行了人工优化
游戏实现 Codex 实现三关、飞出/碰撞动画、失误、重开与完整流程 生成核心逻辑、Pygame UI、系统字体回退和键盘操作 实现了这三个主要逻辑功能 对界面颜色以及箭头数量进行了优化
区分操作音效 Codex 为正确操作和错误操作增加不同声音 使用代码合成箭头“嗖”声与低沉错误音,并增加静默降级和调用测试 根据试听反馈,将最初的上扬音改为更符合飞出动画的气流音 对呈现的音效效果进行了调整
界面与棋盘调整 Codex 将界面改为米白色,并让三关分别为 9、16、25 格且留出更多空格 重设背景、面板、文字、按钮和箭头配色;增加棋盘格数与空格数量测试 三关分别保留 5、10、17 个空格 将颜色调整至理想效果
连续点击优化 Codex 消除箭头后应能立刻点击另一个箭头 将单一动画和全局输入锁改为多动画并行;仅防止同一碰撞箭头重复扣分 可连续快速消除,飞行动画不会相互中断 对并行实现进行了理解优化
箭头颜色统一 Codex 所有箭头改为绿色,碰撞时变红 四个方向统一使用绿色;碰撞箭头临时切换为红色并前冲抖动、回弹 正常与碰撞状态通过绿/红直接区分 删除AI加入的冗余黑黄冲突的可视化
高难度扩展关 Codex 在三个基础关之后增加一个更难的关卡 生成并筛选 8*8布局,使用 DFS 验证可解,并限制开局只有两个合法选择 求解序列包含多个唯一选择步骤,难度高于前三关 进行了试玩并评定难度系数
添加星级评分和提示模块 Chatgpt 为游戏增加关卡星级评分和提示功能,提高反馈性与可玩性 根据通关用时和失误情况设计星级评价,并生成三星显示界面;同时增加提示按钮,通过当前棋盘状态寻找一个可正确点击的箭头并进行黄色高亮 通关后可显示对应星级,提示按钮可显示剩余次数并高亮可行箭头 调整星级评分策略

五、测试结果

测试环境:Windows 11、Python 3.13、Pygame 2.6.1、pytest 8.4.2。

编号 测试内容 预期结果 实际结果 是否通过
T01 点击前方无阻挡的箭头 飞出棋盘并消失 与预期一致 通过
T02 点击前方有阻挡的箭头 保持原位,失误减一 与预期一致 通过
T03 点击边缘且朝外的箭头 正常消除,无越界异常 与预期一致 通过
T04 消除本关全部箭头 显示通关并可进入下一关 与预期一致 通过
T05 失误次数耗尽 显示失败并允许重开 与预期一致 通过
T06 游戏中重新开始 布局、失误和计时恢复 与预期一致 通过

六、PSP 表格

任务 预估耗时(小时) 实际耗时(小时) 差异(小时)
需求分析与游戏设计 0.5 0.5 0
Python 与 Pygame 学习 1.5 2 +0.5
游戏界面实现 0.5 0.5 0
路径与碰撞逻辑实现 2 1 -1
关卡设计 1.5 1.5 0
AIGC 辅助开发 1 1 0
测试与修改 2 2 0
README 与博客撰写 1 1 0
合计 10 9.5 0

七、心得体会

这次作业是我第一次比较完整地使用 AIGC 参与一个小游戏从需求分析到测试交付的全过程。刚开始拿到题目时,我对需要实现哪些界面、怎样表示箭头、如何判断阻挡并没有完整思路。AIGC 能在我完全没有头绪时为我理出思路。

AIGC 生成的结果并不是一次就完全符合要求,实际试玩检验仍然非常重要。最初箭头飞出动画播放时会锁定全部输入,导致我无法立即点击下一个箭头。发现这个问题后,我要求将单一动画改为动画列表,使多个飞出动画能够并行播放,同时只限制同一个正在碰撞的箭头,防止重复扣除失误次数。音效也经过了调整:最初正确操作使用普通上扬提示音,但和箭头飞出的动作不够贴合,后来改成由滤波噪声和扫频合成的“嗖”声。界面颜色同样经历了多次修改,最终使用米白色背景、绿色正常箭头和红色碰撞箭头,使正常状态和错误状态更加直观。这个过程让我认识到,AI 给出的方案只能作为初稿,最终效果必须由人实际体验和判断。

在这个过程中我也深刻体会到了git作为管理工具的重要性,它可以让我在项目之外开拓一个分支,当我对这些分支功能进行测试优化之后确定上线之后再"push",保证版本处于我们能控制的范围内。这也为我对后面的小组作业提供了思路和最基本的方法论。

完成这次作业后,我对软件工程的认识不再只是“把代码写出来”。一个可以提交的项目还需要需求拆分、结构设计、版本管理、自动化测试、文档和真实的运行验证。AIGC 的优势是可以快速提供思路、代码框架和测试用例,提高开发效率;但我们仍然需要理解代码、发现实际体验中的问题,并判断修改是否真正满足需求。今后使用 AIGC 时,我会继续把它作为协作和检查工具,而不是直接接受所有生成结果,并通过测试、试玩和有意义的 Git Commit 保证每一次修改都可验证、可追踪。

八、运行方式

py -3.13 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe main.py

运行自动化测试:

.\.venv\Scripts\python.exe -m pytest -v

九、commit记录

image

posted @ 2026-09-19 09:49  |llin  阅读(21)  评论(0)    收藏  举报