项目 内容
这个作业属于哪个课程 202601 软件工程班级博客
这个作业要求在哪里 第二次个人作业要求
这个作业的目标 使用 Python 和 AIGC 完成"一箭又一箭"小游戏
学号 102401402
GitHub 仓库 https://github.com/Anna-khalil/ArrowAfterArrow

一、项目展示

1.1 开始界面

游戏启动后进入开始界面,包含游戏标题"一箭又一箭"、副标题"看清方向 · 找到出口",以及三个功能按钮:开始游戏、关卡选择、继续游戏。底部附有操作说明。

1.2 游戏界面

游戏界面顶部为 HUD 区域,显示当前关卡名称、剩余箭头数量、用时和剩余失误次数(心形图标)。中部为游戏棋盘,箭头按方向着色(上=绿、下=蓝、左=橙、右=紫)。底部为功能按钮区:重新开始、撤销、提示、AI 求解、返回。

1.3 通关界面

通关后显示星级评价(1-3星)、得分、用时和剩余失误,提供"下一关""重玩""返回主页"按钮。

1.4 关卡选择界面

以 5 列网格展示 10 个关卡,未解锁的关卡显示锁图标。通关后自动解锁下一关。

1.5 关卡可解性验证

使用 AI 求解器自动验证所有 10 个关卡均存在合法通关顺序。


二、项目介绍

2.1 游戏规则

"一箭又一箭"是一类点击式箭头解谜游戏。棋盘中包含若干带方向的箭头,方向分为上、下、左、右四种。玩家点击某个箭头后,程序检查该箭头前进方向上的路径:

  • 如果箭头与棋盘边界之间没有其他箭头阻挡,该箭头飞出棋盘并被消除;
  • 如果路径上存在其他箭头,该箭头不能被消除,并通过晃动、变色提示碰撞,同时消耗一次失误机会;
  • 清除本关全部箭头后进入下一关;
  • 失误次数(每关 3 次)耗尽时本关失败,可重新开始。

2.2 界面设计

采用深色主题(深蓝灰背景),四方向箭头使用不同颜色便于快速识别。界面分为三个主要区域:顶部 HUD(关卡信息)、中部棋盘(游戏主体)、底部按钮(操作功能)。布局简洁,操作直观。

2.3 主要功能与特色

类别 功能
基础 四方向箭头、路径检测、飞出动画、碰撞反馈、失误机制、10 关卡、关卡切换、重新开始
附加 撤销、智能提示(AI 最优下一步)、AI 自动求解演示、关卡选择与解锁、存档系统、计时与得分、星级评价、继续游戏

核心特色:内置基于 DFS 回溯 + 记忆化的 AI 求解器,不仅能自动求解任意关卡,还能为"提示"功能提供最优下一步,而非简单地随机选择一个可消除箭头。


三、实现思路

3.1 箭头、方向和关卡的表示

箭头:使用 Arrow 类表示,核心属性为网格坐标 (row, col) 和方向 direction(字符串 'up'/'down'/'left'/'right')。箭头同时维护动画状态(飞行、晃动、提示高亮)。

方向:使用字典 DIRECTIONS 将方向字符串映射为位移元组 (dx, dy),其中 dx 表示列变化、dy 表示行变化:

DIRECTIONS = {
    'up': (0, -1), 'down': (0, 1),
    'left': (-1, 0), 'right': (1, 0),
}

关卡:每个关卡是一个字典,包含关卡编号、名称、棋盘行列数、最大失误次数和箭头布局列表。箭头布局中每个元素为 (row, col, direction) 元组。共设计 10 个关卡,箭头数量从 5 个递增到 22 个,棋盘从 5×5 扩大到 9×9。所有关卡均通过 AI 求解器验证存在合法通关顺序。

3.2 路径检测方法(核心算法)

路径检测是本作业的核心逻辑,对应评分标准中 25 分的"箭头与路径判断"。

算法思路:对于被点击的箭头,从其所在位置的下一格开始,沿箭头方向逐格扫描,直到超出棋盘边界。如果扫描过程中遇到任意其他箭头,则说明有阻挡,返回 False;如果一直扫描到边界都没有遇到箭头,则返回 True

关键代码board.pyis_path_clear 方法):

