Search code examples
javaswingrecursionpaintcomponent

How to have recursive maze generator draw each step?


I finished writing a program that creates a maze recursively but have not been able to figure out how to have it draw the maze after each step. Any changes to the maze happen before the next recursive call. The maze is prerendered onto a JPanel as a grid of squares, and I have attempted to have the program render each step, by using JPanel.repaint before the next recursive call (I have comments within my generate method where I have previously tried to repaint. No matter what I try, the maze just simply renders the finished product (maze with all of the paths, walls, etc) all at once at the end. Attached is my recursive generate method.

private static boolean generate(int x, int y) {
    System.out.println("xcord: " + x + ", ycord: " + y);
    //panel.repaint(); when i have repaint here, it renders the entire maze at the end
    a[x][y].visited = true;
    if (unvisitedCells == 0) { // if you have visited all of the cells, maze is done generating
        System.out.println("done");
        return true;
    }
    int movesTried = 0; // keeps track of which directions have been tried
    int currentMove = (int) (Math.random() * 4); // try moving a random direction first (0 = north, 1 = east, etc.)
    while (movesTried < 4) { // continue as long as all four moves havent been tried
        // north move
        if (a[x][y].northCell != null && a[x][y].northCell.visited != true && currentMove == 0) {
            a[x][y].northCell.visited = true;
            a[x][y].northWall = false;
            a[x][y].northCell.southWall = false;
            unvisitedCells -= 1;
            // tried repainting here, but had no effect
            if (generate(x, y - 1)) {
                return true; // move successful
            }
        }
        // east move
        if (a[x][y].eastCell != null && a[x][y].eastCell.visited != true && currentMove == 1) {
            a[x][y].eastCell.visited = true;
            a[x][y].eastWall = false;
            a[x][y].eastCell.westWall = false;
            unvisitedCells -= 1;
            // tried repainting here, but had no effect
            if (generate(x + 1, y)) {
                return true; // move successful
            }
        }
        // south move
        if (a[x][y].southCell != null && a[x][y].southCell.visited != true && currentMove == 2) {
            a[x][y].southCell.visited = true;
            a[x][y].southWall = false;
            a[x][y].southCell.northWall = false;
            unvisitedCells -= 1;
            // tried repainting here, but had no effect
            if (generate(x, y + 1)) {
                return true; // move successful
            }
        }
        // west move
        if (a[x][y].westCell != null && a[x][y].westCell.visited != true && currentMove == 3) {
            a[x][y].westCell.visited = true;
            a[x][y].westWall = false;
            a[x][y].westCell.eastWall = false;
            unvisitedCells -= 1;
            // tried repainting here, but had no effect
            if (generate(x - 1, y)) {
                return true; // move successful
            }
        }
        movesTried++; // another move has been tried
        if (currentMove == 3 && movesTried < 4) {
            currentMove = 0; // wraps back to north move if maze started at a move greater than 0, and you
                                // have more moves to try
        } else {
            currentMove++;
        }
    }
    // at this point, all 4 moves have been tried, and there are no possible moves
    // from the current maze cell
    return false;
}

Each cell is rendered individually onto a JPanel, using information kept in a MazeCell class.

public class MazeCell {

public boolean northWall = true;
public boolean eastWall = true;
public boolean southWall = true;
public boolean westWall = true;

public MazeCell northCell = null;
public MazeCell eastCell = null;
public MazeCell southCell = null;
public MazeCell westCell = null;

public boolean visited = false;

}

Here I have set up a JPanel which draws each cell individually, based on whether there is a wall in each of 4 directions.

panel = new JPanel() {

        private static final long serialVersionUID = 1L;

        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            a[0][0].northWall = false;
            a[mazeSize - 1][mazeSize - 1].southWall = false;
            for (int y = 0; y < mazeSize; y++) {
                for (int x = 0; x < mazeSize; x++) {
                    if (a[x][y].northWall) {
                        g.drawLine(100 + (x * 25), 100 + (y * 25), 100 + (x * 25) + 25, 100 + (y * 25));
                    }
                    if (a[x][y].eastWall) {
                        g.drawLine(100 + (x * 25) + 25, 100 + (y * 25), 100 + (x * 25) + 25, 100 + (y * 25) + 25);
                    }
                    if (a[x][y].southWall) {
                        g.drawLine(100 + (x * 25), 100 + (y * 25) + 25, 100 + (x * 25) + 25, 100 + (y * 25) + 25);
                    }
                    if (a[x][y].westWall) {
                        g.drawLine(100 + (x * 25), 100 + (y * 25), 100 + (x * 25), 100 + (y * 25) + 25);
                    }
                }
            }
        }
    };

Solution

  • Warp the long process (generate(int x, int y)) with a SwingWorker, and let it update the GUI. Here is an example:

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Component;
    import java.awt.GridLayout;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.SwingWorker;
    
    public class RecursiveGuiUpdate extends JFrame {
    
        private final int SIZE = 4;
        JLabel[][] grid = new JLabel[SIZE][SIZE];
    
        RecursiveGuiUpdate()    {
    
            setDefaultCloseOperation(DISPOSE_ON_CLOSE);
            add(getGrid(), BorderLayout.NORTH);
            JButton paint = new JButton("Paint");
            paint.addActionListener(a -> updateGui());
            add(paint, BorderLayout.SOUTH);
            pack();
            setVisible(true);
        }
    
        private void updateGui() {
            new Task().execute();
        }
    
        private Component getGrid() {
            JPanel panel = new JPanel(new GridLayout(SIZE, SIZE));
            for(int i=0; i<=(SIZE-1); i++) {
                for(int j=0; j<=(SIZE-1); j++) {
                    JLabel l = new JLabel(i+"-"+j, JLabel.CENTER);
                    l.setOpaque(true);
                    panel.add(l);
                    grid[i][j] = l;
                }
            }
            return panel;
        }
    
        class Task extends SwingWorker<Void,Void> {
    
            @Override
            public Void doInBackground() {
                updateGui(0, 0);
                return null;
            }
    
            @Override
            public void done() { }
    
            //recursively set labels background
            void updateGui(int i, int j) {
    
                System.out.println(i+"-"+j);
                //set random, background color
                grid[i][j].setBackground(new Color((int)(Math.random() * 0x1000000)));
    
                try {
                    Thread.sleep(500); //simulate long process
                } catch (InterruptedException ex) { ex.printStackTrace();}
    
                if((i==(SIZE-1))&&(j==(SIZE-1))) { return; }
    
                if(i<(SIZE-1)) {
                    updateGui(++i, j);
                }else {
                    i=0;
                    updateGui(i, ++j);
                }
            }
        }
    
        public static void main(String[] args)  {
            new RecursiveGuiUpdate();
        }
    }
    

    If needed, you can override process(java.util.List) to get and process interim results.
    If you need help with adapting such solution to your code, please post another question with mcve.