首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 實現迷宮小遊戲:深度優先生成與鍵盤交互完整代碼

     編輯:Java 相關知識

項目概覽

使用 Java Swing 實現隨機迷宮生成(深度優先搜索 + 棧回溯)、鍵盤控制小球移動、路徑顯示提示功能。核心數據結構:二維格子數組 + 父指針形成生成樹。

核心算法:隨機 DFS 生成迷宮

  1. 隨機選擇起始格子入棧
  2. 循環彈出格子,標記已訪問
  3. 隨機順序檢查四鄰居,未訪問者入棧並設置父指針
  4. 棧空時生成完畢,形成無環連通圖(任意兩點唯一路徑)

完整可運行代碼

package maze;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

class Cell {
    static final int UNVISITED = 0, VISITED = 1;
    final int x, y;
    int state = UNVISITED;
    Cell parent = null;
    
    Cell(int x, int y) { this.x = x; this.y = y; }
}

public class MazeGame extends JPanel {
    private final int size, cellSize, padding;
    private final Cell[][] grid;
    private int ballX = 0, ballY = 0;
    private boolean showPath = false;
    
    public MazeGame(int size, int cellSize, int padding) {
        this.size = size;
        this.cellSize = cellSize;
        this.padding = padding;
        this.grid = new Cell[size][size];
        for (int i = 0; i < size; i++)
            for (int j = 0; j < size; j++)
                grid[i][j] = new Cell(i, j);
        generateMaze();
        initInput();
    }
    
    // ===== 迷宮生成 =====
    private void generateMaze() {
        Random rand = new Random();
        Stack stack = new Stack<>();
        Cell start = grid[rand.nextInt(size)][rand.nextInt(size)];
        stack.push(start);
        
        int[] dx = {-1, 0, 1, 0};  // 上右下左
        int[] dy = {0, 1, 0, -1};
        
        while (!stack.isEmpty()) {
            Cell cur = stack.pop();
            if (cur.state == Cell.VISITED) continue;
            cur.state = Cell.VISITED;
            
            // 隨機打亂方向順序
            List dirs = Arrays.asList(0, 1, 2, 3);
            Collections.shuffle(dirs, rand);
            
            for (int dir : dirs) {
                int nx = cur.x + dx[dir], ny = cur.y + dy[dir];
                if (nx < 0 || nx >= size || ny < 0 || ny >= size) continue;
                Cell next = grid[nx][ny];
                if (next.state == Cell.UNVISITED) {
                    next.parent = cur;
                    stack.push(next);
                }
            }
        }
    }
    
