软件工程个人作业(第二次)
一箭又一箭 —— Python + Tkinter 小游戏开发实践
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | 首页 - H202601软件工程与软件工程实践 - 福州大学 - 班级博客 - 博客园 |
| 这个作业要求在哪里 | 2026 秋软件工程个人作业(第二次) - 作业 - H202601软件工程与软件工程实践 - 班级博客 - 博客园 |
| 这个作业的目标 | 使用 Python 和 AIGC 完成“一箭又一箭”小游戏 |
| 学号 | 052401324 |
| GitHub 仓库 | 12aa950/-Python-Tkinter- |
一、项目展示

游戏界面(第 1 关):

通关界面:

失败界面:

二、项目介绍
本项目用 Python + Tkinter 实现了一个“一箭又一箭”风格的点击式箭头解谜小游戏。Tkinter 是 Python 自带的图形库,因此无需安装任何第三方依赖,只要电脑上有 Python 即可运行,非常轻量。
游戏规则
- 棋盘上分布着带方向的箭头,方向分上、下、左、右四种;
- 点击一个箭头,程序检查它“前进方向上到棋盘边界之间”是否还有其他箭头;
- 前方无阻挡 → 箭头沿该方向飞出棋盘并被消除;
- 前方有阻挡 → 箭头不消失,并触发闪红、晃动的碰撞反馈,同时失误次数减 1;
- 清空本关全部箭头 → 通关并进入下一关;
- 失误次数耗尽 → 本关失败,可重新开始。
界面设计
游戏包含“开始界面 / 游戏界面 / 通关界面 / 失败界面”四种画面。游戏界面顶部有状态栏,显示当前关卡、剩余箭头数、剩余失误次数,并提供“重新开始”按钮;中间是 5×5 的棋盘,每个箭头用带方向的蓝色三角表示。
主要功能
- 鼠标点击选择箭头;
- 四种方向的路径阻挡判定;
- 箭头飞出动画与碰撞反馈动画;
- 失误次数系统;
- 3 个手工设计并经程序验证可通关的关卡;
- 通关、失败、重开、关卡切换完整流程。
三、实现思路
3.1 数据表示
用二维字符列表表示棋盘,一个字符代表一个格子:
| 字符 | 含义 |
|---|---|
. |
空格 |
^ |
上箭头 |
v |
下箭头 |
< |
左箭头 |
> |
右箭头 |
方向与其单位位移(行偏移, 列偏移)用字典映射:
DIR_DELTA = {
"^": (-1, 0), # 上
"v": (1, 0), # 下
"<": (0, -1), # 左
">": (0, 1), # 右
}
关卡直接写成字符串列表,例如第 1 关:
[
"..^..",
"..^..",
".....",
"<.<.<",
".....",
]
3.2 路径检测(核心)
这是整个游戏最关键的函数。从被点击的箭头出发,沿着它的前进方向一步步走:每走一步先判断是否越界——越界说明一路畅通,可以飞出;如果在棋盘内遇到了其他箭头,说明被阻挡。
def is_blocked(board, r, c):
"""判断 (r,c) 处箭头在前进方向上、到达边界之前是否还有其他箭头。"""
rows, cols = len(board), len(board[0])
arrow = board[r][c]
dr, dc = DIR_DELTA[arrow]
nr, nc = r + dr, c + dc
while 0 <= nr < rows and 0 <= nc < cols:
if board[nr][nc] != ".": # 路径上遇到其他箭头 → 被阻挡
return True
nr += dr
nc += dc
return False # 一路畅通到达边界 → 可以飞出
边界判断 0 <= nr < rows and 0 <= nc < cols 很关键:它保证了点击边缘朝外的箭头不会发生数组越界。测试项 T03 专门验证了这一情况。
3.3 关卡可解性验证
为了确保每个关卡都能通关,我写了一个求解器:反复找出“前方无阻挡”的箭头并消除,直到清空或卡住;如果最终全部清空,说明该关存在合理的消除顺序。
def solvable(board):
grid = [list(row) for row in board]
rows, cols = len(grid), len(grid[0])
while True:
removed = False
for r in range(rows):
for c in range(cols):
if grid[r][c] != "." and not is_blocked(grid, r, c):
grid[r][c] = "."
removed = True
if not removed:
break
return all(grid[r][c] == "." for r in range(rows) for c in range(cols))
程序在启动时会对全部关卡执行 solvable() 自检,若某关不可解会直接报错退出,从而保证交付的关卡一定是可通关的。
3.4 点击处理与动画
点击处理根据 is_blocked 的结果分两条路:
- 前方无阻挡 → 锁定输入、播放“飞出”动画,动画结束后从棋盘移除该箭头并刷新剩余数量;
- 前方有阻挡 → 失误次数减 1,箭头闪红并左右晃动作为碰撞反馈,动画结束后若失误次数为 0 则进入失败界面。
def on_click(self, r, c):
if self._locked:
return
if self.board[r][c] == ".":
return
if is_blocked(self.board, r, c):
self._locked = True
self.lives -= 1
self._update_info()
self._flash_collision(r, c, lambda: self._after_collision(r, c))
else:
self._locked = True
self._fly_out(r, c)
动画通过 Tkinter 的 after(ms, callback) 定时回调实现:飞出时按方向逐步移动箭头坐标,碰撞时让箭头左右小幅度摆动并变色,结束后统一进入通关/失败判断。
四、AIGC 使用过程
本项目全程使用 Trae(Coding Agent)辅助开发,下面记录 3 次具有代表性的协作过程。
记录 1:路径检测与边界处理
| 项目 | 内容 |
|---|---|
| 子任务 | 路径检测 |
| 借助的 AIGC | Trae |
| 提出要求 | “写一个函数,判断某箭头在前进方向上、到棋盘边界之间是否还有其他箭头,要求不越界” |
| AI 完成情况 | 生成了 is_blocked 的初版逻辑,能沿方向逐格判断并返回是否受阻 |
| 实际效果 | 初版把“越界”与“遇到箭头”的判断顺序写得比较简略,边界情况不够稳 |
| 人工修改 | 我补充并强调了轮巡时的边界条件 0 <= nr < rows and 0 <= nc < cols,并用测试项 T03(边缘朝外箭头)验证不越界后通过 |
记录 2:碰撞晃动动画的调试
| 项目 | 内容 |
|---|---|
| 子任务 | 碰撞动画 |
| 借助的 AIGC | Trae |
| 提出要求 | “点击被阻挡的箭头时,让它闪红并左右晃动作为反馈” |
| AI 完成情况 | 生成了晃动回调,试图通过一个“方向反查”辅助函数来重建箭头坐标 |
| 实际效果 | 该辅助函数引用了一个未定义的属性 self._quiver_dir,运行时直接报错,程序无法继续 |
| 人工修改 | 我重写了晃动逻辑:直接利用碰撞当时仍在棋盘上的箭头方向 ch = self.board[r][c] 重建多边形坐标,左右摆动若干帧后归位并恢复颜色,运行后正常 |
记录 3:失误/重新开始机制的回归修复
| 项目 | 内容 |
|---|---|
| 子任务 | 失误次数与重开机制 |
| 借助的 AIGC | Trae |
| 提出要求 | “失误次数耗尽要显示失败;重新开始要恢复本关初始布局和失误次数” |
| AI 完成情况 | 第一版给 start_level 加了 resume 参数,试图在不同场景下决定是否重置“失误次数” |
| 实际效果 | 但这导致“重新开始”走复用分支时失误次数没有被清零;写自动化测试(T05/T06)时立刻暴露,重开后失误仍为 0 |
| 人工修改 | 我去掉了多余的 resume 分支,让每次进入/重开本关都统一重置失误次数和棋盘,测试随即全部通过 |
小结:AI 在生成整体框架和大部分代码上效率很高,但在“状态是否被正确重置”“边界是否越界”这类细节上容易埋坑。借助自动化测试把它们揪出来,是很有效的手段。
五、测试结果
测试方式:编写自动化测试脚本 test_game.py,在“无头”模式下驱动游戏逻辑与流程(把动画回调替换为同步回调以便不启动事件循环验证规则)。共 15 项断言全部通过。
| 编号 | 测试内容 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 箭头数量减 1 | 通过 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误次数减 1 | 箭头保留、失误 -1 | 通过 |
| T03 | 点击位于边缘且朝向棋盘外的箭头 | 箭头正常消失,不越界错误 | 无越界,正常消除 | 通过 |
| T04 | 消除本关全部箭头 | 显示通关并进入下一关 | 剩余箭头为 0、可进入第 2 关 | 通过 |
| T05 | 失误次数耗尽 | 显示失败并允许重新开始 | 触发失败、重开后失误复位 | 通过 |
| T06 | 游戏进行中重新开始 | 箭头布局和失误次数恢复 | 两者均恢复初始 | 通过 |
除自动化测试外,我还对设计好的 3 个关卡逐一用求解器验证了存在合理通关顺序,并实际试玩确认操作流畅、动画反馈正常。
六、PSP 表格
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1.0 | 1.0 | 0.0 |
| Python 与图形库学习 | 0.5 | 0.5 | 0.0 |
| 游戏界面实现 | 1.5 | 2.0 | +0.5 |
| 路径与碰撞逻辑实现 | 1.5 | 1.5 | 0.0 |
| 关卡设计 | 1.0 | 1.0 | 0.0 |
| AIGC 辅助开发 | 1.5 | 2.0 | +0.5 |
| 测试与修改 | 1.0 | 2.0 | +1.0 |
| README 与博客撰写 | 1.5 | 2.0 | +0.5 |
| 合计 | 9.5 | 12.0 | +2.5 |
七、心得体会
这次作业让我第一次完整体验了“用 AIGC 从零做一个小型应用”的流程。游戏本身难度不大,但麻雀虽小五脏俱全:二维坐标、方向判断、路径检测、动画反馈、状态管理和关卡流程,几乎把课本里的知识点都串了一遍。
AI 带来的帮助:Trae 生成框架代码的速度非常快,接口设计、绘制逻辑这些重复性工作基本交给它之后,我可以用更多精力去关注游戏规则本身。特别是路径检测、飞行动画这类结构清楚的代码,AI 一次生成的可用度很高。
出现的问题:最大的坑集中在细节上——边界越界、状态重置、动画回调引用不存在的方法,这些都是“看起来能跑、一测就翻车”的问题。这让我意识到,AI 生成代码绝不能写完就跑,必须配合针对性测试来验证。
我的收获:一是对“判定 → 反馈 → 状态更新 → 流程推进”这类游戏主循环有了更清晰的认识;二是学会了写自动化测试来检验规则,而不是只在界面上点几下;三是明白了可靠的 AI 协作方式——不仅要会提需求,还要会审查、会测试、会修复 AI 留下的细节问题。AI 是很好的助手,但最终对代码负责的还是自己。
浙公网安备 33010602011771号