DEV Community

Cover image for How to Create Randomly Generated Mazes in JavaScript Using Recursive Backtracking
HexShift
HexShift

Posted on

How to Create Randomly Generated Mazes in JavaScript Using Recursive Backtracking

Procedural generation is a fascinating aspect of programming that can add infinite variety to games and applications. One popular algorithm for procedural maze generation is recursive backtracking. In this article, we’ll walk through building a maze generator in JavaScript using this technique and rendering it with HTML5 canvas.

1. Maze Grid Setup

We'll represent the maze as a 2D grid of cells. Each cell tracks whether its walls (top, right, bottom, left) exist, and whether it has been visited.

const cols = 20;
const rows = 20;
const grid = [];
let current;

class Cell {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.walls = [true, true, true, true]; // top, right, bottom, left
    this.visited = false;
  }

  index(x, y) {
    if (x < 0 || y < 0 || x > cols - 1 || y > rows - 1) return -1;
    return x + y * cols;
  }

  checkNeighbors() {
    const neighbors = [];

    const top = grid[this.index(this.x, this.y - 1)];
    const right = grid[this.index(this.x + 1, this.y)];
    const bottom = grid[this.index(this.x, this.y + 1)];
    const left = grid[this.index(this.x - 1, this.y)];

    [top, right, bottom, left].forEach(cell => {
      if (cell && !cell.visited) neighbors.push(cell);
    });

    if (neighbors.length > 0) {
      const r = Math.floor(Math.random() * neighbors.length);
      return neighbors[r];
    }

    return undefined;
  }
}

2. Canvas Setup

Use an HTML5 canvas to draw the grid and render the maze generation process.

const canvas = document.getElementById("maze");
const ctx = canvas.getContext("2d");
const cellSize = 25;

canvas.width = cols * cellSize;
canvas.height = rows * cellSize;

function drawCell(cell) {
  const x = cell.x * cellSize;
  const y = cell.y * cellSize;

  ctx.strokeStyle = "black";
  ctx.lineWidth = 2;

  if (cell.walls[0]) {
    ctx.beginPath();
    ctx.moveTo(x, y);
    ctx.lineTo(x + cellSize, y);
    ctx.stroke();
  }
  if (cell.walls[1]) {
    ctx.beginPath();
    ctx.moveTo(x + cellSize, y);
    ctx.lineTo(x + cellSize, y + cellSize);
    ctx.stroke();
  }
  if (cell.walls[2]) {
    ctx.beginPath();
    ctx.moveTo(x + cellSize, y + cellSize);
    ctx.lineTo(x, y + cellSize);
    ctx.stroke();
  }
  if (cell.walls[3]) {
    ctx.beginPath();
    ctx.moveTo(x, y + cellSize);
    ctx.lineTo(x, y);
    ctx.stroke();
  }

  if (cell.visited) {
    ctx.fillStyle = "#eee";
    ctx.fillRect(x, y, cellSize, cellSize);
  }
}

3. Maze Generation Logic

Implement recursive backtracking with a stack to navigate through the grid, carving a path and removing walls between cells.

function removeWalls(a, b) {
  const x = a.x - b.x;
  const y = a.y - b.y;

  if (x === 1) {
    a.walls[3] = false;
    b.walls[1] = false;
  } else if (x === -1) {
    a.walls[1] = false;
    b.walls[3] = false;
  }

  if (y === 1) {
    a.walls[0] = false;
    b.walls[2] = false;
  } else if (y === -1) {
    a.walls[2] = false;
    b.walls[0] = false;
  }
}

for (let y = 0; y < rows; y++) {
  for (let x = 0; x < cols; x++) {
    grid.push(new Cell(x, y));
  }
}

current = grid[0];
const stack = [];

function step() {
  current.visited = true;
  const next = current.checkNeighbors();

  if (next) {
    next.visited = true;
    stack.push(current);
    removeWalls(current, next);
    current = next;
  } else if (stack.length > 0) {
    current = stack.pop();
  }
}

function draw() {
  requestAnimationFrame(draw);
  step();

  ctx.clearRect(0, 0, canvas.width, canvas.height);
  grid.forEach(drawCell);
}

draw();

4. Conclusion

This implementation provides a simple yet powerful maze generator using JavaScript and the recursive backtracking algorithm. From here, you can experiment with different algorithms (like Prim’s or Kruskal’s), add player movement, or generate larger/dynamic mazes.

If you enjoyed this post, consider supporting my work: buymeacoffee.com/hexshift

Top comments (0)