1let cells;
2let maze_created = false;
3let won = false;
4let answer_stack = [];
5let answer_found = false;
6let cheatcodes = [];
7const cheatkeys = ["m", "i", "n", "h", "o"];
8let cheatTimeout;
9let isCheatCodeActivated = false;
10
11const rows = 15;
12const cols = 15;
13const visited = new Set();
14const tooltip = document.getElementById("tooltip");
15const w = document.querySelector("#w");
16const s = document.querySelector("#s");
17const a = document.querySelector("#a");
18const d = document.querySelector("#d");
19
20function getRandomUnVisitedNeighbour(current) {
21 const neighbors = [];
22
23 const row = Math.floor(current / cols);
24 const col = current % cols;
25
26 if (row > 0) {
27 neighbors.push(current - cols);
28 }
29
30 if (row < rows - 1) {
31 neighbors.push(current + cols);
32 }
33
34 if (col > 0) {
35 neighbors.push(current - 1);
36 }
37
38 if (col < cols - 1) {
39 neighbors.push(current + 1);
40 }
41
42 const unvisited = neighbors.filter(neighbour => !visited.has(neighbour));
43 if (unvisited.length === 0) {
44 return null;
45 }
46 const randomNeighbour = unvisited[Math.floor(Math.random() * unvisited.length)];
47 return randomNeighbour;
48}
49
50function removeWall(current, next) {
51 /*
52 Explanation of the following code:
53 If the direction is 1, it means that the random neighbour is to the right of the current cell
54 If the direction is -1, it means that the random neighbour is to the left of the current cell
55 If the direction is cols, it means that the random neighbour is below the current cell
56 If the direction is -cols, it means that the random neighbour is above the current cell
57 */
58 const direction = next - current;
59 if (direction === -cols) {
60 cells[current].style.borderTop = "none";
61 cells[next].style.borderBottom = "none";
62 }
63 if (direction === cols) {
64 cells[current].style.borderBottom = "none";
65 cells[next].style.borderTop = "none";
66 }
67 if (direction === -1) {
68 cells[current].style.borderLeft = "none";
69 cells[next].style.borderRight = "none";
70 }
71 if (direction === 1) {
72 cells[current].style.borderRight = "none";
73 cells[next].style.borderLeft = "none";
74 }
75}
76
77async function sleep(ms) {
78 return new Promise(resolve => setTimeout(resolve, ms));
79}
80
81async function dfs(maze, start) {
82 const stack = [start];
83 answer_stack = [start];
84 while (stack.length > 0) {
85 const current = stack.pop();
86 if (current === rows * cols - 1) {
87 answer_found = true;
88 } else if (!answer_found) {
89 answer_stack.pop();
90 }
91 maze.children[current].classList.add("current-gen");
92 visited.add(current);
93 const next = getRandomUnVisitedNeighbour(current);
94 if (next) {
95 removeWall(current, next);
96 stack.push(current);
97 stack.push(next);
98 if (!answer_found) {
99 answer_stack.push(current);
100 answer_stack.push(next);
101 }
102 }
103 await sleep(10);
104 maze.children[current].classList.remove("current-gen");
105 }
106 maze.children[start].classList.add("current");
107 maze_created = true;
108 tooltip.innerHTML = "Use W A S D or arrow keys to move";
109}
110
111function lightUpButtons(btn) {
112 switch (btn) {
113 case "w":
114 w.classList.add("lit");
115 setTimeout(() => { w.classList.remove("lit"); }, 100);
116 break;
117 case "s":
118 s.classList.add("lit");
119 setTimeout(() => { s.classList.remove("lit"); }, 100);
120 break;
121 case "a":
122 a.classList.add("lit");
123 setTimeout(() => { a.classList.remove("lit"); }, 100);
124 break;
125 case "d":
126 d.classList.add("lit");
127 setTimeout(() => { d.classList.remove("lit"); }, 100);
128 break;
129 }
130}
131
132function movement(maze, movement) {
133 if (won) {
134 return;
135 }
136 const current = document.querySelector(".current");
137 const index = Array.from(maze.children).indexOf(current);
138 if (movement === "UP" && current.style.borderTop === "none") {
139 maze.children[index].classList.remove("current");
140 maze.children[index - cols].classList.add("current");
141 }
142 if (movement === "DOWN" && current.style.borderBottom === "none") {
143 maze.children[index].classList.remove("current");
144 maze.children[index + cols].classList.add("current");
145 }
146 if (movement === "LEFT" && current.style.borderLeft === "none") {
147 maze.children[index].classList.remove("current");
148 maze.children[index - 1].classList.add("current");
149 }
150 if (movement === "RIGHT" && current.style.borderRight === "none") {
151 maze.children[index].classList.remove("current");
152 maze.children[index + 1].classList.add("current");
153 }
154 if (document.querySelector(".current").classList.contains("final")) {
155 document.querySelector(".current").classList.add("won");
156 won = true;
157 tooltip.innerHTML = "You won! Press F5 to play again";
158 }
159}
160
161
162document.addEventListener("DOMContentLoaded", async function () {
163 tooltip.innerHTML = "Generating maze..."
164 const maze = document.querySelector("#maze")
165 for (let i = 0; i < rows; i++) {
166 for (let j = 0; j < cols; j++) {
167 const cell_element = document.createElement("div");
168 cell_element.classList.add("cell");
169 maze.appendChild(cell_element);
170 }
171 }
172
173 const initial_position = 0;
174 const final_position = rows * cols - 1;
175
176 cells = document.querySelectorAll(".cell");
177 await dfs(maze, initial_position);
178 cells[final_position].classList.add("final");
179 document.querySelector(".controls").style.visibility = "visible";
180
181 document.addEventListener("keydown", function (event) {
182 if (!maze_created || won || isCheatCodeActivated) {
183 return;
184 }
185 if (event.key === "w" || event.key === "W" || event.key === "ArrowUp") {
186 movement(maze, "UP");
187 lightUpButtons("w");
188 }
189 if (event.key === "s" || event.key === "S" || event.key === "ArrowDown") {
190 movement(maze, "DOWN");
191 lightUpButtons("s");
192 }
193 if (event.key === "a" || event.key === "A" || event.key === "ArrowLeft") {
194 movement(maze, "LEFT");
195 lightUpButtons("a");
196 }
197 if (event.key === "d" || event.key === "D" || event.key === "ArrowRight") {
198 movement(maze, "RIGHT");
199 lightUpButtons("d");
200 }
201 // f5 key
202 if (event.key === "F5") {
203 window.location.reload();
204 }
205
206 // cheatcode
207
208 if (cheatkeys.includes(event.key.toLowerCase())) {
209 cheatcodes.push(event.key.toLowerCase());
210 clearTimeout(cheatTimeout);
211 cheatTimeout = setTimeout(() => {
212 cheatcodes = [];
213 }, 3000);
214
215 if (cheatcodes.length == 5) {
216 let cheatcode = cheatcodes.join("");
217 if (cheatcode === "minho") {
218 isCheatCodeActivated = true;
219 moveAccordingToTheAnswerStack();
220 tooltip.textContent = `Cheat code activated!`;
221 }
222 cheatcodes = [];
223 }
224 } else {
225 cheatcodes = [];
226 }
227 });
228
229 w.onclick = () => {
230 movement(maze, "UP");
231 lightUpButtons("w");
232 }
233
234 s.onclick = () => {
235 movement(maze, "DOWN");
236 lightUpButtons("s");
237 }
238
239 a.onclick = () => {
240 movement(maze, "LEFT");
241 lightUpButtons("a");
242 }
243
244 d.onclick = () => {
245 movement(maze, "RIGHT");
246 lightUpButtons("d");
247 }
248
249});
250
251
252async function moveAccordingToTheAnswerStack() {
253 if (answer_stack.length === 0) {
254 return;
255 }
256 const maze = document.querySelector("#maze");
257 while (answer_stack.length > 0) {
258 let current = document.querySelector(".current");
259 current.style.backgroundColor = "rgba(255, 215, 0, 0.7)";
260 let index = Array.from(maze.children).indexOf(current);
261 let next = answer_stack.shift();
262 if (next === index - cols) {
263 movement(maze, "UP");
264 }
265 if (next === index + cols) {
266 movement(maze, "DOWN");
267 }
268 if (next === index - 1) {
269 movement(maze, "LEFT");
270 }
271 if (next === index + 1) {
272 movement(maze, "RIGHT");
273 }
274 await sleep(50);
275 }
276}