news 2025/12/25 13:59:13

基于Java Swing的迷宫生成与走迷宫游戏(2)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Java Swing的迷宫生成与走迷宫游戏(2)

1、演示视频

基于Java Swing的迷宫生成与走迷宫游戏

2、项目截图

设计说明

3.1 整体架构设计

项目采用分层设计和面向对象的思想,主要分为以下几个模块:

  1. 界面层(UI层):负责图形界面的创建和渲染,包括主窗口、迷宫面板、算法选择下拉框、按钮等组件的初始化和布局。核心类为MazeGame(主窗口)和MazePanel(迷宫绘制面板)。
  2. 算法层:负责迷宫生成的核心逻辑,包含三种算法的实现类(DfsMazeGeneratorPrimMazeGeneratorKruskalMazeGenerator),均实现MazeGenerator接口,统一算法调用规范。
  3. 工具层:包含辅助功能的实现,如并查集(UnionFind)用于克鲁斯卡尔算法,BFS算法用于路径连通性校验。
  4. 逻辑层:负责游戏的核心业务逻辑,如角色移动、迷宫数据管理、路径校验等,集成在MazeGame类中。

3.2 数据结构设计

  • 迷宫数据存储:使用三维布尔数组boolean[][][] walls存储迷宫的墙信息,其中walls[row][col][dir]表示第row行第col列的单元格在dir方向是否有墙(dir:0=上、1=右、2=下、3=左),true表示有墙,false表示无墙。
  • 角色位置存储:使用两个整型变量playerRowplayerCol存储角色的当前行和列坐标。
  • 算法边存储:普里姆算法使用List存储边列表,克鲁斯卡尔算法使用List存储相邻单元格的边信息。

3.3 界面布局设计

主窗口采用BorderLayout布局,分为三个区域:

  • 北部(NORTH):包含游戏说明面板和算法选择面板,采用BorderLayoutFlowLayout组合布局。
  • 中部(CENTER):迷宫绘制面板(MazePanel),是界面的核心区域,负责渲染迷宫和角色。
  • 南部(SOUTH):包含“重新生成迷宫”按钮的面板,采用FlowLayout布局。

3.4 面向对象设计原则

  • 单一职责原则MazePanel仅负责迷宫绘制,算法类仅负责迷宫生成,逻辑类仅负责业务处理。
  • 接口隔离原则:定义MazeGenerator接口,仅包含generate()方法,为算法类提供统一的调用标准。
  • 里氏替换原则:所有算法类均可替换MazeGenerator接口的实现,不影响迷宫生成的核心逻辑。
  • 开闭原则:若需添加新的迷宫生成算法,只需实现MazeGenerator接口,无需修改原有代码。

四、算法说明

4.1 深度优先搜索算法(DFS)

4.1.1 算法原理

深度优先搜索算法是一种“回溯式”的迷宫生成算法,核心思想是从起点开始,随机选择一个未访问的相邻单元格,打破两者之间的墙,然后递归访问该单元格;当当前单元格的所有相邻单元格都被访问后,回溯到上一个单元格,继续处理未访问的单元格,直到所有单元格都被访问完毕。

4.1.2 算法步骤
  1. 初始化迷宫,所有单元格的墙都为存在状态,创建访问标记数组visited,标记单元格是否被访问。
  2. 从起点(0,0)开始,标记该单元格为已访问。
  3. 随机打乱四个方向(上、右、下、左)的顺序,遍历每个方向。
  4. 对于每个方向,判断相邻单元格是否在迷宫范围内且未被访问:
    • 若是,打破当前单元格和相邻单元格之间的墙。
    • 递归访问相邻单元格。
  5. 当所有单元格都被访问后,算法结束,生成迷宫。
4.1.3 算法特点

生成的迷宫具有长路径、少分支、死胡同少的特点,迷宫整体呈现“走廊式”结构,游戏难度中等。

4.2 普里姆算法(Prim)

4.2.1 算法原理

