软件工程第二次作业

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

作业信息

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

一、项目展示

1. 开始界面

image

2. 游戏过程

QQ20260919-215726

3. 通关界面

image

4. 失败界面

image

5. 完整游戏展示


二、项目介绍

1. 游戏简介

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

2. 游戏规则

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

3. 界面设计

游戏界面主要包括:

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

4. 主要功能

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

5. 项目特色

  • 使用 7×7 大小棋盘,增加箭头数量;
  • 美工配色灵感来自于韦斯.安德森导演的作品《布达佩斯大饭店》。界面采用复古梦幻风格,配色以奶油粉、酒红、金色为主,棋盘和结果界面使用圆角卡片与金色边框,整体风格统一;
  • 开始界面使用在网络上找到的一张天空照片作为背景,十分梦幻,符合游戏配色基调。
  • 开始界面增加引用自《伊萨卡岛》的诗句,既符合游戏主题让箭飞出,也表达了对于玩游戏的用户的祝福;
  • 添加点击、飞出、碰撞三种音效,增强操作反馈;
  • 添加箭头飞出的动画效果。

三、实现思路

1. 开发环境

  • 操作系统:Windows 11
  • 编程语言:Python 3.14.7
  • 图形库:Pygame-ce 2.5.8
  • 开发工具:VS Code
  • AIGC 工具:DeepSeek、豆包

2. 项目结构

arrow-game/
├── main.py
├── assets/
│   └── menu_bg.jpg
└── README.md

3. 箭头与方向的表示

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

方向使用字符串表示:

UP = "up"
DOWN = "down"
LEFT = "left"
RIGHT = "right"

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

LEVELS = [
    [
        [RIGHT, None, None, None, None, UP, None],
        [None, None, None, DOWN, None, None, None],
        [None, LEFT, None, None, None, None, None],
        [None, None, None, None, RIGHT, None, None],
        [None, None, UP, None, None, None, None],
        [None, None, None, None, None, DOWN, None],
        [None, LEFT, None, None, None, None, None],
    ],
    ...
]

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

  • 如果 board[r][c] 是 "up"、"down"、"left"、"right",说明该格有对应方向的箭头;
  • 如果 board[r][c] 是 None,说明该格为空。

每个方向对应一个显示符号:

ARROW_SYMBOL = {
    UP: "↑",
    DOWN: "↓",
    LEFT: "←",
    RIGHT: "→",
}

绘制时,根据 board[r][c] 的方向取出对应符号,再画到对应格子的中心位置。

4. 路径检测方法

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

基本思路:

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

实际代码如下:

def can_fly_out(board, row, col, direction):
    if direction == UP:
        for r in range(row - 1, -1, -1):
            if board[r][col] is not None:
                return False
        return True
    if direction == DOWN:
        for r in range(row + 1, ROWS):
            if board[r][col] is not None:
                return False
        return True
    if direction == LEFT:
        for c in range(col - 1, -1, -1):
            if board[row][c] is not None:
                return False
        return True
    if direction == RIGHT:
        for c in range(col + 1, COLS):
            if board[row][c] is not None:
                return False
        return True
    return False

说明:

  • 向上检测时,使用 range(row - 1, -1, -1),从当前行的上一行开始,一直检查到第 0 行;
  • 向下检测时,使用 range(row + 1, ROWS),从当前行的下一行开始,一直检查到最后一行;
  • 向左检测时,使用 range(col - 1, -1, -1),从当前列的左边一列开始,一直检查到第 0 列;
  • 向右检测时,使用 range(col + 1, COLS),从当前列的右边一列开始,一直检查到最后一列。

边界处理:

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

5. 碰撞反馈实现

当箭头被阻挡时,本项目通过晃动、变色和音效三种方式给出反馈,同时失误次数减 1(在游戏里表现为累计失误次数+1)。

实际实现方式:

  • 在全局维护两个变量 shake_cell 和 shake_timer;
  • 点击被阻挡的箭头时,记录该箭头的格子 (row, col) 到 shake_cell,并把 shake_timer 设为 20;
  • 每一帧在主循环中让 shake_timer 减 1,减到 0 时清空 shake_cell;
  • 绘制箭头时,如果发现当前格子是 shake_cell,就根据 shake_timer 的奇偶左右偏移,形成晃动效果,并把颜色改成红色。

相关代码:

