软件工程第二次作业
一、基本信息
| 这个作业属于哪个课程 | 软件工程 |
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/fzu/202601SofwareEngineering/homework/16717 |
| 这个作业的目标 | 使用 Python 和 AIGC 完成"一箭又一箭"小游戏 |
| 学号 | 102401111 |
| GitHub 仓库 | https://github.com/Kerical/arrow |
二、项目展示



三、项目介绍
"一箭又一箭"是一款基于 Pygame 开发的网格逻辑小游戏。玩家在 6×6 棋盘上点击箭头,判断箭头朝向的边界路径上是否有阻挡。若路径畅通,箭头沿方向飞出棋盘消失;若被其他箭头挡住,则扣除一次失误次数。清除当前关卡所有箭头即可过关,失误次数耗尽则失败。
游戏规则:
每个箭头只能沿自身朝向直线移动
路径上不能有其他存活箭头(允许同行/同列,但方向不能互挡)
目标:清空当前关卡所有箭头
失误次数限制:3 次
主要功能:
三关卡递进设计,含策略性"挡路箭头"
箭头飞出动画
被挡时箭头震动+闪烁反馈
中文字体支持
菜单 / 游戏 / 通关 / 失败多状态切换
四、实现思路
4.1 整体架构
游戏采用面向对象设计,核心类为 Arrow(箭头)和 Game(游戏状态管理器)。主循环负责事件处理、状态更新和画面绘制。游戏状态分为:菜单(MENU)、游戏中(PLAYING)、关卡通过(LEVEL_CLEAR)、游戏失败(GAME_OVER)四种,通过 self.state 变量切换。
4.2 数据结构设计
箭头类 Arrow:每个箭头实例封装了自身在棋盘上的所有信息:
class Arrow:
def __init__(self, x, y, direction):
self.cx = x # 所在列,范围 0~5
self.cy = y # 所在行,范围 0~5
self.dir = direction # 方向字符串:"up"/"down"/"left"/"right"
self.alive = True # 是否存活(被消除后设为 False)
self.flying = False # 是否正在播放飞出动画
self.fly_progress = 0 # 飞出动画进度,0.0~1.0
self.fly_target = (0, 0) # 飞出动画终点坐标(像素)
self.shake_timer = 0 # 被挡时震动计时器
self.shake_offset = 0 # 震动偏移量
关卡数据 LEVELS:采用列表嵌套字典结构,每个关卡包含名称和箭头布局。箭头以元组 (列, 行, 方向) 表示,便于手工编辑和扩展:
LEVELS = [
{
"name": "第一关",
"arrows": [
(0, 0, "right"),
(0, 2, "up"),
(2, 5, "left"),
(2, 3, "left"),
],
},
# 第二关、第三关类似...
]
方向映射 DIRS:将方向名映射为网格坐标偏移量,用于路径检测和飞出动画:
DIRS = {
"up": ( 0, -1),
"down": ( 0, 1),
"left": (-1, 0),
"right": ( 1, 0),
}
4.3 坐标系统
游戏使用两套坐标系:
- 网格坐标:(cx, cy),范围 0~5,用于逻辑判断和路径检测。
- 像素坐标:(px, py),通过
grid_to_pixel(cx, cy)转换,用于 Pygame 绘制。转换公式为:px = BOARD_X + cx * CELL_SIZE + CELL_SIZE // 2,py = BOARD_Y + cy * CELL_SIZE + CELL_SIZE // 2。
这种设计将逻辑与渲染解耦,方便后续调整棋盘位置和格子大小。
4.4 路径检测算法(核心)
点击箭头后,调用 blocked_in_direction(arrow) 判断该箭头朝向的路径上是否有阻挡。
- 从被点击箭头的相邻格子开始(
nx = cx + dx, ny = cy + dy)。 - 沿方向逐步移动,每次步进一个格子。
- 每到一个格子,遍历当前所有箭头,检查是否有存活且未飞出的箭头占据该位置。
- 若找到阻挡,立即返回
True。 - 若步进到棋盘边界仍未找到阻挡,返回
False。
def blocked_in_direction(self, arrow):
dx, dy = DIRS[arrow.dir]
nx, ny = arrow.cx + dx, arrow.cy + dy
while 0 <= nx < COLS and 0 <= ny < ROWS:
for a in self.arrows:
if a.alive and not a.flying and a.grid_pos == (nx, ny):
return True # 路径上有箭头,被阻挡
nx += dx
ny += dy
return False # 路径畅通,无阻挡
设计要点:
- 排除
a.flying的箭头,避免正在飞出的箭头被误判为阻挡。 - 边界条件使用
0 <= nx < COLS而非<= COLS - 1,防止索引越界。 - 允许同行/同列但方向不互挡的布局,增加策略深度。
4.5 飞出动画实现
当路径畅通时,箭头不立即消失,而是播放一段飞出动画:
- 调用
fly_out_target(arrow)计算终点:沿方向继续延伸一步到棋盘外。 - 设置
arrow.flying = True,arrow.fly_progress从 0 开始每帧递增。 - 每帧根据
fly_progress用线性插值计算当前像素位置:current = start + (target - start) * progress。 - 当
fly_progress >= 1.0时,设置arrow.alive = False,箭头彻底消失。
插值公式为:
arrow.pixel_pos = (
arrow.pixel_pos[0] + (arrow.fly_target[0] - arrow.pixel_pos[0]) * arrow.fly_progress,
arrow.pixel_pos[1] + (arrow.fly_target[1] - arrow.pixel_pos[1]) * arrow.fly_progress,
)
4.6 碰撞反馈
当路径被阻挡时:
- 触发
arrow.shake_timer = 20(帧数),每帧在绘制时对箭头位置施加正弦偏移量,产生左右震动效果。 - 同时设置失误次数减 1,并在屏幕中央显示"被挡住了!失误。"消息,消息持续 60 帧后自动消失。
- 箭头颜色短暂闪烁为红色(通过
shake_timer控制颜色切换)。
4.7 关卡切换与状态管理
每帧检查当前存活箭头数量:
- 若
alive_count == 0且当前关卡不是最后一关,切换到 LEVEL_CLEAR 状态,显示"关卡通过!"按钮。 - 若
alive_count == 0且是最后一关,显示"游戏通关!"。 - 若
mistakes <= 0,切换到 GAME_OVER 状态,显示失败界面和"重试本关"按钮。
重新开始功能通过重新加载当前关卡的箭头数据实现,重置所有状态变量。
4.8 中文字体处理
Windows 下 Pygame 默认字体不支持中文,解决方案:
try:
font = pygame.font.Font("C:/Windows/Fonts/msyh.ttc", size)
except:
font = pygame.font.SysFont("microsoftyahei", size)
使用 try-except 确保在不同环境下都能正常显示文字。
4.9 主循环结构
while running:
dt = clock.tick(60) # 限制 60 FPS
for event in pygame.event.get():
handle_event(event) # 处理鼠标点击、键盘等
update(dt) # 更新动画进度、震动计时器等
draw(screen) # 根据当前状态绘制画面
pygame.display.flip()
事件处理中,点击逻辑为:遍历所有存活箭头,检查鼠标坐标是否在箭头所在的格子内,若是则调用路径检测并执行业务逻辑。
五、AIGC 使用过程
| 子任务 | 借助何种 AIGC | AI 提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 路径检测逻辑 | deepseek | 生成四个方向的射线检测代码框架 | 向上检测时边界条件写反导致越界 | 修改 while 边界判断顺序,增加 not a.flying 条件 |
| 关卡设计 | deepseek | 生成三关的箭头布局数组 | 第二关存在两个箭头互相正对导致无法通关 | 手工调整箭头位置和方向,反复试玩验证每关可解 |
| 中文乱码修复 | deepseek | 提供 Windows 下 Pygame 中文字体加载方案 | 字体加载成功,中文正常显示 | 增加 try-except 回退到 pygame.font.SysFont |
| 界面布局调整 | deepseek | 分析文字超出界面的原因并给出调整方案 | 调整后底部提示和按钮不再重叠 | 根据实际运行效果微调 BOARD_Y、HEIGHT 等参数 |
六、测试结果
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 箭头播放飞出动画后消失,剩余箭头数减1 | 通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误次数减1 | 箭头震动闪烁,显示"被挡住了!失误。",失误次数减1 | 通过 |
| T03 | 点击位于边缘且朝向棋盘外的箭头 | 箭头正常消失,不发生越界错误 | 箭头直接飞出,无异常报错 | 通过 |
| T04 | 消除本关全部箭头 | 显示通关并进入下一关 | 弹出"关卡通过!"界面,点击"下一关"进入第二关 | 通过 |
| T05 | 失误次数耗尽 | 显示失败并允许重新开始 | 第3次失误后跳转失败界面,点击"重试本关"可重置 | 通过 |
| T06 | 游戏进行中点击"重新开始" | 箭头布局和失误次数恢复 | 箭头全部重置为初始位置,失误次数恢复为3 | 通过 |
测试环境:Windows 11 + Python 3.13 + pygame 2.6.1,采用手工测试。
七、PSP 表格
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1 | 1 | 1 |
| Python 与图形库学习 | 1.5 | 2 | 0.5 |
| 游戏界面实现 | 2 | 2 | 0 |
| 路径与碰撞逻辑实现 | 1.5 | 1.5 | 0 |
| 关卡设计 | 1 | 2 | 1 |
| AIGC 辅助开发 | 2 | 2 | 0 |
| 测试与修改 | 1.5 | 2 | 0.5 |
| README 与博客撰写 | 1 | 2 | 1 |
| 合计 | 11.5 | 15.5 | 4 |
八、心得体会
AI 带来的帮助
快速搭建 Pygame 代码框架与类结构,节省查文档时间
辅助定位中文显示、界面布局、边界判断等问题
提供关卡布局与文档结构参考
出现的问题
AI 生成代码边界条件需人工核对
AI 给出的关卡布局不一定都可解,需试玩调整
界面参数不能只靠 AI 反复改,自己理解坐标后效率更高
收获
掌握 Pygame 事件循环、绘制、动画插值、字体渲染
理解网格游戏坐标映射、射线检测、状态管理
实践"提需求—验证—修改—提交"的 AI 协作流程
完成从开发、测试、文档到 GitHub 提交的完整流程

浙公网安备 33010602011771号