张馨恬软工第二次作业

项目 内容
这个作业属于哪个课程 <202601软件工程>
这个作业要求在哪里 <2026秋软件工程个人作业(第二次)>
这个作业的目标 使用 Python 和 AIGC 完成“一箭又一箭”小游戏
学号 <102401603>
GitHub 仓库 https://github.com/daezxt/arrow-game/tree/main

一、项目展示

1. 开始界面

image

2. 游戏过程

image
image

3. 通关界面

image

4. 失败界面

image

6. 完整游戏展示

https://www.bilibili.com/video/BV1vyei6mESA/?spm_id_from=333.1387.homepage.video_card.click&vd_source=ad723da571f806de623426f87cb2d711


二、项目介绍

1. 游戏简介

本项目实现了一个“一箭又一箭”风格的点击式解谜小游戏。玩家需要观察棋盘中火箭的方向和相互阻挡关系,按照正确顺序点击火箭,使火箭依次飞出棋盘。若火箭前方没有其他火箭阻挡,则飞出并被消除;若前方存在其他火箭,则无法飞出,并产生碰撞反馈,同时消耗一次失误次数。

2. 游戏规则

  • 棋盘中包含若干火箭,方向分为上、下、左、右四种。
  • 玩家通过鼠标点击选择火箭。
  • 点击火箭后,程序检查该火箭前进方向上是否被其他火箭阻挡。
  • 若前方无阻挡,火箭飞出棋盘并消失。
  • 若前方有阻挡,火箭不能消失,并给出明显碰撞反馈。
  • 点击被阻挡的火箭会消耗一次失误机会。
  • 清除本关全部火箭后,进入下一关。
  • 失误次数耗尽时,本关失败,可以重新开始。
  • 游戏提供重新开始按钮,可将当前关卡恢复到初始状态。
  • 全部关卡通关后,从头开始。

3. 界面设计

游戏界面主要包括:

  • 开始界面:显示游戏标题、开始按钮、操作说明。
  • 游戏界面:显示当前关卡、棋盘、剩余火箭数量、剩余失误次数、右下角重新开始按钮。
  • 结果界面:显示通关或失败信息,并提供进入下一关或重新开始按钮。

界面采用渐变背景、圆角卡片、阴影和按钮悬停效果,整体风格统一。

4. 主要功能

  • 图形化游戏界面,包含开始界面、游戏界面、通关界面和失败界面;
  • 棋盘中显示上、下、左、右四种方向的火箭;
  • 鼠标点击火箭进行交互,飞出或者碰撞;
  • 实现路径阻挡检测,正确判断火箭前方是否有其他火箭;
  • 火箭飞出动画:可飞出的火箭沿方向飞出屏幕后消失;
  • 碰撞反馈动画:被阻挡的火箭会左右晃动;
  • 失误次数统计与显示,失误次数耗尽后本关失败;
  • 设计 5 个可正常通关的关卡,棋盘为 5×5;
  • 支持通关后进入下一关、失败后重新开始、游戏中重新开始;
  • 全部通关后从头开始。

5. 项目特色

  • 使用 5×5 棋盘,5 个关卡,遮挡层次逐渐加深;
  • 每个火箭随机分配一种颜色,整局固定,界面更活泼;
  • 火箭带描边和细节(机身、机头、尾翼、尾焰、舷窗);
  • 四个方向的火箭由同一个朝上的火箭旋转得到,形状完全一致;
  • 界面美化:渐变背景、圆角卡片、阴影、按钮悬停变色;
  • 飞出动画:火箭沿方向飞出屏幕后消失,而不是瞬间消失;
  • 碰撞反馈:被阻挡的火箭左右晃动。

三、实现思路

1. 开发环境

  • 操作系统:Windows 11
  • 编程语言:Python 3.13
  • 图形库:Pygame 2.6.1
  • 开发工具:VS Code
  • AIGC 工具:DeepSeek

2. 项目结构

arrow-game/
├── main.py
├── levels.py
└── README.md

3. 火箭与方向的表示

本项目使用二维数组表示整个棋盘,每个格子存储一个方向字符串或 NoneNone 表示该格子为空,没有火箭。

方向使用字符串表示:

'U'  # 上
'D'  # 下
'L'  # 左
'R'  # 右

