第二次作业
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | 202601 软件工程 |
| 这个作业要求在哪里 | 2026秋软件工程个人作业(第二次) |
| 这个作业的目标 | 使用 Python 和 AIGC 完成“一箭又一箭”小游戏 |
| 学号 | 102401312 |
| GitHub 仓库 | ArrowGame |
一、项目展示
开始界面:
游戏过程:
通关界面:
失败界面:
二、项目介绍
1. 游戏规则
游戏中的箭头具有上、下、左、右四种方向。点击箭头后,程序会沿着它指向的方向检查到棋盘边界之间的路径。
如果没有其他箭头阻挡,箭头就会飞出棋盘并被消除;如果存在阻挡,箭头会保留在原位置,通过变色和晃动给出碰撞反馈,同时扣除一次生命。
例如:
→ · · ↑ ·
最左侧箭头朝右,但右侧还有一个向上的箭头,因此不能消除。阻挡是否成立,与前方箭头自身的方向无关,只与它是否占据路径有关。
另一种情况:
↑ · · · →
最右侧箭头朝右,它与右边界之间没有其他箭头,所以可以直接飞出。
每关有三次失误机会,并设置倒计时。清空全部箭头后通关;生命耗尽,或者倒计时结束时棋盘仍未清空,则挑战失败。
2. 界面设计
开始界面采用浅蓝色背景,搭配箭头纹理和彩色标题。中央的蓝色箭头角色使用透明图片,通过上下浮动、左右摇摆和轻微缩放形成动态效果,下方提供“开始游戏”和“选择关卡”入口。
关卡选择界面分为两个区域:
- 基础关卡:第1至第3关,采用单格箭头。
- 挑战关卡:第4、第5关,采用经过多个坐标点的折线箭头。
游戏界面采用深色背景,使箭头与棋盘形成明显对比。顶部显示当前关卡、得分、剩余箭头、生命和倒计时,底部提供自动求解与提示按钮。右上角的设置菜单支持重新开始和返回主页。
倒计时剩余30秒时变为黄色,剩余10秒时变为红色。通关和失败界面会显示本关得分及结果说明。
3. 主要功能和特色
| 功能 | 实现效果 |
|---|---|
| 顺序解锁 | 通关前一关后解锁下一关 |
| 提示 | 高亮一支当前可以安全飞出的箭头 |
| 自动求解 | 逐支消除合法箭头,可手动停止 |
| 计分 | 消除加分,提示和失误扣分 |
| 倒计时 | 限时完成关卡,设置菜单打开时暂停 |
| 本地存档 | 保存最高已解锁关卡 |
| 折线动画 | 箭尾沿原有折线前进,箭头头部向外延伸 |
| exe可执行文件 | 打包成Windows可直接运行的文件 |
消除一支箭头加100分,成功使用一次提示扣50分,损失一次生命扣100分,分数最低为0。AI自动消除同样计分。
| 关卡 | 类型 | 规模(行 × 列) | 时间限制 |
|---|---|---|---|
| 第1关 | 基础关卡 | 5 × 5 | 60秒 |
| 第2关 | 基础关卡 | 5 × 5 | 90秒 |
| 第3关 | 基础关卡 | 6 × 6 | 120秒 |
| 第4关 | 折线挑战 | 24 × 16个坐标点 | 300秒 |
| 第5关 | 折线挑战 | 28 × 18个坐标点 | 420秒 |
挑战关卡是基础玩法的扩展。箭头不再只占一个格子,而是由若干水平、垂直线段组成。这让阻挡关系更加复杂,也需要玩家更仔细地观察箭头的头部方向和线条位置。
三、实现思路
1. 箭头与方向的表示
基础关卡采用二维列表表示棋盘,每个元素对应一个单元格。使用 "^"、"v"、"<"、">" 表示上、下、左、右四种箭头,使用 "." 表示空位置。例如:
board = [
[">", ".", ".", "^", "."],
[".", ".", ".", ".", ">"],
[".", "v", ".", "<", "."],
[".", ".", "^", ".", "."],
["<", ".", ".", ".", "v"],
]
通过 board[row][col] 访问指定位置的箭头。箭头消除后,将对应元素改为 "."。
为了统一处理四个方向,程序将方向转换为行、列的变化量:
DIRECTIONS = {
"^": (-1, 0), # 上:行号减 1
"v": (1, 0), # 下:行号加 1
"<": (0, -1), # 左:列号减 1
">": (0, 1), # 右:列号加 1
}
挑战关卡采用折线箭头,每条箭头用一个字典表示,包含路径坐标和颜色:
arrow = {
"path": [(4, 2), (3, 2), (3, 1), (5, 1)],
"color": (255, 135, 142),
}
坐标统一采用 (行, 列) 格式,路径按“箭尾到箭头”的顺序保存。相邻坐标之间连接水平或竖直线段,最后一个坐标是箭头尖端,最后一段线的朝向就是箭头的前进方向。上述例子最后一段从 (3, 1) 指向 (5, 1),因此箭头朝下。
2. 关卡的表示
所有关卡统一放在 LEVELS 列表中,通过关卡索引读取对应数据。
基础关卡直接保存二维棋盘列表;挑战关卡需要额外记录坐标点阵的行数、列数和折线箭头集合:
{
"type": "polyline",
"rows": 24,
"cols": 16,
"arrows": [
# 各条折线箭头的数据
],
}
开始或重新开始关卡时,对初始关卡数据进行深拷贝,得到本次游戏使用的棋盘。这样,消除箭头只会修改当前棋盘,不会改变原始关卡布局。
3. 基础关卡的路径检测
路径检测的核心是:从箭头前方的第一个单元格开始,沿箭头方向逐格检查,直到遇到其他箭头或到达棋盘外。
具体过程如下:
- 读取被点击箭头的方向,取得行、列变化量。
- 从相邻位置开始检查,避免把箭头自身当成障碍。
- 如果当前位置存在其他箭头,立即判定为被阻挡。
- 如果当前位置为空,就继续向前检查。
- 如果一直没有遇到障碍并走出边界,则判定可以飞出。
对应代码如下:
def can_fly_out(board, row, col):
arrow = board[row][col]
if arrow == ".":
return False
dr, dc = DIRECTIONS[arrow]
check_row = row + dr
check_col = col + dc
while (
0 <= check_row < len(board)
and 0 <= check_col < len(board[0])
):
if board[check_row][check_col] != ".":
return False
check_row += dr
check_col += dc
return True
例如:
→ · · ↑ ·
点击第一个向右的箭头后,程序依次检查其右侧位置。虽然中间两个位置为空,但后面存在向上的箭头,因此不能飞出。障碍箭头的方向不影响判断,只要占据前进路径上的位置,就构成阻挡。
对于位于边缘且朝向棋盘外的箭头,其前方位置已经超出边界,循环不会执行,函数直接返回 True。由于先判断边界再访问列表,也避免了越界访问。
4. 挑战关卡的路径检测
折线箭头占据多个坐标点,因此不能只检查其他箭头的尖端,还要检查它们的整条线段。
首先,将其他折线箭头的每段路径展开为经过的全部整数坐标点。例如:
线段端点:(2, 1) → (2, 4)
实际占据:(2, 1)、(2, 2)、(2, 3)、(2, 4)
把这些坐标存入集合 occupied,就可以通过:
(row, col) in occupied
判断某个坐标是否被其他箭头占据。构建集合时排除当前被点击的箭头,避免把自身路径当成障碍。
随后,根据当前箭头最后两个路径点确定前进方向,从尖端前方的第一个坐标点开始,沿该方向检查到边界:
def can_polyline_fly_out(level, arrow_index):
arrow = level["arrows"][arrow_index]
path = arrow["path"]
previous = path[-2]
head = path[-1]
dr = (head[0] > previous[0]) - (head[0] < previous[0])
dc = (head[1] > previous[1]) - (head[1] < previous[1])
occupied = set()
for index, other in enumerate(level["arrows"]):
if index != arrow_index:
occupied.update(expand_polyline_points(other["path"]))
row = head[0] + dr
col = head[1] + dc
while 0 <= row < level["rows"] and 0 <= col < level["cols"]:
if (row, col) in occupied:
return False
row += dr
col += dc
return True
其中,expand_polyline_points() 负责展开水平、竖直线段上的全部坐标点。
两种关卡的检测原则相同:沿箭头前进方向检查到边界,途中存在其他箭头占据的位置就不能飞出。区别在于,基础关卡直接读取二维列表,挑战关卡则查询其他折线箭头占据的坐标集合。
四、AIGC 使用过程
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 折线挑战关卡设计 | Codex | 用坐标序列表示折线箭头,根据末段确定方向,将其他箭头经过的坐标作为障碍;实现沿路径收缩、向头部方向延伸的飞出动画 | 支持折线挑战玩法;多轮调整后,第四关形成 24×16 点阵、64 支箭头的布局,自动化求解检查能够清空 | 提供参考图片,要求使用坐标点与折线箭头,修改飞出方式、扩大点阵并增加箭头 |
| 关卡选择与分类 | Codex | 将关卡选择界面分为基础关卡和挑战关卡,保留通过前一关才能选择下一关的解锁规则 | 分组更加清晰;首次预览发现挑战标题与卡片阴影距离过近,调整间距后标题完整显示 | 我指定第 1—3 关为基础关卡、第 4—5 关为挑战关卡,要求未解锁关卡不能直接进入 |
| 自动求解与手动停止 | Codex | 复用路径检测寻找可飞出的箭头,每次消除一支,等待动画完成后继续,并增加停止控制 | 自动化检查中,五关分别可用 8、17、26、64、87 步清空;停止后不再启动下一支箭头,当前飞行动画继续完成 | 我要求自动求解逐个执行,而不是立即清空棋盘,要求求解过程中可以手动停止 |
| 提示功能与提示按钮 | Codex | 查找一支可飞出的箭头,通过金色高亮和脉冲圆环提示位置;按参考图绘制灯泡按钮,后续接入提示扣分规则 | 五关初始状态都能找到合法提示,查找过程不修改棋盘;提示不会直接消除箭头,成功提示后扣 50 分 | 提出点击按钮提示一条可飞出的箭头,补充灯泡按钮参考图,随后要求使用提示扣分 |
| 开始界面的动态箭头角色 | Codex | 根据参考图生成透明背景的蓝色箭头角色,并加入上下浮动、轻微旋转和缩放动画 | 早期造型与参考图存在差异,直接使用原图又保留了背景;改用透明 PNG 后,检查确认透明通道有效,不同时刻的动画帧不同 | 多次提供参考图,并明确提出“只需要箭头,不需要图片的背景,并且箭头要是动态的” |
五、测试结果
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失,不扣生命 | 点击第1关 (3, 2) 的向上箭头后,该位置变为空格,生成飞行动画;时间推进后动画结束,生命保持3,得分增加至100 |
通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误机会减少1,并触发碰撞反馈 | 点击第1关 (0, 0) 的向右箭头后,棋盘数据保持不变,生命由3降为2;碰撞目标记录为 (0, 0),未生成飞行动画 |
通过 |
| T03 | 点击位于边缘且朝向棋盘外的箭头 | 箭头正常消失,不发生越界错误 | 分别测试上边缘 (0, 3)、右边缘 (1, 4)、左边缘 (4, 0)、下边缘 (4, 4);四个子场景均正常移除箭头并结束动画,生命保持3,未发生异常 |
通过 |
| T04 | 消除本关全部箭头 | 显示通关结果,解锁下一关,并能进入下一关 | 启动自动求解清空第1关后,程序进入成功结果界面,得分为800;调用解锁保存接口,参数为第2关的内部索引1;点击“下一关”后加载第2关初始棋盘 | 通过 |
| T05 | 失误次数耗尽 | 显示失败结果,并允许重新开始 | 三次点击受阻箭头后生命降为0;碰撞反馈等待结束后,程序进入失败结果界面;点击重新挑战后,第1关棋盘恢复,生命恢复为3 | 通过 |
| T06 | 游戏进行中重新开始 | 箭头布局和失误机会恢复为初始状态 | 先消除一支箭头,再碰撞一次,使棋盘改变且生命变为2;打开设置并点击重新开始后,布局与第1关初始数据一致,生命恢复为3,得分归零,倒计时恢复为60秒 | 通过 |
六、PSP表格
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 0.5 | 0.3 | -0.2 |
| Python 与图形库学习 | 1 | 0.5 | -0.5 |
| 游戏界面实现 | 0.3 | 0.3 | 0 |
| 路径与碰撞逻辑实现 | 1.0 | 1.5 | +0.5 |
| 关卡设计 | 2.0 | 3.0 | +1.0 |
| AIGC 辅助开发 | 2.0 | 3.0 | +1.0 |
| 测试与修改 | 3.0 | 4.5 | +1.5 |
| README 与博客撰写 | 2.0 | 1.5 | -0.5 |
| 合计 | 11.8 | 14.6 | +2.8 |
七、心得体会
AI 带来的帮助
在本次项目中,AI 帮助我将游戏需求逐步转化为具体实现。从基础箭头的路径检测,到折线关卡、自动求解、提示和计分功能,AI 提供了代码实现与思路说明,降低了从零开始开发的难度。在界面设计方面,我可以通过参考图片和文字描述表达想法,再根据效果逐步调整。AI 还辅助整理了 README、测试记录等文档,让我对完整项目的开发流程有了更清晰的认识。
开发中出现的问题
AI 的首次输出不一定完全符合需求。例如,折线箭头需要沿自身路径飞出,而不是整体平移;开始界面的角色需要去除背景并加入动画。这些细节需要我进一步说明,才能逐步接近期望效果。随着功能增加,还需要考虑提示扣分、倒计时、自动求解与手动操作之间的配合,不能只关注新增功能是否独立可用。
此外,程序通过自动化检查,不代表游戏体验就一定理想。关卡是否有趣、布局是否拥挤、动画速度是否合适,仍需要结合实际运行和人工试玩判断。
自己的收获
通过这次开发,我逐步理解了如何用二维列表表示棋盘、用方向向量统一处理四个方向,以及通过逐格扫描判断箭头能否飞出。对于折线箭头,我也认识到必须检查整条线占据的坐标,而不能只检测箭头尖端。
在与 AI 协作的过程中,我学会了将较大的目标拆分成具体任务,并通过明确的反馈推动修改。我认识到,使用 AI 不只是获取代码,更重要的是理解实现思路、检查结果并判断是否满足需求。AI 可以提高开发效率,但需求选择、结果验证以及对最终作品的责任,仍然需要由自己承担。

浙公网安备 33010602011771号