2

I am trying to visualize a Maze-Generator and i don't know how i can make a delay everytime a Cell/Node is visited. I have read that you shouldn't use Thread.sleep() in a GUI application because it messes with the event dispatch Thread, so i tried utilizing the Swing.Timer, however this also did'nt work out for me. Everytime i tried it, the program just froze and the Window that popped up was blank.

This is the GUI-Class which i use to visualize each step of the Path-finding-Method:

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class GUI extends JPanel{
    static int frameWidth = 1920;
    static int frameHeight = 1080;
    Timer timer;
    int CELL_SIZE = 30;
    int STROKE_WIDTH = 4;
    int x = 60;
    int y = 30;
    boolean solve = false;
    MazeWilson m;

    public GUI() {
        timer = new Timer(1000,this);
        timer.start();
        m = new MazeWilson(x, y);
        m.generatePath();
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        this.setBackground(Color.WHITE);
        Graphics2D g2 = (Graphics2D) g;
        g2.setStroke(new BasicStroke(STROKE_WIDTH));
        g2.setColor(Color.BLACK);
        for (int i = 0; i < m.height; i++) {
            for (int j = 0; j < m.width; j++) {
                g2.drawRect(10 + CELL_SIZE * j, 10 + CELL_SIZE * i, CELL_SIZE, CELL_SIZE);
            }
        }
        g.setColor(Color.WHITE);
        for (int i = 0; i < m.height; i++) {
            for (int j = 0; j < m.width; j++) {
                if (!m.field[i][j].walls[0]) {
                    g2.drawLine(10 + STROKE_WIDTH + CELL_SIZE * j, 10 + CELL_SIZE * (i + 1), 10 - STROKE_WIDTH + CELL_SIZE * (j + 1), 10 + CELL_SIZE * (i + 1));
                }
                if (!m.field[i][j].walls[1]) {
                    g2.drawLine(10 + CELL_SIZE * j, 10 + STROKE_WIDTH + CELL_SIZE * i, 10 + CELL_SIZE * j, 10 - STROKE_WIDTH + CELL_SIZE * (i + 1));
                }
                if (!m.field[i][j].walls[2]) {
                    g2.drawLine(10 + STROKE_WIDTH + CELL_SIZE * j, 10 + CELL_SIZE * i, 10 - STROKE_WIDTH + CELL_SIZE * (j + 1), 10 + CELL_SIZE * i);
                }
                if (!m.field[i][j].walls[3]) {
                    g2.drawLine(10 + CELL_SIZE * (j + 1), 10 + STROKE_WIDTH + CELL_SIZE * i, 10 + CELL_SIZE * (j + 1), 10 - STROKE_WIDTH + CELL_SIZE * (i + 1));
                }
            }
        }

        if (solve) {
            drawSolution(g2);
        }
    }

    public static void main(String[] args) {
        JFrame f = new JFrame("Maze");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        GUI g = new GUI();
        f.add(g);
        JButton solveMaze = new JButton("Lösen");
        solveMaze.setFont(new Font("Arial", Font.PLAIN, 40));
        solveMaze.setPreferredSize(new Dimension(30, 30));
        solveMaze.setBackground(Color.GREEN);
        solveMaze.setOpaque(true);
        f.add(solveMaze, BorderLayout.SOUTH);
        f.setExtendedState(JFrame.MAXIMIZED_BOTH);
        f.getContentPane().setBackground(Color.BLACK);
        f.setVisible(true);
        f.toFront();
    }

    public void drawSolution(Graphics g) {
        Cell c = m.findPath();
        g.setColor(Color.CYAN);
        for (; c.parent != null; c = c.parent) {
            g.fillRect(10 + STROKE_WIDTH/2 + c.x * CELL_SIZE, 10 + STROKE_WIDTH/2 + c.y * CELL_SIZE, CELL_SIZE - STROKE_WIDTH, CELL_SIZE - STROKE_WIDTH);

            switch (c.parent.y - c.y) {
            case 1: {
                g.fillRect(10 + STROKE_WIDTH/2 + c.x * CELL_SIZE, 10 + STROKE_WIDTH/2 + c.y * CELL_SIZE, CELL_SIZE - STROKE_WIDTH, CELL_SIZE + CELL_SIZE/4 - STROKE_WIDTH);
                break;
            }
            case -1: {
                g.fillRect(10 + STROKE_WIDTH/2 + c.x * CELL_SIZE, 10 + STROKE_WIDTH/2 + c.parent.y * CELL_SIZE, CELL_SIZE - STROKE_WIDTH, CELL_SIZE + CELL_SIZE/4 - STROKE_WIDTH);
                break;
            }
            }
            switch (c.parent.x - c.x ) {

                case 1: {
                    g.fillRect(10 + STROKE_WIDTH/2 + c.x * CELL_SIZE, 10 + STROKE_WIDTH/2 + c.y * CELL_SIZE, CELL_SIZE + CELL_SIZE/4 - STROKE_WIDTH, CELL_SIZE - STROKE_WIDTH);
                    break;
                }
                case -1: {
                    g.fillRect(10 + STROKE_WIDTH/2 + c.parent.x * CELL_SIZE, 10 + STROKE_WIDTH/2 + c.y * CELL_SIZE, CELL_SIZE + CELL_SIZE/4 - STROKE_WIDTH, CELL_SIZE - STROKE_WIDTH);
                    break;
                }
            }
        }
        g.fillRect(10 + STROKE_WIDTH/2 + c.x * CELL_SIZE, 10 + STROKE_WIDTH/2 + c.y * CELL_SIZE, CELL_SIZE - STROKE_WIDTH, CELL_SIZE - STROKE_WIDTH);

    }
}

