2026 秋软件工程个人作业(第二次)

作业信息

项目内容 内容
这个作业属于哪个课程 H202601软件工程与软件工程实践
这个作业要求在哪里 2026秋软件工程个人作业(第二次)
这个作业的目标 使用 Python 和 AIGC 完成“一箭又一箭”小游戏
学号 052402234
GitHub 仓库 https://github.com/monkery-king-nice/one-arrow-after-another

1.项目展示

1.1 游戏运行视频

为了更加直观地展示游戏的实际运行效果,我录制了游戏运行过程,包括开始游戏、箭头移动、碰撞反馈、关卡切换以及最终通关等过程。

点击观看“一箭又一箭”游戏运行演示视频

1.2 开始界面

游戏开始界面

1.3 游戏过程

游戏过程

1.4 通关界面

通关界面

1.5 失败界面

失败界面

2. 项目介绍

2.1 游戏规则

“一箭又一箭”是一款基于 Python 和 Pygame 开发的点击式箭头解谜小游戏。游戏采用 8×8 的棋盘作为主要游戏区域,棋盘中会放置不同方向的箭头。

玩家需要观察每个箭头的方向,并点击箭头进行移动:

  • 箭头方向为上、下、左、右四种方向。
  • 点击箭头后,程序会检测箭头沿自身方向到棋盘边缘的路径上是否存在其他箭头。
  • 如果路径上没有其他箭头,当前箭头可以顺利飞出棋盘,并从棋盘中消失。
  • 如果路径上存在其他箭头,则当前箭头会被阻挡,触发碰撞动画,并扣除 1 次失误次数。
  • 玩家每一关初始拥有 3 次失误机会,当失误次数降为 0 后,完成当前碰撞动画即可进入失败界面。
  • 当棋盘上的所有箭头都被成功消除后,即可完成当前关卡并进入下一关。
  • 游戏共设计了 3 个关卡,全部完成后进入最终通关界面。

2.2 界面设计

游戏主要由开始界面、游戏界面和结果界面组成。

开始界面

开始界面用于展示游戏名称、基本玩法说明以及“开始游戏”按钮,玩家点击按钮后进入第 1 关。

游戏界面

游戏界面以 8×8 棋盘为核心区域,棋盘中显示不同方向的箭头,同时在界面上显示当前关卡、分数、剩余失误次数、计时等信息。玩家通过鼠标点击箭头进行解谜。

结果界面

当玩家完成全部关卡后,会进入通关结果界面,显示最终游戏结果、得分和星级评价,并提供重新开始游戏的操作。

2.3 主要功能

本游戏主要实现了以下功能:

  1. 箭头点击与方向判断:支持上、下、左、右四个方向的箭头。
  2. 路径检测:点击箭头后,检测其前进方向上是否存在其他箭头。
  3. 箭头飞出动画:没有障碍时,箭头沿自身方向移动并飞出棋盘。
  4. 碰撞反馈动画:箭头被阻挡时,会向障碍箭头移动、发生碰撞并震动,然后沿原路径返回。
  5. 失误次数管理:每次点击被阻挡的箭头会消耗 1 次失误机会。
  6. 多关卡系统:游戏包含 3 个可通关关卡,完成当前关卡后进入下一关。
  7. 计分系统:成功消除箭头可以获得分数,发生错误会扣分,完成关卡还可以获得额外奖励。
  8. 计时系统:记录玩家完成关卡所使用的时间,并参与最终评价。
  9. 星级评价:根据游戏过程中的表现进行星级评价。
  10. 通关与失败判定:根据箭头是否全部消除以及失误次数是否耗尽判断游戏结果。
  11. 重新开始功能:在通关或失败后可以重新开始游戏。

2.4 游戏特色

本游戏的主要特色在于规则简单,但需要玩家进行观察和思考。玩家不能只根据箭头本身的方向进行点击,而需要提前判断箭头前方是否存在其他箭头,因此具有一定的解谜和策略性。

