Skip to content

Commit

Permalink
Added multiple maze generation algorithms
Browse files Browse the repository at this point in the history
- added growing tree algorithm
- added binary tree generator
- added sidewinder algorithm
- removed mazeGenerator.js
  • Loading branch information
tobinatore committed Sep 4, 2020
1 parent c6aa1bc commit a8beede
Show file tree
Hide file tree
Showing 5 changed files with 403 additions and 14 deletions.
68 changes: 68 additions & 0 deletions assets/js/Maze/binary_tree_generator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/** Class for generating a maze using the binary tree generator.
* The maze generated by this class can be represented as a binary tree.
*/
class BinaryTreeGenerator {
constructor() {}

/**
* Fills the grid with walls as preparation for the algorithm.
*/
fillGrid() {
for (let y = 0; y < gridData.length; y++) {
for (let x = 0; x < gridData[y].length; x++) {
gridData[y][x].type = "wall";
d3.select("#node-" + y + "-" + x)
.transition()
.duration(500)
.attr("fill", "#171717");
}
}
}

/**
* Function for running the binary tree generator.
* Loops over the whole grid and generates the maze row by row.
*/
async startMaze() {
// fill the grid so the algorithm can carve the paths
this.fillGrid();

for (let y = 1; y < gridData.length - 1; y += 2) {
for (let x = 1; x < gridData[y].length - 1; x += 2) {
let dirs = [];

// adding the directions the algorithm can carve
// the path in to an array.
if (y > 1) {
// carve upwards
dirs.push({ y: y - 1, x });
}
if (x < gridData[y].length - 2) {
// carve right
dirs.push({ y: y, x: x + 1 });
}

// set the current cell to type "floor"
gridData[y][x].type = "floor";
await colorBlock("#node-" + y + "-" + x, "#FFFFFF", 100, 10);

// The last cell in the first row has no direction to carve in.
// It is the only cell where the length of the dirs array is 0.
if (dirs.length != 0) {
// randomly choose a direction
let taken = dirs.length > 1 ? Math.round(Math.random()) : 0;
var direction = dirs[taken];

// update the cell in the direction that was chosen earlier
gridData[direction.y][direction.x].type = "floor";
await colorBlock(
"#node-" + direction.y + "-" + direction.x,
"#FFFFFF",
100,
10
);
}
}
}
}
}
121 changes: 121 additions & 0 deletions assets/js/Maze/growing_tree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/** Class for generating a maze using the growing tree algorithm. */
class GrowingTree {
constructor() {}

/**
* Generates the maze using the growing tree algorithm.
* @param {Object} cell - The starting cell, usually randomly chosen.
*/
async startMaze(cell) {
// filling the grid so the algorithm can carve out ways
this.fillGrid();

// declaring a list of unvisited cells
// and a list of possible directions
let cells = [];
let dirs = [
{ dy: 0, dx: 2 },
{ dy: 2, dx: 0 },
{ dy: -2, dx: 0 },
{ dy: 0, dx: -2 },
];

let width = gridData[0].length;
let height = gridData.length;

cells.push(cell);

// running as long as there are unvisited cells left
while (cells.length != 0) {
// shuffling the list of directions
dirs = this.shuffle(dirs);

let index = Math.round(Math.random() * (cells.length - 1));

let x = cells[index].x;
let y = cells[index].y;

gridData[y][x] = "floor";
await colorBlock("#node-" + y + "-" + x, "#fff", 100, 10);

// looping over the list of directions to check for unvisited neighbours
for (const direction in dirs) {
let dx = dirs[direction].dx;
let dy = dirs[direction].dy;

let nx = x + dx;
let ny = y + dy;

// check if the cell in the current direction
// is within bounds and unvisited
if (
nx > 0 &&
ny > 0 &&
nx < width &&
ny < height &&
gridData[ny][nx].type == "wall"
) {
// set the cell to type "floor"
// color it and add it to the list
// of cells to check
gridData[ny][nx] = "floor";
await colorBlock("#node-" + ny + "-" + nx, "#fff", 10, 0);

// updating the cell between the other cells
if (dx != 0) {
gridData[y][x + dx / 2] = "floor";
await colorBlock(
"#node-" + y + "-" + (x + dx / 2),
"#fff",
100,
10
);
} else {
gridData[y + dy / 2][x] = "floor";
await colorBlock("#node-" + (y + dy / 2) + "-" + x, "#fff", 10, 0);
}

cells.push({ x: nx, y: ny });
index = undefined;
break;
}
}
// there were nor unvisited neighbours
// -> delete cell from the list
if (index != undefined) {
cells.splice(index, 1);
}
}
}

/**
* Fills the grid with walls as preparation for the algorithm.
*/
fillGrid() {
for (let y = 0; y < gridData.length; y++) {
for (let x = 0; x < gridData[y].length; x++) {
gridData[y][x].type = "wall";
d3.select("#node-" + y + "-" + x)
.transition()
.duration(500)
.attr("fill", "#171717");
}
}
}

/**
* Fisher-Yates shuffle for shuffling the list of neighbouring cells.
* @param {Object[]} array - The list of neighbouring cells
* @returns {Object[]} - The shuffled list
*/
shuffle(array) {
var j, x, i;
for (i = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = array[i];
array[i] = array[j];
array[j] = x;
}
return array;
}
}
50 changes: 49 additions & 1 deletion assets/js/mazeGenerator.js → assets/js/Maze/recursive_dfs.js
Original file line number Diff line number Diff line change
@@ -1,69 +1,105 @@
/** Class for generating a maze using recursive DFS */
class RecursiveDFS {
constructor() {}

/**
* Fills the whole grid with wall tiles and caals the
* function which generates the maze.
* @param {Object} cell - X and Y coordinates of the randomly chosen starting cell
*/
async startMaze(cell) {
this.fillGrid();
await this.generateMaze(cell);
}

/**
* Generates the maze by recursively calling itself
* and carving out a random path.
* @param {Object} cell - X and Y coordinates of the randomly chosen starting cell
*/
async generateMaze(cell) {
var x = cell.x;
var y = cell.y;

// Setting the starting cell to type 'floor'
gridData[y][x].type = "floor";
await colorBlock("#node-" + y + "-" + x, "#FFF", 500);
await colorBlock("#node-" + y + "-" + x, "#FFF", 100, 10);

// getting the neighbouring cells in random order
var neighbours = this.shuffle(this.getNeighbours(cell));

// looping through the list of neighbours and recursively calling
// this function to build a path through the grid
for (const nb in neighbours) {
if (this.adjacentRooms(neighbours[nb]) <= 2) {
await this.generateMaze(neighbours[nb]);
}
}
}

/**
* Returns the number of cells adjacent to the input cell which have the type 'floor'.
* @param {Object} cell - X and Y coordinates of the cell whose neighbours we want
* @returns {number} - Number of cells with type 'floor' adjacent to current cell
*/
adjacentRooms(cell) {
var adj = 0;
var x = cell.x;
var y = cell.y;

if (x - 1 >= 0 && gridData[y][x - 1].type == "floor") {
// left
adj++;
}
if (x - 1 >= 0 && y - 1 >= 0 && gridData[y - 1][x - 1].type == "floor") {
// top-left
adj++;
}
if (
x - 1 >= 0 &&
y + 1 < gridData.length &&
gridData[y + 1][x - 1].type == "floor"
) {
// bottom-left
adj++;
}
if (
y - 1 >= 0 &&
x + 1 < gridData[0].length &&
gridData[y - 1][x + 1].type == "floor"
) {
// top-right
adj++;
}
if (
y + 1 < gridData.length &&
x + 1 < gridData[0].length &&
gridData[y + 1][x + 1].type == "floor"
) {
// bottom-right
adj++;
}
if (x + 1 < gridData[0].length && gridData[y][x + 1].type == "floor") {
// right
adj++;
}
if (y - 1 >= 0 && gridData[y - 1][x].type == "floor") {
//top
adj++;
}
if (y + 1 < gridData.length && gridData[y + 1][x].type == "floor") {
//bottom
adj++;
}

return adj;
}

/**
* Fisher-Yates shuffle for shuffling the list of neighbouring cells.
* @param {Object[]} array - The list of neighbouring cells
* @returns {Object[]} - The shuffled list
*/
shuffle(array) {
var j, x, i;
for (i = array.length - 1; i > 0; i--) {
Expand All @@ -75,6 +111,11 @@ class RecursiveDFS {
return array;
}

/**
* Returns all cells adjacent to the currently examined cell.
* @param {Object} cell - The node for which we want the neighbours
* @returns {Object[]} -The list of adjacent cells
*/
getNeighbours(cell) {
var x = cell.x;
var y = cell.y;
Expand All @@ -83,24 +124,31 @@ class RecursiveDFS {

if (x - 1 >= 1 && gridData[y][x - 1]) {
if (gridData[y][x - 1].type == "wall")
// left
nbs.push({ x: gridData[y][x - 1].col, y: gridData[y][x - 1].row });
}
if (x + 1 < gridData[0].length - 1 && gridData[y][x + 1]) {
if (gridData[y][x + 1].type == "wall")
//right
nbs.push({ x: gridData[y][x + 1].col, y: gridData[y][x + 1].row });
}
if (y - 1 >= 1 && gridData[y - 1][x]) {
if (gridData[y - 1][x].type == "wall")
//top
nbs.push({ x: gridData[y - 1][x].col, y: gridData[y - 1][x].row });
}
if (y + 1 < gridData.length - 1 && gridData[y + 1][x]) {
if (gridData[y + 1][x].type == "wall")
//bottom
nbs.push({ x: gridData[y + 1][x].col, y: gridData[y + 1][x].row });
}

return nbs;
}

/**
* Fills the grid with walls as preparation for the recursive DFS.
*/
fillGrid() {
for (let y = 0; y < gridData.length; y++) {
for (let x = 0; x < gridData[y].length; x++) {
Expand Down
Loading

0 comments on commit a8beede

Please sign in to comment.