├── assets
├── tutorial
│ ├── draw-wall.gif
│ ├── generate-maze.gif
│ ├── pixel_tutorial.png
│ ├── speed_tutorial.png
│ ├── algorithm_tutorial.png
│ ├── drag-source-target.gif
│ ├── man-thinking.svg
│ ├── man-with-maze.svg
│ └── man-clicking-visualize.svg
└── icon
│ ├── caret-down.svg
│ ├── angle-double-down.svg
│ ├── target.svg
│ ├── flask.svg
│ ├── source.svg
│ └── lightbulb-alt.svg
├── README.md
├── css
├── utility.css
└── main.css
├── index.html
└── app.js
/assets/tutorial/draw-wall.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/path-finder-YourTube-tutorial/HEAD/assets/tutorial/draw-wall.gif
--------------------------------------------------------------------------------
/assets/tutorial/generate-maze.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/path-finder-YourTube-tutorial/HEAD/assets/tutorial/generate-maze.gif
--------------------------------------------------------------------------------
/assets/tutorial/pixel_tutorial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/path-finder-YourTube-tutorial/HEAD/assets/tutorial/pixel_tutorial.png
--------------------------------------------------------------------------------
/assets/tutorial/speed_tutorial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/path-finder-YourTube-tutorial/HEAD/assets/tutorial/speed_tutorial.png
--------------------------------------------------------------------------------
/assets/tutorial/algorithm_tutorial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/path-finder-YourTube-tutorial/HEAD/assets/tutorial/algorithm_tutorial.png
--------------------------------------------------------------------------------
/assets/tutorial/drag-source-target.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/path-finder-YourTube-tutorial/HEAD/assets/tutorial/drag-source-target.gif
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Links
2 |
3 | [YouTube Playlist](https://www.youtube.com/watch?v=Oaf9mR9oDT8&list=PLZ92O1inS6VmlSaCzdxm5_Jf2IyJesCF4&pp=iAQB).
4 |
5 | [Path finder visualizer](https://path-explorer.netlify.app/).
--------------------------------------------------------------------------------
/assets/icon/caret-down.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/icon/angle-double-down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icon/target.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icon/flask.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icon/source.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icon/lightbulb-alt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/css/utility.css:
--------------------------------------------------------------------------------
1 | .caret {
2 | display: block;
3 | aspect-ratio: 1;
4 | width: 14px;
5 | background: url(../assets/icon/caret-down.svg);
6 | }
7 |
8 | .icon {
9 | aspect-ratio: 1;
10 | width: 20px;
11 | }
12 |
13 | .wall {
14 | border-color: var(--clr-navy);
15 | animation: wallAnimation .5s ease-out forwards;
16 | }
17 |
18 | .path {
19 | animation: pathAnimation 1.5s ease-out forwards;
20 | }
21 |
22 | .visited {
23 | animation: visitedAnimation 1.5s ease-out forwards;
24 | }
25 |
26 | .unvisited {
27 | border: 1px solid var(--light-line);
28 | }
29 |
30 | .source {
31 | background: url(../assets/icon/source.svg);
32 | }
33 |
34 | .target {
35 | background: url(../assets/icon/target.svg);
36 | }
37 |
38 |
39 |
40 | /* animation */
41 | @keyframes wallAnimation {
42 |
43 | 0% {
44 | transform: scale(.3);
45 | background-color: var(--wall-clr);
46 | }
47 |
48 | 50% {
49 | transform: scale(1.1);
50 | background-color: var(--wall-clr);
51 | }
52 |
53 | 100% {
54 | transform: scale(1.0);
55 | background-color: var(--wall-clr);
56 | }
57 | }
58 |
59 | @keyframes pathAnimation {
60 | 0% {
61 | transform: scale(.6);
62 | background-color: var(--path-clr);
63 | }
64 |
65 | 50% {
66 | transform: scale(1);
67 | background-color: var(--path-clr);
68 | }
69 |
70 | 100% {
71 | transform: scale(1.0);
72 | background-color: var(--path-clr);
73 | }
74 | }
75 |
76 | @keyframes visitedAnimation {
77 | 0% {
78 | transform: scale(.3);
79 | background-color: rgba(0, 0, 66, 0.75);
80 | border-radius: 100%;
81 | }
82 |
83 | 50% {
84 | background-color: rgba(17, 104, 217, 0.75);
85 | }
86 |
87 | 75% {
88 | transform: scale(1);
89 | background-color: rgba(0, 217, 159, 0.75);
90 | }
91 |
92 | 100% {
93 | transform: scale(1);
94 | background-color: var(--visited-clr);
95 | }
96 | }
97 |
98 |
99 |
--------------------------------------------------------------------------------
/css/main.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --clr-navy: #34495e;
3 | --primary-clr: #6563ff;
4 | --secondary-clr: #c4c3fc;
5 | --light-line: #e4e2ed;
6 | --cell-width: 22px;
7 | --light-text: #686673;
8 | --light-dot: #adaac0;
9 | --wall-clr: #0c3547;
10 | --path-clr: #fffe6a;
11 | --visited-clr: #40cee3;
12 | }
13 |
14 | *{
15 | margin: 0;
16 | padding: 0;
17 | box-sizing: border-box;
18 | font-family: sans-serif;
19 | text-decoration: none;
20 | list-style: none;
21 | }
22 |
23 | body{
24 | width: 100vw;
25 | height: 100vh;
26 | }
27 | .template{
28 | width: 100%;
29 | height: 100%;
30 | display: grid;
31 | grid-template-rows: auto 1fr;
32 | grid-gap: 10px;
33 | }
34 |
35 |
36 | nav{
37 | padding: 4px;
38 | white-space: nowrap;
39 | display: flex;
40 | align-items: center;
41 | justify-content: space-between;
42 | background-color: var(--clr-navy);
43 | }
44 |
45 | .logo{
46 | padding: 0 16px;
47 | color: white;
48 | font-size: 20px;
49 | font-weight: 600;
50 | }
51 |
52 | .btn{
53 | background-color: var(--primary-clr);
54 | color: white;
55 | padding: 8px 16px;
56 | border-radius: 5px;
57 | cursor: pointer;
58 | }
59 |
60 | .nav-menu{
61 | display: flex;
62 | align-items: center;
63 | }
64 | .nav-menu>li.active>a{
65 | background-color: var(--primary-clr);
66 | }
67 | .nav-menu>li.active>a:hover{
68 | color: white;
69 | }
70 | .nav-menu>li>a:hover{
71 | color: var(--secondary-clr);
72 | }
73 | .nav-menu>li>a{
74 | color: white;
75 | padding: 8px 16px;
76 | display: block;
77 | font-weight: 600;
78 | display: flex;
79 | align-items: center;
80 | gap: 8px;
81 | }
82 | .drop-box{
83 | position: relative;
84 | }
85 | .drop-box.active .drop-menu{
86 | display: block;
87 | }
88 | .drop-box .drop-menu{
89 | position: absolute;
90 | right: 0;
91 | z-index: 100;
92 | top: calc(100% + 20px);
93 | background-color: var(--clr-navy);
94 | min-width: 120px;
95 | padding: 4px;
96 | border-radius: 8px;
97 | display: none;
98 | }
99 |
100 | .drop-menu>li>a:hover,
101 | .drop-menu>li.active>a{
102 | background-color: var(--primary-clr);
103 | }
104 | .drop-menu>li>a{
105 | color: white;
106 | padding: 8px;
107 | display: block;
108 | border-radius: 4px;
109 | }
110 |
111 | .guide-bar{
112 | text-transform: capitalize;
113 | font-size: 14px;
114 | display: flex;
115 | gap: 16px;
116 | padding: 8px;
117 | }
118 | .guide-bar>li{
119 | display: flex;
120 | align-items: center;
121 | gap: 4px;
122 | }
123 |
124 | #board{
125 | overflow: hidden;
126 | }
127 |
128 | .row{
129 | display: flex;
130 | }
131 | .col{
132 | aspect-ratio: 1;
133 | width: 100%;
134 | min-width: var(--cell-width);
135 | border: 1px solid var(--light-line);
136 | }
137 |
138 | @media screen and (max-width: 1000px){
139 | nav{
140 | display: grid;
141 | grid-template-columns: 1fr 1fr 1fr;
142 | grid-row-gap: 4px;
143 | }
144 | .btn{
145 | justify-self: start;
146 | }
147 | .nav-menu{
148 | grid-column: 1/-1;
149 | width: max-content;
150 | }
151 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | path finder
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
61 |
62 |
63 | -
64 |
65 | source
66 |
67 | -
68 |
69 | target
70 |
71 | -
72 |
73 | unvisited
74 |
75 | -
76 |
77 | visited
78 |
79 | -
80 |
81 | wall
82 |
83 | -
84 |
85 | path
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | const board = document.getElementById('board');
2 | var cells;
3 | let row, col;
4 | var matrix;
5 | let source_Cordinate;
6 | let target_Cordinate;
7 | renderBoard();
8 | function renderBoard(cellWidth = 22) {
9 | const root = document.documentElement;
10 | root.style.setProperty('--cell-width', `${cellWidth}px`);
11 | row = Math.floor(board.clientHeight / cellWidth);
12 | col = Math.floor(board.clientWidth / cellWidth);
13 | board.innerHTML = '';
14 | cells = [];
15 | matrix = [];
16 |
17 | for (let i = 0; i < row; i++) {
18 | const rowArr = [];
19 | const rowElement = document.createElement('div');
20 | rowElement.classList.add('row');
21 | rowElement.setAttribute('id', `${i}`);
22 |
23 | for (let j = 0; j < col; j++) {
24 | const colElement = document.createElement('div');
25 | colElement.classList.add('col');
26 | colElement.setAttribute('id', `${i}-${j}`);
27 | cells.push(colElement);
28 | rowArr.push(colElement);
29 | rowElement.appendChild(colElement);
30 | }
31 |
32 | matrix.push(rowArr);
33 | board.appendChild(rowElement);
34 | }
35 |
36 | source_Cordinate = set('source');
37 | target_Cordinate = set('target');
38 | boardInteraction(cells);
39 | }
40 |
41 | const navOptions = document.querySelectorAll('.nav-menu>li>a');
42 | var dropOptions = null;
43 |
44 | const removeActive = (elements, parent = false) => {
45 | elements.forEach(element => {
46 | if (parent) element = element.parentElement;
47 | element.classList.remove('active');
48 | });
49 | }
50 |
51 | navOptions.forEach(navOption => {
52 | navOption.addEventListener('click', () => {
53 | const li = navOption.parentElement;
54 | if (li.classList.contains('active')) {
55 | li.classList.remove('active');
56 | return;
57 | }
58 | removeActive(navOptions, true);
59 | li.classList.add('active');
60 |
61 | if (li.classList.contains('drop-box')) {
62 | dropOptions = li.querySelectorAll('.drop-menu>li');
63 |
64 | toggle_dropOption(navOption.innerText);
65 | }
66 | })
67 | })
68 |
69 |
70 | let pixelSize = 22;
71 | let speed = 'normal';
72 | let algorithm = 'BFS';
73 | const visualizeBtn = document.getElementById('visualize');
74 |
75 | function toggle_dropOption(target) {
76 | dropOptions.forEach(dropOption => {
77 | dropOption.addEventListener('click', () => {
78 | removeActive(dropOptions);
79 | dropOption.classList.add('active');
80 |
81 | if (target === 'pixel') {
82 | pixelSize = +dropOption.innerText.replace('px', '');
83 | renderBoard(pixelSize);
84 | }
85 | else if (target === 'speed') {
86 | speed = dropOption.innerText;
87 | }
88 | else {
89 | algorithm = dropOption.innerText.split(' ')[0];
90 | visualizeBtn.innerText = `visualize ${algorithm}`
91 | }
92 |
93 | removeActive(navOptions, true);
94 | })
95 | })
96 | }
97 |
98 | document.addEventListener('click', (e) => {
99 | const navMenu = document.querySelector('.nav-menu');
100 |
101 | if (!navMenu.contains(e.target)) {
102 | removeActive(navOptions, true);
103 | }
104 | })
105 |
106 |
107 |
108 | function isValid(x, y) {
109 | return (x >= 0 && y >= 0 && x < row && y < col)
110 | }
111 | function set(className, x = -1, y = -1) {
112 | if (isValid(x, y)) {
113 | matrix[x][y].classList.add(className);
114 | }
115 | else {
116 | x = Math.floor(Math.random() * row);
117 | y = Math.floor(Math.random() * col);
118 | matrix[x][y].classList.add(className);
119 | }
120 |
121 | return { x, y };
122 | }
123 |
124 |
125 | //=======================================
126 | //======== board interaction 🔵👆 ======
127 | //=======================================
128 |
129 | function boardInteraction(cells) {
130 | let isDrawing = false;
131 | let isDragging = false;
132 | let DragPoint = null;
133 |
134 | cells.forEach((cell) => {
135 |
136 | const pointerdown = (e) => {
137 | if (e.target.classList.contains('source')) {
138 | DragPoint = 'source';
139 | isDragging = true;
140 | }
141 | else if (e.target.classList.contains('target')) {
142 | DragPoint = 'target';
143 | isDragging = true;
144 | }
145 | else {
146 | isDrawing = true;
147 | }
148 | }
149 |
150 | const pointermove = (e) => {
151 | if (isDrawing && !e.target.classList.contains('source') && !e.target.classList.contains('target')) {
152 | e.target.classList.add('wall');
153 | }
154 | else if (DragPoint && isDragging) {
155 | cells.forEach(cell => {
156 | cell.classList.remove(`${DragPoint}`);
157 | })
158 |
159 | e.target.classList.add(`${DragPoint}`)
160 | const cordinate = e.target.id.split('-');
161 |
162 | if (DragPoint === 'source') {
163 | source_Cordinate.x = +cordinate[0];
164 | source_Cordinate.y = +cordinate[1];
165 | }
166 | else {
167 | target_Cordinate.x = +cordinate[0];
168 | target_Cordinate.y = +cordinate[1];
169 | }
170 | }
171 | }
172 | const pointerup = () => {
173 | isDragging = false;
174 | isDrawing = false;
175 | DragPoint = null;
176 | }
177 | cell.addEventListener('pointerdown', pointerdown);
178 | cell.addEventListener('pointermove', pointermove);
179 | cell.addEventListener('pointerup', pointerup);
180 | cell.addEventListener('click', () => {
181 | cell.classList.toggle('wall');
182 | })
183 | });
184 | }
185 |
186 | const clearPathBtn = document.getElementById('clearPath');
187 | const clearBoardBtn = document.getElementById('clearBoard');
188 |
189 | clearPathBtn.addEventListener('click', clearPath);
190 | clearBoardBtn.addEventListener('click', clearBoard);
191 |
192 | function clearPath() {
193 | cells.forEach(cell => {
194 | cell.classList.remove('visited');
195 | cell.classList.remove('path');
196 | })
197 | }
198 |
199 | function clearBoard() {
200 | cells.forEach(cell => {
201 | cell.classList.remove('visited');
202 | cell.classList.remove('wall');
203 | cell.classList.remove('path');
204 | })
205 | }
206 |
207 |
208 |
209 |
210 | //=======================================
211 | //========= MAZE GENERATION 🧩🧩 =======
212 | //=======================================
213 |
214 | var wallToAnimate;
215 | const generateMazeBtn = document.getElementById('generateMazeBtn');
216 |
217 | generateMazeBtn.addEventListener('click', () => {
218 | wallToAnimate = [];
219 | generateMaze(0, row - 1, 0, col - 1, false, 'horizontal');
220 | animate(wallToAnimate, 'wall');
221 | })
222 |
223 | function generateMaze(rowStart, rowEnd, colStart, colEnd, surroundingWall, orientation) {
224 | if (rowStart > rowEnd || colStart > colEnd) {
225 | return;
226 | }
227 |
228 | if (!surroundingWall) {
229 | for (let i = 0; i < col; i++) {
230 | if (!matrix[0][i].classList.contains('source') && !matrix[0][i].classList.contains('target'))
231 | wallToAnimate.push(matrix[0][i]);
232 |
233 | if (!matrix[row - 1][i].classList.contains('source') && !matrix[row - 1][i].classList.contains('target'))
234 | wallToAnimate.push(matrix[row - 1][i]);
235 | }
236 |
237 | for (let i = 0; i < row; i++) {
238 | if (!matrix[i][0].classList.contains('source') && !matrix[i][0].classList.contains('target'))
239 | wallToAnimate.push(matrix[i][0]);
240 |
241 | if (!matrix[i][col - 1].classList.contains('source') && !matrix[i][col - 1].classList.contains('target'))
242 | wallToAnimate.push(matrix[i][col - 1]);
243 | }
244 |
245 | surroundingWall = true;
246 | }
247 |
248 | if (orientation === 'horizontal') {
249 | let possibleRows = [];
250 | for (let i = rowStart; i <= rowEnd; i += 2) {
251 | possibleRows.push(i);
252 | }
253 |
254 | let possibleCols = [];
255 | for (let i = colStart - 1; i <= colEnd + 1; i += 2) {
256 | if (i > 0 && i < col - 1)
257 | possibleCols.push(i);
258 | }
259 |
260 | let currentRow = possibleRows[Math.floor(Math.random() * possibleRows.length)];
261 | let randomCol = possibleCols[Math.floor(Math.random() * possibleCols.length)];
262 |
263 | for (let i = colStart - 1; i <= colEnd + 1; i++) {
264 | const cell = matrix[currentRow][i];
265 |
266 | if (!cell || i === randomCol || cell.classList.contains('source') || cell.classList.contains('target'))
267 | continue;
268 |
269 | wallToAnimate.push(cell);
270 | }
271 | //upper subDivision
272 | generateMaze(rowStart, currentRow - 2, colStart, colEnd, surroundingWall, (currentRow - 2 - rowStart > colEnd - colStart) ? 'horizontal' : 'vertical');
273 |
274 | //bottom subDivision
275 | generateMaze(currentRow + 2, rowEnd, colStart, colEnd, surroundingWall, (rowEnd - (currentRow + 2) > colEnd - colStart) ? 'horizontal' : 'vertical');
276 | }
277 | else {
278 | let possibleCols = [];
279 | for (let i = colStart; i <= colEnd; i += 2) {
280 | possibleCols.push(i);
281 | }
282 |
283 | let possibleRows = [];
284 | for (let i = rowStart - 1; i <= rowEnd + 1; i += 2) {
285 | if (i > 0 && i < row - 1)
286 | possibleRows.push(i);
287 | }
288 |
289 | let currentCol = possibleCols[Math.floor(Math.random() * possibleCols.length)];
290 | let randomRow = possibleRows[Math.floor(Math.random() * possibleRows.length)];
291 |
292 |
293 | for (let i = rowStart - 1; i <= rowEnd + 1; i++) {
294 | if (!matrix[i]) continue;
295 |
296 | const cell = matrix[i][currentCol];
297 |
298 | if (i === randomRow || cell.classList.contains('source') || cell.classList.contains('target'))
299 | continue;
300 |
301 | wallToAnimate.push(cell);
302 | }
303 |
304 | //right subDivision
305 | generateMaze(rowStart, rowEnd, colStart, currentCol - 2, surroundingWall, (rowEnd - rowStart > currentCol - 2 - colStart) ? 'horizontal' : 'vertical');
306 |
307 | //right subDivision
308 | generateMaze(rowStart, rowEnd, currentCol + 2, colEnd, surroundingWall, (rowEnd - rowStart > colEnd - (currentCol + 2)) ? 'horizontal' : 'vertical');
309 | }
310 |
311 | }
312 |
313 |
314 |
315 |
316 |
317 |
318 | //=======================================
319 | //=========== Path Finding ⚙️🦾 ========
320 | //=======================================
321 | var visitedCell;
322 | var pathToAnimate;
323 |
324 | visualizeBtn.addEventListener('click', () => {
325 | clearPath();
326 | visitedCell = [];
327 | pathToAnimate = [];
328 |
329 | switch (algorithm) {
330 | case 'BFS':
331 | BFS();
332 | break;
333 | case 'Dijkstar\'s':
334 | Dijsktra();
335 | break;
336 | case 'greedy':
337 | Greedy();
338 | break;
339 | case 'A*':
340 | Astar();
341 | break;
342 | case 'DFS':
343 | if (DFS(source_Cordinate)) visitedCell.push(matrix[source_Cordinate.x][source_Cordinate.y]);
344 |
345 | break;
346 |
347 | default:
348 | break;
349 | }
350 | animate(visitedCell, 'visited');
351 | })
352 |
353 |
354 |
355 |
356 |
357 | //===================== BFS ==================
358 |
359 | function BFS() {
360 | const queue = [];
361 | const visited = new Set();
362 | const parent = new Map();
363 |
364 | queue.push(source_Cordinate);
365 | visited.add(`${source_Cordinate.x}-${source_Cordinate.y}`);
366 |
367 | while (queue.length > 0) {
368 | const current = queue.shift();
369 | visitedCell.push(matrix[current.x][current.y]);
370 |
371 | //you find the target
372 | if (current.x === target_Cordinate.x && current.y === target_Cordinate.y) {
373 | getPath(parent, target_Cordinate);
374 | return;
375 | }
376 |
377 | const neighbours = [
378 | { x: current.x - 1, y: current.y },//up
379 | { x: current.x, y: current.y + 1 },//right
380 | { x: current.x + 1, y: current.y },//bottom
381 | { x: current.x, y: current.y - 1 }//right
382 | ];
383 |
384 | for (const neighbour of neighbours) {
385 | const key = `${neighbour.x}-${neighbour.y}`;
386 |
387 | if (isValid(neighbour.x, neighbour.y) &&
388 | !visited.has(key) &&
389 | !matrix[neighbour.x][neighbour.y].classList.contains('wall')
390 | ) {
391 | queue.push(neighbour);
392 | visited.add(key);
393 | parent.set(key, current);
394 | }
395 | }
396 | }
397 | }
398 |
399 | function animate(elements, className) {
400 | let delay = 10;
401 | if (className === 'path')
402 | delay *= 3.5;
403 |
404 | for (let i = 0; i < elements.length; i++) {
405 | setTimeout(() => {
406 | elements[i].classList.remove('visited');
407 | elements[i].classList.add(className);
408 | if (i === elements.length - 1 && className === 'visited') {
409 | animate(pathToAnimate, 'path');
410 | }
411 | }, delay * i);
412 | }
413 | }
414 |
415 | function getPath(parent, target) {
416 | if (!target) return;
417 | pathToAnimate.push(matrix[target.x][target.y]);
418 |
419 | const p = parent.get(`${target.x}-${target.y}`);
420 | getPath(parent, p);
421 | }
422 |
423 |
424 |
425 | //============ Dijsktra's Algorithm ==========
426 |
427 | class PriorityQueue {
428 | constructor() {
429 | this.elements = [];
430 | this.length = 0;
431 | }
432 | push(data) {
433 | this.elements.push(data);
434 | this.length++;
435 | this.upHeapify(this.length - 1);
436 | }
437 | pop() {
438 | this.swap(0, this.length - 1);
439 | const popped = this.elements.pop();
440 | this.length--;
441 | this.downheapify(0);
442 | return popped;
443 | }
444 |
445 | upHeapify(i) {
446 | if (i === 0) return;
447 | const parent = Math.floor((i - 1) / 2);
448 | if (this.elements[i].cost < this.elements[parent].cost) {
449 | this.swap(parent, i);
450 | this.upHeapify(parent);
451 | }
452 | }
453 | downheapify(i) {
454 | let minNode = i;
455 | const leftChild = (2 * i) + 1;
456 | const rightChild = (2 * i) + 2;
457 |
458 | if (leftChild < this.length && this.elements[leftChild].cost < this.elements[minNode].cost) {
459 | minNode = leftChild;
460 | }
461 | if (rightChild < this.length && this.elements[rightChild].cost < this.elements[minNode].cost) {
462 | minNode = rightChild;
463 | }
464 |
465 | if (minNode !== i) {
466 | this.swap(minNode, i);
467 | this.downheapify(minNode);
468 | }
469 | }
470 | isEmpty() {
471 | return this.length === 0;
472 | }
473 | swap(x, y) {
474 | [this.elements[x], this.elements[y]] = [this.elements[y], this.elements[x]];
475 | }
476 | }
477 |
478 | function Dijsktra() {
479 | const pq = new PriorityQueue();
480 | const parent = new Map();
481 | const distance = [];
482 |
483 | for (let i = 0; i < row; i++) {
484 | const INF = [];
485 | for (let j = 0; j < col; j++) {
486 | INF.push(Infinity);
487 | }
488 | distance.push(INF);
489 | }
490 |
491 | distance[source_Cordinate.x][source_Cordinate.y] = 0;
492 | pq.push({ cordinate: source_Cordinate, cost: 0 });
493 |
494 | while (!pq.isEmpty()) {
495 | const { cordinate: current, cost: distanceSoFar } = pq.pop();
496 | visitedCell.push(matrix[current.x][current.y]);
497 |
498 | //you find the target
499 | if (current.x === target_Cordinate.x && current.y === target_Cordinate.y) {
500 | getPath(parent, target_Cordinate);
501 | return;
502 | }
503 |
504 | const neighbours = [
505 | { x: current.x - 1, y: current.y },//up
506 | { x: current.x, y: current.y + 1 },//right
507 | { x: current.x + 1, y: current.y },//bottom
508 | { x: current.x, y: current.y - 1 }//right
509 | ];
510 |
511 | for (const neighbour of neighbours) {
512 | const key = `${neighbour.x}-${neighbour.y}`;
513 |
514 | if (isValid(neighbour.x, neighbour.y) &&
515 | !matrix[neighbour.x][neighbour.y].classList.contains('wall')
516 | ) {
517 | //Assuming edge weight = 1, between adjacent vertices
518 | const edgeWeight = 1;
519 | const distanceToNeighbour = distanceSoFar + edgeWeight;
520 |
521 | if (distanceToNeighbour < distance[neighbour.x][neighbour.y]) {
522 | distance[neighbour.x][neighbour.y] = distanceToNeighbour;
523 | pq.push({ cordinate: neighbour, cost: distanceToNeighbour });
524 | parent.set(key, current);
525 | }
526 | }
527 | }
528 | }
529 | }
530 |
531 |
532 | //============== Greedy Algorithm ============
533 |
534 | function heuristicValue(node) {
535 | return Math.abs(node.x - target_Cordinate.x) + Math.abs(node.y - target_Cordinate.y);
536 | }
537 |
538 | function Greedy() {
539 | const queue = new PriorityQueue();
540 | const visited = new Set();
541 | const parent = new Map();
542 |
543 | queue.push({ cordinate: source_Cordinate, cost: heuristicValue(source_Cordinate) });
544 | visited.add(`${source_Cordinate.x}-${source_Cordinate.y}`);
545 |
546 | while (queue.length > 0) {
547 | const { cordinate: current } = queue.pop();
548 | visitedCell.push(matrix[current.x][current.y]);
549 |
550 | //you find the target
551 | if (current.x === target_Cordinate.x && current.y === target_Cordinate.y) {
552 | getPath(parent, target_Cordinate);
553 | return;
554 | }
555 |
556 | const neighbours = [
557 | { x: current.x - 1, y: current.y },//up
558 | { x: current.x, y: current.y + 1 },//right
559 | { x: current.x + 1, y: current.y },//bottom
560 | { x: current.x, y: current.y - 1 }//right
561 | ];
562 |
563 | for (const neighbour of neighbours) {
564 | const key = `${neighbour.x}-${neighbour.y}`;
565 |
566 | if (isValid(neighbour.x, neighbour.y) &&
567 | !visited.has(key) &&
568 | !matrix[neighbour.x][neighbour.y].classList.contains('wall')
569 | ) {
570 | queue.push({ cordinate: neighbour, cost: heuristicValue(neighbour) });
571 | visited.add(key);
572 | parent.set(key, current);
573 | }
574 | }
575 | }
576 | }
577 |
578 |
579 | //============== Astar Algorithm =============
580 | //Dijkstar's + Greedy = Astar
581 | //distance + heuristic
582 |
583 | function Astar() {
584 | const queue = new PriorityQueue();;
585 | const visited = new Set();//closedset
586 | const queued = new Set();//openset
587 | const parent = new Map();
588 | const gScore = [];
589 |
590 | for (let i = 0; i < row; i++) {
591 | const INF = [];
592 | for (let j = 0; j < col; j++) {
593 | INF.push(Infinity);
594 | }
595 | gScore.push(INF);
596 | }
597 |
598 | gScore[source_Cordinate.x][source_Cordinate.y] = 0;
599 | queue.push({ cordinate: source_Cordinate, cost: heuristicValue(source_Cordinate) });
600 | visited.add(`${source_Cordinate.x}-${source_Cordinate.y}`);
601 |
602 | while (queue.length > 0) {
603 | const { cordinate: current } = queue.pop();
604 | visitedCell.push(matrix[current.x][current.y]);
605 |
606 | //you find the target
607 | if (current.x === target_Cordinate.x && current.y === target_Cordinate.y) {
608 | getPath(parent, target_Cordinate);
609 | return;
610 | }
611 |
612 | visited.add(`${current.x}-${current.y}`);
613 |
614 | const neighbours = [
615 | { x: current.x - 1, y: current.y },//up
616 | { x: current.x, y: current.y + 1 },//right
617 | { x: current.x + 1, y: current.y },//bottom
618 | { x: current.x, y: current.y - 1 }//right
619 | ];
620 |
621 | for (const neighbour of neighbours) {
622 | const key = `${neighbour.x}-${neighbour.y}`;
623 |
624 | if (isValid(neighbour.x, neighbour.y) &&
625 | !visited.has(key) &&
626 | !queued.has(key) &&
627 | !matrix[neighbour.x][neighbour.y].classList.contains('wall')
628 | ) {
629 |
630 | //Assuming edge weight = 1, between adjacent vertices
631 | const edgeWeight = 1;
632 | const gScoreToNeighbour = gScore[current.x][current.y] + edgeWeight;
633 | const fScore = gScoreToNeighbour + heuristicValue(neighbour);
634 |
635 | if (gScoreToNeighbour < gScore[neighbour.x][neighbour.y]) {
636 | gScore[neighbour.x][neighbour.y] = gScoreToNeighbour;
637 |
638 | queue.push({ cordinate: neighbour, cost: fScore });
639 | queued.add(key);//openset
640 |
641 | parent.set(key, current);
642 | }
643 | }
644 | }
645 | }
646 | }
647 |
648 | //=============== DFS Algorithm ==============
649 | const visited = new Set();
650 | function DFS(current) {
651 | //base case
652 | if (current.x === target_Cordinate.x && current.y === target_Cordinate.y) {
653 | return true;
654 | }
655 |
656 | visitedCell.push(matrix[current.x][current.y]);
657 | visited.add(`${current.x}-${current.y}`);
658 |
659 | const neighbours = [
660 | { x: current.x - 1, y: current.y },//up
661 | { x: current.x, y: current.y + 1 },//right
662 | { x: current.x + 1, y: current.y },//bottom
663 | { x: current.x, y: current.y - 1 }//right
664 | ];
665 |
666 | for (const neighbour of neighbours) {
667 | if (isValid(neighbour.x, neighbour.y) &&
668 | !visited.has(`${neighbour.x}-${neighbour.y}`) &&
669 | !matrix[neighbour.x][neighbour.y].classList.contains('wall')) {
670 | if (DFS(neighbour)) {
671 | pathToAnimate.push(matrix[neighbour.x][neighbour.y]);
672 | return true;
673 | }
674 |
675 | }
676 | }
677 |
678 | return false;
679 | }
680 |
--------------------------------------------------------------------------------
/assets/tutorial/man-thinking.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
346 |
--------------------------------------------------------------------------------
/assets/tutorial/man-with-maze.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
751 |
--------------------------------------------------------------------------------
/assets/tutorial/man-clicking-visualize.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------