You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swing迷宫生成工具界面修改无效:removeWall函数问题排查

问题描述

我用Swing开发了一个迷宫生成工具,GridPanel按指定宽高绘制网格,网格从(30,30)起始,到(x-60,y-60)结束,通过scaleX和scaleY实现动态缩放。Cell对象包含坐标及东南西北四个方向的墙坐标数组。GenerateMaze采用递归回溯算法生成完美迷宫(虽未完全实现,但预期能看到生成结果)。目前程序界面可正常显示和关闭,但removeWall函数无法实现预期的拆墙效果,添加repaint()也未解决问题,求排查原因。

相关代码

GridPanel类

public class GridPanel extends JPanel {
    private class Cell {
        private int y, x;
        private int[] south;
        private int[] north;
        private int[] west;
        private int[] east;

        private Cell(int y, int x) {
            this.y = y;
            this.x = x;

            this.south = new int[4];
            this.north = new int[4];
            this.west = new int[4];
            this.east = new int[4];
        }
    }

    private int w, h;
    private final int scaleX, scaleY;
    private final int Vx, Vy;
    private final Cell[][] grid;
    private boolean[][] discovered;

    public GridPanel(int w, int h) {
        setLayout(new GridLayout(1, 1));

        this.w = w;
        this.h = h;

        scaleX = (int) (w / Math.sqrt(w));
        scaleY = (int) (h / Math.sqrt(h));

        Vx = w / scaleX;
        Vy = w / scaleY;

        discovered = new boolean[Vy][Vx];

        grid = new Cell[Vy][Vx];
        init(grid);
    }

    private void init(Cell[][] grid) {
        for (int j = 0; j < grid.length; j++) {
            for (int i = 0; i < grid[0].length; i++) {
                grid[j][i] = new Cell(j, i);
            }
        }
    }

    @Override
    protected void paintComponent(Graphics g) {
        setDoubleBuffered(true);
        g.setColor(Color.CYAN);

        for (int y = 30, j = 0; y < h - 60; y += scaleY, j++) {
            for (int x = 30, i = 0; x < w - 60; x += scaleX, i++) {
                Cell cell = grid[j][i];

                cell.east = new int[]{x, y, x, y + scaleY};
                cell.west = new int[]{x + scaleX, y, x + scaleX, y + scaleY};
                cell.north = new int[]{x, y, x + scaleX, y};
                cell.south = new int[]{x, y + scaleY, x + scaleX, y + scaleY};

                g.drawLine(cell.east[0], cell.east[1], cell.east[2], cell.east[3]);
                g.drawLine(cell.west[0], cell.west[1], cell.west[2], cell.west[3]);
                g.drawLine(cell.south[0], cell.south[1], cell.south[2], cell.south[3]);
                g.drawLine(cell.north[0], cell.north[1], cell.north[2], cell.north[3]);
            }
        }

        generateMaze(g, 0, 0);
    }

    private void generateMaze(Graphics g, int y, int x) {
        discovered[y][x] = true;

        while (true) {
            Cell current = grid[y][x];

            if (validUp(y) && !discovered[y - 1][x]) {
                removeWall(g, current.north);
                generateMaze(g, y - 1, x);
            }
            if (validDown(y) && !discovered[y + 1][x]) {
                removeWall(g, current.south);
                generateMaze(g, y + 1, x);
            }
            if (validRight(x) && !discovered[y][x + 1]) {
                removeWall(g, current.east);
                generateMaze(g, y, x + 1);
            }
            if (validLeft(x) && !discovered[y][x - 1]) {
                removeWall(g, current.west);
                generateMaze(g, y, x - 1);
            }

            // All neighbors have been visited, break the loop
            break;
        }
    }


    private boolean validLeft(int x) {
        if (x - 1 < 0) return false;
        return true;
    }

    private boolean validRight(int x) {
        if (x + 1 < grid[0].length) return true;
        return false;
    }

    private void removeWall(Graphics g, int[] coordinate) {
        SwingUtilities.invokeLater(() -> {
            g.setColor(Color.BLACK);
            g.drawLine(coordinate[0], coordinate[1], coordinate[2], coordinate[3]);
        });
    }

    private boolean validDown(int y) {
        if (y + 1 < grid.length) return true;
        return false;
    }

    private boolean validUp(int y) {
        if (y - 1 < 0) return false;
        return true;
    }
}

Maze类(应用框架)

public class Maze extends JFrame {
    private int w, h;

    public Maze(int w, int h) throws HeadlessException {
        super("Perfect Maze");
        setResizable(false);
        setBackground(Color.BLACK);

        this.w = w;
        this.h = h;

        add(new GridPanel(w, h));

        setSize(w, h);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }
}

Main类(运行入口)

public class Main {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            new Maze(800, 800);
        });
    }
}
问题原因及修复方案