普里姆算法原本用于求解最小生成树,此处改编为迷宫生成算法,核心思想是从起点开始,将相邻的墙加入边列表,然后随机选择一条边,若边的另一侧单元格未被访问,则打破墙并将该单元格加入已访问集合,同时将该单元格的相邻墙加入边列表;重复此过程,直到所有单元格都被访问完毕。

4.2.2 算法步骤
  1. 初始化迷宫,所有单元格的墙都为存在状态,创建访问标记数组visited,标记起点(0,0)为已访问。
  2. 将起点的所有相邻墙加入边列表edgeList
  3. 当边列表不为空时:
    • 随机选择一条边,移除该边并获取对应的单元格和方向。
    • 计算该方向的相邻单元格,判断是否在迷宫范围内且未被访问。
    • 若是,打破当前单元格和相邻单元格之间的墙,标记相邻单元格为已访问,将相邻单元格的所有相邻墙加入边列表。
  4. 当边列表为空时,算法结束,生成迷宫。
4.2.3 算法特点

生成的迷宫具有路径均匀、分支适中的特点,迷宫整体结构平衡,游戏难度比DFS算法生成的迷宫稍高。

4.3 克鲁斯卡尔算法(Kruskal)

4.3.1 算法原理

克鲁斯卡尔算法同样源于最小生成树算法,核心思想是将每个单元格视为一个独立的集合,将所有相邻单元格的边(仅考虑右和下方向,避免重复)加入边列表并随机打乱,然后遍历每条边,若边的两个单元格属于不同的集合,则打破墙并合并两个集合;重复此过程,直到所有单元格属于同一个集合。

4.3.2 算法步骤
  1. 初始化迷宫,所有单元格的墙都为存在状态,创建并查集(UnionFind),每个单元格对应一个独立的集合。
  2. 遍历所有单元格,将右方向和下方向的相邻边加入边列表edges
  3. 随机打乱边列表的顺序。
  4. 遍历每条边,获取边的两个单元格:
    • 通过并查集判断两个单元格是否属于同一集合。
    • 若不属于,打破两个单元格之间的墙,合并两个集合。
  5. 当所有边遍历完毕后,算法结束,生成迷宫。
4.3.3 算法特点

生成的迷宫具有多分支、短路径、死胡同多的特点,迷宫结构复杂,游戏难度最高。

4.4 BFS路径校验算法

4.4.1 算法原理

广度优先搜索(BFS)算法用于校验迷宫起点到终点的连通性,核心思想是从起点开始,逐层遍历相邻的可达单元格,若能遍历到终点,则说明路径连通;否则,手动打通从终点到起点的路径,确保游戏可玩。

4.4.2 算法作用

防止因算法异常或边界情况导致迷宫起点和终点不连通,保证游戏的基本可玩性。

五、测试说明

5.1 测试环境

测试项配置/环境
操作系统Windows 10/11、macOS、Linux(Ubuntu 20.04)
JDK版本JDK 8、JDK 11、JDK 17(兼容Java SE 8及以上版本)
运行方式命令行(javac + java)、IDE(IntelliJ IDEA、Eclipse)

5.2 功能测试

测试用例测试步骤预期结果测试结果
界面显示测试运行程序,观察界面是否正常显示主窗口正常弹出,迷宫、起点、终点、角色、下拉框、按钮均显示正常通过
算法选择测试选择不同的算法,点击“重新生成迷宫”按钮根据选择的算法生成不同风格的迷宫通过
角色移动测试使用方向键/WASD键控制角色移动角色可在无墙的方向移动,遇到墙时无法移动通过
终点到达测试控制角色从起点移动到终点弹出“游戏胜利”提示框通过
迷宫重置测试点击“重新生成迷宫”按钮生成新的迷宫,角色回到起点通过
路径连通性测试多次生成迷宫,检查起点到终点是否连通所有迷宫的起点到终点均连通,无孤立情况通过

5.3 性能测试

  • 迷宫生成速度:对于15×25的迷宫,三种算法的生成时间均在100ms以内,无明显卡顿。
  • 角色移动流畅度:角色移动时界面刷新流畅,无延迟或闪烁现象。
  • 内存占用:程序运行时内存占用约50MB,资源消耗低。