def is_path_clear(self, arrow):
    r, c = arrow.row, arrow.col
    dx, dy = DIRECTIONS[arrow.direction]
    # dx 影响列(col),dy 影响行(row)
    nr, nc = r + dy, c + dx
    while 0 <= nr < self.rows and 0 <= nc < self.cols:
        if self.grid[nr][nc] is not None:
            return False
        nr += dy
        nc += dx
    return True

边界处理:当箭头位于棋盘边缘且朝向棋盘外时(如最右列的朝右箭头),循环的初始位置 (nr, nc) 已经超出棋盘范围,循环一次都不执行,直接返回 True。这保证了边缘朝外的箭头可以正常飞出,不会发生数组越界错误。

坐标对应关系:开发中最容易出错的点是方向向量与网格坐标的对应关系。direction=(dx, dy) 中,dx 对应列的变化量,dy 对应行的变化量。因此下一格的坐标为 nr = r + dy, nc = c + dx,而非直觉上的 nr = r + dx, nc = c + dy

3.3 AI 求解器

AI 求解器使用 DFS(深度优先搜索)回溯 + 记忆化(memoization) 算法。

算法思路

  1. 如果棋盘已无箭头,返回空列表(通关);
  2. 获取所有当前可消除的箭头;
  3. 依次尝试消除每个可消除箭头(临时移除),递归求解剩余棋盘;
  4. 如果递归返回有效解,则将当前箭头加入解的开头并返回;
  5. 如果所有尝试都失败,返回 None(无解)。

记忆化优化:将棋盘状态序列化为不可变元组(所有箭头的 (row, col, direction) 排序后组成的元组)作为 key,缓存该状态的求解结果。避免重复计算相同棋盘状态,大幅提升求解效率。

关键代码solver.py):

def _dfs(self, board):
    if not board.has_arrows():
        return []
    key = board.get_state_key()
    if key in self.memo:
        return self.memo[key]

    for arrow in board.get_clear_arrows():
        board.remove_arrow(arrow)
        result = self._dfs(board)
        board.add_arrow(arrow.row, arrow.col, arrow.direction, arrow=arrow)
        if result is not None:
            solution = [(arrow.row, arrow.col, arrow.direction)] + result
            self.memo[key] = solution
            return solution

    self.memo[key] = None
    return None

求解器有两个应用场景:

  • 智能提示:调用求解器获取完整通关步骤,取第一步作为提示,高亮对应箭头;
  • AI 自动求解:获取完整步骤后,每隔 0.55 秒自动执行一步,演示通关过程。

3.4 撤销与存档

撤销:使用历史栈(列表)实现。每次成功消除箭头前,将当前棋盘的箭头状态和失误次数保存为快照压入栈中。撤销时弹出栈顶快照,重建棋盘和失误次数。

存档:使用 JSON 文件保存游戏进度,包括已解锁关卡、当前关卡索引、各关卡最高分、最短用时和最高星级。通关后自动更新存档并解锁下一关。游戏启动时读取存档,"继续游戏"按钮可从上次进度继续。


四、AIGC 使用过程

开发过程中主要使用 Cursor(AI 代码编辑器)和 DeepSeek(代码推理与答疑)两款 AIGC 工具。以下记录 4 次具有代表性的使用过程。

记录 1:路径检测算法生成与坐标调试

项目 内容
子任务 四方向路径检测算法
借助何种 AIGC Cursor
AI 实现或提供了什么 输入需求后,Cursor 生成了 is_path_clear 方法的完整代码,包括方向向量定义和逐格扫描逻辑
效果如何 代码结构清晰,但方向向量与网格坐标的对应关系写反了(nr = r + dx, nc = c + dy),导致向上/向下检测时实际扫描的是左右方向,测试时边缘箭头判定错误
人工修改 修正坐标对应关系为 nr = r + dy, nc = c + dx,并补充了 4 个方向的边界测试用例,验证通过

记录 2:AI 求解器递归溢出问题修复