核心问题分析

  1. 绘制与数据生成严重耦合
    paintComponent是Swing负责界面绘制的核心方法,每次repaint都会触发执行。你在该方法中直接调用generateMaze,导致每次绘制都尝试重新生成迷宫;同时discovered数组初始化后不会重置,第二次绘制时所有单元格都已标记为已访问,无法继续生成。此外,在绘制过程中直接操作Graphics对象修改界面,违背了Swing“数据驱动绘制”的设计原则。

  2. removeWall实现逻辑错误

    • 你用SwingUtilities.invokeLater包裹墙的覆盖操作,但此时传入的Graphics对象已经是旧的上下文——paintComponent执行完成后,这个Graphics会被系统回收,后续在invokeLater中使用它会导致绘制无效。
    • 所谓“拆墙”不应该是用背景色重画,而应该用数据记录哪些墙需要隐藏,绘制时只渲染存在的墙。
  3. 递归回溯算法不完整
    原算法没有随机选择邻居,而是按固定顺序(上、下、右、左)访问,且while循环仅执行一次就break,完全失去了“回溯”的核心逻辑,导致迷宫生成不完整,也无法正确触发拆墙操作。

修复后的关键代码

1. 修改Cell类,添加墙状态记录

private class Cell {
    private int y, x;
    // true表示墙存在,false表示墙已移除
    private boolean southWall = true;
    private boolean northWall = true;
    private boolean westWall = true;
    private boolean eastWall = true;
    private int[] south;
    private int[] north;
    private int[] west;
    private int[] east;

    private Cell(int y, int x) {
        this.y = y;
        this.x = x;
        this.south = new int[4];
        this.north = new int[4];
        this.west = new int[4];
        this.east = new int[4];
    }
}

2. 调整迷宫生成逻辑,只修改数据不操作绘制

把generateMaze移到GridPanel构造方法中,提前生成迷宫数据:

public GridPanel(int w, int h) {
    setLayout(new GridLayout(1, 1));
    this.w = w;
    this.h = h;

    scaleX = (int) (w / Math.sqrt(w));
    scaleY = (int) (h / Math.sqrt(h));

    Vx = w / scaleX;
    Vy = w / scaleY;

    discovered = new boolean[Vy][Vx];
    grid = new Cell[Vy][Vx];
    init(grid);
    // 提前生成迷宫数据
    generateMaze(0, 0);
}

private void generateMaze(int y, int x) {
    discovered[y][x] = true;
    // 随机打乱邻居顺序,实现真正的回溯算法
    List<int[]> neighbors = new ArrayList<>();
    neighbors.add(new int[]{y-1, x, 0}); // 上:当前北墙,邻居南墙
    neighbors.add(new int[]{y+1, x, 1}); // 下:当前南墙,邻居北墙
    neighbors.add(new int[]{y, x+1, 2}); // 右:当前东墙,邻居西墙
    neighbors.add(new int[]{y, x-1, 3}); // 左:当前西墙,邻居东墙
    Collections.shuffle(neighbors);

    for (int[] neighbor : neighbors) {
        int ny = neighbor[0];
        int nx = neighbor[1];
        int wallType = neighbor[2];

        if (ny >= 0 && ny < Vy && nx >=0 && nx < Vx && !discovered[ny][nx]) {
            Cell current = grid[y][x];
            Cell next = grid[ny][nx];
            // 移除当前单元格与邻居之间的墙
            switch (wallType) {
                case 0:
                    current.northWall = false;
                    next.southWall = false;
                    break;
                case 1:
                    current.southWall = false;
                    next.northWall = false;
                    break;
                case 2:
                    current.eastWall = false;
                    next.westWall = false;
                    break;
                case 3:
                    current.westWall = false;
                    next.eastWall = false;
                    break;
            }
            generateMaze(ny, nx);
        }
    }
}

3. 修改paintComponent,根据墙状态绘制

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g); // 必须调用,清空旧绘制内容
    setDoubleBuffered(true);
    // 填充背景为黑色
    g.setColor(Color.BLACK);
    g.fillRect(0, 0, getWidth(), getHeight());
    g.setColor(Color.CYAN);

    for (int y = 30, j = 0; y < h - 60; y += scaleY, j++) {
        for (int x = 30, i = 0; x < w - 60; x += scaleX, i++) {
            Cell cell = grid[j][i];
            // 计算墙的坐标
            cell.east = new int[]{x + scaleX, y, x + scaleX, y + scaleY};
            cell.west = new int[]{x, y, x, y + scaleY};
            cell.north = new int[]{x, y, x + scaleX, y};
            cell.south = new int[]{x, y + scaleY, x + scaleX, y + scaleY};

            // 只绘制存在的墙
            if (cell.eastWall) {
                g.drawLine(cell.east[0], cell.east[1], cell.east[2], cell.east[3]);
            }
            if (cell.westWall) {
                g.drawLine(cell.west[0], cell.west[1], cell.west[2], cell.west[3]);
            }
            if (cell.southWall) {
                g.drawLine(cell.south[0], cell.south[1], cell.south[2], cell.south[3]);
            }
            if (cell.northWall) {
                g.drawLine(cell.north[0], cell.north[1], cell.north[2], cell.north[3]);
            }
        }
    }
}

额外说明

  • 移除了原有的removeWall方法,因为现在通过数据记录墙的状态,不需要再临时覆盖绘制。
  • 随机打乱邻居顺序后,生成的迷宫会更自然,符合递归回溯算法的特性。
  • 调用super.paintComponent(g)是Swing绘制的规范操作,确保每次绘制前清空画布,避免旧内容残留。

内容的提问来源于stack exchange,提问作者Giorgi Chapidze

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 16:28:10