例如,一关的棋盘数据如下:

LEVELS = [
    [
        ['U', 'U', None, 'U', 'U'],
        ['L', None, 'R', None, 'R'],
        [None, 'D', 'R', 'U', None],
        ['L', None, 'L', None, 'R'],
        ['D', 'D', None, 'D', 'D'],
    ],
    ...
]

棋盘为 5×5,board[r][c] 表示第 r 行第 c 列的格子:

  • 如果 board[r][c]'U''D''L''R',说明该格有对应方向的火箭;
  • 如果 board[r][c]None,说明该格为空。

绘制时,根据 board[r][c] 的方向,把同一个朝上的火箭 Surface 旋转到对应方向,再画到格子中心。

4. 路径检测方法

路径检测是游戏的核心逻辑。点击某个火箭后,根据火箭方向判断其前方是否存在其他火箭。

基本思路:

  • 获取当前火箭的行、列、方向;
  • 根据方向确定检测路径:
    • 向上:同一列,行号减小;
    • 向下:同一列,行号增大;
    • 向左:同一行,列号减小;
    • 向右:同一行,列号增大;
  • 从当前火箭位置出发,沿对应方向逐格检查;
  • 如果遇到其他火箭,则说明被阻挡,返回 False
  • 如果直到边界都没有遇到其他火箭,则说明可以飞出,返回 True

实际代码如下:

def can_fly(self, r, c):
    arrow = self.board[r][c]
    if arrow is None:
        return False

    if arrow == 'U':
        for i in range(r - 1, -1, -1):
            if self.board[i][c] is not None:
                return False
    elif arrow == 'D':
        for i in range(r + 1, self.rows):
            if self.board[i][c] is not None:
                return False
    elif arrow == 'L':
        for j in range(c - 1, -1, -1):
            if self.board[r][j] is not None:
                return False
    elif arrow == 'R':
        for j in range(c + 1, self.cols):
            if self.board[r][j] is not None:
                return False
    return True

说明:

  • 向上检测时,使用 range(r - 1, -1, -1),从当前行的上一行开始,一直检查到第 0 行;
  • 向下检测时,使用 range(r + 1, self.rows),从当前行的下一行开始,一直检查到最后一行;
  • 向左检测时,使用 range(c - 1, -1, -1),从当前列的左边一列开始,一直检查到第 0 列;
  • 向右检测时,使用 range(c + 1, self.cols),从当前列的右边一列开始,一直检查到最后一列。

边界处理:

  • range 的结束位置使用 -1self.rows / self.cols,保证不会越界;
  • 如果路径上全是 None,循环结束后返回 True,表示可以飞出;
  • 如果中途遇到非 None,立即返回 False,表示被阻挡。

5. 碰撞反馈实现

当火箭被阻挡时,本项目通过晃动给出反馈,同时失误次数减 1。

实际实现方式:

  • animations 列表里记录一个 shake 动画,包含格子位置和计时;
  • 点击被阻挡的火箭时,把该火箭的 (r, c)"shake": True 加入 animations
  • 每帧在 update_animations 里让 anim["t"] 增加,超过 0.4 秒后移除;
  • 绘制时,如果发现当前格子是晃动动画,就根据 anim["t"] 的奇偶左右偏移,形成晃动效果。

相关代码:

def update_animations(game, dt):
    for anim in game.animations[:]:
        anim["t"] += dt
        if anim.get("shake"):
            if anim["t"] > 0.4:
                game.animations.remove(anim)
        else:
            if anim["t"] > 1.5:
                game.animations.remove(anim)

绘制时:

if anim.get("shake"):
    arrow = game.board[r][c]
    if arrow is None:
        continue
    offset = int(6 * (1 if int(anim["t"] * 20) % 2 == 0 else -1))
    cx += offset

说明:

  • anim["t"] 控制晃动持续时间,数值越大晃动越久;
  • int(anim["t"] * 20) % 2 决定当前帧向左还是向右偏移,形成左右晃动;
  • 偏移量 6 像素,幅度不大但能明显看到;
  • 触发碰撞时失误次数减 1,达到 0 时本关失败。

6. 飞出动画实现

原来火箭飞出时是瞬间消失,后来改成沿方向飞出屏幕后再消失。

