2026 秋软件工程个人作业(第二次)

项目 内容
这个作业属于哪个课程 H202601软件工程与软件工程实践
这个作业要求在哪里 2026 秋软件工程个人作业(第二次)
这个作业的目标 使用 Python 和 AIGC 完成“一箭又一箭”小游戏
学号 102402119
GitHub 仓库 仓库连接

项目展示

1.1游戏开始界面

image

1.2游戏过程与通关界面

游戏过程

1.3碰撞反馈与失败界面

一箭又一箭失败

二、项目介绍

2.1 游戏规则

本项目实现 “一箭又又一箭” 点击解谜小游戏,游戏棋盘为 6×6 网格,棋盘上分布上、下、左、右四个方向的箭头。
玩家点击棋盘上的箭头,程序沿着箭头前进方向做路径检测:

  1. 若箭头到棋盘边界之间不存在其他箭头阻挡,该箭头执行飞行动画,飞出棋盘后消失移除。
  2. 若前进路径上存在其他箭头阻挡,箭头无法飞出,触发红色闪烁碰撞反馈,同时消耗一次失误机会。
  3. 玩家总共有 3 次失误机会,失误次数耗尽则本关游戏失败,可以点击按钮重新开始当前关卡。
  4. 将本关卡棋盘上所有箭头全部清除后,即可通关进入下一关。
  5. 项目内置 3 个设计完成的可通关关卡,全部关卡通关后展示全部通关结束界面。

2.2 界面设计

程序一共包含三类界面:开始界面、游戏游玩界面、结算界面(通关界面、失败界面)。

  1. 开始界面:展示游戏标题 “一箭又一箭”,提供蓝色【开始游戏】按钮,点击后进入第一关。
  2. 游戏游玩界面:绘制 6×6 网格棋盘,渲染棋盘内各个方向箭头;界面左上角显示当前关卡编号、剩余箭头数量、剩余失误次数信息。箭头被阻挡时会闪烁红色作为碰撞反馈,可飞出的箭头会播放飞出屏幕的动画。
  3. 通关界面:区分单关通关与全部通关。单关通关显示 “本关通关!”,提供【下一关】按钮;全部关卡通关显示 “全部通关!”。
  4. 失败界面:显示 “游戏失败” 提示,提供黄色【重新开始】按钮,点击重置当前关卡回到初始状态。

2.3 主要功能

  1. 图形化交互界面,支持鼠标点击选择棋盘上的箭头。
  2. 路径检测算法:沿箭头行进方向遍历行 / 列,检测路径上是否存在其他箭头障碍物。
  3. 箭头飞行动画与碰撞反馈动画:可飞出箭头播放位移飞出动画;被阻挡箭头红色闪烁提示碰撞。

三、 实现思路(迷宫类游戏,箭头 / 方向 / 关卡 + 路径检测)

3.1 箭头、方向表示

用枚举 / 常量代表 4 个移动方向:上、下、左、右。

//方向常量
const int UP = 0;
const int DOWN = 1;
const int LEFT = 2;
const int RIGHT = 3;
//方向偏移:{dx,dy},对应行、列的变化
int dirs[4][2]={{-1,0},{1,0},{0,-1},{0,1}};

箭头就是方向的可视化:向上箭头对应UP,移动坐标按dirs[UP]做坐标更新。

3.2 关卡如何表示

使用二维数组存储迷宫地图:

  • 0代表通路可以走;
  • 1代表墙壁障碍物;
    起点、终点记录一对坐标 (start_x,start_y)(end_x,end_y)
    每一关就是一套不同的二维地图数组。
//示例关卡地图
int map[5][5]={
    {1,1,1,1,1},
    {1,0,0,0,1},
    {1,1,1,0,1},
    {1,0,0,0,1},
    {1,1,1,1,1}
};

3.3 路径检测方法

路径检测作用:判断朝某个方向移动后,下一个格子是否越界、是否撞墙,能不能通行。
检测逻辑:

  1. 根据当前位置(x,y) + 方向偏移,算出下一位置nx = x+dx , ny = y+dy
  2. 边界检测:nxny不能小于 0,不能超过地图行列下标
  3. 障碍物检测:map[nx][ny]不等于墙壁(不为 1)
  4. 全部满足,说明该方向路径可行,可以移动;否则不能走。

