软件工程课程第二次个人作业
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | H202601软件工程与软件工程实践 |
| 这个作业要求在哪里 | 作业链接 |
| 这个作业的目标 | 使用 Python 和 AIGC 完成“一箭又一箭”小游戏 |
| 学号 | 102402137 |
| GitHub 仓库 | 仓库链接 |
一、项目展示
1.游戏开始界面

2.游戏过程界面

3.游戏通关界面

4.游戏失败界面

GIF演示

二、项目介绍:
1.游戏规则
棋盘上散布着若干带方向的箭头,方向只有上、下、左、右四种。点击某个箭头后,程序检查它前进方向上、到棋盘边界之间有没有别的箭头:
| 情况 | 结果 |
|---|---|
| 前方没有其他箭头 | 箭头沿自己的方向飞出棋盘,被消除 |
| 前方有其他箭头阻挡 | 飞不出去,消耗一次失误机会,并给出碰撞反馈 |
- 清空本关全部箭头 → 通关
- 失误次数耗尽 → 本关失败,可重新开始
- 点击空格子或棋盘外不扣失误
判定只考虑同行 / 同列、箭头与边界之间的直线路径,不考虑拐弯或连锁反应。
2.界面设计
整体是深色科技风,用渐变和光晕做层次,避免大面积纯色块带来的廉价感。
四个界面状态
| 界面 | 内容 |
|---|---|
| 开始界面 | 游戏标题、玩法说明面板、开始 / 继续 / 新游戏 / 选择关卡 / 退出按钮、右上角音效开关 |
| 游戏界面 | 顶部信息栏、自适应棋盘、底部操作栏 |
| 选关界面 | 12 张关卡卡片(4×3 网格) |
| 结算浮层 | 半透明遮罩 + 结果面板(通关 / 失败) |
游戏界面的三段式布局
- 顶部信息栏:做成一张浮起的圆角卡片而不是通栏色块。左侧是关卡编号与关卡名(带强调竖线),中间是三个状态胶囊——剩余箭头(蓝)、剩余失误(按剩余量在绿/橙/红之间切换)、用时(紫,超过目标用时会转橙,提示已经没有速度奖励),右侧是「重新开始」「选择关卡」。
- 棋盘:外框用渐变 + 内凹的格子井,格子做成有顶部高光和底部暗边的"键帽"质感。棋盘尺寸会按关卡自动计算格子边长并居中,4×4 到 7×7 都不用手工调。
- 底部操作栏:左侧是操作提示(被临时消息占用时会让位),右侧是「音效」「撤销 ×3」「提示 ×3」三个按钮。
视觉细节
- 箭头按方向用四种颜色区分(上蓝、下橙、左绿、右红),带柔和外发光、渐变主体和落地投影
- 背景是渐变 + 顶部冷色光晕 + 46 个缓慢上浮的粒子 + 四角暗角
- 结算面板顶部有圆形徽章(通关是对勾、失败是叉),背后一圈同色光晕
动画与音效
- 悬停:格子高亮和箭头放大都是平滑过渡(指数逼近),移开时淡出而不是硬切
- 入场:进入关卡时箭头按对角线顺序逐个弹出,带轻微过冲回弹
- 飞出:箭头逐渐加速飞出,身后拖一条随速度拉长的渐隐轨迹
- 碰撞:朝阻挡方向冲一下 → 带回弹地退回 → 左右晃动 → 闪白,同时格子扩散一圈红色光环
- 切界面:从黑场淡入,不硬切
- 音效:7 个音效(飞出、被挡住、提示、撤销、点击、通关、失败)
3.主要功能和特色
| 功能 | 说明 |
|---|---|
| 关卡系统 | 12 关,分入门 / 进阶 / 挑战三档(各 4 关),棋盘从 4×4 到 7×7,失误上限 3~5 次 |
| 关卡选择 | 卡片式选关,显示棋盘尺寸、箭头数、难度档,通关后显示最高分与最快用时 |
| 进度解锁 | 通关前一关才开放下一关(可在配置里一键改为全部开放) |
| 计时 | 实时计时,本关结束时停表;超过目标用时胶囊变色提示 |
| 计分 | 基础分 + 速度奖励 + 失误奖励 − 提示扣分 − 撤销扣分,结算面板逐项列出 |
| 成绩记录 | 每关独立保留最高分与最快用时,主菜单与选关界面显示总分 |
| 提示 | 每关 3 次,用金色脉冲光环标出一个当前就能消掉的箭头 |
| 撤销 | 每关 3 次,可退掉一次消除或一次误点(含退回失误) |
| 自动存档 | 长期成绩与当前这一局分开存两个文件,随时关窗口都能接着玩 |
| 音效开关 | 三个界面都有「音效:开 / 关」按钮,实时显示当前状态 |
三、实现思路:
1.箭头与方向怎么表示
方向用字符串:"up" / "down" / "left" / "right"。
方向统一转成位移向量:
# 屏幕坐标:行号向下增大,列号向右增大
DIRECTION_VECTORS = {
"up": (-1, 0),
"down": ( 1, 0),
"left": ( 0, -1),
"right": ( 0, 1),
}
"沿箭头方向前进一格"就统一成 (row + dr, col + dc),判断路径、算到边界的距离、算到阻挡者的距离,全都共用同一套向量,不需要为四个方向各写一个分支。
棋盘上的箭头用字典存:
arrows = {(0, 3): "down", (2, 4): "right", ...} # (行, 列) -> 方向
键是格子坐标,值是方向。选字典而不是二维数组,原因有三个:
- 棋盘大部分格子是空的,字典只存有箭头的格子,省空间也省遍历
- 判断"某格有没有箭头"是 O(1),路径检测要反复做这个判断
- 消掉箭头就是
pop,撤销就是重新赋值,不需要维护额外的状态
箭头图形的画法:只用一份归一化坐标描述"向上"的轮廓,其它方向靠绕中心旋转 90° 的整数倍得到。
_ARROW_SHAPE = (
(0.00, -0.34), # 箭尖
(0.22, -0.06), # 箭头右角
(0.09, -0.06), # 箭杆右上
(0.09, 0.32), # 箭杆右下
(-0.09, 0.32), # 箭杆左下
(-0.09, -0.06), # 箭杆左上
(-0.22, -0.06), # 箭头左角
)
_DIR_TURNS = {"up": 0, "right": 1, "down": 2, "left": 3}
旋转用的是屏幕坐标系下的 90° 顺时针公式:
x, y = -y, x
朝上时箭尖在 (0, -0.34);转一次得到 (0.34, 0),正好朝右。这样只需要维护一份轮廓数据,四个方向共用,改箭头造型时也只改一处。
2.关卡怎么表示
用 ASCII 图写布局,. 是空格子,^ v < > 是四个方向的箭头:
_level("初次搭箭", "入门", 17, 3, """
....
.<.v
..>.
.^..
"""),
五个参数依次是关卡名、难度档、难度分、失误上限、布局。
解析过程:先用 textwrap.dedent 去掉源码缩进,再逐行逐字符查表映射,把每一个箭头收集成 (行, 列, 方向):
CHAR_TO_DIR = {"^": "up", "v": "down", "<": "left", ">": "right"}
lines = [line.rstrip() for line in textwrap.dedent(art).strip("\n").split("\n")]
for row, line in enumerate(lines):
for col, char in enumerate(line):
direction = CHAR_TO_DIR.get(char)
if direction is not None:
arrows.append((row, col, direction))
最后装进一个 frozen dataclass:
@dataclass(frozen=True)
class Level:
name: str
tier: str
difficulty: int
rows: int
cols: int
max_mistakes: int
arrows: tuple
难度分用三个可量化的指标算出来:
难度分 = 箭头数 × 3 + 最长依赖链 × 2 + 被挡住的箭头数
3.路径检测
基本思路
因为作业统一成"单格箭头 + 同行同列直线判定",所以判定退化成一件很简单的事:从箭头所在格子出发,沿着方向向量一格一格往前走,看会不会撞到别的箭头。
def _blocker_on(board, rows, cols, cell):
"""在给定的棋盘上,沿箭头方向找第一个挡住它的箭头。"""
direction = board.get(cell)
if direction is None:
return None
dr, dc = DIRECTION_VECTORS[direction]
r, c = cell[0] + dr, cell[1] + dc # 从箭头前方一格开始
while 0 <= r < rows and 0 <= c < cols: # 一直走到棋盘外
if (r, c) in board:
return (r, c) # 撞到了,它就是阻挡者
r += dr
c += dc
return None # 一路畅通,可以飞出
这个函数实现射线探测功能,用来查找当前箭头视线中第一个阻挡它的箭头。输入包含存储箭头位置与方向的棋盘字典、棋盘行列数和当前箭头坐标。棋盘字典只保存放置了箭头的格子,空白格子不会记录在内。
程序先读取当前格子箭头的方向,若该位置不存在箭头,直接返回空值。随后取出箭头对应的移动向量,从箭头前方相邻格子开始扫描,不会把箭头自身当作障碍物。
循环会沿着箭头方向不断移动坐标,只要坐标没有超出棋盘边界就持续探测。每检查一格,就判断该位置是否存在箭头;一旦遇到箭头,立刻返回这个阻挡箭头的坐标,终止探测。如果一路扫描直到走出棋盘都没有发现其他箭头,函数最终返回 None,代表这条视线没有任何遮挡。
返回 None 就表示"前方到边界之间没有别的箭头",也就是这个箭头能飞出棋盘。
direction = board.get(cell)
if direction is None: return None
读取当前格子箭头方向,无箭头直接返回空。
dr, dc = DIRECTION_VECTORS[direction]
r, c = cell[0] + dr, cell[1] + dc
获取方向步长,从箭头前方一格开始探测。
while 0 <= r < rows and 0 <= c < cols:
在棋盘范围内持续向前遍历。
if (r, c) in board: return (r, c)
r += dr; c += dc
遇到第一个箭头就返回它的坐标,否则继续前进。
return None
走出棋盘仍未碰到箭头,说明无障碍物。
四、AIGC 使用过程
首先,我先把一箭又一箭游戏制作的任务要求发给AI

