AIGC标识 软件工程第二次作业

软件工程第二次作业——《一箭又一箭》小游戏

项目 内容
这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzu/202601SofwareEngineering
这个作业要求在哪里 https://edu.cnblogs.com/campus/fzu/202601SofwareEngineering/homework/16717
这个作业的目标 使用 Python 和 AIGC 完成“一箭又一箭”小游戏
学号 102401135
GitHub 仓库 https://github.com/Victoria029/arrow_escape

一、项目展示

1. 开始界面

开始界面
开始界面

2. 游戏进行中

游戏过程演示
游戏画面

3. 碰撞反馈

碰撞画面

4. 通关界面

通关界面

5. 失败界面

失败画面

6. 全部通关界面

通关动画


二、项目介绍

2.1 游戏规则

“一箭又一箭”是一款点击式箭头解谜小游戏。棋盘为 5×5 的网格,上面分布着若干带方向的箭头(上、下、左、右四种)。

玩家用鼠标左键点击某个箭头后,程序会检查该箭头前进方向上是否有其他箭头阻挡:

  • 无阻挡:箭头沿自身方向飞出棋盘并消失;
  • 有阻挡:箭头不能消除,会向前撞击阻挡的箭头,然后弹回原位,同时失误次数减 1;
  • 清空当前关卡的全部箭头即通关,可进入下一关;
  • 失误次数达到 3 次时,本关失败,可以重新开始。

关键判定:阻挡是否成立,只与前方路径上是否有箭头占据有关,与被阻挡箭头自身的朝向无关。例如棋盘第一行是 → · · ↑ ·,第一个箭头朝右,但它的右侧有另一个箭头,因此不能飞出。

2.2 界面设计

游戏共包含 6 个界面:

  • 开始界面:显示游戏标题、副标题、四个方向装饰箭头,以及“开始游戏”“选择关卡”“退出游戏”三个按钮;
  • 选关界面:以列表形式列出全部 5 个关卡,每行显示关卡序号、名称和箭头数量,点击即可进入对应关卡;
  • 游戏界面:顶部显示当前关卡、失误次数和剩余箭头数;中部为 5×5 棋盘;网格下方有“撤销”和“提示”按钮,最底部有“重新开始”和“主菜单”按钮;
  • 通关结算界面:半透明遮罩层上方显示白色卡片,包含“恭喜过关!”、用时/失误/提示次数统计,以及“主菜单”“重玩本关”“下一关”三个按钮;
  • 失败结算界面:显示“失败”和“失误次数用尽了”,附统计信息,提供“主菜单”和“重试本关”两个按钮;
  • 全部通关界面:显示“恭喜通过所有关卡!”的最终庆祝画面。

界面采用浅灰色背景、浅蓝色箭头、白色网格线的配色方案。箭头由箭杆和箭头两部分组成,成功飞出和碰撞回弹都有平滑的动画反馈。

2.3 主要功能

  • 方向系统:支持上、下、左、右四种箭头,用 Direction 枚举统一表示;
  • 路径检测:沿箭头方向逐格检查同一行或同一列是否有其他箭头阻挡;
  • 飞出动画:箭头以每帧 20 像素的速度平滑飞出窗口,飞出屏幕后自动清理;
  • 碰撞动画:箭头根据与阻挡物的实际距离,使用正弦函数实现“前进→弹回”的平滑往复位移,同时箭头变红;
  • 失误管理:每关最多 3 次失误,界面顶部实时显示;
  • 撤销功能:记录操作历史快照,支持撤销上一步操作,失误次数同步回退;
  • 提示功能:自动搜索一个当前可飞出的箭头并用黄色圆圈高亮;
  • 关卡系统:共 5 关,每关使用逆向生成算法随机生成箭头布局,保证 100% 有解;
  • 统计系统:记录每关用时、失误次数和提示次数,在结算界面展示。

2.4 特色

  • 随机生成保证有解:使用“逆向生成算法”,从空棋盘开始,每次放置箭头时只选择路径畅通的方向。这样按放置顺序的倒序点击必定能全部消除,从根本上杜绝了死锁;
  • 拟真碰撞反馈:箭头不是简单地原地晃动,而是根据与阻挡物的实际距离,精准地撞上去再弹回;
  • 动画期间防连点:箭头正在播放碰撞动画时,禁止对该箭头重复点击,避免失误次数被重复扣除;
  • 胜利结算时机优化:最后一个箭头飞出后,等待飞行动画完全播放完毕才弹出结算界面,让玩家能看到完整的消除过程;
  • 分层架构:引擎逻辑(core/)、界面渲染(ui/)、数据模型(models/)、关卡数据(levels/)相互独立。

三、实现思路

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

方向的表示

四种方向使用 Python 的 Enum 枚举类型统一表示:

class Direction(Enum):
    UP = 1
    DOWN = 2
    LEFT = 3
    RIGHT = 4

使用枚举而不是字符串或整数,好处是类型安全、可读性强,在 if-elif 分支判断时不会因为拼写错误导致 bug。

