2026秋软件工程个人作业(第二次)
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/fzu/202601SofwareEngineering |
|---|---|
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/fzu/202601SofwareEngineering/homework/16717 |
| 这个作业的目标 | 使用 Python 和 AIGC 完成“一箭又一箭”小游戏 |
| 学号 | 102401605 |
| GitHub 仓库 | https://github.com/asdwrv/Arrow_Game |
1.项目展示:
开始界面

游戏过程

通关与失败界面


2.项目介绍
游戏规则
棋盘是 3×3 的格子,每格一个箭头,朝上、下、左、右四个方向之一。
- 点击箭头,如果它指向的方向上没有其他箭头挡路,它就飞出去消失。
- 如果前方有箭头挡着,它就晃动一下,失误 +1。
- 失误满 3 次,游戏失败。
- 清空棋盘上所有箭头,就过关。通过 3 关,游戏胜利。
界面设计
- 深色主题:深蓝背景、深灰格子、浅灰网格线,箭头是蓝色加白色描边,醒目护眼。
- 三个界面:开始界面、游戏界面、结束界面(失败或通关)。
- HUD 信息:左上角显示计时,中上显示关卡,右上显示失误次数,左下提示按 F1 重开。
主要功能
- 3 个关卡:设计时保证每关都有解,不会卡死。
- F1 重开本关:游戏中随时按 F1,当前关重置,失误清零,正在飞的箭头立即消失,但不回主菜单、不换关。
- 通关动画:清空箭头后先显示绿色大字 "Level Clear!",停 1 秒再进下一关。
- 计时器:实时显示用时,结束时冻结,方便挑战更短时间。
- 点击反馈:成功飞出并淡出,被阻挡则晃动。
- 任意键重开:失败或通关后,按任意键从第 1 关重新开始。
游戏特色
- 规则简单,但要动脑:需要规划点击顺序,一步步清出通道。
- 不会死局:每关都验证过,每一步都至少有一个箭头能飞出去。
- 视觉清晰:箭头方向一目了然,成功失败反馈明确。
- 可竞速:计时器让玩家可以挑战更快通关。
操作
| 场景 | 操作 |
|---|---|
| 开始界面 | 按任意键开始 |
| 游戏中 | 鼠标左键点箭头 |
| 游戏中 | 按 F1 重开本关 |
| 失败或通关 | 按任意键重新开始 |
3.实现思路:
箭头表示
每个箭头是一个 Arrow 对象,存三个关键信息:
- row、col:在 3×3 棋盘上的行号和列号。
- direction:方向,用整数表示——UP=0、DOWN=1、LEFT=2、RIGHT=3。
class Arrow:
def __init__(self, row, col, direction):
self.row = row
self.col = col
self.direction = direction
self.state = 'idle' # idle / flying / shaking
self.offset_x = 0 # 飞行动画位移
self.offset_y = 0
self.alpha = 255 # 淡出透明度
箭头分两个列表存放:
- self.arrows:还在棋盘上的。
- self.flying_arrows:已经飞出、正在播放动画的。
方向表示
方向映射成行、列的步进量 (dr, dc):
DIR_VECTORS = {
UP: (-1, 0), # 行号减 1
DOWN: (1, 0), # 行号加 1
LEFT: (0, -1), # 列号减 1
RIGHT: (0, 1) # 列号加 1
}
飞行动画和路径检测都用这个步进量:
dr, dc = DIR_VECTORS[self.direction]
self.offset_x += dc * self.fly_speed
self.offset_y += dr * self.fly_speed
绘制时不靠旋转,而是给四个方向各写一组多边形顶点,保证画出来的方向和逻辑方向一致:
if direction == UP:
points = [(s, s*0.3), (s*1.6, s*1.5), (s, s*1.1), (s*0.4, s*1.5)]
elif direction == DOWN:
points = [(s, s*1.7), (s*0.4, s*0.5), (s, s*0.9), (s*1.6, s*0.5)]
elif direction == LEFT:
points = [(s*0.3, s), (s*1.5, s*0.4), (s*1.1, s), (s*1.5, s*1.6)]
elif direction == RIGHT:
points = [(s*1.7, s), (s*0.5, s*0.4), (s*0.9, s), (s*0.5, s*1.6)]
三、关卡表示
每个关卡是一个 3×3 的二维列表,每个格子存一个方向常量:
self.level_data = [
# 第 1 关
[
[UP, UP, UP],
[LEFT, UP, RIGHT],
[DOWN, DOWN, DOWN]
],
# 第 2 关
[
[LEFT, UP, RIGHT],
[UP, DOWN, UP],
[LEFT, DOWN, LEFT]
],
# 第 3 关
[
[UP, UP, UP],
[LEFT, DOWN, RIGHT],
[DOWN, DOWN, DOWN]
]
]
切换关卡时,reset_level 读取对应数据,遍历 9 个格子,给每格生成一个 Arrow 放进 self.arrows:
def reset_level(self, level):
self.arrows.clear()
self.flying_arrows.clear()
data = self.level_data[level - 1]
for r in range(GRID_SIZE):
for c in range(GRID_SIZE):
direction = data[r][c]
if direction is not None:
self.arrows.append(Arrow(r, c, direction))
self.mistakes = 0
关卡设计保证每步都有箭头能飞出,不会卡死。
路径检测
核心是 is_path_clear 函数,思路是从箭头前方一格开始,沿它指向的方向一格一格往外走,直到走出棋盘。
def is_path_clear(self, arrow):
# 1. 拿到方向步进量
dr, dc = DIR_VECTORS[arrow.direction]
# 2. 从箭头前方第一格开始
r = arrow.row + dr
c = arrow.col + dc
# 3. 只要还在棋盘内,就一格一格走
while 0 <= r < GRID_SIZE and 0 <= c < GRID_SIZE:
# 检查这一格有没有其他箭头
for other in self.arrows:
if other is arrow: # 跳过自己
continue
if other.row == r and other.col == c:
return False # 被阻挡
# 没被阻挡,继续朝同方向走一步
r += dr
c += dc
# 4. 走出棋盘都没碰到箭头 → 畅通
return True
- 朝上只查同一列上方;
- 朝下只查同一列下方;
- 朝左只查同一行左边;
- 朝右只查同一行右边。
不会看反方向或垂直方向。
点击和过关
玩家点击时,check_click 找到点中的箭头,调用 is_path_clear:
def check_click(self, pos):
if self.state != STATE_PLAYING:
return
if len(self.flying_arrows) > 0: # 动画中禁止点击
return
for arrow in reversed(self.arrows):
if not arrow.is_idle():
continue
cx, cy = arrow.get_center()
rect = pygame.Rect(cx - 50, cy - 50, 100, 100)
if rect.collidepoint(pos):
if self.is_path_clear(arrow):
# 可以飞出
self.arrows.remove(arrow)
arrow.start_fly()
self.flying_arrows.append(arrow)
else:
# 被阻挡,晃动 + 失误
arrow.start_shake()
self.mistakes += 1
if self.mistakes >= MAX_MISTAKES:
self.state = STATE_GAME_OVER
return
过关判定放在动画之后:只有 self.arrow 为空且 self.flying_arrows 也为空时,才进入下一关:
def update(self):
# 更新飞行动画,飞出屏幕的移除
for arrow in self.flying_arrows[:]:
if not arrow.update():
self.flying_arrows.remove(arrow)
# 更新棋盘上箭头的晃动动画
for arrow in self.arrows:
arrow.update()
# 全部清空后才过关
if self.state == STATE_PLAYING:
if len(self.arrows) == 0 and len(self.flying_arrows) == 0:
self.next_level()
这样最后一个箭头的飞行动画能完整播完。
4.AIGC 使用过程:
AIGC 使用过程记录
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 中文显示乱码 | DeepSeek | 建议将 pygame.font.Font(None, size) 改为 pygame.font.SysFont('simhei', size),并给出多种中文字体备选方案。 | 中文方块问题解决,但部分系统找不到指定字体,需要多字体回退。 | 增加字体缓存和多种字体名回退列表,并最终改用全英文界面。 |
| 路径检测逻辑 | DeepSeek | 生成基于 DIR_VECTORS 的逐格遍历代码,沿箭头方向检查是否有其他箭头阻挡。 | 初始版本混淆行/列与坐标,所有箭头都能飞出,完全失效。 | 重写 is_path_clear,严格用 row += dr、col += dc 遍历,并加 if other is arrow: continue 跳过自身。 |
| 关卡布局设计 | DeepSeek | 提供 3 个 3×3 的二维数组布局,声称“保证有解”。 | AI 生成的第二关出现死锁,6 个箭头互相阻挡,玩到一半必定失败。 | 手工推演清空顺序,重新设计关卡布局,每步都有箭头能飞出。 |
| 箭头绘制方向错位 | DeepSeek | 使用旋转角度方式绘制四个方向的箭头。 | 由于 Pygame 屏幕 y 轴向下,旋转公式导致“逻辑朝上、画出来朝下”,视觉与逻辑脱节。 | 放弃旋转,改为给四个方向各写一组多边形顶点,确保视觉方向与逻辑方向 100% 一致。 |
| 通关动画与重开功能 | DeepSeek | 生成“Level Clear!”停留 1 秒的过渡状态、F1 重开本关、结束界面按任意键重开等逻辑。 | 动画基本可用,但重开时没有清空正在飞行的箭头,旧箭头仍在屏幕上飞。 | 在 restart_current_level 中补上 self.flying_arrows.clear(),并调整动画时长和晃动幅度。 |
5.测试结果
测试用例与结果
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 箭头沿指向方向飞出并消失 | 通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误次数加 1 | 箭头原地晃动不消失,失误计数加1,HUD 右上角数字变红 | 通过 |
| T03 | 点击位于边缘且朝向棋盘外的箭头 | 箭头正常消失,不发生越界错误 | 第一关边缘的朝外箭头点击后飞出,飞出过程中坐标超出棋盘范围且不报错 | 通过 |
| T04 | 消除本关全部箭头 | 显示通关并进入下一关 | 最后一个箭头飞完后,显示绿字 "Level Clear!",停留约 1 秒后自动进入下一关,关卡数 +1 | 通过 |
| T05 | 失误次数耗尽 | 显示失败并允许重新开始 | 第 3 次失误后,界面切换到 "GAME OVER",显示失败原因、用时和到达关卡;按任意键从第 1 关重新开始 | 通过 |
| T06 | 游戏进行中重新开始 | 箭头布局和失误次数恢复 | 按 F1 后,当前关卡箭头全部恢复到初始布局,失误次数清零,关卡数不变 | 通过 |
6.PSP 表格
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1.0 | 1.0 | 0 |
| Python 与图形库学习 | 1.5 | 1.0 | -0.5 |
| 游戏界面实现 | 1.5 | 2.0 | +0.5 |
| 路径与碰撞逻辑实现 | 1.5 | 2.5 | +1.0 |
| 关卡设计 | 1.0 | 1.5 | +0.5 |
| AIGC 辅助开发 | 1.0 | 1.5 | +0.5 |
| 测试与修改 | 1.5 | 2.0 | +0.5 |
| README 与博客撰写 | 1.0 | 1.0 | 0 |
| 合计 | 10.0 | 12.5 | +2.5 |
7.心得体会
在开发“一箭又一箭”游戏的过程中,AI 帮助我快速搭建了代码框架、提供了动画实现公式和多种问题解决方案,显著提升了开发效率,但也暴露了它在空间逻辑、关卡可解问题、路径检测方向混乱等问题都需要手工推演和实机测试来修正。这次经历让我深刻体会到,AI 是高效的助手而非可靠的替代品,核心逻辑必须自己把关,逐格遍历方式写路径检测、用顶点直接绘制替代旋转,这些经验比代码本身更有价值。
浙公网安备 33010602011771号