然后,我将任务分解为多个不同的部分,例如,我首先先让AI帮我制作游戏界面

在任务过程中,我也会对已完成的部分进行一点优化

| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 分析游戏任务 | DeepSeek | 了解任务的需求 | 能完成任务并列出任务的要求 | 无 |
| 制作游戏界面 | DeepSeek | 完成了游戏的主界面、游戏界面等 | 基本可用 | 删去了多余的按键 |
| 完善关卡部分 | DeekSeek | 增加关卡数量与关卡选择功能 | 基本可用 | 对关卡中箭头位置进行调整 |
| 添加游戏音效 | DeepSeek | 添加了游戏点击、通关时等音效 | 不错 | 无 |
| 增加额外功能 | DeepSeek | 添加了提示、撤回等功能 | 基本可用 | 调整按键位置 |
五、测试结果
| 编号 | 测试内容 | 预期结果 | 实际结果 | 实际结果与预期是否相同 |
|---|---|---|---|---|
| T01 | 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 点击后箭头飞出棋盘并消失 | 是 |
| T02 | 点击前方有阻挡的箭头 | 箭头不消失,失误次数减 1 | 发出错误提示,失误次数减 1 | 是 |
| T03 | 点击位于边缘且朝向棋盘外的箭头 | 箭头正常消失,不发生越界错误 | 箭头消失 | 是 |
| T04 | 消除本关全部箭头 | 显示通关并进入下一关 | 显示通关并进入下一关的界面 | 是 |
| T05 | 失误次数耗尽 | 显示失败并允许重新开始 | 显示失败与重新开始的界面 | 是 |
| T06 | 游戏进行中重新开始 | 箭头布局和失误次数恢复 | 恢复关卡的箭头、失误次数、提示次数等 | 是 |
| T07 | 静音按键是否有用 | 按键能关闭/打开声音 | 按键能开关声音 | 是 |
| T08 | 点击新游戏按钮 | 清除先前所有游戏记录 | 清除记录并重新开始 | 是 |
| T09 | 提示按键是否有用 | 选出能消除的箭头 | 显示出可消除的箭头 | 是 |
| T10 | 退出游戏后是否能保存游戏进度 | 退出后可以保存游戏进度 | 退出后可以保存游戏进度 | 是 |
六、PSP 表格: 填写各阶段预计耗时和实际耗时;
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1.5 | 1 | -0.5 |
| Python 与图形库学习 | 2 | 2.5 | 0.5 |
| 游戏界面实现 | 1.5 | 1.5 | 0 |
| 路径与碰撞逻辑实现 | 2 | 1.5 | -0.5 |
| 关卡设计 | 1.5 | 1.5 | 0 |
| AIGC 辅助开发 | 2 | 1.5 | -0.5 |
| 测试与修改 | 2 | 2.5 | 0.5 |
| README 与博客撰写 | 2 | 2 | 0 |
| 合计 | 14.5 | 14 | -0.5 |
七、心得体会
AI把想法变成可运行代码的速度,是手写完全比不了的,从空目录到一个能玩、能存档、有音效的完整游戏,整个过程我基本没有卡在"这个 API 怎么写"上。界面布局、事件循环、状态机、存档序列化这些模板化的部分,AI 一次就能给出能跑的版本。
AI承担了大量机械性、重复性工作,能显著提高工作效率,12 个关卡的布局、配色方案、样式微调、参数调整,这些活写起来不复杂但极其琐碎。尤其是关卡布局,我需要反复调整二十多轮,每次都要重新验证。
AI有时候做的快捷键是没有效果的,要自己测试时才能检查出来
有些事 AI 判断不了,例如"好不好看"这种主观标准:AI 能画出来,但"够不够好看"得靠你反复看截图、提意见才能解决。

浙公网安备 33010602011771号