项目 内容
子任务 AI 自动求解器调试
借助何种 AIGC DeepSeek + Cursor
AI 实现或提供了什么 先在 DeepSeek 中询问"箭头消除游戏如何用回溯算法求解",得到 DFS 思路和伪代码;然后在 Cursor 中生成具体 Python 实现
效果如何 基础 DFS 可以求解小关卡(5×5),但在第 6 关开始出现 RecursionError: maximum recursion depth exceeded。排查发现:回溯恢复时 add_arrow 创建了新的 Arrow 对象,导致后续 remove_arrowself.grid[arrow.row][arrow.col] is arrow 的身份检查失败(grid 中是新对象,不是旧对象),箭头没有被真正移除,递归在原地循环
人工修改 1. 修改 add_arrow 方法,增加 arrow=None 参数,允许传入已有对象;2. 回溯时调用 add_arrow(..., arrow=arrow) 恢复原对象引用;3. 去掉 remove_arrow 中的 is 身份检查,改为按位置直接清空 grid,提升健壮性。修复后第 6-10 关求解正常

记录 3:关卡不可解问题与反向构造法

项目 内容
子任务 10 个关卡设计与验证
借助何种 AIGC DeepSeek
AI 实现或提供了什么 手动设计前 5 关并验证可解后,第 6-10 关手动设计出现 5 关全部无解(求解器返回 None)。询问 DeepSeek"如何保证随机生成的箭头关卡一定可解",AI 给出了反向构造法思路:从空棋盘开始,每次添加一个当前可消除的箭头,添加顺序的反向即为合法消除顺序
效果如何 基于该思路编写了随机关卡生成器(固定随机种子保证可复现),自动生成第 6-10 关;生成后用求解器逐一验证,全部 10 关均确认存在合法通关顺序
人工修改 调整了生成器的棋盘大小和箭头数量参数(第 10 关为 9×9、22 个箭头),确保难度递增合理

记录 4:测试脚本路径问题

项目 内容
子任务 关卡可解性测试脚本
借助何种 AIGC Cursor
AI 实现或提供了什么 生成了 verify_levels.py 测试脚本,自动遍历所有关卡调用求解器验证
效果如何 在项目根目录运行正常,但将脚本移到 tests/ 子目录后运行报 ModuleNotFoundError: No module named 'board',因为 sys.path 只加了脚本所在目录而非项目根目录
人工修改 sys.path.insert 的路径从 os.path.dirname(os.path.abspath(__file__)) 改为上两级目录 os.path.dirname(os.path.dirname(os.path.abspath(__file__))),修复后在 tests/ 目录下正常运行

五、测试结果

5.1 功能测试(T01-T06)

编号 测试内容 预期结果 实际结果 是否通过
T01 点击前方无阻挡的箭头 箭头飞出棋盘并消失,剩余箭头数 -1 箭头沿方向飞出并缩小消失,剩余箭头数正确减少 通过
T02 点击前方有阻挡的箭头 箭头不消失,失误次数 -1,有碰撞反馈 箭头变红并左右晃动后回到原位,失误心形 -1 通过
T03 点击边缘且朝向棋盘外的箭头 箭头正常消失,不发生越界错误 最右列朝右、最左列朝左、最上行朝上、最下行朝下的箭头均正常飞出,无异常 通过
T04 消除本关全部箭头 显示通关界面,可进入下一关 最后一个箭头飞出后自动切换到通关界面,显示星级和得分,点击"下一关"成功进入下一关 通过
T05 失误次数耗尽 显示失败界面,允许重新开始 连续点击被阻挡箭头 3 次后切换到失败界面,点击"重新开始"恢复当前关卡 通过
T06 游戏进行中重新开始 箭头布局和失误次数恢复初始状态 消除部分箭头并扣除失误后点击"重新开始",棋盘恢复初始布局,失误次数恢复为 3 通过

5.2 附加功能测试

测试内容 预期结果 实际结果 是否通过
撤销功能 撤销后箭头和失误次数恢复到上一步 成功恢复,多次撤销可逐步回退 通过
提示功能 高亮一个可消除箭头 金色闪烁高亮,且该箭头确实可消除 通过
AI 自动求解 自动演示完整通关步骤 逐步自动点击,最终通关 通过
关卡解锁 通关后下一关解锁 选关界面中下一关变为可点击 通过
存档持久化 关闭后重开,进度保留 存档文件正确写入,重开后"继续游戏"进入上次关卡 通过
全部关卡可解性 10 关均有解 求解器验证全部通过 通过

六、PSP 表格

