├── README.md ├── package.json ├── index.html ├── cell.js └── main.js /README.md: -------------------------------------------------------------------------------- 1 | # Maze generation visualization 2 | in vanilla JavaScript 3 | 4 | 5 | ## Try 6 | https://pakastin.github.io/maze 7 | 8 | ## Algorithm 9 | https://en.wikipedia.org/wiki/Maze_generation_algorithm#Randomized_depth-first_search 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maze", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Maze 7 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /cell.js: -------------------------------------------------------------------------------- 1 | const posDiff = { 2 | top: [0, -1], 3 | left: [-1, 0], 4 | right: [1, 0], 5 | bottom: [0, 1] 6 | }; 7 | 8 | const posFrom = { 9 | top: 'bottom', 10 | left: 'right', 11 | right: 'left', 12 | bottom: 'top' 13 | }; 14 | 15 | export default class Cell { 16 | constructor (maze, x, y) { 17 | this.maze = maze; 18 | this.x = x; 19 | this.y = y; 20 | this.visited = false; 21 | this.walls = { 22 | top: true, 23 | left: true, 24 | right: true, 25 | bottom: true 26 | }; 27 | } 28 | 29 | visit (pos) { 30 | this.walls[pos] = false; 31 | this.neighbor(pos).walls[posFrom[pos]] = false; 32 | this.updateBorders(); 33 | this.neighbor(pos).updateBorders(); 34 | } 35 | 36 | updateBorders () { 37 | for (const pos in this.walls) { 38 | if (this.walls[pos]) { 39 | this.$td.classList.add(pos); 40 | } else { 41 | this.$td.classList.remove(pos); 42 | } 43 | } 44 | } 45 | 46 | get neighbors () { 47 | return ['top', 'left', 'right', 'bottom'].map(pos => ({ 48 | pos, 49 | cell: this.neighbor(pos) 50 | })).filter(neighbor => neighbor.cell); 51 | } 52 | 53 | get unvisitedNeighbors () { 54 | return this.neighbors.filter(neighbor => !neighbor.cell.visited); 55 | } 56 | 57 | neighbor (pos) { 58 | const { maze } = this; 59 | const [xDiff, yDiff] = posDiff[pos]; 60 | const x = this.x + xDiff; 61 | const y = this.y + yDiff; 62 | return maze[y] && maze[y][x]; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import Cell from './cell.js'; 2 | 3 | const GRID_WIDTH = 48; 4 | const GRID_HEIGHT = 32; 5 | 6 | // Create maze grid 7 | 8 | const maze = []; 9 | 10 | for (let y = 0; y < GRID_HEIGHT; y++) { 11 | maze[y] = []; 12 | 13 | for (let x = 0; x < GRID_WIDTH; x++) { 14 | maze[y][x] = new Cell(maze, x, y); 15 | 16 | if (x === 0 && y === 0) { 17 | maze[y][x].walls.top = false; 18 | } else if (x === GRID_WIDTH - 1 && y === GRID_HEIGHT - 1) { 19 | maze[y][x].walls.bottom = false; 20 | } 21 | } 22 | } 23 | 24 | // Create maze DOM table 25 | 26 | const $table = document.createElement('table'); 27 | 28 | for (let y = 0; y < GRID_HEIGHT; y++) { 29 | const $tr = document.createElement('tr'); 30 | 31 | for (let x = 0; x < GRID_WIDTH; x++) { 32 | const $td = document.createElement('td'); 33 | maze[y][x].$td = $td; 34 | 35 | maze[y][x].updateBorders(); 36 | 37 | $tr.appendChild($td); 38 | } 39 | $table.appendChild($tr); 40 | } 41 | 42 | document.body.appendChild($table); 43 | 44 | // Generate maze 45 | 46 | const stack = []; 47 | 48 | maze[0][0].visited = true; 49 | 50 | stack.push(maze[0][0]); 51 | 52 | (async () => { 53 | while (stack.length) { 54 | const current = stack.pop(); 55 | current.$td.classList.add('black'); 56 | 57 | if (current.unvisitedNeighbors.length) { 58 | const visitIndex = Math.random() * current.unvisitedNeighbors.length | 0; 59 | const { pos, cell: neighbor } = current.unvisitedNeighbors[visitIndex]; 60 | 61 | current.visit(pos); 62 | neighbor.visited = true; 63 | 64 | if (current.unvisitedNeighbors.length) { 65 | stack.push(current); 66 | } else { 67 | current.$td.classList.add('ready'); 68 | } 69 | if (neighbor.unvisitedNeighbors.length) { 70 | stack.push(neighbor); 71 | } else { 72 | neighbor.$td.classList.add('ready'); 73 | } 74 | } else { 75 | current.$td.classList.add('ready'); 76 | } 77 | await new Promise(resolve => requestAnimationFrame(resolve)); 78 | current.$td.classList.remove('black'); 79 | } 80 | })(); 81 | --------------------------------------------------------------------------------