关键代码片段

// 判断从(x,y)向direction方向是否可以走
bool canMove(int x,int y,int direction,int map[][5]){
    int dx = dirs[direction][0];
    int dy = dirs[direction][1];
    int nx = x + dx;
    int ny = y + dy;
    //边界判断
    if(nx<0||nx>=5||ny<0||ny>=5)
        return false;
    //墙壁判断
    if(map[nx][ny]==1)
        return false;
    return true;
}

完整代码:

点击查看代码
import pygame
import sys
# 常量定义
WIDTH = 800
HEIGHT = 600
GRID_SIZE = 80
GRID_OFFSET_X = 80
GRID_OFFSET_Y = 120
MAX_MISTAKE = 3
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (120, 120, 120)
BLUE = (30, 80, 220)
RED = (220, 30, 30)
YELLOW = (255, 200, 0)
# 游戏状态
STATE_START = 0
STATE_PLAY = 1
STATE_WIN = 2
STATE_LOSE = 3
# 方向常量
DIR_RIGHT = 0
DIR_UP = 1
DIR_DOWN = 2
DIR_LEFT = 3
# 方向偏移:(行变化, 列变化)
DIR_DELTA = [
    (0, 1),   # 右
    (-1, 0),  # 上
    (1, 0),   # 下
    (0, -1)   # 左
]
# 方向像素增量(飞行动画每帧移动像素)
DIR_PIXEL_DELTA = [
    (6, 0),   # 右
    (0, -6),  # 上
    (0, 6),   # 下
    (-6, 0)   # 左
]
# 方向符号
DIR_SYMBOL = {
    DIR_RIGHT: "→",
    DIR_UP: "↑",
    DIR_DOWN: "↓",
    DIR_LEFT: "←"
}
class Arrow:
    def __init__(self, row, col, dire):
        self.row = row
        self.col = col
        self.dir = dire
        self.flash_timer = 0
        self.flying = False
        # 动画坐标,静止时等于格子中心
        self.anim_x, self.anim_y = self.get_center()
    def get_center(self):
        x = GRID_OFFSET_X + self.col * GRID_SIZE + GRID_SIZE // 2
        y = GRID_OFFSET_Y + self.row * GRID_SIZE + GRID_SIZE // 2
        return x, y
    def get_rect(self):
        return pygame.Rect(
            GRID_OFFSET_X + self.col * GRID_SIZE,
            GRID_OFFSET_Y + self.row * GRID_SIZE,
            GRID_SIZE,
            GRID_SIZE
        )
    def get_color(self):
        if self.flash_timer > 0:
            return RED
        return BLUE
    def start_fly(self):
        self.flying = True
        self.anim_x, self.anim_y = self.get_center()
    def update_anim(self):
        if not self.flying:
            return
        dx, dy = DIR_PIXEL_DELTA[self.dir]
        self.anim_x += dx
        self.anim_y += dy
    def is_out_of_screen(self):
        # 判断飞出可视区域
        return not (0 < self.anim_x < WIDTH and 0 < self.anim_y < HEIGHT)
