软件工程第二次作业
软件工程课程第二次个人作业——一箭又一箭
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/fzu/202601SofwareEngineering |
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/fzu/202601SofwareEngineering/homework/16717 |
| 这个作业的目标 | 使用 Python 和 AIGC 完成“一箭又一箭”小游戏 |
| 学号 | 102402117 |
| GitHub 仓库 | https://github.com/chizhi2006/chizhi2006 |
一、项目展示
-
开始界面截图:
![c5d868f739ea868ab0a242f587a9c952]()
-
游戏过程截图:
![967a029764d5321a8c662e64d205ca61]()
![32bc5b0ac1cceee6b44ae3b80ce96aa5]()
![a4130422faad9863d84a920c48ba3c77]()
-
通关界面截图:
![12a56ad7eaa67fc5f718cf43638a53fb]()
-
失败界面截图:
![48c771457eea6e8068458b7340d04d24]()
二、项目介绍
1. 游戏规则
棋盘为 5×5 网格,每个格子可能有一个箭头,方向为上、下、左、右。
点击箭头后,程序检查该箭头前进方向到棋盘边界之间是否还有其它箭头:
- 没有阻挡:箭头飞出并消失;
- 有阻挡:箭头不能消失,发生碰撞反馈,失误次数减 1;
- 清空全部箭头:通关,进入下一关;
- 失误次数耗尽:本关失败,可重新开始。
2. 界面设计
- 开始界面:标题、说明、开始按钮;
- 游戏界面:关卡、剩余箭头、剩余失误、棋盘、重新开始按钮;
- 结果界面:通关或失败提示,以及下一关/重玩按钮。
3. 主要功能
- 四种方向箭头;
- 鼠标点击选择;
- 路径阻挡判断;
- 飞出动画与碰撞抖动;
- 失误次数管理;
- 3 个可通关关卡;
- 重新开始;
- 通关/失败切换。
4. 特色
- Tkinter 实现,无需额外安装图形库;
- 箭头飞出有移动动画;
- 碰撞时箭头变红并抖动;
- 关卡数据与逻辑分离,方便扩展。
三、实现思路
1. 箭头、方向与关卡表示
- 方向用整数表示:
0=上, 1=下, 2=左, 3=右; - 方向向量
DIRS:0:(-1,0), 1:(1,0), 2:(0,-1), 3:(0,1); - 棋盘用二维列表
board[5][5],存方向整数或None; - 关卡数据
LEVELS是列表,每关是若干[行, 列, 方向]。
2. 路径检测方法
can_fly(r, c):
- 取出
(r,c)处箭头的方向d; - 根据
DIRS[d]得到dr, dc; - 从
(r+dr, c+dc)开始,沿方向逐格前进; - 如果遇到任意非空格子,返回
False; - 如果走到棋盘外,返回
True。
3. 交互与动画
- Canvas 绑定
<Button-1>,把点击坐标转成行列; - 能飞:逻辑删除,
canvas.move分步移动箭头,最后删除; - 不能飞:失误 +1,箭头变红,用
after做左右抖动,再恢复。
四、关键代码及说明
1. 方向与关卡数据
DIRS = {0: (-1, 0), 1: (1, 0), 2: (0, -1), 3: (0, 1)}
ARROW_CHARS = {0: '↑', 1: '↓', 2: '←', 3: '→'}
LEVELS = [
[[0,0,3], [0,4,1], [2,1,0], [4,2,0], [4,4,2]],
[[0,4,1], [1,2,1], [2,0,3], [2,2,3], [2,4,3], [3,1,3], [4,0,0]],
[[0,4,2], [1,0,3], [1,3,1], [2,2,0], [2,4,0], [3,3,1], [4,0,0], [4,4,2]]
]
说明:
DIRS 把方向整数映射成行列增量,方便计算路径。
ARROW_CHARS 用于在界面上显示对应箭头字符。
LEVELS 保存 3 个关卡,每个箭头用 [行, 列, 方向] 表示,与逻辑完全分离,后续增加关卡只需改这里。
2. 路径检测核心
def can_fly(self, r, c):
d = self.board[r][c]
dr, dc = DIRS[d]
nr, nc = r + dr, c + dc
while 0 <= nr < GRID_N and 0 <= nc < GRID_N:
if self.board[nr][nc] is not None:
return False
nr += dr
nc += dc
return True
说明:
这是游戏最核心的判断逻辑。
从当前格子沿方向逐格前进,只要遇到任意非空格子就返回 False。
走出棋盘范围仍没遇到阻挡,就返回 True。
边界条件 0 <= nr < GRID_N 保证了不会数组越界。
3. 飞出动画与状态更新
def fly_out(self, r, c):
d = self.board[r][c]
self.board[r][c] = None
item = self.arrow_items[r][c]
self.arrow_items[r][c] = None
if item:
dr, dc = DIRS[d]
dx, dy = dc * CELL_SIZE, dr * CELL_SIZE
steps = 8
def animate(step):
if step < steps:
self.canvas.move(item, dx/steps, dy/steps)
self.root.after(20, lambda: animate(step+1))
else:
self.canvas.delete(item)
animate(0)
self.update_info()
remaining = sum(1 for rr in range(GRID_N) for cc in range(GRID_N) if self.board[rr][cc] is not None)
if remaining == 0:
self.state = "CLEAR"
self.root.after(300, self.show_result)
说明:
先做逻辑删除,把 board[r][c] 设为 None。
用 canvas.move 分 8 步移动箭头,每步 20ms,形成飞出动画。
动画结束后删除 Canvas 上的箭头对象。
更新信息栏,并检查是否所有箭头都已清除,若是则进入通关状态。
4. 碰撞反馈与失误处理
def collide(self, r, c):
self.mistakes += 1
self.update_info()
item = self.arrow_items[r][c]
if item:
self.canvas.itemconfig(item, fill="#f46b6b")
original_x = MARGIN + c*CELL_SIZE + CELL_SIZE//2
original_y = MARGIN + r*CELL_SIZE + CELL_SIZE//2
offsets = [-8, 8, -6, 6, -3, 3, 0]
def shake(idx):
if idx < len(offsets):
self.canvas.coords(item, original_x + offsets[idx], original_y)
self.root.after(40, lambda: shake(idx+1))
else:
self.canvas.coords(item, original_x, original_y)
self.canvas.itemconfig(item, fill="#60cafa")
shake(0)
if self.mistakes >= MAX_MISTAKES:
self.state = "OVER"
self.root.after(400, self.show_result)
说明:
失误次数加 1,并刷新界面。
箭头变红,通过 offsets 列表在水平方向左右移动,形成抖动效果。
抖动结束后恢复颜色和位置。
若失误达到上限,切换为失败状态并显示结果界面。
五、AIGC 使用过程
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 路径检测 | DeepSeek | 生成四方向检测思路 | 基本正确,但边界条件需明确 | 改为 while 0 <= nr < GRID_N |
| 关卡设计 | DeepSeek | 生成 3 个关卡数组 | 部分布局可能卡死 | 手工试玩并调整箭头位置 |
| 碰撞动画 | DeepSeek | 提供 Tkinter 抖动代码 | 可用 | 调整抖动幅度和延时 |
| 界面布局 | DeepSeek | 建议 Frame 切换方案 | 结构清晰 | 改为三个 Frame 切换 |
| Bug 修复 | DeepSeek | 分析飞出后坐标越界 | 定位到动画步长 | 限制步数并删除 item |
六、测试结果
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出并消失 | 箭头飞出并消失 | 通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误减 1 | 箭头抖动变红,失误减 1 | 通过 |
| T03 | 点击边缘且朝外的箭头 | 正常消失,无越界错误 | 正常消失 | 通过 |
| T04 | 消除本关全部箭头 | 显示通关并进入下一关 | 显示通关,可点下一关 | 通过 |
| T05 | 失误次数耗尽 | 显示失败并允许重新开始 | 显示失败,可重玩 | 通过 |
| T06 | 游戏进行中重新开始 | 布局和失误恢复 | 恢复初始状态 | 通过 |
七、PSP 表格
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1 | 0.5 | 0.5 |
| Python 与图形库学习 | 2 | 1 | 1 |
| 游戏界面实现 | 2 | 0.2 | 1.8 |
| 路径与碰撞逻辑实现 | 2 | 3 | 1 |
| 关卡设计 | 1 | 0.2 | 0.8 |
| AIGC 辅助开发 | 2 | 4 | 2 |
| 测试与修改 | 1.5 | 2 | 0.5 |
| README 与博客撰写 | 2 | 0.5 | 1.5 |
| 合计 | 13.5 | 11.4 | 2.1 |
八、心得体会
- AI 在生成界面框架、方向检测、动画代码方面效率很高;
- 但 AI 给出的边界条件、关卡可解性需要自己验证;
- 通过实际试玩发现并修改了若干问题;
- 学会了把游戏状态、棋盘数据、界面渲染分开管理;
- 理解了路径检测的本质是沿方向逐格检查。






浙公网安备 33010602011771号