软件工程第二次作业

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

一.项目展示

ScreenShot_2026-09-17_204232_503

1

2

屏幕录制 2026-09-17 204947 (1)

二.项目介绍

游戏规则

本游戏是一款箭头消除类小游戏。棋盘网格内放置指向上、下、左、右四个方向的箭头。玩家点击箭头后,程序沿箭头指向的方向进行路径检测:若前进方向没有其他箭头阻挡,该箭头飞出棋盘并消失;若路径上存在其他箭头,则箭头无法移动,界面给出碰撞提示。玩家一共有 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 < rows0 <= 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 给的布局存在死锁,通过人工调整箭头坐标,才让每一关都有解。

posted @ 2026-09-19 23:44  fswtkb  阅读(0)  评论(0)    收藏  举报