实现方式:

  • 点击可飞出的火箭时,从 board 里移除该火箭,同时把 (r, c, dir) 加入 animations"shake"False
  • 绘制时,棋盘只画没有动画的火箭;动画层单独画所有动画中的火箭;
  • 飞出动画按 speed = 1200 像素/秒沿方向移动,anim["t"] 超过 1.5 秒后移除。

相关代码:

else:
    arrow = anim["dir"]
    speed = 1200
    dist = speed * anim["t"]
    if arrow == 'U':
        cy -= dist
    elif arrow == 'D':
        cy += dist
    elif arrow == 'L':
        cx -= dist
    elif arrow == 'R':
        cx += dist

说明:

  • 飞出动画独立于 board 单独绘制,因为火箭已经从 board 里移除了;
  • 棋盘绘制时用 anim_cells 跳过正在动画的格子,避免重复画;
  • 飞出速度 1200 像素/秒,1.5 秒足够飞出屏幕。

7. 关卡表示与切换

本项目使用二维数组表示关卡。每个关卡是一个 5×5 的二维列表,格子中存储方向字符串或 None。所有关卡统一使用 3 次失误机会。

关卡数据示例:

LEVELS = [
    [
        ['U', 'U', None, 'U', 'U'],
        ['L', None, 'R', None, 'R'],
        [None, 'D', 'R', 'U', None],
        ['L', None, 'L', None, 'R'],
        ['D', 'D', None, 'D', 'D'],
    ],
    ...
]

关卡加载逻辑:

def load_level(self, index):
    raw = LEVELS[index]
    self.board = [row[:] for row in raw]
    self.rows = len(self.board)
    self.cols = len(self.board[0])
    self.mistakes_left = 3
    self.remaining = sum(1 for row in self.board for cell in row if cell)

    palette = [...]
    self.colors = {}
    for r in range(self.rows):
        for c in range(self.cols):
            if self.board[r][c] is not None:
                self.colors[(r, c)] = random.choice(palette)

说明:

  • LEVELS[index] 取出对应关卡;
  • 使用 row[:] 复制每一行,避免直接修改原始关卡数据;
  • 加载时重置失误次数、剩余火箭数、火箭颜色和动画状态;
  • 游戏状态切换到 "playing"

通关与切换:

  • 每次成功消除一个火箭后,remaining 减 1;
  • 如果 remaining 为 0,把状态切换为 "win"
  • 通关界面显示“下一关”按钮,点击后 level_index 加一,加载下一关;
  • 如果是最后一关,状态切换为 "all_win",显示“恭喜通关”界面。

失败与重新开始:

  • 每次被阻挡时,失误次数减 1;
  • 如果失误次数达到 0,把状态切换为 "lose"
  • 失败界面显示“重新开始”按钮,点击后重新加载当前关卡。

游戏中也可以点击右下角“重新开始”按钮,将当前关卡恢复到初始状态。

8. 游戏状态管理

本项目使用一个全局变量 scene 来表示当前游戏状态,通过判断 scene 的值来决定绘制哪个界面、响应哪些点击事件。

状态定义:

scene = "start"     # 开始界面
scene = "playing"   # 游戏中
scene = "result"    # 通关或失败
scene = "all_win"   # 全部通关

四个状态分别对应:

  • "start":开始界面;
  • "playing":游戏中;
  • "result":通关或失败;
  • "all_win":全部通关。

状态切换逻辑:

  • 点击“开始游戏”按钮时,level_index 重置为 0,创建 Game(0),状态切换为 "playing"
  • 在游戏中,如果 remaining 为 0,状态切换为 "result"result_win = True
  • 在游戏中,如果 mistakes_left 为 0,状态切换为 "result"result_win = False
  • 点击“下一关”按钮时,如果还有下一关,level_index 加一,进入下一关;否则状态切换为 "all_win"
  • 点击“重新开始”按钮时,调用 restart() 重新加载当前关卡;
  • 点击“从头开始”按钮时,重新创建 Game(0),状态切换为 "playing"

主循环里根据状态决定绘制内容:

if scene == "start":
    start_btn = draw_start_screen(screen)
elif scene == "playing":
    draw_gradient_background(screen)
    update_animations(game, dt)
    draw_hud(screen, game)
    restart_btn = draw_board(screen, game)