此外,游戏加入了箭头飞出、碰撞、返回等动画效果,使操作结果更加直观。针对关卡中可能出现的相互阻挡问题,还增加了关卡可解性验证机制,在开发阶段通过程序模拟箭头消除过程,确保设计的 3 个关卡均能够完成。

在此基础上,游戏进一步加入了分数、计时和星级评价,使玩家除了完成关卡之外,还可以尝试用更少的失误和更短的时间获得更高评价,从而增加游戏的可玩性。

3.实现思路

3.1 箭头和棋盘的表示

本游戏采用 8×8 的二维数组表示游戏棋盘。棋盘中的每一个位置对应一个格子,通过行和列确定箭头的位置。

使用字符串表示当前格子中的箭头方向:

"up"
"down"
"left"
"right"

如果当前格子没有箭头,则使用空字符串 "" 表示。

例如第 1 关的部分数据如下:

LEVEL_1 = [
    ["right", "", "", "", "", "", "", ""],
    ["", "", "", "", "", "", "", "left"],
    ["down", "", "", "", "", "", "", ""],
    ...
]

其中:

LEVEL_1[0][0] = "right"

表示第 1 行第 1 列存在一个向右的箭头;

LEVEL_1[1][7] = "left"

表示第 2 行第 8 列存在一个向左的箭头;

LEVEL_1[2][0] = "down"

表示第 3 行第 1 列存在一个向下的箭头。

因此,一个箭头可以由以下两个信息确定:

箭头位置:行号 + 列号
箭头方向:up / down / left / right

当前实际进行游戏的棋盘使用 BOARD 保存。进入某个关卡时,将原始关卡数据复制到 BOARD

BOARD = copy.deepcopy(LEVELS[level_index])

这样玩家消除箭头时只修改当前棋盘,不会影响原始关卡数据。

3.2 方向的表示

游戏中共有上、下、左、右四个方向。程序使用 DIRECTIONS 字典统一表示方向:

DIRECTIONS = {
    "up": (0, -1),
    "down": (0, 1),
    "left": (-1, 0),
    "right": (1, 0),
}

在棋盘的行列坐标中,四个方向对应的变化为:

up    :行 - 1
down  :行 + 1
left  :列 - 1
right :列 + 1

这种表示方式可以将方向转换成统一的坐标变化量,使程序在进行路径检测和箭头移动时都可以使用相同的数据。

例如,当箭头方向为 right 时:

dx, dy = DIRECTIONS["right"]

可以获得对应的方向信息,然后在动画中按照该方向更新箭头的位置。

需要注意的是,棋盘的二维数组坐标和屏幕像素坐标存在区别。棋盘中主要使用“行、列”确定位置,而箭头飞行动画使用 x、y 像素坐标。因此,程序在绘制和动画过程中会将棋盘中的行列位置转换成屏幕坐标。

3.3 关卡的表示

游戏共设计了三个关卡:

LEVEL_1 = [...]
LEVEL_2 = [...]
LEVEL_3 = [...]

LEVELS = [LEVEL_1, LEVEL_2, LEVEL_3]

每一个关卡本质上都是一个 8×8 的二维数组。

程序通过:

current_level

记录当前关卡编号。其中:

0 → 第 1 关
1 → 第 2 关
2 → 第 3 关

进入新关卡时,通过:

load_level(level_index)

加载对应的棋盘数据。

def load_level(level_index):
    global BOARD, current_level
    current_level = level_index
    BOARD = copy.deepcopy(LEVELS[level_index])

同时,进入新关卡还会重新设置本关的失误次数、得分和计时等数据,而整个游戏的累计总分不会被清空。


3.4路径检测算法设计

路径检测的基本思想

本游戏最核心的算法是判断当前点击的箭头能否直接飞出棋盘。

箭头只能沿自身指向的方向移动,因此不需要进行复杂的寻路。程序只需要从当前箭头所在的位置开始,沿箭头指向的方向逐格检查。

