├── 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 |
--------------------------------------------------------------------------------