The generatePath-Method generates the maze and the findPath-Method solves the maze. I want the drawSolution-function to have a delay in each iteration, so that it progressively shows each Cell that is visited during the process.

Salad King
  • 23
  • 4
  • 2
    Where is the implementation of `ActionListener` ?? – Antoniossss Aug 31 '21 at 13:37
  • Use a SwingWorker. Then you can use Thread.sleep() and "publish" the results of each iteration of your max. See: https://stackoverflow.com/a/64196256/131872 for an example of this approach. – camickr Aug 31 '21 at 13:41
  • 1
    [This](https://stackoverflow.com/a/68933401/3992939) answer has a full running example of just that. – c0der Aug 31 '21 at 15:24
  • 1
    Without implementing the `actionPerformed` method, this code shouldn’t even pass the compiler. Besides that, it would be far more readable without those tons of empty lines we have to scroll through. – Holger Aug 31 '21 at 15:29

1 Answers1

3

Swing is a single Thread library. All painting tasks are executed by the EDT.
Running long processes (such path finding) on the EDT keeps it busy, so it does not do update the gui (gui becomes unresponsive, freezes).
Run the long process on a different thread, but remember that all Swing gui updates needs to be done by the EDT.
The different thread is where you can apply a delay for slowing down the process for better visualization.
A good tool to perform such tasks is a SwingWorker.
You can find an example demonstrating the basic structure here.

If you only want to do incremental painting which does not involve long processes you can use a Swing Timer:

import java.awt.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.Timer;

public class GUI extends JPanel{

    private static final int ROWS = 9, COLS = 6, SIZE = 45, BORDER = 2, DELAY = 500;
    private static final Color BOARD_COLOR = Color.BLACK, CELL = Color.YELLOW, PATH = Color.RED;
    private final List<int[]> directions = Arrays.asList(new int[][] {{1,0},{0,1}} ); // move down or right 
    private final Dimension size;
    private final List<Point> path;
    private final Point start, end;
    private Timer timer;
    public GUI() {
        int x = BORDER + COLS*(SIZE + BORDER);
        int y = BORDER + ROWS*(SIZE + BORDER);

        start = new Point(0,0);
        end = new Point(COLS-1, ROWS-1);
        path = new ArrayList<>();
        path.add(start);

        size = new Dimension(x,y);
        this.initBoard();
    }

    @Override
    public Dimension getPreferredSize() {
        return size;
    }
    public void initBoard() {
        JFrame f = new JFrame("Grid By Painting");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLayout(new GridBagLayout());
        f.add(this);
        f.pack();
        f.setVisible(true);
        timer = new Timer(DELAY, e->incrementPath());
        timer.start();
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        int width = getWidth(); int height = getHeight();
        int cellWidth = (width - BORDER) / COLS  - BORDER;
        int cellHeight = (height -BORDER)/ ROWS - BORDER ;
        int pathWidth = cellWidth / 4;
        int pathHeight = cellHeight / 4;

        //draw board
        g.setColor(BOARD_COLOR);
        g.fillRect(0, 0, width, height);

        //draw square cells
        for (int col = 0; col < COLS; col++) {
            for (int row = 0; row < ROWS; row++) {
                int x = BORDER + col*(cellWidth + BORDER);
                int y = BORDER + row*(cellHeight + BORDER);
                g.setColor(CELL);
                g.fillRect(x, y, cellWidth, cellHeight);

                //draw path
                if(! path.contains( new Point(col,row) )) {
                    continue;
                }
                g.setColor(PATH);
                int xPath = x + cellWidth/2 - pathWidth/2;
                int yPath = y + cellHeight/2 - pathHeight/2;
                g.fillOval(xPath, yPath, pathWidth, pathHeight);
            }
        }
    }

    //construct an arbitrary path for demonstration purposes  
    private void incrementPath() {
        //get a random direction 
        Collections.shuffle(directions);
        int[] direction = directions.get(0);

        Point lastPointInPath = path.get(path.size()-1);
        Point newPoint = new Point(lastPointInPath.x + direction[0], lastPointInPath.y + direction[1]);
        if(! isValid(newPoint) || path.contains(newPoint)) return;
        path.add(newPoint);
        repaint();

        if(newPoint.equals(end)){//found target
            timer.stop();
        }
    }

    private boolean isValid(Point newPoint) {
        return newPoint.x >=0 && newPoint.x < COLS
                && newPoint.y >=0 && newPoint.y < ROWS;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(()->new GUI());
    }
}

enter image description here

c0der
  • 18,467
  • 6
  • 33
  • 65