基本规则为:

从当前箭头开始
      ↓
按照箭头方向逐格检查
      ↓
检查路径上是否存在其他箭头
   ↙              ↘
存在                不存在
 ↓                    ↓
被其他箭头阻挡        可以飞出棋盘

如果路径上存在其他箭头,则当前箭头不能飞出;如果一直检查到棋盘边界都没有发现其他箭头,则当前箭头可以飞出。

因此,本游戏的路径检测实际上是一个沿行或列进行的线性搜索。

核心路径检测函数

程序使用 can_arrow_exit() 判断当前棋盘中的箭头能否飞出:

def can_arrow_exit(row, col):
    return can_arrow_exit_on_board(BOARD, row, col)

真正执行检测的是 can_arrow_exit_on_board()

对于向右的箭头,程序从当前箭头右侧的第一个格子开始,一直检查到棋盘边界:

if direction == "right":
    for c in range(col + 1, GRID_SIZE):
        if board[row][c]:
            return False
    return True

其含义是:

  1. 当前箭头位于 (row, col)
  2. col + 1 开始检查右侧格子;
  3. 如果发现某个格子不为空,说明该位置存在其他箭头;
  4. 当前箭头的路径被阻挡,返回 False
  5. 如果一直检查到棋盘边界都没有发现箭头,则返回 True

其他三个方向采用相同的思想。

向左:

elif direction == "left":
    for c in range(col - 1, -1, -1):
        if board[row][c]:
            return False
    return True

向下:

elif direction == "down":
    for r in range(row + 1, GRID_SIZE):
        if board[r][col]:
            return False
    return True

向上:

elif direction == "up":
    for r in range(row - 1, -1, -1):
        if board[r][col]:
            return False
    return True

因此,路径检测的关键并不是寻找一条新的路径,而是判断:

当前箭头 → 棋盘边界

这一条直线路径上是否存在其他箭头。

路径检测示例

例如当前棋盘某一行存在:

→     →       空       空
A     B

假设 A 是向右的箭头。

程序会从 A 右侧的格子开始检查:

A → B

因为 B 是一个箭头,所以检测到障碍后直接返回:

False

此时 A 不能飞出。

如果玩家先点击 B,并且 B 可以飞出,那么程序会将 BBOARD 中删除:

BOARD[fa["row"]][fa["col"]] = ""

此时棋盘变成:

→     空       空       空
A

玩家再次点击 A 时,程序重新进行路径检测。由于 A 到棋盘边界之间已经没有其他箭头,因此返回:

True

于是 A 就可以飞出。

这也是本游戏解谜玩法的核心:通过不断消除当前能够飞出的箭头,逐渐解除其他箭头的阻挡。


3.5阻挡箭头的检测

can_arrow_exit() 返回 False 时,程序还需要知道究竟是哪一个箭头挡住了当前箭头。

因此设计了:

find_blocking_arrow(row, col)

该函数同样沿当前箭头的方向逐格搜索,但与路径检测不同,它的目的不是判断能否飞出,而是寻找路径上的第一个障碍箭头。

例如向右搜索:

if direction == "right":
    for c in range(col + 1, GRID_SIZE):
        if BOARD[row][c]:
            return row, c

找到第一个箭头后立即返回其行列位置。

例如:

→     空     ↓     →     空
A           B

如果 A 向右,那么程序从 A 右侧开始检查,首先遇到 B,于是返回:

B 的行号和列号

而不是继续寻找后面的箭头。

这样程序就能够计算当前箭头应该向前移动到什么位置,然后播放:

向前移动
    ↓
接近障碍箭头
    ↓
碰撞震动
    ↓
原路返回

的动画效果。


3.6玩家点击后的完整处理流程

玩家点击棋盘后,程序首先根据鼠标坐标计算出对应的行和列:

cell = get_clicked_cell(board_x, board_y, event.pos)