elif scene == "result":
    result_btn = draw_result_screen(screen, game, result_win)
elif scene == "all_win":
    all_win_btn = draw_all_win_screen(screen)

说明:

  • 开始界面和游戏界面分开绘制;
  • 通关、失败和全部通关界面独立绘制;
  • 点击事件也按状态分发,避免在开始界面误触棋盘逻辑。

四、AIGC 使用过程

AIGC 使用记录 1

项目 内容
子任务 环境配置与 Pygame 兼容性问题
借助何种 AIGC 技术 DeepSeek
我的提问 / 要求 运行 python main.py 时报 ModuleNotFoundError: No module named 'pygame',以及 SysFont 在 Windows 上读字体注册表报 TypeError
AI 实现或提供了什么 建议先激活 .venv 再运行,或者直接用 .venv\Scripts\python.exe main.py;对于 SysFont 报错,建议改用 pygame.font.Font("C:/Windows/Fonts/simhei.ttf", size)
效果如何 虚拟环境用对后 pygame 能正常导入;字体改用文件路径后不再报错
人工修改 FONT_PATH 固定为 C:/Windows/Fonts/simhei.ttf,并确认 Windows 上存在该字体

AIGC 使用记录 2

项目 内容
子任务 火箭绘制与方向一致性
借助何种 AIGC 技术 DeepSeek
我的提问 / 要求 左右方向的火箭和上下方向的火箭长得不一样,机头和机身分离
AI 实现或提供了什么 建议只画一个朝上的火箭 Surface,然后用 pygame.transform.rotate 旋转到对应方向,四个方向形状完全一致
效果如何 四个方向火箭形状统一,不再出现机头机身分离
人工修改 加入 _rocket_cache 缓存,同一颜色只生成一次,避免每帧重复旋转

AIGC 使用记录 3

项目 内容
子任务 飞出动画
借助何种 AIGC 技术 DeepSeek
我的提问 / 要求 火箭飞出棋盘时,不想让它瞬间消失,想做成沿方向飞出的动画
AI 实现或提供了什么 建议用 animations 列表记录正在飞出的火箭,每帧更新坐标,飞出屏幕后删除;并提醒棋盘绘制要跳过正在动画的格子
效果如何 动画比较自然,火箭沿方向飞出屏幕后消失
人工修改 调整 speed = 1200,动画时长 1.5 秒,并加入 anim_cells 避免重复绘制

AIGC 使用记录 4

项目 内容
子任务 界面美化
借助何种 AIGC 技术 DeepSeek
我的提问 / 要求 界面太单调,想加渐变背景、圆角卡片、阴影和按钮悬停效果
AI 实现或提供了什么 给出了渐变背景逐行绘制、圆角卡片用 border_radius、阴影用偏移矩形、按钮悬停用 mouse.get_pos() 判断的方案
效果如何 整体风格统一,视觉上比纯色界面好很多
人工修改 调整了配色(淡蓝渐变、白色卡片、蓝色/绿色按钮),并统一了字体和间距

AIGC 使用记录 5

项目 内容
子任务 关卡设计
借助何种 AIGC 技术 DeepSeek
我的提问 / 要求 第 2 关有两个火箭互相卡住,无法通关;想把关卡改复杂但不能有无法出去的
AI 实现或提供了什么 分析了互锁原因(同一行/列两个火箭面对面),给出了避免互锁的设计原则,并提供了 5 个可通关关卡
效果如何 5 个关卡都能正常通关,遮挡层次逐渐加深
人工修改 逐关试玩验证,确认每关都有合理通关顺序

五、测试结果

1. 测试环境

  • 操作系统:Windows 11
  • Python 版本:Python 3.13
  • 图形库版本:Pygame 2.6.1
  • 运行方式:在项目目录下执行 python main.py

2. 测试用例与结果