箭头的表示

每个箭头由一个 Arrow 类表示,包含位置(行列坐标)、方向、可见状态,以及用于动画的像素坐标:

class Arrow:
    def __init__(self, row, col, direction):
        self.row = row
        self.col = col
        self.direction = direction
        self.is_visible = True
        self.is_colliding = False
        self.collide_timer = 0
        self.collide_offset = 15
        # 动画像素坐标
        self.base_px = GRID_OFFSET_X + col * CELL_SIZE + CELL_SIZE // 2
        self.base_py = GRID_OFFSET_Y + row * CELL_SIZE + CELL_SIZE // 2
        self.px = self.base_px
        self.py = self.base_py

关键设计是将逻辑坐标和像素坐标分离:row/col 用于路径检测和游戏逻辑,px/py 用于界面渲染和动画。这样逻辑层完全不需要关心箭头在屏幕上的具体位置。

棋盘的表示

棋盘使用 5×5 的二维列表表示,每个元素要么是 None(空格),要么是一个 Arrow 对象:

self.grid = [[None for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]

关卡的表示

关卡数据由 levels/level_data.py 中的 generate_level() 函数动态生成:

def generate_level(num_arrows):
    grid = [[None for _ in range(5)] for _ in range(5)]
    positions = [(r, c) for r in range(5) for c in range(5)]
    random.shuffle(positions)
    arrows = []
    for r, c in positions:
        if len(arrows) >= num_arrows:
            break
        random.shuffle(directions)
        for d in directions:
            # 检查该方向路径上是否有其他箭头
            if is_path_clear(grid, r, c, d):
                grid[r][c] = d
                arrows.append((r, c, d))
                break
    random.shuffle(arrows)
    return arrows

3.2 路径检测方法

路径检测是整个游戏最核心的逻辑。给定一个箭头,需要判断它沿自身方向前进到棋盘边界之间,是否存在其他箭头。

实现方式是将阻挡判定封装为 get_blocking_arrow() 方法,返回路径上第一个阻挡的 Arrow 对象,如果没有阻挡则返回 None:

def get_blocking_arrow(self, arrow):
    r, c = arrow.row, arrow.col
    if arrow.direction == Direction.UP:
        for i in range(r - 1, -1, -1):
            if self.grid[i][c] is not None:
                return self.grid[i][c]
    elif arrow.direction == Direction.DOWN:
        for i in range(r + 1, GRID_SIZE):
            if self.grid[i][c] is not None:
                return self.grid[i][c]
    elif arrow.direction == Direction.LEFT:
        for i in range(c - 1, -1, -1):
            if self.grid[r][i] is not None:
                return self.grid[r][i]
    elif arrow.direction == Direction.RIGHT:
        for i in range(c + 1, GRID_SIZE):
            if self.grid[r][i] is not None:
                return self.grid[r][i]
    return None

这个方法返回的不是布尔值而是具体的 Arrow 对象,使得调用方既能判断“是否被阻挡”,又能获取“被谁阻挡”的信息——碰撞动画的距离计算正是依赖这一点。

  • 边界安全:使用 range(r - 1, -1, -1) 这样的写法,当箭头位于棋盘边缘(r = 0)时,range 会直接为空,循环体不会执行,天然避免了数组越界问题。

3.3 碰撞动画的实现

当箭头被阻挡时,我希望它能根据与阻挡物的实际距离撞过去再弹回来,而不是简单地原地晃动。实现方式是使用正弦函数产生平滑的往复位移:

distance = abs(arrow.row - blocking_arrow.row) + abs(arrow.col - blocking_arrow.col)
arrow.collide_offset = max(15, distance * CELL_SIZE - 50)

然后在 update() 中逐帧计算偏移:

progress = (30 - arrow.collide_timer) / 30
offset = math.sin(progress * math.pi) * arrow.collide_offset

sin(0) = 0,sin(π/2) = 1,sin(π) = 0。当 progress 从 0 变到 1 时,offset 先增大到最大值再回到 0,形成一次完整的“前进→弹回”运动。最大偏移量由 collide_offset 控制,保证了箭头会刚好碰到阻挡物边缘再返回。

3.4 本项目的文件结构

ruangong2/
├── main.py                  # 程序入口
├── config.py                # 全局配置(窗口尺寸、颜色、网格大小等)
├── levels/
│   └── level_data.py        # 关卡的逆向生成算法
├── models/
│   └── data_model.py        # Arrow 类和 Direction 枚举
├── core/
│   └── game_engine.py       # 核心逻辑:路径检测、状态管理、动画更新
├── ui/
│   └── game_ui.py           # 界面绘制:棋盘、箭头、按钮、结算画面
└── utils/
    └── logger.py            # 日志工具

四、AIGC 使用过程

以下记录来自本次项目的实际协作过程。

子任务 借助何种 AIGC 技术 AI 实现或提供了什么 效果如何 人工修改
路径检测与核心逻辑 DeepSeek 生成四方向路径检测代码和游戏状态机框架 逻辑基本正确,但坐标计算存在偏差 手动修正了 handle_click 中的像素坐标到网格坐标的映射,补充了边界安全处理
箭头绘制与动画 DeepSeek 生成箭头绘制代码和碰撞动画方案 初始版本箭头尺寸过大超出格子,弹跳幅度也不合理 调整为箭杆+箭头分离绘制,根据阻挡距离动态计算碰撞偏移量,使用正弦函数实现平滑弹回
关卡的逆向生成算法 DeepSeek 提出“从空棋盘开始,每次只放置路径畅通的箭头”的逆向生成思路 完美解决了随机关卡出现死锁的问题 补充了最终打乱顺序的逻辑,增加解谜难度;调整了每关的箭头数量梯度(8→12→14→16→18)
界面布局与结算画面 DeepSeek 生成开始界面、选关界面、通关/失败结算界面的完整代码 结算界面出现频闪,按钮位置遮挡了信息栏 pygame.display.flip() 统一移到主循环末尾解决频闪;重新规划了所有按钮的坐标布局

AIGC 使用中的典型问题与解决过程:

  1. 路径检测的越界问题:DeepSeek 最初生成的代码使用 board[row - 1][col] 直接访问数组,当箭头位于棋盘边缘时会产生负索引。我改用了 range(r - 1, -1, -1) 的写法,让 range 自然处理边界情况。

  2. 关卡死锁问题:在使用固定关卡数据时,多次出现箭头互相阻挡导致无法通关的情况(例如 A 朝右挡住 B,B 朝左挡住 A)。最终通过与 DeepSeek 讨论,确定了“逆向生成算法”方案——每次放置箭头时只选择当前路径畅通的方向,从根本上保证了可解性。

  3. 碰撞动画的真实感:DeepSeek 最初实现的碰撞动画只是原地左右晃动,与视觉预期差距很大。我提出“箭头应该先碰到阻挡物再弹回”的需求后,DeepSeek 给出了使用正弦函数和动态偏移量的方案,效果非常自然。

五、测试结果

编号 测试内容 预期结果 实际结果 是否通过
T01 点击前方无阻挡的箭头 箭头飞出棋盘并消失,剩余箭头数减 1 箭头平滑飞出屏幕,计数正确更新
T02 点击前方有阻挡的箭头 箭头不消失,失误次数减 1 箭头变红并向前撞击阻挡物后弹回,失误次数加 1
T03 点击位于边缘且朝向棋盘外的箭头 箭头正常消失,不发生越界错误 箭头顺利飞出,无报错
T04 消除本关全部箭头 显示通关结算并进入下一关 最后一个箭头飞出的动画播放完毕后弹出结算界面
T05 失误次数耗尽 显示失败界面并允许重新开始 显示“失败”和“失误次数用尽了”,提供重试按钮
T06 游戏进行中重新开始 箭头布局和失误次数恢复初始状态 棋盘恢复到当前关卡的初始布局,失误次数归零

六、PSP 表格

任务 预估耗时(小时) 实际耗时(小时) 差异(小时)
需求分析与游戏设计 1 2 +1
Python 与图形库学习 3 4 +1
游戏界面实现 3 5 +2
路径与碰撞逻辑实现 2 4 +2
关卡设计 2 3 +1
AIGC 辅助开发 4 5 +1
测试与修改 3 4 +1
README 与博客撰写 2 3 +1
合计 20 30 +10

七、心得体会

这次作业是我第一次系统性地将 AIGC 工具引入到完整的软件开发流程中,收获远超预期。

AI 带来的帮助:最明显的效率提升体现在“从 0 到 1”的阶段。以前遇到一个不熟悉的图形库(比如 Pygame),我需要花大量时间查文档、看教程。但这次,我只需要用自然语言描述需求,AI 就能快速给出可运行的代码框架,让我把精力集中在逻辑设计和体验优化上,而不是纠结 API 的用法。

遇到的问题:但 AI 生成的代码从来不是“拿来即用”的。路径检测的边界条件、碰撞动画的真实感、界面按钮的坐标布局——这些问题 AI 给出的第一版几乎都有瑕疵。更重要的是,AI 在设计固定关卡时,多次生成了互相阻挡的死锁关卡,导致游戏根本无法通关。这让我意识到,AI 擅长“写代码”,但不擅长“验证逻辑”,最终的把关必须由人来完成。

我的收获:最大的成长是学会了“带着验证的心态使用 AI”。每一段 AI 生成的代码,我都会实际运行、观察效果,发现问题后不只是说“有 bug”,而是精确描述现象和预期,让 AI 理解问题本质后再给出修复方案。这个“提出需求 → 验证结果 → 精准反馈 → 迭代改进”的循环,本身就是一种高效的开发方法论。

posted @ 2026-09-20 21:39  Victoria_L  阅读(6)  评论(0)    收藏  举报