如果点击的位置存在箭头,则读取:

direction = BOARD[row][col]

然后进行路径检测:

if can_arrow_exit(row, col):

此时有两种情况。

第一种情况是路径畅通:

点击箭头
   ↓
路径检测
   ↓
没有其他箭头阻挡
   ↓
启动飞出动画
   ↓
箭头离开棋盘
   ↓
从 BOARD 删除
   ↓
得分 +100

第二种情况是路径被阻挡:

点击箭头
   ↓
路径检测
   ↓
发现其他箭头
   ↓
寻找第一个障碍箭头
   ↓
播放碰撞动画
   ↓
失误次数 -1
   ↓
得分 -50

其中,飞出棋盘的箭头并不会在点击的瞬间立即从 BOARD 删除,而是等箭头完成飞出动画后再删除:

if is_flying_arrow_off_board(fa, board_x, board_y):
    BOARD[fa["row"]][fa["col"]] = ""
    add_arrow_score()

这样可以保证玩家能够看到完整的箭头飞出动画,同时棋盘数据也能正确反映箭头是否已经被消除。


3.7关卡可解性检测

为了避免设计出无法通关的关卡,程序还增加了 is_level_solvable() 函数,对关卡进行自动验证。

其基本思路是复制一份关卡数据,然后模拟玩家不断消除可以直接飞出的箭头:

board = copy.deepcopy(level)

每一轮程序寻找当前所有能够飞出的箭头:

exitable = []

for r in range(GRID_SIZE):
    for c in range(GRID_SIZE):
        if board[r][c] and can_arrow_exit_on_board(board, r, c):
            exitable.append((r, c))

如果找到可以飞出的箭头,就模拟将其中一个删除:

r, c = exitable[0]
order.append((r, c, board[r][c]))
board[r][c] = ""

然后重新进行路径检测。

例如:

A → B → C

如果 C 可以首先飞出:

A → B

接下来 B 可以飞出:

A

最后 A 也可以飞出:

空棋盘

如果某一轮已经没有任何可以飞出的箭头,但棋盘上仍然存在箭头,则说明当前关卡出现死锁:

if not exitable:
    break

最后程序检查棋盘是否还存在箭头:

for r in range(GRID_SIZE):
    for c in range(GRID_SIZE):
        if board[r][c]:
            return False, order

如果仍有箭头,则返回 False,表示该关卡不可解;如果所有箭头都被模拟删除,则返回 True

程序启动时通过:

verify_levels()

对三个关卡进行验证,并在控制台输出验证结果和一种可行的通关顺序。

4. AIGC 开发记录

在“一箭又一箭”的开发过程中,我使用 TRAE 和 ChatGPT 对部分功能进行辅助开发。通过 AI 生成代码、分析问题和辅助修改,再结合实际运行结果进行测试和调整,逐步完善游戏功能。

子任务 借助何种 AIGC 技术 AI 实现或提供了什么 实际效果如何 人工修改
游戏基础框架 TRAE 生成 Pygame 游戏窗口、8×8 棋盘、四种方向箭头以及鼠标点击检测代码 游戏可以正常启动,棋盘和箭头能够正常显示,鼠标可以识别点击位置 运行程序进行测试,检查棋盘内外点击是否正常
箭头路径检测 TRAE 实现 can_arrow_exit() 函数,对 updownleftright 四个方向进行路径检测 能够判断箭头前方是否存在其他箭头,并确定当前箭头能否飞出棋盘 对四个方向和棋盘边界进行实际测试,并检查边界条件
关卡设计与可解性验证 TRAE + ChatGPT 生成关卡数组,并设计 is_level_solvable() 函数模拟箭头依次飞出 初始关卡中出现过箭头相互阻挡、无法继续的情况 根据实际试玩调整关卡布局,并通过程序验证三个关卡均可以完成
失误次数与游戏失败机制 TRAE + ChatGPT 增加 MAX_MISTAKESmistakes_left 等变量,并完善 game_over 状态和重新开始流程 点击被阻挡箭头可以扣除失误次数,次数归零后进入游戏失败状态 实际测试错误点击、次数归零、失败界面和重新开始功能
被阻挡箭头碰撞动画 TRAE + ChatGPT 实现 find_blocking_arrow(),并增加箭头前进、碰撞震动、返回等动画状态 被阻挡箭头可以先向障碍箭头移动,碰撞后震动,再沿原路径返回 根据实际动画效果调整移动速度、碰撞距离和动画过程,并保证箭头返回后保持原来的指向
碰撞动画 Bug 修复 TRAE + ChatGPT 分析 draw_board() 和碰撞动画绘制逻辑,定位障碍箭头在部分动画阶段没有被绘制的问题 找到了障碍箭头在 moving_forwardmoving_back 阶段短暂消失的原因 修改绘制逻辑并重新运行测试,确认障碍箭头能够持续显示

