Intro
Still working on PathFinding.java. This time I concentrate on three private methods of GridModel that are responsible for generating random perfect mazes. A maze is perfect if for each two cells in the maze there is only and exactly one route between them.
How it looks like:
A random perfect maze in a 2D grid graph. Also, I have a short animation: https://youtu.be/IFwTL4puA9c?si=20r0yoliPI73XubY
Code
private void drawViaDFS(Random rnd) {
int rows = height;
int cols = width;
if (height % 2 == 0) {
rows--;
}
if (width % 2 == 0) {
cols--;
}
int roomCols = (cols - 1) / 2; // Number of "rooms" horizontally.
int roomRows = (rows - 1) / 2; // Number of "rooms" vertically.
boolean[][] visited = new boolean[roomRows]
[roomCols];
// Pick a random room (mapped to grid coords: rr -> 2 * rr + 1):
int rr = rnd.nextInt(roomRows);
int cc = rnd.nextInt(roomCols);
// Carve the inital room from its wall:
carveCell(cc, rr);
// Initialize DFS stack:
Deque<int[]> stack = new ArrayDeque<>();
visited[rr][cc] = true;
stack.push(new int[]{ cc, rr });
// 4-neighbor moves in room space
int[] deltaRowOffsets = { -1, 0, 1, 0 };
int[] deltaColOffsets = { 0, 1, 0, -1 };
int[] directionIndices = { 0, 1, 2, 3 };
while (!stack.isEmpty()) {
int[] cur = stack.peek();
int c0 = cur[0];
int r0 = cur[1];
// Fisher–Yates shuffle:
for (int i = 3; i > 0; i--) {
int j = rnd.nextInt(i + 1);
int t = directionIndices[i];
directionIndices[i] = directionIndices[j];
directionIndices[j] = t;
}
boolean moved = false;
for (int directionIndex : directionIndices) {
int r1 = r0 + deltaRowOffsets[directionIndex];
int c1 = c0 + deltaColOffsets[directionIndex];
if (0 <= r1
&& r1 < roomRows
&& 0 <= c1
&& c1 < roomCols
&& !visited[r1][c1]) {
// Carve passage between rooms (r0, c0) and (r1, c1):
carveBetween(c0,
r0,
c1,
r1);
carveCell(c1, r1);
visited[r1][c1] = true;
stack.push(new int[]{ c1, r1 });
moved = true;
break;
}
}
if (!moved) {
stack.pop();
}
}
}
private void carveCell(int roomX, int roomY) {
int x = 2 * roomX + 1;
int y = 2 * roomY + 1;
setCellType(x, y, CellType.FREE);
}
private void carveBetween(int roomX0,
int roomY0,
int roomX1,
int roomY1) {
int cellX0 = 2 * roomX0 + 1;
int cellY0 = 2 * roomY0 + 1;
int cellX1 = 2 * roomX1 + 1;
int cellY1 = 2 * roomY1 + 1;
int x = (cellX0 + cellX1) / 2;
int y = (cellY0 + cellY1) / 2;
setCellType(x, y, CellType.FREE);
}
Critique request
My primary concern this time is the code readability and variable naming.
-
2\$\begingroup\$ You're using a two element array to represent a row-column coordinate. Don't be lazy, just create a record for that. You can reuse that in your Cell class instead of storing x and y (and there's an inconsistency, use x,y or row,col everywhere, don't mix). \$\endgroup\$TorbenPutkonen– TorbenPutkonen2025年09月15日 11:17:57 +00:00Commented Sep 15 at 11:17
-
\$\begingroup\$ @TorbenPutkonen Yes, Sir! \$\endgroup\$coderodde– coderodde2025年09月15日 13:51:18 +00:00Commented Sep 15 at 13:51
-
2\$\begingroup\$ Not a review, but the maze demonstrated in the screenshot is not a perfect maze, ~4 tiles down-left of the red tile there is a cycle of floor tiles, this allows multiple routes between pairs of tiles (it's also in a completely isolated section of maze, meaning there are several pairs of tiles with no route between them). \$\endgroup\$Nick is tired– Nick is tired2025年09月16日 01:54:07 +00:00Commented Sep 16 at 1:54