5.4 边界测试

  • 修改单元格数量(如5×5、30×50),测试迷宫生成和显示是否正常。
  • 修改单元格尺寸(如CELL_SIZE=20、CELL_SIZE=50),测试界面适配是否正常。
  • 连续多次点击“重新生成迷宫”按钮,测试程序是否出现内存泄漏或卡顿。

六、关键代码

6.1 主窗口初始化代码

/** * 构造方法:初始化游戏窗口和所有组件 * 完成窗口布局、事件绑定、迷宫初始化等核心操作 */ public MazeGame() { // 1. 设置窗口基本属性 this.setTitle("迷宫生成与走迷宫游戏"); // 窗口标题 this.setDefaultCloseOperation(EXIT_ON_CLOSE); // 关闭窗口时退出程序 this.setLayout(new BorderLayout(5, 5)); // 窗口布局:边界布局,组件间距5像素 this.setResizable(true); // 允许窗口大小调整 // 关键:设置窗口的最小尺寸,确保迷宫能完整显示(迷宫尺寸+顶部/底部面板的预留空间) this.setMinimumSize(new Dimension(MAZE_WIDTH + 20, MAZE_HEIGHT + 100)); // 2. 初始化顶部面板(游戏说明 + 算法选择) JPanel topPanel = new JPanel(new BorderLayout()); topPanel.add(createInfoPanel(), BorderLayout.CENTER); // 添加游戏说明面板 topPanel.add(createAlgorithmPanel(), BorderLayout.EAST); // 添加算法选择面板 this.add(topPanel, BorderLayout.NORTH); // 顶部面板放入窗口北部 // 3. 初始化迷宫绘制面板 mazePanel = new MazePanel(); // 设置面板的首选尺寸(核心:强制面板使用迷宫的实际像素尺寸) mazePanel.setPreferredSize(new Dimension(MAZE_WIDTH, MAZE_HEIGHT)); // 设置面板最小尺寸,防止窗口缩小后迷宫被遮挡 mazePanel.setMinimumSize(new Dimension(MAZE_WIDTH, MAZE_HEIGHT)); mazePanel.setFocusable(true); // 面板需要获取焦点以接收键盘事件 mazePanel.requestFocusInWindow(); // 主动获取焦点 // 绑定键盘事件到迷宫面板(确保事件响应) mazePanel.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { movePlayer(e.getKeyCode()); mazePanel.repaint(); } }); this.add(mazePanel, BorderLayout.CENTER); // 迷宫面板放入窗口中部(核心区域) // 4. 初始化底部面板(重新生成迷宫按钮) JPanel buttonPanel = new JPanel(); JButton resetButton = new JButton("重新生成迷宫"); // 绑定按钮点击事件:点击后重新初始化迷宫并绘制 resetButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { initMaze(); // 重新生成迷宫 mazePanel.repaint(); // 重绘面板 mazePanel.requestFocusInWindow(); // 重新获取焦点 } }); buttonPanel.add(resetButton); this.add(buttonPanel, BorderLayout.SOUTH); // 底部面板放入窗口南部 // 5. 初始化迷宫(首次生成) initMaze(); // 6. 调整窗口大小、居中并**显式设置可见** this.pack(); // 自适应组件大小(现在会根据面板的首选尺寸计算) this.setLocationRelativeTo(null); // 窗口居中 this.setVisible(true); // 关键:让窗口显示在屏幕上 }

6.2 迷宫绘制面板代码

/** * 迷宫绘制面板(继承JPanel,重写paintComponent方法实现自定义绘制) * 功能:绘制迷宫墙、起点(绿色)、终点(红色)、玩家(蓝色) */ class MazePanel extends JPanel { /** * 重写paintComponent方法:执行绘制逻辑 * @param g 绘图上下文对象 */ @Override protected void paintComponent(Graphics g) { super.paintComponent(g); // 调用父类方法,确保面板正常渲染 // 1. 绘制背景:白色 g.setColor(Color.WHITE); g.fillRect(0, 0, getWidth(), getHeight()); // 2. 绘制迷宫墙:黑色 g.setColor(Color.BLACK); for (int i = 0; i < ROWS; i++) { for (int j = 0; j < COLS; j++) { int x = j * CELL_SIZE; // 单元格左上角x坐标 int y = i * CELL_SIZE; // 单元格左上角y坐标 // 绘制上墙:水平直线(x, y)到(x+CELL_SIZE, y) if (walls[i][j][0]) { g.drawLine(x, y, x + CELL_SIZE, y); } // 绘制右墙:垂直直线(x+CELL_SIZE, y)到(x+CELL_SIZE, y+CELL_SIZE) if (walls[i][j][1]) { g.drawLine(x + CELL_SIZE, y, x + CELL_SIZE, y + CELL_SIZE); } // 绘制下墙:水平直线(x, y+CELL_SIZE)到(x+CELL_SIZE, y+CELL_SIZE) if (walls[i][j][2]) { g.drawLine(x, y + CELL_SIZE, x + CELL_SIZE, y + CELL_SIZE); } // 绘制左墙:垂直直线(x, y)到(x, y+CELL_SIZE) if (walls[i][j][3]) { g.drawLine(x, y, x, y + CELL_SIZE); } } } // 3. 绘制起点:绿色圆形(左上角,偏移5像素避免贴墙) g.setColor(Color.GREEN); g.fillOval(5, 5, CELL_SIZE - 10, CELL_SIZE - 10); // 4. 绘制终点:红色圆形(右下角,偏移5像素避免贴墙) g.setColor(Color.RED); g.fillOval(endCol * CELL_SIZE + 5, endRow * CELL_SIZE + 5, CELL_SIZE - 10, CELL_SIZE - 10); // 5. 绘制玩家:蓝色圆形(当前位置,偏移5像素避免贴墙) g.setColor(Color.BLUE); g.fillOval(playerCol * CELL_SIZE + 5, playerRow * CELL_SIZE + 5, CELL_SIZE - 10, CELL_SIZE - 10); } /** * 重写processMouseEvent方法:确保面板点击时获取焦点 * 解决键盘事件无法响应的问题 * @param e 鼠标事件 */ @Override protected void processMouseEvent(java.awt.event.MouseEvent e) { super.processMouseEvent(e); this.requestFocusInWindow(); // 获取焦点 } }

6.3 DFS迷宫生成算法代码

/** * 深度优先搜索(DFS)迷宫生成算法实现类 * 算法原理: * 1. 从起点开始,随机选择一个未访问的相邻单元格 * 2. 打破两者之间的墙,递归访问该单元格 * 3. 若当前单元格的所有相邻单元格都已访问,则回溯 * 特点:生成的迷宫有较长的连续路径,较少的分支 */ class DfsMazeGenerator implements MazeGenerator { /** 迷宫行数 */ private final int rows; /** 迷宫列数 */ private final int cols; /** 迷宫墙数据(引用外部数组,直接修改) */ private final boolean[][][] walls; /** 访问标记数组:记录单元格是否被访问过 */ private final boolean[][] visited; /** * 构造方法:初始化算法参数 * @param rows 迷宫行数 * @param cols 迷宫列数 * @param walls 迷宫墙数据数组 */ public DfsMazeGenerator(int rows, int cols, boolean[][][] walls) { this.rows = rows; this.cols = cols; this.walls = walls; this.visited = new boolean[rows][cols]; // 初始化访问标记数组 } /** * 实现接口的generate方法:启动DFS生成迷宫 * 从起点(0,0)开始递归遍历 */ @Override public void generate() { dfs(0, 0); // 调用DFS递归方法,起点为(0,0) } /** * DFS递归核心方法:遍历单元格并生成路径 * @param row 当前单元格行坐标 * @param col 当前单元格列坐标 */ private void dfs(int row, int col) { // 1. 标记当前单元格为已访问 visited[row][col] = true; // 2. 定义四个移动方向:上、右、下、左(与walls的dir索引对应) int[][] directions = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; // 随机打乱方向顺序(实现随机DFS,避免路径固定) List dirList = new ArrayList<>(); for (int i = 0; i < 4; i++) { dirList.add(i); } Collections.shuffle(dirList, new Random()); // 3. 遍历所有方向(随机顺序) for (int dir : dirList) { int newRow = row + directions[dir][0]; // 新单元格行坐标 int newCol = col + directions[dir][1]; // 新单元格列坐标 // 校验条件:新单元格在边界内 且 未被访问 if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols && !visited[newRow][newCol]) { // 4. 打破当前单元格与新单元格之间的墙 walls[row][col][dir] = false; // 打破新单元格对应方向的墙(如当前单元格右墙对应新单元格左墙) walls[newRow][newCol][(dir + 2) % 4] = false; // 5. 递归访问新单元格 dfs(newRow, newCol); } } } }

6.4 克鲁斯卡尔算法的并查集代码

/** * 并查集(Union-Find)数据结构内部类 * 功能:管理元素的集合归属,支持查找和合并操作 * 优化:路径压缩(find方法)、按秩合并(union方法) */ class UnionFind { /** 父节点数组:parent[i]表示i的父节点 */ private final int[] parent; /** 秩数组:rank[i]表示以i为根的树的高度(用于按秩合并) */ private final int[] rank; /** * 构造方法:初始化并查集 * @param size 元素数量(此处为单元格总数) */ public UnionFind(int size) { parent = new int[size]; rank = new int[size]; // 初始化:每个元素的父节点是自己,秩为0 for (int i = 0; i < size; i++) { parent[i] = i; rank[i] = 0; } } /** * 查找元素的根节点(带路径压缩优化) * 路径压缩:将元素直接指向根节点,减少后续查找次数 * @param x 待查找的元素 * @return 元素x的根节点 */ public int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); // 递归压缩路径 } return parent[x]; } /** * 合并两个元素所在的集合(按秩合并优化) * 按秩合并:将秩小的树合并到秩大的树下,保持树的平衡 * @param x 元素x * @param y 元素y */ public void union(int x, int y) { int rootX = find(x); int rootY = find(y); // 若已在同一集合,无需合并 if (rootX == rootY) { return; } // 按秩合并 if (rank[rootX] < rank[rootY]) { parent[rootX] = rootY; // 小秩合并到大秩 } else if (rank[rootX] > rank[rootY]) { parent[rootY] = rootX; // 小秩合并到大秩 } else { parent[rootY] = rootX; // 秩相等,合并后根节点秩+1 rank[rootX]++; } } }

6.5 角色移动代码

/** * 玩家移动处理方法 * 响应键盘按键(方向键/WASD),更新玩家位置并判断是否到达终点 * @param keyCode 键盘按键编码 */ private void movePlayer(int keyCode) { // 若已到达终点,不处理移动 if (playerRow == endRow && playerCol == endCol) { return; } // 边界兜底:防止玩家坐标超出迷宫范围 if (playerRow < 0) playerRow = 0; if (playerRow >= ROWS) playerRow = ROWS - 1; if (playerCol < 0) playerCol = 0; if (playerCol >= COLS) playerCol = COLS - 1; // 根据按键处理移动 switch (keyCode) { case KeyEvent.VK_UP: // 上方向键 case KeyEvent.VK_W: // W键(上) // 条件:无上墙 且 不在第一行 if (!walls[playerRow][playerCol][0] && playerRow > 0) { playerRow--; // 行坐标减1 } break; case KeyEvent.VK_RIGHT: // 右方向键 case KeyEvent.VK_D: // D键(右) // 条件:无右墙 且 不在最后一列 if (!walls[playerRow][playerCol][1] && playerCol < COLS - 1) { playerCol++; // 列坐标加1 } break; case KeyEvent.VK_DOWN: // 下方向键 case KeyEvent.VK_S: // S键(下) // 条件:无下墙 且 不在最后一行 if (!walls[playerRow][playerCol][2] && playerRow < ROWS - 1) { playerRow++; // 行坐标加1 } break; case KeyEvent.VK_LEFT: // 左方向键 case KeyEvent.VK_A: // A键(左) // 条件:无左墙 且 不在第一列 if (!walls[playerRow][playerCol][3] && playerCol > 0) { playerCol--; // 列坐标减1 } break; default: // 其他按键不处理 break; } // 判断是否到达终点,若到达则弹出提示框 if (playerRow == endRow && playerCol == endCol) { JOptionPane.showMessageDialog(this, "恭喜你!成功走出迷宫!", "游戏胜利", JOptionPane.INFORMATION_MESSAGE); mazePanel.requestFocusInWindow(); // 重新获取焦点(防止提示框后焦点丢失) } }

备注:以上代码为项目核心代码片段,完整代码包含所有算法实现、界面逻辑和辅助功能,可直接编译运行。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/24 21:20:04

使用LobeChat搭建团队内部智能客服系统的完整流程

使用 LobeChat 搭建团队内部智能客服系统的完整流程 在企业数字化转型不断深入的今天&#xff0c;员工对信息获取效率的要求越来越高。一个新入职的同事想了解年假政策&#xff0c;却要翻遍OA公告、HR手册和部门群聊记录&#xff1b;IT支持团队每天重复回答“如何连接公司Wi-Fi…

作者头像 李华
网站建设 2025/12/19 5:37:00

告别盲测,预见温度:安科瑞如何用无线技术革新变电站安全

01 引言 深夜监控中心&#xff0c;显示屏上一个光点突然由绿转红&#xff0c;数字从65℃迅速攀升至130℃——某变电站高压开关柜触头异常升温&#xff0c;一场潜在的停电事故在警报声中得以避免。 2022年夏季高温期间&#xff0c;内蒙古某多晶硅生产基地的106面10kV高压柜母排、…

作者头像 李华
网站建设 2025/12/25 12:58:30

AI重塑投资:东方智谷发布“东方灯塔”智慧财富系统,开启智能Pre-IPO投资时代

近日&#xff0c;国内领先的金融科技机构东方智谷资本研究院正式发布其核心科技平台——“东方灯塔智慧财富系统”。该系统深度融合人工智能与金融投资&#xff0c;旨在通过科技力量破解Pre-IPO 投资信息不对称、门槛高、专业要求强等难题&#xff0c;为投资者提供一个透明、智…

作者头像 李华
网站建设 2025/12/15 18:06:39

基于Vue的流动人口管理系统t94nu(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末

系统程序文件列表 系统功能 用户,城市人口,流入登记,流出登记,居住证办理,人口普查,暂住证办理,户籍迁移,管辖单位,社区援助 开题报告内容 《基于Vue的流动人口管理系统设计与实现》开题报告 一、选题背景、研究意义及国内外研究现状 1. 选题背景 随着我国城市化进程的快速…

作者头像 李华
网站建设 2025/12/15 18:06:32

数字孪生如何重塑现代制造体系?

概述 在制造业不断迈向高质量发展的过程中&#xff0c;传统依赖经验和事后分析的管理模式正逐渐显露出局限性。生产流程复杂、设备数量庞大、工艺参数多变&#xff0c;使得制造企业在效率提升、质量控制和成本管理方面面临更高要求。数字孪生技术的出现&#xff0c;为制造业提…

作者头像 李华
网站建设 2025/12/15 18:06:19

从工程资产到即时工具:AI 原生软件开发与氛围编程的范式分化

目录 前言1. 两种开发模式的起点差异1.1 AI 原生软件开发的基本立场1.2 氛围编程的基本立场 2. 对软件工程原则的态度分化2.1 AI 原生软件对传统工程原则的延续2.2 氛围编程对工程约束的主动放弃 3. 技术栈与工具链的分野3.1 AI 原生软件的技术栈特征3.2 氛围编程的极简技术形态…

作者头像 李华