软件工程第二次作业
2026秋软件工程个人作业(第二次)——“一箭又一箭”小游戏
作业信息
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | <202601软件工程> |
| 这个作业要求在哪里 | <2026秋软件工程个人作业(第二次)> |
| 这个作业的目标 | 使用 Python 和 AIGC 完成“一箭又一箭”小游戏 |
| 学号 | 152402217 |
| GitHub 仓库 | <arrow-game> |
一、项目展示
1. 开始界面
2. 游戏过程
3. 通关界面
4. 失败界面
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. 路径检测方法
路径检测是游戏的核心逻辑。点击某个箭头后,根据箭头方向判断其前方是否存在其他箭头。
基本思路:
- 获取当前箭头的行、列、方向;
- 根据方向确定检测路径:
- 向上:同一列,行号减小;
- 向下:同一列,行号增大;
- 向左:同一行,列号减小;
- 向右:同一行,列号增大;
- 从当前箭头位置出发,沿对应方向逐格检查;
- 如果遇到其他箭头,则说明被阻挡,返回
False; - 如果直到边界都没有遇到其他箭头,则说明可以飞出,返回
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的使用方法和游戏开发的相关流程和可能遇见的问题。对个人有着很大提升作用的一次经历。





浙公网安备 33010602011771号