2026 年软件工程与软件工程实践第2次作业
一、作业信息
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | 2026 年软件工程与软件工程实践 |
| 这个作业要求在哪里 | 第二次个人作业:利用 AIGC 完成小游戏 |
| 这个作业的目标 | 使用 Python 和 AIGC 完成一箭又一箭小游戏 |
| 学号 | 102402135 |
| GitHub 仓库 | https://github.com/APKJH/arrow_game |
二、项目展示
开始界面

游戏内画面

失误画面

飞出动画

单关通关结算画面

失败画面

通关画面

关卡均已试玩,都可通关,功能均正常
三、项目介绍
本项目是一款使用 Python 开发的箭头方向类小游戏
1. 游戏规则
玩家需要根据游戏棋盘上的箭头方向进行操作,使箭头按照正确的路径移动。
游戏过程中主要需要完成以下操作:
(1)根据箭头的方向判断下一步移动位置;
(2)按照正确的路径完成关卡;
(3)如果走错路径,则产生碰撞或失误;
(4)游戏记录玩家的失误次数;
(5)完成规定路径后进入通关状态。
2. 界面设计
游戏界面主要由以下部分组成:
(1)游戏标题;
(2)游戏棋盘;
(3)不同方向的箭头;
(4)当前游戏状态;
(5)失误次数等提示信息;
(6)通关和失败反馈界面。
整体界面比较简洁,使玩家可以快速理解当前箭头方向和游戏状态。
3. 主要功能
本项目主要实现了:
(1)游戏开始与初始化;
(2)游戏棋盘绘制;
(3)四个方向箭头的显示;
(4)箭头移动;
(5)四方向路径检测;
(6)碰撞检测;
(7)失误次数记录;
(8)通关判断;
(9)失败反馈。
4. 项目特色
本项目的主要特点是将箭头方向与路径判断结合起来。玩家不能简单地进行随机移动,而需要根据当前箭头的方向判断下一步应该如何操作,从而完成整个路径。
四、实现思路
4.1 数据表示
本项目使用 Python 和 Pygame 实现,游戏棋盘采用 5×5 的二维网格进行表示。程序通过行号 row、列号 col 和方向 direction 描述一个箭头的位置和移动方向。
在程序中,棋盘的基本参数定义如下:
BOARD_SIZE = 500
CELL_SIZE = BOARD_SIZE // GRID_SIZE
BOARD_X = (WIDTH - BOARD_SIZE) // 2
BOARD_Y = 150
MAX_MISTAKES = 3
其中 GRID_SIZE 表示棋盘为 5×5,BOARD_SIZE 表示棋盘的像素大小,CELL_SIZE 根据棋盘大小自动计算单个格子的尺寸。这样设计可以避免直接写死每个格子的像素位置,使棋盘坐标和屏幕坐标之间的转换更加清晰。
箭头对象
游戏中的箭头使用 Arrow 类表示:
def __init__(self, row, col, direction):
self.row = row
self.col = col
self.direction = direction
self.blocked_effect = 0
其中:
row:箭头所在的行;
col:箭头所在的列;
direction:箭头当前的方向;
blocked_effect:箭头被阻挡后用于产生视觉反馈的状态变量。
这种表示方法将箭头的位置和方向统一封装到对象中,后续进行路径检测、鼠标点击判断和动画处理时都可以直接使用箭头对象
4.2 箭头方向表示
程序没有分别为上、下、左、右编写四套完全独立的移动逻辑,而是使用方向向量统一表示。
核心代码如下:
DIRECTION_VECTOR = {
"U": (-1, 0),
"D": (1, 0),
"L": (0, -1),
"R": (0, 1)
}
这里使用 (行变化, 列变化) 表示箭头移动方向:
| 方向 | 行变化 | 列变化 |
|---|---|---|
U |
-1 | 0 |
D |
1 | 0 |
L |
0 | -1 |
R |
0 | 1 |
例如当前箭头位于 (2,2):
如果方向为 U,下一位置为 (1,2);
如果方向为 D,下一位置为 (3,2);
如果方向为 L,下一位置为 (2,1);
如果方向为 R,下一位置为 (2,3)。
这种方式把四个方向统一成了一个坐标变化问题,使后面的路径检测可以使用同一套算法处理,而不需要重复编写四套代码。
4.3 关卡数据设计
本项目没有在程序运行过程中随机生成箭头,而是采用固定关卡数据的方式设计游戏。
程序使用 LEVELS 保存所有关卡,每个箭头使用:
(row, col, direction)
表示。
例如第一关中包含:
[
(0, 0, "R"),
(0, 2, "D"),
(2, 2, "L"),
(2, 0, "D"),
(4, 0, "R"),
(4, 3, "U"),
]
这样可以直接通过修改 LEVELS 中的数据调整关卡布局,而不需要修改游戏核心逻辑。
目前程序设置了 6 个固定关卡,箭头数量随着关卡增加而增加:
第 1 关:12 个箭头
第 2 关:14 个箭头
第 3 关:16 个箭头
第 4 关:18 个箭头
第 5 关:20 个箭头
第 6 关:22 个箭头
因此游戏的基本难度是通过增加箭头数量以及调整箭头排列方式实现的。
4.4 路径阻挡检测
路径阻挡检测是本项目最核心的游戏逻辑之一。
游戏规则并不是简单判断“下一格有没有箭头”,而是需要判断当前箭头沿自身方向直到棋盘边界的整条路径上是否存在其他箭头。
程序通过 is_blocked() 函数完成这一判断。
核心代码如下:
def is_blocked(self, arrow):
dr, dc = DIRECTION_VECTOR[arrow.direction]
row = arrow.row + dr
col = arrow.col + dc
while (
0 <= row < GRID_SIZE
and 0 <= col < GRID_SIZE
):
for other in self.arrows:
if (
other is not arrow
and other.row == row
and other.col == col
):
return True
row += dr
col += dc
return False
具体实现过程
首先,根据当前箭头的方向取得方向向量:
dr, dc = DIRECTION_VECTOR[arrow.direction]
然后从当前箭头的下一格开始检查:
row = arrow.row + dr
col = arrow.col + dc
接下来使用 while 循环沿箭头指向的方向不断移动:
while (
0 <= row < GRID_SIZE
and 0 <= col < GRID_SIZE
):
这样可以保证检查范围始终位于 5×5 棋盘内部。
在每一个经过的格子中,再遍历当前关卡中的其他箭头:
for other in self.arrows:
如果发现其他箭头的坐标与当前检查位置相同:
other.row == row
and other.col == col
就说明当前箭头的前方存在阻挡,因此返回:
return True
如果一直检查到棋盘边界都没有发现其他箭头,则返回:
return False
因此,整个路径检测过程可以概括为:
获取当前箭头
↓
读取箭头方向
↓
计算下一格位置
↓
沿箭头方向逐格检查
↓
是否发现其他箭头?
↙ ↘
是 否
↓ ↓
返回 True 继续检查
↓
到达棋盘边界
↓
返回 False
这种实现相比只检查相邻格更加符合本游戏的实际规则,因为一个箭头可能被同一方向上更远位置的箭头阻挡。
4.5 鼠标点击与箭头定位
本游戏采用鼠标作为主要操作方式,因此需要把鼠标的屏幕坐标转换成棋盘中的行列坐标。
程序首先判断鼠标是否位于棋盘范围:
if not (
BOARD_X <= mx < BOARD_X + BOARD_SIZE
and
BOARD_Y <= my < BOARD_Y + BOARD_SIZE
):
return None
确定鼠标位于棋盘之后,再通过:
col = int(
(mx - BOARD_X) / CELL_SIZE
)
row = int(
(my - BOARD_Y) / CELL_SIZE
)
将鼠标的像素坐标转换成棋盘坐标。
之后遍历当前关卡中的箭头:
for arrow in self.arrows:
if (
arrow.row == row
and arrow.col == col
):
return arrow
最终得到玩家点击的具体箭头对象。
这样就实现了:
鼠标点击
↓
获取屏幕坐标
↓
判断是否位于棋盘
↓
转换为 row / col
↓
查找对应 Arrow 对象
↓
执行点击逻辑
这部分代码将界面输入和游戏内部数据结构连接起来。
4.6 箭头点击与碰撞反馈
玩家点击箭头后,程序会进入 click_arrow() 函数。
首先判断当前是否允许进行新的操作:
if arrow is None:
return
if self.animation is not None:
return
if self.fail_timer > 0:
return
这样可以避免在箭头动画播放过程中重复点击,也避免游戏失败状态下继续操作。
之后调用核心的路径检测函数:
if self.is_blocked(arrow):
self.mistakes += 1
arrow.blocked_effect = 0.25
self.message = "前方有箭头阻挡!"
self.message_timer = 1.0
如果箭头被阻挡,则:
失误次数 mistakes 加 1;
设置 blocked_effect 产生视觉反馈;
显示“前方有箭头阻挡!”提示;
设置提示持续时间。
同时程序设置了最大失误次数:
if self.mistakes >= MAX_MISTAKES:
self.fail_timer = 0.7
return
当失误次数达到 3 次后,进入失败处理流程。
4.7 箭头飞出动画
如果箭头没有被阻挡,程序不会直接把箭头瞬间删除,而是调用:
self.arrows.remove(arrow)
self.start_flight(arrow)
让箭头先从棋盘中移除,再执行飞出动画。
在 start_flight() 中,程序首先获取箭头的中心位置:
start_x, start_y = arrow.center()
然后根据箭头方向计算飞出的目标位置。
例如向右:
if arrow.direction == "R":
target_x = WIDTH + 150
target_y = start_y
向左:
elif arrow.direction == "L":
target_x = -150
target_y = start_y
向上和向下则分别设置对应的纵坐标。
程序进一步计算起点和终点之间的距离,并根据距离确定动画持续时间:
distance = start.distance_to(target)
duration = max(
0.45,
min(
0.85,
distance / 850
)
)
最后将动画相关信息保存到 self.animation 中:
self.animation = {
"arrow": arrow,
"start": start,
"target": target,
"elapsed": 0,
"duration": duration
}
这样就可以在游戏更新过程中逐帧计算箭头的位置,从而形成箭头飞出棋盘的动画效果。
4.8 游戏状态管理
为了管理开始界面、游戏过程、通关、失败和最终结果,程序在 ArrowGame 类中定义了多个游戏状态:
class ArrowGame:
MENU = "menu"
PLAYING = "playing"
LEVEL_CLEAR = "level_clear"
LEVEL_FAIL = "level_fail"
FINAL = "final"
同时使用:
self.state = self.MENU
保存当前游戏状态。
因此整个游戏流程可以表示为:
MENU
↓
PLAYING
↓
┌───────────────┐
↓ ↓
LEVEL_CLEAR LEVEL_FAIL
↓ ↓
下一关 重新挑战
↓
PLAYING
↓
FINAL
这种状态机式的设计可以把不同界面的逻辑进行区分,避免把菜单、游戏、通关和失败代码全部混在一起,提高程序结构的清晰度。
4.9 关卡初始化与数据重置
每进入一个关卡,程序通过 start_level() 根据 LEVELS 中保存的数据创建对应的箭头:
def start_level(self, level_index):
self.current_level = level_index
self.arrows = []
for row, col, direction in LEVELS[level_index]:
self.arrows.append(
Arrow(
row,
col,
direction
)
)
同时重置当前关卡相关的数据:
self.mistakes = 0
self.level_time = 0
self.animation = None
self.fail_timer = 0
self.message = ""
self.message_timer = 0
self.hovered_arrow = None
最后记录关卡开始时间并进入游戏状态:
self.level_start_time = time.time()
self.state = self.PLAYING
这样每一关开始时都能够获得一个独立、干净的游戏状态。
4.10 通关与失败处理
当所有箭头都已经从棋盘中清除后,程序在动画更新过程中判断:
if len(self.arrows) == 0:
然后计算当前关卡耗时:
self.level_time = (
time.time()
- self.level_start_time
)
如果已经完成最后一关:
if (
self.current_level
== len(LEVELS) - 1
):
self.state = self.FINAL
else:
self.state = self.LEVEL_CLEAR
这样可以区分普通关卡完成和整个游戏完成。
另一方面,如果玩家失误次数达到上限,程序会进入失败状态:
if self.fail_timer <= 0:
self.total_mistakes += self.mistakes
self.state = self.LEVEL_FAIL
因此程序最终形成了比较完整的“输入 → 路径判断 → 正确/错误反馈 → 动画 → 关卡状态变化”的游戏逻辑闭环。
五、AIGC 使用过程
本次项目开发过程中,我使用 AIGC 工具辅助完成游戏设计、代码实现、问题分析和测试修改。主要协作过程如下:
| 次数 | 协作内容 | 使用的 AIGC 工具 | AI 主要提供的帮助 | 我的实际处理 |
|---|---|---|---|---|
| 第 1 次 | 游戏整体设计 | ChatGPT | 根据作业要求分析游戏玩法,设计棋盘、箭头、关卡和游戏状态等基本结构 | 根据作业要求筛选方案,确定最终游戏玩法,并完成项目基本框架 |
| 第 2 次 | 路径检测实现 | ChatGPT / Codex | 分析箭头四个方向的移动方式,并提供路径检测的实现思路 | 理解方向向量和坐标变化后,根据自己的代码结构进行修改和测试 |
| 第 3 次 | 关卡设计 | Codex | 根据游戏规则生成多关卡箭头数据,并分析不同关卡的难度变化 | 对生成的关卡数据进行检查和调整,使箭头数量和路径难度逐渐增加 |
| 第 4 次 | 碰撞反馈与失误次数 | ChatGPT / Codex | 分析箭头被阻挡后的处理方式,并增加失误次数和碰撞反馈 | 根据实际运行效果修改反馈逻辑,并测试连续失误情况下的游戏状态 |
| 第 5 次 | 自动化测试 | ChatGPT / Codex | 根据游戏规则设计测试项目,分析路径检测、通关和失败等情况 | 编写测试程序并实际运行,对测试结果进行检查和修改 |
六、测试结果
为了验证游戏主要功能是否正常,我对游戏的核心功能进行了测试。测试内容包括箭头点击、路径检测、碰撞反馈、失误次数、重新开始以及关卡切换等。
| 编号 | 测试内容 | 操作步骤 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 启动第一关,点击任意前方没有其他箭头的箭头 | 箭头沿对应方向飞出棋盘 | 箭头正常飞出棋盘 | 通过 |
| T02 | 点击被阻挡的箭头 | 点击前方存在其他箭头的箭头 | 箭头不能移动,并产生阻挡反馈 | 箭头未消失,产生碰撞反馈,失误次数 +1 | 通过 |
| T03 | 四方向路径检测 | 分别测试上、下、左、右四个方向的箭头 | 程序能够正确判断不同方向上的阻挡情况 | 四个方向均能够正常检测 | 通过 |
| T04 | 关卡完成 | 按照正确路径依次消除当前关卡箭头 | 当前关卡完成并进入下一关 | 箭头全部消除后正常进入下一关 | 通过 |
| T05 | 失误次数达到上限 | 连续进行 3 次被阻挡的错误操作 | 达到最大失误次数后进入失败状态 | 游戏正常显示失败状态 | 通过 |
| T06 | 失败后重新开始 | 在失败界面选择重新开始 | 游戏重新初始化当前关卡 | 箭头、失误次数等数据正常恢复 | 通过 |
| T07 | 多关卡切换 | 依次完成前 5 个关卡 | 每关完成后进入下一关 | 6 个关卡均能够正常切换 | 通过 |
| T08 | 最终通关 | 完成最后一关的全部箭头 | 显示最终通关界面 | 正常进入最终通关界面 | 通过 |
测试结论:
通过上述测试可以看出,游戏的主要功能均能够正常运行,路径检测、碰撞反馈、失误次数、关卡切换和最终通关等功能均达到预期效果。
测试过程中发现的问题主要集中在部分交互逻辑和界面反馈方面,经过修改后重新进行测试,最终测试结果均通过。
七、PSP 表格
PSP(Personal Software Process)用于记录项目开发过程中各阶段的预计时间和实际时间,并通过两者之间的差异分析开发过程。
| 任务 | 预计耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1.0 | 0.8 | -0.2 |
| Python 与图形库学习 | 1.5 | 0.8 | -0.7 |
| 游戏界面实现 | 3.0 | 3.2 | +0.2 |
| 路径与碰撞逻辑实现 | 2.5 | 2.8 | +0.3 |
| 关卡设计 | 1.5 | 1.8 | +0.3 |
| AIGC 辅助开发 | 1.5 | 1.5 | 0.0 |
| 测试与修改 | 2.0 | 2.2 | +0.2 |
| README 与博客撰写 | 2.0 | 2.9 | +0.9 |
| 合计 | 15.0 | 16.0 | +1.0 |
| PSP 分析 |
从 PSP 数据可以看出,本次项目预计需要 15.0 小时,实际使用约 16.0 小时,总体比预计时间多 1.0 小时。
其中,游戏界面、路径与碰撞逻辑以及测试阶段实际耗时略高于预计时间,主要是因为开发过程中需要反复运行程序并修改细节。
README 和博客撰写阶段耗时相对较多,主要是需要整理项目结构、运行结果、测试过程以及 AIGC 协作记录。
八、心得体会
通过这次小游戏开发,我对 Python 项目的实际开发流程有了更加直观的认识。以前编写程序时比较关注单个功能是否能够运行,这次则需要从游戏设计、界面、功能、测试等多个方面考虑整个项目。
AIGC 在分析程序结构、寻找错误原因以及设计路径检测逻辑时,可以帮助我快速找到解决问题的思路。但是 AI 给出的代码并不一定能够直接运行,需要结合自己的项目进行修改和测试。
通过这次作业,我不仅进一步熟悉了 Python 编程,也体会到了 AIGC 更适合作为开发过程中的辅助工具,而不是完全代替自己编程。最终程序还是需要自己理解、运行、测试和修改。

浙公网安备 33010602011771号