5.测试结果

编号 测试内容 预期结果 实际结果 测试结论
T01 点击前方无阻挡的箭头 箭头沿自身方向飞出棋盘并消失 箭头正常飞出棋盘,完全离开后消失 通过
T02 点击前方有阻挡的箭头 箭头不消失,失误次数减 1 箭头向前移动后与障碍箭头碰撞、震动并返回,失误次数减 1 通过
T03 点击位于边缘且朝向棋盘外的箭头 箭头正常消失,不发生越界错误 箭头正常飞出棋盘并消失,程序运行正常 通过
T04 消除本关全部箭头 显示通关并进入下一关 本关所有箭头消除后进入关卡完成状态,可以继续进入下一关 通过
T05 失误次数耗尽 显示失败并允许重新开始 失误次数降至 0 后进入游戏失败状态,可以重新开始 通过
T06 游戏进行中重新开始 箭头布局和失误次数恢复 重新开始后恢复第 1 关初始布局,失误次数恢复为 3 次 通过

6.PSP表格

任务 预估耗时(分钟) 实际耗时(分钟) 差异(分钟)
需求分析与游戏设计 30 25 -5
Python 与图形库学习 60 75 +15
游戏界面实现 30 20 -10
路径与碰撞逻辑实现 60 55 -5
关卡设计 30 35 +5
AIGC 辅助开发 40 30 -10
测试与修改 20 50 +30
README 与博客撰写 30 60 +30
合计 300 350 +50

7.心得体会

通过本次“一箭又一箭”小游戏的开发,我对 AI 辅助软件开发有了更加直观的认识。AI 可以在代码编写、功能实现和问题分析等方面提供很大的帮助,尤其是在一些重复性较高的代码编写以及功能实现过程中,可以明显提高开发效率。例如,本项目中的箭头路径检测、关卡可解性验证、失误次数、碰撞动画等功能,都得到了 AI 的辅助。

但在实际使用过程中,我也发现 AI 生成的代码并不能完全保证正确。有些代码虽然能够运行,但实际效果与预期存在差异。例如在碰撞动画开发过程中,曾经出现障碍箭头短暂消失的问题,需要通过实际运行和观察发现 Bug,再让 AI 分析具体原因并进行修改。关卡设计过程中也发现,AI 生成的部分布局可能存在无法通关的情况,因此不能完全依赖 AI 的结果,还需要自己进行测试和验证。

通过这次实践,我认识到 AIGC 更适合作为软件开发过程中的辅助工具,而不是完全代替程序员。开发者仍然需要负责分析需求、判断代码是否合理、运行程序、发现问题并进行修改。只有将 AI 生成、人工分析和实际测试结合起来,才能得到比较可靠的最终结果。

在本次项目中,我不仅进一步熟悉了 Python 和 Pygame 的使用,也加深了对程序状态管理、路径检测、动画处理、关卡设计和软件测试的理解。同时,我也学会了如何更加准确地向 AI 描述问题,以及如何根据程序实际运行结果不断调整和完善代码。这些经验对以后使用 AI 辅助完成更复杂的软件开发任务具有一定帮助。

