I'm sketching the grid on the user interface using the width and height that I specified when I created the GridPanel, accordingly. For representational purposes, I am laying the grid starting at 30, 30, and ending at x - 60, y - 60. Depending on the grid dimensions, dynamic shrink and grow are implemented using scaleX and scaleY.
The image below displays the internals of the cell object.
GenerateMaze is a recursive backtracking algorithm used to generate perfect mazes. (Surely not complete, but I still should be able to see the result.)
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;
}
}
° Application Frame:
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);
}
}
° Runner:
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new Maze(800, 800);
});
}
}
Given that the user interface appears and the application closes successfully, I believe the removeWall function is the cause of the issue.
It did not work when I attempted to add method: repaint() at the end of the method.
Introduction
I redid your drawing code and came up with the following GUI. This is your maze grid with no walls removed.
If you expand the GUI by clicking the box in the upper right, the grid resizes. It's hard to see because Stack Overflow scales the image.
Explanation
Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Learning Swing with the NetBeans IDE section. Pay particular attention to the Performing Custom Painting section.
When I create a Swing GUI, I use the model-view-controller (MVC) pattern. This pattern allows me to separate my concerns and focus on one small part of the Java application at a time.
A Swing model is made up of one or more plain Java getter/setter classes.
A Swing view is made up of one
JFrame
and as manyJPanels
orJDialogs
as you need.Each Action or Listener is a Swing controller. There's usually not one controller to "rule them all".
Model
I rewrote your Cell class. You don't need the coordinate because the cell position in the grid provides the coordinate. The coordinate is the x, y position of the cell in the grid, starting with 0,0 and going up to 39,39.
Your code to create the maze will remove the walls of a cell by setting the appropriate
boolean
tofalse
; The Swing view code will paint the status of the grid. Period. Nothing else.Here's the
PerfectMazeModel
class.Here we create the grid. You don't have to make the grid square.
View
I use a
JFrame
and create a drawingJPanel
. The drawing code is a bit complicated.We get the cell dimension based on the current width and height of the drawing
JPanel
.x and y are the logical coordinates of the grid cell. dx and dy are the upper left corner of the grid cell. cx and cy are the upper right and lower left points of the grid cell. We draw the line based on the boolean flag.
The cell/grid model isn't perfect because the east line of one cell is the same line as the west line of the adjacent cell. The same holds true for the north and south lines of a cell.
Controller
Since I'm just showing the model and drawing code, there are no controllers. I've left the addition of the code to remove lines for you.
Code
Here's the complete runnable code. I made the additional classes inner classes so I could post this code as one block.