软件工程第二次作业
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | H202601软件工程与软件工程实践(福州大学-计算机与大数据学院) |
| 这个作业要求在哪里 | 2026 秋软件工程个人作业(第二次) |
| 这个作业的目标 | 使用 Python 和 AIGC 完成“一箭又一箭”小游戏 |
| 学号 | 072401214 |
| GitHub 仓库 | https://github.com/wangenjia/homework.git |
一.项目展示




二.项目介绍
游戏规则
本游戏是一款箭头消除类小游戏。棋盘网格内放置指向上、下、左、右四个方向的箭头。玩家点击箭头后,程序沿箭头指向的方向进行路径检测:若前进方向没有其他箭头阻挡,该箭头飞出棋盘并消失;若路径上存在其他箭头,则箭头无法移动,界面给出碰撞提示。玩家一共有 3 次失误机会,点击被阻挡的箭头会消耗一次失误次数;清空棋盘所有箭头即可通关进入下一关,失误次数耗尽则游戏失败,玩家可随时点击按钮重新开始当前关卡。
界面设计
游戏界面包含开始界面、游戏界面、通关 / 失败界面。游戏主界面展示当前关卡、游戏棋盘、剩余箭头数量、剩余失误次数,还有重新开始按钮。箭头飞出、发生碰撞时搭配简单动画或视觉反馈。
三.实现思路
方向
为了将四个方向的移动逻辑统一处理,程序中使用一个字典 DIRS 来集中管理方向
DIRS = {
"up": {"dr": -1, "dc": 0, "glyph": "↑"},
"down": {"dr": 1, "dc": 0, "glyph": "↓"},
"left": {"dr": 0, "dc": -1, "glyph": "←"},
"right": {"dr": 0, "dc": 1, "glyph": "→"},
}
dr 和 dc 分别代表行和列的偏移量,这样在任何方向判定时只需要取出对应的 dr 和 dc 进行坐标累加
箭头
"arrows": [
{"r": 0, "c": 0, "dir": "right"},
{"r": 1, "c": 1, "dir": "up"},
{"r": 2, "c": 3, "dir": "down"},
{"r": 3, "c": 4, "dir": "left"},
{"r": 4, "c": 0, "dir": "right"},
{"r": 5, "c": 2, "dir": "up"},
]
一个箭头对象是一个包含三个整数的字典:{"r": 行号, "c": 列号, "dir": 方向字符串}
关卡
LEVELS = [
{
"rows": 6, "cols": 6, "mistakes": 3,
"arrows": [
{"r": 0, "c": 0, "dir": "right"},
{"r": 1, "c": 1, "dir": "up"},
{"r": 2, "c": 3, "dir": "down"},
{"r": 3, "c": 4, "dir": "left"},
{"r": 4, "c": 0, "dir": "right"},
{"r": 5, "c": 2, "dir": "up"},
],
},
{
"rows": 6, "cols": 6, "mistakes": 3,
"arrows": [
{"r": 0, "c": 0, "dir": "right"},
{"r": 0, "c": 2, "dir": "up"},
{"r": 1, "c": 0, "dir": "up"},
{"r": 2, "c": 1, "dir": "right"},
{"r": 2, "c": 4, "dir": "down"},
{"r": 3, "c": 1, "dir": "down"},
{"r": 4, "c": 3, "dir": "left"},
{"r": 4, "c": 5, "dir": "up"},
{"r": 5, "c": 2, "dir": "right"},
],
},
{
"rows": 6, "cols": 6, "mistakes": 4,
"arrows": [
{"r": 0, "c": 3, "dir": "down"},
{"r": 0, "c": 5, "dir": "left"},
{"r": 1, "c": 1, "dir": "right"},
{"r": 1, "c": 4, "dir": "up"},
{"r": 2, "c": 0, "dir": "up"},
{"r": 2, "c": 3, "dir": "down"},
{"r": 3, "c": 2, "dir": "left"},
{"r": 3, "c": 5, "dir": "up"},
{"r": 4, "c": 0, "dir": "right"},
{"r": 4, "c": 5, "dir": "up"},
{"r": 5, "c": 2, "dir": "up"},
{"r": 5, "c": 4, "dir": "right"},
],
},
]
路径检测方式
程序采用线性扫描法来检测前方是否有阻挡箭头,直接基于一维的箭头列表进行迭代。
具体执行步骤如下:
1.获取方向偏移量:根据当前点击箭头的 dir,从 DIRS 字典中获取对应的行偏移量 dr 和列偏移量 dc。
2.初始化扫描坐标:以当前箭头的坐标为起点,向该方向前进一步,即 r = arrow["r"] + dr,c = arrow["c"] + dc。
3.循环迭代检测:使用 while 循环不断更新 r 和 c,并判断两种情况:
越界检测:若 r 或 c 超出了棋盘范围(r < 0 或 r >= rows,c < 0 或 c >= cols),则说明路径畅通无阻,跳出循环,返回 None。
碰撞检测:在每次循环中,调用 arrow_at(state, r, c) 在当前的箭头列表中查找对应坐标。如果找到了箭头,说明前方有阻挡,立即返回阻挡者。
4.判定逻辑:
若 find_blocker 返回 None : 前方无阻挡,该箭头执行飞出逻辑(从列表中移除),判定成功。
若返回了阻挡者对象 : 前方有阻挡,判定碰撞,扣除一次失误次数,并给前端返回碰撞反馈信号。
def build_state(idx: int) -> dict:
lv = LEVELS[idx]
return {
"level_index": idx,
"level_total": len(LEVELS),
"rows": lv["rows"],
"cols": lv["cols"],
"mistakes_total": lv["mistakes"],
"mistakes_left": lv["mistakes"],
"arrows": copy.deepcopy(lv["arrows"]),
"locked": False,
}
def arrow_at(state, r, c):
for a in state["arrows"]:
if a["r"] == r and a["c"] == c:
return a
return None
def find_blocker(state, arrow):
dr = DIRS[arrow["dir"]]["dr"]
dc = DIRS[arrow["dir"]]["dc"]
r = arrow["r"] + dr
c = arrow["c"] + dc
while 0 <= r < state["rows"] and 0 <= c < state["cols"]:
hit = arrow_at(state, r, c)
if hit:
return hit
r += dr
c += dc
return None
def public_state(state):
return {
"level_index": state["level_index"],
"level_total": state["level_total"],
"rows": state["rows"],
"cols": state["cols"],
"mistakes_left": state["mistakes_left"],
"arrows_left": len(state["arrows"]),
"arrows": state["arrows"],
"locked": state["locked"],
}
四.测试要求
| 测试内容 | 预期结果 | 实际结果 |
|---|---|---|
| 点击前方无阻挡的箭头 | 箭头飞出棋盘并消失 | 平滑飞出并消失 |
| 点击前方有阻挡的箭头 | 箭头不消失,失误次数减 1 | 点击箭头和遮挡箭头都晃动,显示红色-1效果 |
| 点击位于边缘且朝向棋盘外的箭头 | 箭头正常消失,不发生越界错误 | 平滑飞出并消失 |
| 消除本关全部箭头 | 显示通关并进入下一关 | 显示“通关成功”,可选择进入下一关或重玩本关 |
| 失误次数耗尽 | 显示失败并允许重新开始 | 显示“关卡失败”,可重玩本关或返回首页 |
| 游戏进行中重新开始 | 箭头布局和失误次数恢复 | 箭头布局和失误次数恢复 |
五.上传github
https://github.com/wangenjia/homework.git
六.AI使用
以下 Markdown 表格可直接复制到博客中:
| 子任务 | 借助何种 AIGC 技术 | AI 实现或提供了什么 | 效果如何 | 人工修改 |
|---|---|---|---|---|
| 路径检测 | deepseek | 生成基于 DIRS 字典和 while 循环的逐格扫描算法,判断箭头前方是否被阻挡 |
基本可用,但向上检测时边界判断不严谨,负坐标会反向索引 | 修改循环条件为 0 <= r < rows 且 0 <= c < cols,并补充启动前自检 |
| 关卡设计 | deepseek | 生成 3 组 6×6 关卡的箭头坐标和方向数据 | 初始布局存在互相死锁,无法通关 | 调整箭头坐标,并在启动时用 assert 校验每关都有箭头 |
| 碰撞动画 | chatgpt | 补全箭头抖动、被撞箭头闪红、飘字 -1 的 CSS keyframes 和 JS 类切换逻辑 |
动画基本可用,但动画结束后前端状态未同步 | 加入 busy 锁防止连点,动画结束后回填后端返回的真实状态 |
| 前后端架构 | ClaudeCode | 推荐单文件内嵌 HTML Flask + templates 分离结构,并生成 session 管理对局状态的方案 |
报错 | Flask + templates 分离结构,并生成 session 管理对局状态的方案 |
七.时间
| 任务 | 预估耗时(小时) | 实际耗时(小时) | 差异(小时) |
|---|---|---|---|
| 需求分析与游戏设计 | 1.0 | 1.0 | 0 |
| 游戏界面实现 | 3.0 | 4.0 | +1.0 |
| 路径与碰撞逻辑实现 | 2.0 | 2.5 | +0.5 |
| 关卡设计 | 1.0 | 2.0 | +1 |
| AIGC 辅助开发 | 1.5 | 2.0 | +0.5 |
| 测试与修改 | 2.0 | 2.5 | +0.5 |
| README 与博客撰写 | 2.0 | 1.5 | -0.5 |
| 合计 | 12.5 | 15.5 | +3.0 |
八.心得体会
AI 带来的帮助
最开始我对 Flask 和前端交互只有模糊概念。AI 很快就帮我搭出了可运行的骨架:用 DIRS 字典统一处理四个方向,用 find_blocker 做路径检测,用 fetch 调后端 API。碰到不懂的报错把截图发过去,它基本能定位到具体行和具体原因。git commit`拆分、README 结构、PSP 表格这些流程性的东西,AI 也给了可直接套用的模板,省了大量查资料的时间。
出现的问题
AI 生成的代码不是拿来就能跑的。最典型的一次是前端模板只写了 JS 片段,;还有一次是 render_template(index.html, ...) 少写了引号,导致启动就报 NameError。这些问题 AI 第一次给方案时并没有提前预警,都是运行起来才暴露的。
自己的收获
最大的收获是不能把 AI 当“自动写代码机,而要当成提示者。它给出思路和草稿,我来判断、修改、验证。比如路径检测循环,AI 一开始写的边界条件是错的,向上检测会越界,修改后才正常。再比如关卡数据,AI 给的布局存在死锁,通过人工调整箭头坐标,才让每一关都有解。

浙公网安备 33010602011771号