8.扩展功能说明

得分系统

在基础箭头消除玩法的基础上,程序增加了得分机制,使玩家的操作结果能够通过分数进行体现。

程序主要设置了以下几种得分情况:

  • 成功消除一个箭头可以获得基础分数;
  • 点击被阻挡的箭头会产生扣分;
  • 完成本关后可以获得额外的关卡奖励;
  • 如果玩家在规定时间内完成关卡,还可以根据剩余时间获得时间奖励。

程序中的主要得分参数如下:

SCORE_ARROW = 100
SCORE_PENALTY = 50
SCORE_LEVEL_BONUS = 500
TIME_BONUS_LIMIT = 30.0
TIME_BONUS_PER_SEC = 20

通过这些设置,可以鼓励玩家尽量减少错误操作,并在较短时间内完成关卡。

计时功能

游戏增加了关卡计时功能,用于记录玩家完成当前关卡所使用的时间。

每进入一个新的关卡,都会重新开始计时。玩家完成关卡后,根据本关的完成时间计算相应的时间奖励。

计时功能不仅可以作为星级评价的依据,也可以增加游戏的挑战性,使玩家不仅需要考虑箭头的消除顺序,还需要尽量提高完成速度。

星级评价

程序根据玩家完成关卡所使用的时间进行星级评价。

当前程序设置了三个时间等级:

STAR3_TIME_LIMIT = 15.0
STAR2_TIME_LIMIT = 30.0

评价规则可以概括为:

完成时间 星级评价
不超过 15 秒 三星
超过 15 秒但不超过 30 秒 二星
超过 30 秒 一星

因此,玩家在完成同一个关卡时,所用时间不同会得到不同的星级评价。

可执行文件打包与 GitHub 发布

为了方便其他用户直接运行游戏,我在完成 Python + Pygame 程序开发后,进一步将项目打包为 Windows 可执行文件,并发布到 GitHub Releases。

首先使用 PyInstaller 对游戏程序进行打包。通过编写 一箭又一箭.spec 配置文件,设置程序入口、程序名称以及单文件模式和无控制台窗口模式,然后执行打包命令:

pyinstaller 一箭又一箭.spec

打包完成后,在 dist/ 目录下生成了游戏的可执行文件。打包后的程序约 14 MB,可以直接在 Windows 系统中运行。经过实际启动测试,确认打包后的游戏能够正常打开并运行。

为了避免较大的二进制文件影响源代码仓库,我没有将 build/dist/ 目录提交到 Git 仓库,而是将可执行文件作为正式发行版本上传到 GitHub Releases。

具体发布过程如下:

  1. 在 GitHub 仓库中进入 Releases,创建新的 Release。
  2. 创建版本标签 v1.0
  3. 填写版本标题和游戏功能说明。
  4. 将打包得到的 .exe 文件上传到 Release 附件。
  5. 发布 Release。

发布完成后,其他用户可以直接通过 GitHub 仓库的 Releases 页面获取游戏的可执行文件,不需要下载源代码,也不需要提前安装 Python 和 Pygame 环境。

在发布过程中,上传中文名称的 一箭又一箭.exe 时曾出现 GitHub 将文件名显示为 default.exe 的问题。经过排查后,将可执行文件复制并重命名为英文名称 OneArrowAfterAnother_v1.0.exe 后重新上传,文件名显示恢复正常,最终成功完成发布。

通过这一过程,我不仅完成了游戏功能开发,还进一步完成了程序的打包和发布,使“一箭又一箭”从只能在开发环境中运行的 Python 程序,变成了可以直接提供给其他用户运行的 Windows 可执行程序。

posted @ 2026-09-15 16:36  郭泽强  阅读(18)  评论(0)    收藏  举报