编号 测试内容 预期结果 实际结果 是否通过
T01 点击前方无阻挡的火箭 火箭飞出棋盘并消失 火箭沿方向飞出,飞出屏幕后消失 通过
T02 点击前方有阻挡的火箭 火箭不消失,失误次数减 1 火箭左右晃动,失误次数减 1 通过
T03 点击位于边缘且朝向棋盘外的火箭 火箭正常消失,不发生越界错误 火箭正常飞出,无越界报错 通过
T04 消除本关全部火箭 显示通关并进入下一关 显示“通关!”,点击“下一关”进入下一关 通过
T05 失误次数耗尽 显示失败并允许重新开始 显示“失败!”,点击“重新开始”恢复 通过
T06 游戏进行中重新开始 火箭布局和失误次数恢复 点击“重新开始”后布局和失误次数都恢复 通过

3. 补充测试

除 T01–T06 外,还进行了以下补充测试:

  • 连续点击同一个能飞出的火箭:第一次飞出后,该格子变为空,再次点击不再有反应;
  • 点击空白格子:不触发任何操作;
  • 点击棋盘外区域:不触发任何操作,游戏状态不受影响;
  • 多次重新开始:关卡布局和失误次数都能正确恢复;
  • 窗口关闭:点击右上角关闭按钮,程序正常退出。

4. 测试结论

经过测试,本项目的基础功能均能正常运行:

  • 开始界面、游戏界面、通关界面、失败界面和全部通关界面显示正常;
  • 四种方向的火箭都能正确显示和响应点击;
  • 路径判断正确,能区分前方是否有火箭阻挡;
  • 位于边缘、朝向棋盘外的火箭能正常飞出,没有出现越界错误;
  • 被阻挡的火箭会晃动,并消耗一次失误机会;
  • 5 个关卡都经过实际试玩,均能正常通关;
  • 失误次数耗尽后显示“失败”,可以重新开始;
  • 游戏中可以随时重新开始,状态能正确恢复。

测试过程中发现并修复的问题:

  • 火箭飞出动画最初没有显示,原因是 board 里已经清空、绘制循环找不到火箭;改为动画层独立绘制后修复;
  • 左右方向火箭机头机身分离,改为统一朝上绘制再旋转后修复;
  • 第 2 关曾有两个火箭互锁,重新设计关卡后修复。

六、PSP 表格

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

七、心得体会

1. AIGC 带来的帮助

  • 帮我快速了解 Pygame 的基本用法,包括窗口创建、事件循环、绘制图形和文字;
  • 帮我生成路径检测的初版代码,让我先有一个可运行的版本,再在此基础上修改;
  • 帮我设计关卡的数据结构,确定用二维数组存储方向和空位;
  • 帮我定位路径检测中的越界问题、字体加载问题、虚拟环境问题;
  • 帮我实现火箭绘制、碰撞晃动、飞出动画和界面美化;
  • 帮我想界面美化的方向,给出渐变背景、圆角卡片、按钮悬停等建议;
  • 帮我分析关卡互锁原因,给出可通关的关卡设计。

2. 遇到的问题

  • 虚拟环境没激活,导致 ModuleNotFoundError: No module named 'pygame'
  • pygame.font.SysFont 在 Windows 上读字体注册表报 TypeError,改用字体文件路径解决;
  • 左右方向火箭机头机身分离,改为统一朝上绘制再旋转后解决;
  • 飞出动画最初没有显示,改为动画层独立绘制后解决;
  • 第 2 关曾有两个火箭互锁,重新设计关卡后解决;
  • 通关后闪退,排查后确认是场景切换逻辑问题,调整后解决。

3. 我的收获

这是我第一次使用 Python 和 Pygame 开发一个完整的小游戏。从环境配置、虚拟环境激活,到路径检测、动画实现、界面美化,每一步都遇到了不同的问题。AI 帮我快速搭起了可运行的框架,但真正让程序稳定、好玩、好看的,还是自己反复试玩、调试和修改。

我学会了:

  • 如何用二维数组表示棋盘和关卡;
  • 如何用方向字符串和循环实现路径检测;
  • 如何用 animations 列表管理飞出和晃动动画;
  • 如何用 pygame.transform.rotate 保持四个方向形状一致;
  • 如何用渐变、圆角、阴影、悬停美化界面;
  • 如何设计可通关、有层次、不互锁的关卡。

这次作业让我对 Pygame 和游戏开发流程有了完整的认识,也让我更清楚 AI 能帮什么、自己必须做什么。

posted on 2026-09-20 19:43  大鹅zxt  阅读(12)  评论(0)    收藏  举报