    // ===== 繪製 =====
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        drawGrid(g);
        drawWalls(g);
        drawEntranceExit(g);
        drawBall(g);
        if (showPath) drawSolutionPath(g);
    }
    
    private void drawGrid(Graphics g) {
        for (int i = 0; i <= size; i++) {
            g.drawLine(padding + i * cellSize, padding, 
                       padding + i * cellSize, padding + size * cellSize);
            g.drawLine(padding, padding + i * cellSize,
                       padding + size * cellSize, padding + i * cellSize);
        }
    }
    
    private void drawWalls(Graphics g) {
        g.setColor(getBackground());
        for (int i = size - 1; i >= 0; i--) {
            for (int j = size - 1; j >= 0; j--) {
                Cell p = grid[i][j].parent;
                if (p != null) eraseWall(g, i, j, p.x, p.y);
            }
        }
    }
    
    private void eraseWall(Graphics g, int x1, int y1, int x2, int y2) {
        int sx = padding + Math.max(y1, y2) * cellSize;
        int sy = padding + Math.max(x1, x2) * cellSize;
        int ex = (x1 == x2) ? sx + cellSize : sx;
        int ey = (y1 == y2) ? sy + cellSize : sy;
        if (sx != ex) { sx++; ex--; } else { sy++; ey--; }
        g.drawLine(sx, sy, ex, ey);
    }
    
    private void drawEntranceExit(Graphics g) {
        g.setColor(Color.BLACK);
        g.drawLine(padding, padding + 1, padding, padding + cellSize - 1); // 入口
        int end = padding + size * cellSize;
        g.drawLine(end, end - 1, end, end - cellSize + 1); // 出口
    }
    
    private void drawBall(Graphics g) {
        g.setColor(Color.RED);
        int cx = padding + ballY * cellSize + cellSize / 2;
        int cy = padding + ballX * cellSize + cellSize / 2;
        g.fillOval(cx - cellSize / 3, cy - cellSize / 3, cellSize / 2, cellSize / 2);
    }
    
    private void drawSolutionPath(Graphics g) {
        // 從終點回溯到起點繪製路徑
        g.setColor(new Color(255, 165, 0)); // 橙色
        Cell cur = grid[size - 1][size - 1];
        while (cur.parent != null) {
            drawPathSegment(g, cur, cur.parent);
            cur = cur.parent;
        }
    }
    
    private void drawPathSegment(Graphics g, Cell a, Cell b) {
        int x1 = padding + a.y * cellSize + cellSize / 2;
        int y1 = padding + a.x * cellSize + cellSize / 2;
        int x2 = padding + b.y * cellSize + cellSize / 2;
        int y2 = padding + b.x * cellSize + cellSize / 2;
        g.drawLine(x1, y1, x2, y2);
    }
    
    // ===== 鍵盤控制 =====
    private void initInput() {
        setFocusable(true);
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                int nx = ballX, ny = ballY;
                switch (e.getKeyCode()) {
                    case KeyEvent.VK_UP    -> nx--;
                    case KeyEvent.VK_DOWN  -> nx++;
                    case KeyEvent.VK_LEFT  -> ny--;
                    case KeyEvent.VK_RIGHT -> ny++;
                    case KeyEvent.VK_SPACE -> { showPath = !showPath; repaint(); return; }
                }
                // 只能沿父子連接移動(即走通道)
                if (inBounds(nx, ny) && connected(ballX, ballY, nx, ny)) {
                    ballX = nx; ballY = ny;
                    repaint();
                    checkWin();
                }
            }
        });
    }
    
    private boolean inBounds(int x, int y) {
        return x >= 0 && x < size && y >= 0 && y < size;
    }
    
    private boolean connected(int x1, int y1, int x2, int y2) {
        Cell a = grid[x1][y1], b = grid[x2][y2];
        return a.parent == b || b.parent == a;
    }
    
    private void checkWin() {
        if (ballX == size - 1 && ballY == size - 1) {
            JOptionPane.showMessageDialog(this, "恭喜通關!", "勝利", JOptionPane.INFORMATION_MESSAGE);
            resetGame();
        }
    }
    
    private void resetGame() {
        ballX = ballY = 0;
        showPath = false;
        for (int i = 0; i < size; i++)
            for (int j = 0; j < size; j++) {
                grid[i][j].state = Cell.UNVISITED;
                grid[i][j].parent = null;
            }
        generateMaze();
        repaint();
    }
    
    // ===== 啟動入口 =====
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("迷宮遊戲 - 方向鍵移動,空格顯示路徑");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(new MazeGame(30, 20, 20));
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

關鍵設計亮點

模塊技術要點
迷宮生成隨機 DFS + 顯式棧,非遞歸,避免棧溢出;父指針隱式存儲樹結構
牆壁繪製僅擦除父子格子間共用邊,其餘保留,O(n²) 遍歷繪製
移動邏輯僅允許父子相連格子間移動,天然防止穿牆
路徑提示空格切換,從終點沿父指針回溯繪製
線程安全Swing 事件分發線程中操作狀態,repaint 觸發重繪

擴展方向

  • 算法替換:Prim 算法、Kruskal 算法、Wilson 算法生成不同風格迷宮
  • 視覺增強:材質貼圖、粒子效果、霧效(未探索區域)
  • 玩法豐富:倒計時、道具、敵人、多關卡、自動尋路演示(A* / BFS)
  • 架構升級:模型-視圖-控制器分離,支持無界面模式單元測試

這個項目完美展示了圖論算法、Swing 繪製、事件驅動編程的綜合應用,是 Java 圖形界面入門的經典實戰。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved