Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Animated Maze Generator</title>
<style>body {background: #111; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; font-family: monospace; color: white;} canvas {border: 2px solid #aaa;}</style>
</head>
<body>
<canvas id="mazeCanvas"></canvas>
<script>
const canvas = document.getElementById('mazeCanvas');
const ctx = canvas.getContext('2d');
// Dimensions (30 cells wide, 20 cells high)
const COLS = 30;
const ROWS = 20;
const CELL_SIZE = 20;
canvas.width = COLS * CELL_SIZE + CELL_SIZE / 2; // Account for outer walls/padding
canvas.height = ROWS * CELL_SIZE + CELL_SIZE / 2;
// State variables
let mazeGrid = Array(ROWS).fill(0).map(() => Array(COLS).fill(false));
let stack = [];
let carvedCount = 0;
let animationFrameId = null;
let isRunning = true;
const COLOR_WALL = '#444';
const COLOR_PATH = '#1a1a1a';
const COLOR_STACK = 'rgba(70, 70, 255, 0.8)'; // Semi-transparent blue for visited path
const COLOR_CURRENT = 'yellow';
function getNeighbors(r, c) {
let neighbors = [];
[[0, -1], [0, 1], [-1, 0], [1, 0]].forEach(([dr, dc]) => {
const nr = r + dr;
const nc = c + dc;
if (nr >= 0 && nr < ROWS && nc >= 0 && nc < COLS && !mazeGrid[nr][nc]) {
neighbors.push({r: nr, c: nc});
}
});
return neighbors;
}
function setVisited(r, c) {
if (r >= 0 && r < ROWS && c >= 0 && c < COLS) {
mazeGrid[r][c] = true;
}
}
// Recursive Backtracker modified to be iterative/yield-like for animation
function carve(r, c) {
if (!isRunning) return Promise.resolve();
stack.push({ r, c });
setVisited(r, c);
carvedCount++;
let current = { r, c };
// Draw the wall disappearing (the actual carving step)
drawCarvingStep(current.r, current.c);
// Find unvisited neighbors
let neighbors = getNeighbors(r, c);
neighbors.sort(() => Math.random() - 0.5); // Random order
for (const neighbor of neighbors) {
if (!isRunning) return Promise.resolve();
// Calculate the wall coordinates between current and neighbor
const dr = current.r === neighbor.r ? 0 : Math.sign(neighbor.r - current.r);
const dc = current.c === neighbor.c ? 0 : Math.sign(neighbor.c - current.c);
// The wall is one cell away from the center of both cells
let wallR = (current.r + neighbor.r) / 2;
let wallC = (current.c + neighbor.c) / 2;
// This function needs to simulate a recursive call that pauses for animation
return new Promise(resolve => {
setTimeout(() => {
if (!isRunning) return resolve();
// Draw the wall removal and path connection
drawWallRemoval(current.r, current.c, neighbor.r, neighbor.c);
carve(neighbor.r, neighbor.c).then(() => {
resolve();
});
}, 5); // Small delay for visibility
});
}
}
// --- Drawing Functions ---
function drawRect(x, y, w, h, color) {
ctx.fillStyle = color;
ctx.fillRect(x + 1, y + 1, w - 1, h - 1); // Subtracting padding/border effect
}
function drawInitialMaze() {
// Draw all cells as solid blocks (walls) initially
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
drawRect(c * CELL_SIZE, r * CELL_SIZE, CELL_SIZE, CELL_SIZE, COLOR_WALL);
}
}
}
function drawCurrentHighlight(r, c) {
// Highlight the current cell (where generation paused)
ctx.fillStyle = 'rgba(255, 255, 0, 0.3)';
const x = c * CELL_SIZE;
const y = r * CELL_SIZE;
ctx.fillRect(x + 1, y + 1, CELL_SIZE - 2, CELL_SIZE - 2);
}
function drawStackPath() {
// Draw all previously visited cells (the stack path)
let pathSurface = document.createElement('canvas');
pathSurface.width = canvas.width;
pathSurface.height = canvas.height;
let pCtx = pathSurface.getContext('2d');
pCtx.fillStyle = COLOR_STACK;
for (let i = 0; i < stack.length - 1; i++) { // Don't draw the current cell as part of 'path' if we are carving it now
const p = stack[i];
pCtx.fillRect(p.c * CELL_SIZE + 1, p.r * CELL_SIZE + 1, CELL_SIZE - 2, CELL_SIZE - 2);
}
ctx.drawImage(pathSurface, 0, 0);
}
function drawCarvingStep(r, c) {
// Draw the current cell as part of the path/visited set
ctx.fillStyle = COLOR_STACK;
const x = c * CELL_SIZE;
const y = r * CELL_SIZE;
ctx.fillRect(x + 1, y + 1, CELL_SIZE - 2, CELL_SIZE - 2);
}
function drawWallRemoval(r1, c1, r2, c2) {
// Calculate the coordinates of the wall between (r1, c1) and (r2, c2)
const pathX = Math.min(c1, c2) * CELL_SIZE + CELL_SIZE / 2;
const pathY = Math.min(r1, r2) * CELL_SIZE + CELL_SIZE / 2;
// If horizontal move (same row): wall is at the edge of the column index change
if (r1 === r2) {
// Wall coordinates are between c1 and c2
const startCol = Math.min(c1, c2);
const endCol = Math.max(c1, c2);
const wallX = (startCol + 1) * CELL_SIZE - CELL_SIZE / 2;
const wallY = r1 * CELL_SIZE + CELL_SIZE/2;
ctx.fillStyle = '#333'; // Fading out the wall
ctx.fillRect(wallX, wallY, (endCol - startCol) * CELL_SIZE, 1);
// If vertical move (same column): wall is at the edge of the row index change
} else {
// Wall coordinates are between r1 and r2
const startRow = Math.min(r1, r2);
const endRow = Math.max(r1, r2);
const wallX = c1 * CELL_SIZE + CELL_SIZE/2;
const wallY = (startRow + 1) * CELL_SIZE - CELL_SIZE / 2;
ctx.fillStyle = '#333'; // Fading out the wall
ctx.fillRect(wallX, wallY, 1, (endRow - startRow) * CELL_SIZE);
}
}
// --- Animation Loop and Control ---
async function animate() {
if (!isRunning || stack.length === 0 && carvedCount > 0) {
clearInterval(animationFrameId);
// Maze Complete Handling
const display = document.getElementById('mazeCanvas');
display.style.transition = 'opacity 1s';
display.style.opacity = '0';
setTimeout(() => {
console.log("Maze complete! Restarting...");
resetState();
animate(); // Loop automatically
display.style.opacity = '1';
}, 1000);
return;
}
// Clear canvas for the frame
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw existing walls (base layer) and paths
drawInitialMaze(); // Redraw initial state
drawStackPath(); // Overwrite visited cells with path color
const r = stack[stack.length - 1].r;
const c = stack[stack.length - 1].c;
// Draw the current cell highlight (before carving happens)
drawCurrentHighlight(r, c);
if (carvedCount < COLS * ROWS) {
await carve(r, c); // This function handles drawing and recursion/delay
}
animationFrameId = requestAnimationFrame(animate);
}
function resetState() {
mazeGrid.fill(false);
stack = [];
carvedCount = 0;
document.getElementById('mazeCanvas').style.opacity = '1';
}
// Start the process immediately
resetState();
animate();
</script>
</body>
</html>
```