shake_cell = None
shake_timer = 0
def draw_arrow(row, col, direction):
    ...
    cx = BOARD_X + col * CELL_SIZE + CELL_SIZE // 2
    cy = BOARD_Y + row * CELL_SIZE + CELL_SIZE // 2

    if shake_cell == (row, col):
        cx += 5 if (shake_timer // 3) % 2 == 0 else -5

    color = ERROR_COLOR if shake_cell == (row, col) else ARROW_COLORS[direction]
    ...
    if shake_timer > 0:
        shake_timer -= 1
        if shake_timer == 0:
            shake_cell = None

说明:

  • shake_timer 控制晃动持续帧数,数值越大晃动越久;
  • (shake_timer // 3) % 2 决定当前帧向左还是向右偏移,形成左右晃动;
  • 偏移量 5 像素,幅度不大但能明显看到;
  • 同时把箭头颜色改成 ERROR_COLOR,让玩家更容易注意到这次点击是无效的;
  • 触发碰撞时还会播放 SOUND_BLOCK 音效,并让失误次数加一。

6. 关卡表示与切换

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

关卡数据示例:

LEVELS = [
    [
        [RIGHT, None, None, None, None, UP, None],
        [None, None, None, DOWN, None, None, None],
        [None, LEFT, None, None, None, None, None],
        [None, None, None, None, RIGHT, None, None],
        [None, None, UP, None, None, None, None],
        [None, None, None, None, None, DOWN, None],
        [None, LEFT, None, None, None, None, None],
    ],
    ...
]

MAX_MISTAKES = 3

关卡加载逻辑:

def load_level(index):
    global board, selected, mistakes, state, shake_cell, shake_timer
    board = [row[:] for row in LEVELS[index]]
    selected = None
    mistakes = 0
    state = STATE_PLAYING
    shake_cell = None
    shake_timer = 0

说明:

  • LEVELS[index] 取出对应关卡;
  • 使用 row[:] 复制每一行,避免直接修改原始关卡数据;
  • 加载时重置选中状态、失误次数、碰撞状态;
  • 游戏状态切换到 STATE_PLAYING。

通关与切换:

  • 每次成功消除一个箭头后,调用 count_arrows() 统计剩余箭头;
  • 如果剩余箭头为 0,把状态切换为 STATE_WIN;
  • 通关界面显示“下一关”按钮,点击后 level_index 加一,加载下一关;
  • 如果是最后一关,显示“恭喜,全部通关!”,并提供“重新开始”和“退出”按钮。

失败与重新开始:

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

游戏中也可以点击“重新开始”按钮,将当前关卡恢复到初始状态;点击“返回主界面”按钮则回到开始界面。

7. 游戏状态管理

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

状态定义:

STATE_MENU = "menu"
STATE_PLAYING = "playing"
STATE_WIN = "win"
STATE_LOSE = "lose"

state = STATE_MENU

四个状态分别对应:

  • STATE_MENU:开始界面;
  • STATE_PLAYING:游戏中;
  • STATE_WIN:通关;
  • STATE_LOSE:失败。

状态切换逻辑:

  • 点击“开始游戏”按钮时,level_index 重置为 0,调用 load_level() 加载第一关,状态切换为 STATE_PLAYING;
  • 在游戏中,如果剩余箭头为 0,状态切换为 STATE_WIN;
  • 在游戏中,如果失误次数达到 MAX_MISTAKES,状态切换为 STATE_LOSE;
  • 点击“下一关”按钮时,如果还有下一关,level_index 加一,调用 load_level() 进入下一关;
  • 点击“重新开始本关”按钮时,调用 load_level(level_index) 重新加载当前关卡;
  • 点击“返回主界面”按钮时,状态切换为 STATE_MENU。

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

    if state == STATE_MENU:
        draw_menu()
    else:
        draw_board()
        draw_ui()
        if state == STATE_WIN:
            draw_win_screen()
        elif state == STATE_LOSE:
            draw_lose_screen()

说明:

  • 开始界面和游戏界面分开绘制;
  • 通关和失败界面在游戏界面上叠加半透明遮罩和结果卡片;
  • 点击事件也按状态分发,避免在开始界面误触棋盘逻辑。

用 state 变量管理状态的好处是实现简单、逻辑集中,适合这种规模的小游戏。


四、AIGC 使用过程

AIGC 使用记录 1

项目 内容
子任务 字体统一与界面美化
借助何种 AIGC 技术 deepseek
我的提问 / 要求 游戏里字体不好看,并让界面风格更精致,想用类似宋体
AI 实现或提供了什么 建议用字体文件路径加载系统字体,给出了 pygame.font.Font("C:/WINDOWS/FONTS/SIMSUN.TTC", 大小) 的写法,并提醒箭头符号可能不在新宋体里,需要单独指定字体
效果如何 游戏界面文字统一成新宋体,但箭头符号一开始显示成方框
人工修改 把箭头单独用 pygame.font.SysFont("microsoftyahei", 48) 渲染,其他文字统一用新宋体,问题解决

AIGC 使用记录 2

项目 内容
子任务 箭头颜色区分
借助何种 AIGC 技术 deepseek
我的提问 / 要求 箭头全是黄色,太单调,想让不同方向的箭头用不同颜色
AI 实现或提供了什么 建议用字典把方向映射到颜色,给出 ARROW_COLORS = {"up": ..., "down": ..., "left": ..., "right": ...} 的写法
效果如何 箭头按方向分色,界面更清晰,也更容易分辨方向
人工修改 结合复古梦幻风格,把颜色调整为薄荷绿、酒红、金色、酒店粉,并和选中、碰撞时的颜色区分开

AIGC 使用记录 3

项目 内容
子任务 界面美化
借助何种 AIGC 技术 豆包
我的提问 / 要求 想把界面做成布达佩斯大饭店那种复古梦幻风格,请给配色和布局建议
AI 实现或提供了什么 给出了奶油粉、酒红、金色为主的配色方案,以及圆角卡片、金色边框、按钮样式等建议 。并且生成了实际参考效果图。
效果如何 整体风格统一,视觉上比纯色且丑陋的界面好很多
人工修改 参考了配色以及布局建议,上面填写文字由我改动,没有使用ai方案。以及挑选了合适并可实现的功能进行了画面布局上的更改。

AIGC 使用记录 4

项目 内容
子任务 飞出动画
借助何种 AIGC 技术 DeepSeek
我的提问 / 要求 箭头飞出棋盘时,不想让它瞬间消失,想做成沿方向飞出的动画
AI 实现或提供了什么 建议用一个列表记录正在飞出的箭头,每帧更新它们的坐标,飞出窗口后删除
效果如何 方案可用,动画比较自然
人工修改 调整了飞行速度 speed = 12,并加入边界判断,箭头完全飞出窗口后再从列表中移除

五、测试结果

1. 测试环境

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

2. 测试用例与结果

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

3. 补充测试

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

  • 连续点击同一个能飞出的箭头:第一次飞出后,该格子变为空,再次点击不再有反应;
  • 点击空白格子:不触发任何操作,选中状态被清除;
  • 点击棋盘外区域:不触发任何操作,游戏状态不受影响;
  • 通关最后一关:显示“恭喜,全部通关!”,同时出现“重新开始”和“退出”两个按钮;
  • 点击“退出”按钮:程序正常退出,没有报错;
  • 游戏中点击“返回主界面”:回到开始界面,再次点击“开始游戏”会从第一关重新开始;
  • 失误次数达到 3 次后:显示“失败!”,点击“重新开始本关”后失误次数重置为 0;
  • 多次重新开始:关卡布局和失误次数都能正确恢复,没有出现残留状态;
  • 窗口关闭:点击右上角关闭按钮,程序正常退出,没有卡死;
  • 不同窗口大小:调整窗口尺寸后,棋盘和按钮位置正常,文字没有明显重叠。

4. 测试结论

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

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

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

  • 碰撞动画最初没有复位,箭头会停在偏移位置,加入计时器后恢复正常;
  • 部分关卡最初设计不合理,存在无法通关的情况,重新设计后已验证可通关。

六、PSP 表格

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

七、心得体会

1. AIGC 带来的帮助

  • 帮我快速了解 Pygame 的基本用法,包括窗口创建、事件循环、绘制图形和文字;
  • 帮我生成路径检测的初版代码,让我先有一个可运行的版本,再在此基础上修改;
  • 帮我设计关卡的数据结构,确定用二维数组存储方向和空位;
  • 帮我定位路径检测中的越界问题,以及方向判断错误;
  • 帮我实现碰撞晃动、飞出动画和音效;
  • 帮我想界面美化的方向,给出配色、圆角卡片、按钮样式等建议;

2. 遇到的问题

  • git一开始安装失败,登录弹窗卡住呈透明空白状态;
  • 提示文字的位置不对,没有在格子的正中间;
  • 碰撞动画最初没有考虑结束后复位,箭头会停在偏移位置;
  • 对 Pygame 的事件循环不熟悉,一开始不清楚 MOUSEBUTTONDOWN 和坐标转换的关系;
  • 字体和箭头符号不统一,部分字符显示成方框,后来单独给箭头指定了字体;
  • 窗口尺寸调整后,棋盘和按钮位置一度错乱,需要重新调整布局参数。

3. 我的收获

这是我第一次使用python以及pygame来开发一个简单的小游戏。首先,为了和github比较好的搭配,我安装了git。在安装的时候遇到了问题,卡住不能登录,最后是使用了token来解决的。这是继上次hugging face体验之后第二次使用token。在AI的帮助下,我一步一步的先搭起来简单的小程序功能,实现了箭头的检测和飞离。在基础界面都保证完成且可用后,我开始了漫长而折磨的调整过程。为了让游戏更美观一些,我将界面的总体风格调整后,出现了文字位置混乱等错误。经过无数次反复的调整,报错,我一点点的将每一个按钮,每一行提示文字调整到了正确且美观的位置上。我在游戏的主界面用风景照片来作为背景,一定程度上丰富了游戏的视觉效果。而后,添加了一点附加功能--即给点击按钮和箭头添加音效,以及增加箭头飞走的动画--原来的版本是箭头直接消失。

总之,这是一次全新的尝试和体验,也让我进一步了解到pygame的使用方法和游戏开发的相关流程和可能遇见的问题。对个人有着很大提升作用的一次经历。

posted @ 2026-09-19 23:59  illusion111  阅读(17)  评论(0)    收藏  举报