任务 预估耗时(小时) 实际耗时(小时) 差异(小时)
需求分析与游戏设计 1.5 1.0 -0.5
Python 与图形库学习 2.0 1.5 -0.5
游戏界面实现 3.0 3.5 +0.5
路径与碰撞逻辑实现 2.5 2.0 -0.5
关卡设计 2.0 3.0 +1.0
AIGC 辅助开发 2.0 2.5 +0.5
进阶功能开发(求解器/撤销/存档) 3.0 4.0 +1.0
测试与修改 2.0 2.5 +0.5
README 与博客撰写 2.0 2.0 0
合计 20.0 22.0 +2.0

七、心得体会

AI 带来的帮助

  1. 代码生成效率高:Cursor 的代码补全和对话功能极大提升了编码速度,尤其是界面布局、按钮组件等重复性代码,AI 可以一次生成可用的骨架,省去了查文档的时间。
  2. 算法思路清晰:在设计 AI 求解器时,DeepSeek 给出的 DFS 回溯 + 记忆化思路非常清晰,比自己从零摸索节省了大量时间。尤其是在关卡设计遇到困难时,AI 给出的"反向构造法"直接解决了关卡不可解的问题。
  3. Bug 定位快:遇到 RecursionError 等问题时,将报错信息和相关代码粘贴给 AI,能快速定位到回溯恢复时对象引用不一致的根本原因。

开发中遇到的主要问题

  1. 求解器回溯的对象引用问题(最棘手):基础 DFS 写好后,前 5 关正常,但第 6 关开始就报 RecursionError。一开始以为是递归深度太大,后来发现根本原因是:回溯恢复时用 add_arrow 创建了新的 Arrow 对象,导致 remove_arrow 中的 is 身份检查失败,箭头没有被真正移除,求解器在原地无限递归。修复方法是让 add_arrow 支持传入已有对象,回溯时恢复原引用。这个问题让我深刻体会到"回溯算法不仅要恢复状态,还要保持对象引用一致性"。
  2. 关卡不可解:手动设计 10 个关卡时,前 5 关靠反向思路勉强设计出来,但第 6-10 关越设计越乱,求解器验证全部返回无解。最后借助 AI 给出的反向构造法,写了一个自动生成器,从空棋盘开始每次添加一个可消除的箭头,才系统地解决了关卡设计问题。这也说明靠人工肉眼验证复杂棋盘的可解性非常不可靠,必须用算法验证。
  3. 路径检测坐标方向:AI 生成的路径检测代码把 dx/dyrow/col 的对应关系写反了,导致向上/向下检测时实际扫描的是左右方向。这个问题不报错,只是游戏行为不对,很隐蔽。后来通过单独写测试脚本逐方向验证才发现。
  4. 测试脚本路径问题:把测试脚本移到 tests/ 子目录后,sys.path 没有包含项目根目录,导致找不到模块。这个小问题暴露了在多目录项目中如何正确设置 Python 模块搜索路径的细节。
  5. README 图片路径:用 Typora 写 README 时图片默认用本地绝对路径,push 到 GitHub 后图片全部裂图。后来把图片放到仓库的 docs/images/ 目录下,改用相对路径才解决。

自己的收获

  1. 对 AIGC 的定位更清晰:AI 是高效的"副驾驶",能快速生成代码骨架和思路,但核心逻辑的正确性、边界条件的处理、代码间的一致性仍需开发者自己把控。AI 生成的代码不能直接用,必须自己读一遍并写测试验证——路径坐标写反、回溯对象引用错误这两个问题,如果不是亲自测试根本发现不了。
  2. 软件工程实践能力提升:从需求分析、模块设计、编码实现到测试验证,完整经历了一个小型软件项目的开发流程。模块化的代码结构(棋盘、状态、求解器、界面分离)让后续维护和功能扩展变得容易。
  3. 算法理解加深:通过亲手实现 DFS 回溯 + 记忆化求解器,对状态空间搜索、剪枝、记忆化等概念有了更直观的理解。尤其是记忆化优化前后的性能差异(从递归溢出到秒级求解),让我切身体会到算法优化的力量。
  4. 测试的重要性:作业要求的 T01-T06 测试用例看起来简单,但实际开发中正是这些测试帮助发现了路径坐标错误、边缘越界等问题。尤其是写了一个自动化的关卡可解性验证脚本后,每改一次关卡数据都能立刻确认是否可解,大大提升了迭代效率。
posted on 2026-09-19 11:08  Anna_2333  阅读(24)  评论(0)    收藏  举报