class Game:
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
        pygame.display.set_caption("一箭又一箭")
        self.clock = pygame.time.Clock()
        self.state = STATE_START
        self.cur_level = 0
        self.arrows = []
        self.mistake_left = MAX_MISTAKE
        self.arrow_remain = 0
        self.font_big = pygame.font.SysFont("simhei", 36)
        self.font_mid = pygame.font.SysFont("simhei", 28)
        self.font_arrow = pygame.font.SysFont("simhei", 42)
        # 【修改】游玩界面重新开始按钮放在右上角,避开棋盘,不遮挡方格
        self.btn_restart = pygame.Rect(WIDTH - 140, 10, 130, 45)
        self.start_btn = pygame.Rect(WIDTH // 2 - 100, HEIGHT // 2 - 30, 200, 60)
        self.next_level_btn = pygame.Rect(WIDTH // 2 - 100, HEIGHT // 2 + 20, 200, 60)
        # 三个关卡:(行, 列, 方向)
        self.levels = [
            [
                Arrow(1, 1, DIR_RIGHT),
                Arrow(1, 4, DIR_UP),
                Arrow(3, 2, DIR_DOWN),
                Arrow(4, 5, DIR_LEFT),
            ],
            [
                Arrow(0, 2, DIR_RIGHT),
                Arrow(2, 0, DIR_DOWN),
                Arrow(3, 4, DIR_LEFT),
                Arrow(5, 1, DIR_UP),
                Arrow(1, 3, DIR_RIGHT),
            ],
            [
                Arrow(0, 0, DIR_DOWN),
                Arrow(1, 5, DIR_LEFT),
                Arrow(2, 2, DIR_UP),
                Arrow(4, 1, DIR_RIGHT),
                Arrow(5, 4, DIR_DOWN),
                Arrow(3, 3, DIR_LEFT),
            ]
        ]
    def load_level(self, idx):
        self.arrows = []
        for a in self.levels[idx]:
            self.arrows.append(Arrow(a.row, a.col, a.dir))
        self.arrow_remain = len(self.arrows)
        self.mistake_left = MAX_MISTAKE
        self.state = STATE_PLAY
    def draw_start(self):
        self.screen.fill(WHITE)
        title = self.font_big.render("一箭又一箭", True, BLACK)
        self.screen.blit(title, (WIDTH // 2 - title.get_width() // 2, 160))
        pygame.draw.rect(self.screen, BLUE, self.start_btn)
        btn_text = self.font_mid.render("开始游戏", True, WHITE)
        self.screen.blit(
            btn_text,
            (
                self.start_btn.x + self.start_btn.width // 2 - btn_text.get_width() // 2,
                self.start_btn.y + self.start_btn.height // 2 - btn_text.get_height() // 2,
            )
        )
        pygame.display.flip()
    def draw_game(self):
        self.screen.fill(WHITE)
        text_lv = self.font_big.render(f"关卡:{self.cur_level + 1}", True, BLACK)
        text_arrow = self.font_big.render(f"剩余箭头:{self.arrow_remain}", True, BLACK)
        text_mistake = self.font_big.render(f"剩余失误:{self.mistake_left}", True, RED)
        self.screen.blit(text_lv, (20, 10))
        self.screen.blit(text_arrow, (20, 45))
        self.screen.blit(text_mistake, (20, 80))

        # 【新增】游戏进行界面绘制右上角重新开始按钮
        pygame.draw.rect(self.screen, YELLOW, self.btn_restart)
        rt = self.font_mid.render("重新开始", True, BLACK)
        self.screen.blit(
            rt,
            (
                self.btn_restart.x + self.btn_restart.width // 2 - rt.get_width() // 2,
                self.btn_restart.y + self.btn_restart.height // 2 - rt.get_height() // 2,
            )
        )

        # 绘制网格
        for r in range(6):
            for c in range(6):
                rect = pygame.Rect(
                    GRID_OFFSET_X + c * GRID_SIZE,
                    GRID_OFFSET_Y + r * GRID_SIZE,
                    GRID_SIZE,
                    GRID_SIZE
                )
                pygame.draw.rect(self.screen, BLACK, rect, 2)
        # 绘制箭头
        for ar in self.arrows:
            color = ar.get_color()
            symbol = DIR_SYMBOL[ar.dir]
            text = self.font_arrow.render(symbol, True, color)
            # 飞行状态使用动画坐标;静止使用格子中心
            if ar.flying:
                x, y = ar.anim_x, ar.anim_y
            else:
                x, y = ar.get_center()
            self.screen.blit(
                text,
                (
                    x - text.get_width() // 2,
                    y - text.get_height() // 2
                )
            )
        pygame.display.flip()
    def draw_win(self):
        self.screen.fill(WHITE)
        if self.cur_level + 1 >= len(self.levels):
            t = self.font_big.render("全部通关!", True, BLUE)
        else:
            t = self.font_big.render("本关通关!", True, BLUE)
            pygame.draw.rect(self.screen, BLUE, self.next_level_btn)
            btn_text = self.font_mid.render("下一关", True, WHITE)
            self.screen.blit(
                btn_text,
                (
                    self.next_level_btn.x + self.next_level_btn.width // 2 - btn_text.get_width() // 2,
                    self.next_level_btn.y + self.next_level_btn.height // 2 - btn_text.get_height() // 2,
                )
            )
        self.screen.blit(t, (WIDTH // 2 - t.get_width() // 2, HEIGHT // 2 - 40))
        pygame.display.flip()
    def draw_lose(self):
        self.screen.fill(WHITE)
        t = self.font_big.render("游戏失败", True, RED)
        tip = self.font_mid.render("点击重新开始重试本关", True, GRAY)
        self.screen.blit(t, (WIDTH // 2 - t.get_width() // 2, HEIGHT // 2 - 60))
        self.screen.blit(tip, (WIDTH // 2 - tip.get_width() // 2, HEIGHT // 2 - 10))
        # 失败界面也绘制重启按钮
        pygame.draw.rect(self.screen, YELLOW, self.btn_restart)
        rt = self.font_mid.render("重新开始", True, BLACK)
        self.screen.blit(
            rt,
            (
                self.btn_restart.x + self.btn_restart.width // 2 - rt.get_width() // 2,
                self.btn_restart.y + self.btn_restart.height // 2 - rt.get_height() // 2,
            )
        )
        pygame.display.flip()
    def find_path_target(self, arrow):
        """沿箭头方向路径检测,返回路径上第一个阻挡箭头;没有阻挡返回 None"""
        dr, dc = DIR_DELTA[arrow.dir]
        cr = arrow.row + dr
        cc = arrow.col + dc
        while 0 <= cr < 6 and 0 <= cc < 6:
            for target in self.arrows:
                if target is not arrow and target.row == cr and target.col == cc:
                    return target
            cr += dr
            cc += dc
        return None
    def handle_click_grid(self, mx, my):
        clicked_arrow = None
        for ar in self.arrows:
            if ar.get_rect().collidepoint(mx, my) and not ar.flying:
                clicked_arrow = ar
                break
        if clicked_arrow is None:
            return
        target = self.find_path_target(clicked_arrow)
        if target is not None:
            # 前方有遮挡:闪红提示,扣除失误
            clicked_arrow.flash_timer = 15
            self.mistake_left -= 1
            if self.mistake_left <= 0:
                self.state = STATE_LOSE
        else:
            # 前方无遮挡:启动飞行动画,不扣失误
            clicked_arrow.start_fly()
    def update(self):
        # 更新闪烁计时
        for ar in self.arrows:
            if ar.flash_timer > 0:
                ar.flash_timer -= 1
            if ar.flying:
                ar.update_anim()
        # 飞出屏幕的箭头移除
        remove_list = []
        for ar in self.arrows:
            if ar.flying and ar.is_out_of_screen():
                remove_list.append(ar)
        for ar in remove_list:
            self.arrows.remove(ar)
        self.arrow_remain = len(self.arrows)
        if self.arrow_remain <= 0 and self.state == STATE_PLAY:
            self.state = STATE_WIN
    def run(self):
        while True:
            mx, my = pygame.mouse.get_pos()
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
                if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                    if self.state == STATE_START:
                        if self.start_btn.collidepoint(mx, my):
                            self.cur_level = 0
                            self.load_level(0)
                    elif self.state == STATE_PLAY:
                        #【新增】游玩状态:优先判断点击重新开始按钮
                        if self.btn_restart.collidepoint(mx, my):
                            self.load_level(self.cur_level)
                        else:
                            self.handle_click_grid(mx, my)
                    elif self.state == STATE_WIN:
                        if self.cur_level + 1 < len(self.levels):
                            if self.next_level_btn.collidepoint(mx, my):
                                self.cur_level += 1
                                self.load_level(self.cur_level)
                    elif self.state == STATE_LOSE:
                        if self.btn_restart.collidepoint(mx, my):
                            self.load_level(self.cur_level)
            self.update()
            if self.state == STATE_START:
                self.draw_start()
            elif self.state == STATE_PLAY:
                self.draw_game()
            elif self.state == STATE_WIN:
                self.draw_win()
            elif self.state == STATE_LOSE:
                self.draw_lose()
            self.clock.tick(60)
if __name__ == "__main__":
    try:
        g = Game()
        g.run()
    except Exception as e:
        print(e)
        input("按回车退出")


四、AIGC 使用过程

AIGC使用过程记录表

子任务 借助何种AIGC技术 AI实现或提供了什么 效果如何 人工修改
实现箭头路径检测 ChatGPT 生成四个方向的检测代码,判断箭头前进方向是否存在其他箭头 向上判断时出现越界,部分方向检测结果不准确 修改边界条件,统一方向偏移量,使路径检测按同一规则判断同行/同列箭头
实现箭头飞行动画 Copilot 补全箭头晃动或移动动画代码,建议箭头飞出棋盘后消失 基本可用,但动画速度和飞出逻辑不稳定 调整动画速度,明确飞行状态与静止状态,箭头飞出边界后从列表移除
实现碰撞反馈 Copilot 提供碰撞反馈实现思路,建议碰撞时闪烁红色并扣除失误 初始逻辑把碰撞反馈加在障碍物上,不符合题目要求 将碰撞反馈改为作用于被点击的箭头;前方有阻挡时,被点击箭头闪红并扣失误;前方无阻挡时箭头飞出
修复程序闪退与细节问题 ChatGPT 根据报错定位对象属性问题,给出修复方案 修复了闪退,但部分UI与交互细节仍不符合要求 补全失误计数与失误提醒UI,过滤空白格子点击响应,优化文字位置与按钮逻辑

五、测试结果

编号 测试内容 预期结果 实际结果 是否通过
T01 点击前方无阻挡的箭头 箭头飞出棋盘并消失 箭头飞出棋盘并消失 通过
T02 点击前方有阻挡的箭头 箭头不消失,失误次数减 1 箭头保留不动,失误次数‑1 通过
T03 点击位于边缘且朝向棋盘外的箭头 箭头正常消失,不发生越界错误 箭头消失,程序无数组越界崩溃 通过
T04 消除本关全部箭头 显示通关并进入下一关 弹出通关提示,自动加载下一关布局 通过
T05 失误次数耗尽 显示失败并允许重新开始 弹出失败界面,可点击按钮重玩本关 通过
T06 游戏进行中重新开始 箭头布局和失误次数恢复 棋盘箭头重置,失误次数回到初始值 通过

六、PSP表格

任务 预估耗时(小时) 实际耗时(小时) 差异(小时)
需求分析与游戏设计 2 2.5 +0.5
Python 与图形库学习 3 4 +1
游戏界面实现 3 2.5 -0.5
路径与碰撞逻辑实现 4 5 +1
关卡设计 2 1.5 -0.5
AIGC 辅助开发 1 1 0
测试与修改 2 3 +1
README 与博客撰写 2 2 0
合计 19 21.5 +2.5

七、心得体会

本次箭头小游戏开发过程中,AI 给我带来了不少帮助。遇到图形库 API 不熟悉时,AI 可以快速提供示例代码,帮我理解函数用法;在碰撞检测、边界判断等逻辑调试阶段,能够协助分析报错信息,定位代码 BUG;同时也辅助我完成测试用例、工时表格、博客文档的撰写,节省了整理文档的时间。
项目过程中也出现了不少问题。AI 生成的代码有时会和我的项目整体结构不匹配,不能直接复制运行,需要手动修改适配;部分游戏边界逻辑 AI 考虑不全,会出现数组越界、箭头状态异常的问题;有时候给出的方案存在逻辑漏洞,不能完全依赖,需要自己逐行阅读理解,结合需求进行验证。
经过本次项目,我的收获很多。我熟悉了 Python 图形库的基础使用,掌握了游戏碰撞、关卡切换、状态管理的实现思路;学会了划分开发任务,预估与统计项目工时,懂得编写测试用例验证程序功能;也明白了 AIGC 只是辅助工具,开发时要做到理解代码、独立思考,对 AI 输出内容进行甄别校验,不能直接照搬,提升了自己调试排错与工程实践的能力。

posted @ 2026-09-22 19:01  罗cf  阅读(2)  评论(0)    收藏  举报