├── .gitattributes ├── videos ├── part1.gif └── part2.gif ├── js ├── Pathfinding Algs │ ├── alg.js │ ├── dfs.js │ ├── bfs.js │ ├── greedy.js │ ├── dijkstra.js │ └── astar.js ├── grid.js ├── maze.js ├── tiles.js ├── sorting.js └── index.js ├── sorting.html ├── index.html ├── README.md └── style.css /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /videos/part1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/btschneid/Pathfinding-and-Sorting-Visualizer/HEAD/videos/part1.gif -------------------------------------------------------------------------------- /videos/part2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/btschneid/Pathfinding-and-Sorting-Visualizer/HEAD/videos/part2.gif -------------------------------------------------------------------------------- /js/Pathfinding Algs/alg.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Algorithms 3 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | let visitedTiles = [] 5 | 6 | const buildGraph = () => { 7 | const graph = {}; 8 | 9 | for (let i = 0; i < tiles.length; i++) { 10 | const tile = tiles[i]; 11 | graph[tile.number] = []; 12 | 13 | for (let j = 0; j < tile.neighbors.length; j++) { 14 | const neighbor = tile.neighbors[j]; 15 | graph[tile.number].push(neighbor.number); 16 | } 17 | } 18 | return graph; 19 | } 20 | 21 | const resetVisitedTiles = () => { 22 | for (let i = 0; i < visitedTiles.length; i++) { 23 | if (visitedTiles[i].number !== startTile && visitedTiles[i].number !== endTile) { 24 | visitedTiles[i].element.style.backgroundColor = 'white'; 25 | } 26 | } 27 | 28 | visitedTiles = []; 29 | } 30 | 31 | 32 | const checkToStart = () => { 33 | return (endTile > -1 && startTile > -1); 34 | } -------------------------------------------------------------------------------- /js/Pathfinding Algs/dfs.js: -------------------------------------------------------------------------------- 1 | /////////////////////// 2 | // DFS 3 | /////////////////////// 4 | 5 | const dfs = () => { 6 | pathFindingDone = false; 7 | currentAlg = 1; 8 | dfsTime(30) 9 | } 10 | 11 | async function dfsTime(delayTime) { 12 | selectedColor = ''; 13 | if (!checkToStart()) { 14 | return; 15 | } 16 | 17 | editMode = false; 18 | resetOn = false; 19 | updateTileNeighbors(); 20 | const graph = buildGraph(); 21 | const visited = new Set([startTile]); 22 | const array = [[startTile, 0]]; 23 | const prev = {}; 24 | let endNode = null; 25 | resetVisitedTiles(); 26 | 27 | outerLoop : while (array.length > 0) { 28 | if (resetOn) { 29 | break; 30 | } 31 | if (delayTime > 0) { 32 | await delay(delayTime); 33 | } 34 | const [node, distance] = array.pop(); 35 | 36 | if (node == endTile) { 37 | endNode = node; 38 | break; 39 | } 40 | 41 | for (let neighbor of graph[node]) { 42 | if (!visited.has(neighbor)) { 43 | visited.add(neighbor); 44 | array.push([neighbor, distance + 1]); 45 | if (neighbor !== startTile && neighbor !== endTile) { 46 | if (delayTime == 0) { 47 | tiles[neighbor].element.style.backgroundColor = '#75a7e6'; 48 | } else { 49 | visitedAnimation(tiles[neighbor], '#75a7e6'); 50 | } 51 | visitedTiles.push(tiles[neighbor]); 52 | } 53 | prev[neighbor] = node; 54 | if (endTile == neighbor) { 55 | endNode = neighbor; 56 | break outerLoop; 57 | } 58 | } 59 | } 60 | } 61 | 62 | // Color the path from end to start 63 | let node = endNode; 64 | while (node != startTile && !resetOn) { 65 | if (node !== endNode) { 66 | if (delayTime == 0) { 67 | tiles[node].element.style.backgroundColor = '#f5ff5e'; 68 | } else { 69 | visitedAnimation(tiles[node], '#f5ff5e'); 70 | await delay(delayTime); 71 | } 72 | } 73 | node = prev[node]; 74 | } 75 | 76 | pathFindingDone = true; 77 | 78 | } -------------------------------------------------------------------------------- /js/Pathfinding Algs/bfs.js: -------------------------------------------------------------------------------- 1 | /////////////////////// 2 | // BFS 3 | /////////////////////// 4 | 5 | const bfs = () => { 6 | pathFindingDone = false; 7 | currentAlg = 0; 8 | bfsTime(30) 9 | } 10 | 11 | async function bfsTime(delayTime) { 12 | selectedColor = ''; 13 | if (!checkToStart()) { 14 | return; 15 | } 16 | 17 | editMode = false; 18 | resetOn = false; 19 | updateTileNeighbors(); 20 | const graph = buildGraph(); 21 | const visited = new Set([startTile]); 22 | const queue = [[startTile, 0]]; 23 | const prev = {}; 24 | let endNode = null; 25 | resetVisitedTiles(); 26 | 27 | outerLoop : while (queue.length > 0) { 28 | if (resetOn) { 29 | break; 30 | } 31 | if (delayTime > 0) { 32 | await delay(delayTime); 33 | } 34 | const [node, distance] = queue.shift(); 35 | 36 | if (node == endTile) { 37 | endNode = node; 38 | break; 39 | } 40 | 41 | for (let neighbor of graph[node]) { 42 | if (!visited.has(neighbor)) { 43 | visited.add(neighbor); 44 | queue.push([neighbor, distance + 1]); 45 | if (neighbor !== startTile && neighbor !== endTile) { 46 | if (delayTime == 0) { 47 | tiles[neighbor].element.style.backgroundColor = '#75a7e6'; 48 | } else { 49 | visitedAnimation(tiles[neighbor], '#75a7e6'); 50 | } 51 | visitedTiles.push(tiles[neighbor]); 52 | } 53 | prev[neighbor] = node; 54 | if (endTile == neighbor) { 55 | endNode = neighbor; 56 | break outerLoop; 57 | } 58 | } 59 | } 60 | } 61 | 62 | // Color the path from end to start 63 | let node = endNode; 64 | while (node != startTile && !resetOn) { 65 | if (node !== endNode) { 66 | 67 | if (delayTime == 0) { 68 | tiles[node].element.style.backgroundColor = '#f5ff5e'; 69 | } else { 70 | visitedAnimation(tiles[node], '#f5ff5e'); 71 | await delay(delayTime); 72 | } 73 | } 74 | node = prev[node]; 75 | } 76 | 77 | pathFindingDone = true; 78 | 79 | } -------------------------------------------------------------------------------- /sorting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sorting Algorthims 5 | 6 | 7 | 8 |
9 | 10 |
11 |
12 |
13 | 14 | 15 |
16 |
17 | 18 | 19 |
20 |
21 | 22 | 23 |
24 |
25 | 26 | 27 |
28 |
29 |
30 |

Comparisons: 0

31 |

Swaps: 0

32 |

Time complexity:

33 |
34 |
35 | 36 |
37 | 38 | 39 |
40 |
41 | 42 | 43 |
44 |
45 |
46 |
47 | 48 | 49 |
50 |
51 | 54 | 57 |
58 | 59 |
60 | 61 |
62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /js/grid.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // CREATING GRID 3 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | 5 | // Grid Size 6 | const gridSizeX = 37; 7 | const gridSizeY = 15; 8 | let tileNumber = gridSizeX * gridSizeY; 9 | let tiles = []; 10 | let editMode = true; 11 | let resetOn = false; 12 | 13 | // Creates the tile map 14 | const createTiles = () => { 15 | tiles = []; 16 | const gridContainer = document.querySelector(".grid-container"); 17 | gridContainer.innerHTML = ""; 18 | const tileSize = 49; 19 | 20 | // Appends the tiles to the grid Container 21 | for (let i = 0; i < tileNumber; i++) { 22 | row = Math.floor(i / gridSizeX); 23 | col = i % gridSizeX; 24 | const tile = new Tile(i, row, col); 25 | const tileElement = tile.createElement(tileSize); 26 | tiles.push(tile); 27 | gridContainer.appendChild(tileElement); 28 | } 29 | } 30 | 31 | const updateNeighbors = (tile, allTiles) => { 32 | // Calculate the indices of the neighboring tiles 33 | const indices = [ 34 | tile.number - gridSizeX, // Top 35 | tile.number + gridSizeX, // Bottom 36 | tile.number - 1, // Left 37 | tile.number + 1, // Right 38 | ]; 39 | 40 | // Clear the existing neighbors array 41 | tile.neighbors = []; 42 | 43 | // Add the neighboring tiles to the neighbors array 44 | indices.forEach((index) => { 45 | // Check if the index is within the bounds of the array 46 | if (index >= 0 && index < tileNumber) { 47 | const neighbor = allTiles[index]; 48 | 49 | // Check if the neighbor is in the same row or column as the tile 50 | if (neighbor.row === tile.row || neighbor.col === tile.col) { 51 | if (!neighbor.isTileWall()) tile.neighbors.push(neighbor); 52 | } 53 | } 54 | }); 55 | } 56 | 57 | const updateTileNeighbors = () => { 58 | for (let j = 0; j < tiles.length; j++) { 59 | updateNeighbors(tiles[j], tiles) 60 | } 61 | } 62 | 63 | // Resets the tile map 64 | async function reset() { 65 | openTiles = []; 66 | clickedOnToMove = -1; 67 | startTile = -1; 68 | endTile = -1; 69 | previousStartTile = null; 70 | previousEndTile = null; 71 | isMouseDown = false; 72 | resetOn = true; 73 | editMode = true; 74 | currentAlg = -1; 75 | pathFindingDone = false; 76 | await delay(50); 77 | visitedTiles = [] 78 | selectedColor = ''; 79 | createTiles(); 80 | } 81 | 82 | // Changes the color of the tile 83 | let selectedColor = ''; 84 | const changeColor = (color) => { 85 | selectedColor = color; 86 | } 87 | 88 | window.onload = function() { 89 | createTiles(); 90 | } -------------------------------------------------------------------------------- /js/Pathfinding Algs/greedy.js: -------------------------------------------------------------------------------- 1 | /////////////////////// 2 | // Greedy BFS 3 | /////////////////////// 4 | 5 | const addToQueueGreed = (queue, tile, type) => { 6 | let endR = getRow(endTile); 7 | let endC = getCol(endTile); 8 | let dis = -1; 9 | 10 | if (type === 'euclidean') { 11 | dis = Math.sqrt(Math.pow(getCol(tile) - endC, 2) + Math.pow(getRow(tile) - endR, 2)); 12 | } else if (type === 'manhattan') { 13 | dis = Math.abs(getCol(tile) - endC) + Math.abs(getRow(tile) - endR); 14 | } 15 | 16 | const newItem = [tile, dis]; 17 | 18 | let i = 0; 19 | 20 | // Iterate through the queue and find the index where the new item should be inserted based on its fCost 21 | while (i < queue.length && queue[i][1] <= dis) { 22 | i++; 23 | } 24 | 25 | // Insert the new item at the correct index 26 | queue.splice(i, 0, newItem); 27 | 28 | return queue; 29 | } 30 | 31 | const greedyBFS = (type) => { 32 | pathFindingDone = false; 33 | if (type === 'euclidean') { 34 | currentAlg = 6; 35 | } else { 36 | currentAlg = 7; 37 | } 38 | greedyTime(30, type) 39 | } 40 | 41 | async function greedyTime(delayTime, type) { 42 | selectedColor = ''; 43 | if (!checkToStart()) { 44 | return; 45 | } 46 | 47 | editMode = false; 48 | resetOn = false; 49 | updateTileNeighbors(); 50 | const graph = buildGraph(); 51 | const visited = new Set([startTile]); 52 | let prioQueue = [[startTile, 0]]; 53 | const prev = {}; 54 | let endNode = null; 55 | resetVisitedTiles(); 56 | 57 | outerLoop : while (prioQueue.length > 0) { 58 | if (resetOn) { 59 | break; 60 | } 61 | if (delayTime > 0) { 62 | await delay(delayTime); 63 | } 64 | 65 | const [node, distance] = prioQueue.shift(); 66 | 67 | if (node == endTile) { 68 | endNode = node; 69 | break; 70 | } 71 | 72 | for (let neighbor of graph[node]) { 73 | if (!visited.has(neighbor)) { 74 | visited.add(neighbor); 75 | prioQueue = addToQueueGreed(prioQueue, neighbor, type); 76 | 77 | if (neighbor !== startTile && neighbor !== endTile) { 78 | if (delayTime == 0) { 79 | tiles[neighbor].element.style.backgroundColor = '#75a7e6'; 80 | } else { 81 | visitedAnimation(tiles[neighbor], '#75a7e6'); 82 | } 83 | visitedTiles.push(tiles[neighbor]); 84 | } 85 | prev[neighbor] = node; 86 | if (endTile == neighbor) { 87 | endNode = neighbor; 88 | break outerLoop; 89 | } 90 | } 91 | } 92 | 93 | 94 | } 95 | 96 | // Color the path from end to start 97 | let node = endNode; 98 | while (node != startTile && !resetOn) { 99 | if (node !== endNode) { 100 | if (delayTime == 0) { 101 | tiles[node].element.style.backgroundColor = '#f5ff5e'; 102 | } else { 103 | visitedAnimation(tiles[node], '#f5ff5e'); 104 | await delay(delayTime); 105 | } 106 | } 107 | node = prev[node]; 108 | } 109 | 110 | // Color the path from end to start 111 | 112 | pathFindingDone = true; 113 | } -------------------------------------------------------------------------------- /js/Pathfinding Algs/dijkstra.js: -------------------------------------------------------------------------------- 1 | /////////////////////// 2 | // Dijkstra 3 | /////////////////////// 4 | 5 | const addToQueueDij = (queue, tile, type) => { 6 | let startR = getRow(startTile); 7 | let startC = getCol(startTile); 8 | let dis = -1; 9 | 10 | if (type === 'euclidean') { 11 | dis = Math.sqrt(Math.pow(getCol(tile) - startC, 2) + Math.pow(getRow(tile) - startR, 2)); 12 | } else if (type === 'manhattan') { 13 | dis = Math.abs(getCol(tile) - startC) + Math.abs(getRow(tile) - startR); 14 | } 15 | 16 | const newItem = [tile, dis]; 17 | 18 | let i = 0; 19 | 20 | // Iterate through the queue and find the index where the new item should be inserted based on its fCost 21 | while (i < queue.length && queue[i][1] <= dis) { 22 | i++; 23 | } 24 | 25 | // Insert the new item at the correct index 26 | queue.splice(i, 0, newItem); 27 | 28 | return queue; 29 | } 30 | 31 | const dijkstra = (type) => { 32 | pathFindingDone = false; 33 | if (type === 'euclidean') { 34 | currentAlg = 2; 35 | } else { 36 | currentAlg = 3; 37 | } 38 | dijTime(30, type) 39 | } 40 | 41 | async function dijTime(delayTime, type) { 42 | selectedColor = ''; 43 | if (!checkToStart()) { 44 | return; 45 | } 46 | 47 | editMode = false; 48 | resetOn = false; 49 | updateTileNeighbors(); 50 | const graph = buildGraph(); 51 | const visited = new Set([startTile]); 52 | let prioQueue = [[startTile, 0]]; 53 | const prev = {}; 54 | let endNode = null; 55 | resetVisitedTiles(); 56 | 57 | outerLoop : while (prioQueue.length > 0) { 58 | if (resetOn) { 59 | break; 60 | } 61 | if (delayTime > 0) { 62 | await delay(delayTime); 63 | } 64 | 65 | const [node, distance] = prioQueue.shift(); 66 | 67 | if (node == endTile) { 68 | endNode = node; 69 | break; 70 | } 71 | 72 | for (let neighbor of graph[node]) { 73 | if (!visited.has(neighbor)) { 74 | visited.add(neighbor); 75 | prioQueue = addToQueueDij(prioQueue, neighbor, type); 76 | 77 | if (neighbor !== startTile && neighbor !== endTile) { 78 | if (delayTime == 0) { 79 | tiles[neighbor].element.style.backgroundColor = '#75a7e6'; 80 | } else { 81 | visitedAnimation(tiles[neighbor], '#75a7e6'); 82 | } 83 | visitedTiles.push(tiles[neighbor]); 84 | } 85 | prev[neighbor] = node; 86 | if (endTile == neighbor) { 87 | endNode = neighbor; 88 | break outerLoop; 89 | } 90 | } 91 | } 92 | 93 | 94 | } 95 | 96 | // Color the path from end to start 97 | let node = endNode; 98 | while (node != startTile && !resetOn) { 99 | if (node !== endNode) { 100 | if (delayTime == 0) { 101 | tiles[node].element.style.backgroundColor = '#f5ff5e'; 102 | } else { 103 | visitedAnimation(tiles[node], '#f5ff5e'); 104 | await delay(delayTime); 105 | } 106 | } 107 | node = prev[node]; 108 | } 109 | 110 | // Color the path from end to start 111 | 112 | pathFindingDone = true; 113 | } -------------------------------------------------------------------------------- /js/Pathfinding Algs/astar.js: -------------------------------------------------------------------------------- 1 | /////////////////////// 2 | // AStar 3 | /////////////////////// 4 | 5 | const getRow = (num) => { 6 | return Math.floor(num / gridSizeX); 7 | } 8 | 9 | const getCol = (num) => { 10 | return num % gridSizeX; 11 | } 12 | 13 | const calculateFCost = (tile, type) => { 14 | let gCost, hCost = -1; 15 | let endR = getRow(endTile); 16 | let endC = getCol(endTile); 17 | let startR = getRow(startTile); 18 | let startC = getCol(startTile); 19 | 20 | if (type == 'euclidean') { 21 | gCost = Math.sqrt(Math.pow(getCol(tile) - startC, 2) + Math.pow(getRow(tile) - startR, 2)); 22 | hCost = Math.sqrt(Math.pow(getCol(tile) - endC, 2) + Math.pow(getRow(tile) - endR, 2)); 23 | } else if (type == 'manhattan') { 24 | gCost = Math.abs(getCol(tile) - startC) + Math.abs(getRow(tile) - startR); 25 | hCost = Math.abs(getCol(tile) - endC) + Math.abs(getRow(tile) - endR); 26 | } else { 27 | gCost = Math.abs(getCol(tile) - startC) + Math.abs(getRow(tile) - startR); 28 | hCost = Math.abs(getCol(tile) - endC) + Math.abs(getRow(tile) - endR); 29 | } 30 | 31 | return gCost + hCost; 32 | } 33 | 34 | const addToQueue = (queue, tile, type) => { 35 | const fCost = calculateFCost(tile, type); 36 | const newItem = [tile, fCost]; 37 | 38 | let i = 0; 39 | 40 | // Iterate through the queue and find the index where the new item should be inserted based on its fCost 41 | while (i < queue.length && queue[i][1] <= fCost) { 42 | i++; 43 | } 44 | 45 | // Insert the new item at the correct index 46 | queue.splice(i, 0, newItem); 47 | 48 | return queue; 49 | } 50 | 51 | 52 | const aStar = (type) => { 53 | pathFindingDone = false; 54 | if (type === 'euclidean') { 55 | currentAlg = 4; 56 | } else { 57 | currentAlg = 5; 58 | } 59 | astarTime(30, type) 60 | } 61 | 62 | async function astarTime(delayTime, type) { 63 | selectedColor = ''; 64 | if (!checkToStart()) { 65 | return; 66 | } 67 | 68 | editMode = false; 69 | resetOn = false; 70 | updateTileNeighbors(); 71 | const graph = buildGraph(); 72 | const visited = new Set([startTile]); 73 | let prioQueue = [[startTile, calculateFCost(startTile, type)]]; 74 | const prev = {}; 75 | let endNode = null; 76 | resetVisitedTiles(); 77 | 78 | outerLoop : while (prioQueue.length > 0) { 79 | if (resetOn) { 80 | break; 81 | } 82 | if (delayTime > 0) { 83 | await delay(delayTime); 84 | } 85 | 86 | const [node, fCost] = prioQueue.shift(); 87 | 88 | if (node == endTile) { 89 | endNode = node; 90 | break; 91 | } 92 | 93 | for (let neighbor of graph[node]) { 94 | if (!visited.has(neighbor)) { 95 | visited.add(neighbor); 96 | prioQueue = addToQueue(prioQueue, neighbor, type); 97 | 98 | if (neighbor !== startTile && neighbor !== endTile) { 99 | if (delayTime == 0) { 100 | tiles[neighbor].element.style.backgroundColor = '#75a7e6'; 101 | } else { 102 | visitedAnimation(tiles[neighbor], '#75a7e6'); 103 | } 104 | visitedTiles.push(tiles[neighbor]); 105 | } 106 | prev[neighbor] = node; 107 | if (endTile == neighbor) { 108 | endNode = neighbor; 109 | break outerLoop; 110 | } 111 | } 112 | } 113 | 114 | 115 | } 116 | 117 | // Color the path from end to start 118 | let node = endNode; 119 | while (node != startTile && !resetOn) { 120 | if (node !== endNode) { 121 | if (delayTime == 0) { 122 | tiles[node].element.style.backgroundColor = '#f5ff5e'; 123 | } else { 124 | visitedAnimation(tiles[node], '#f5ff5e'); 125 | await delay(delayTime); 126 | } 127 | } 128 | node = prev[node]; 129 | } 130 | 131 | // Color the path from end to start 132 | 133 | pathFindingDone = true; 134 | } -------------------------------------------------------------------------------- /js/maze.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Maze 3 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | let openTiles = []; 5 | 6 | function maze() { 7 | pathFindingDone = false; 8 | for (let i = 0; i < tiles.length; i++) { 9 | let r = getRow(tiles[i].number); 10 | let c = getCol(tiles[i].number); 11 | if (r % 2 == 0 && c % 2 == 0) { 12 | tiles[i].setTileWall(); 13 | visitedAnimation(tiles[i], 'black'); 14 | } 15 | else if (r % 2 != 1 || c % 2 != 1) { 16 | visitedAnimation(tiles[i], 'black'); 17 | if (r == 0 || r == gridSizeY - 1 || c == 0 || c == gridSizeX - 1) { 18 | tiles[i].setTileWall(); 19 | } 20 | } else { 21 | openTiles.push(tiles[i]); 22 | } 23 | 24 | } 25 | createMazeNeighbors(); 26 | dfsMaze(); 27 | } 28 | 29 | const createMazeNeighbors = () => { 30 | for (let i = 0; i < openTiles.length; i++) { 31 | let tile = openTiles[i]; 32 | // Calculate the indices of the neighboring tiles 33 | const indices = [ 34 | tile.number - (gridSizeX * 2), // Top 35 | tile.number + (gridSizeX * 2), // Bottom 36 | tile.number - 2, // Left 37 | tile.number + 2, // Right 38 | ]; 39 | 40 | // Clear the existing neighbors array 41 | tile.neighbors = []; 42 | 43 | // Add the neighboring tiles to the neighbors array 44 | indices.forEach((index) => { 45 | // Check if the index is within the bounds of the array 46 | if (index >= 0 && index < ((gridSizeX) * (gridSizeY))) { 47 | const neighbor = tiles[index]; 48 | 49 | // Check if the neighbor is in the same row or column as the tile 50 | if (neighbor.row === tile.row || neighbor.col === tile.col) { 51 | if (!neighbor.isTileWall()) tile.neighbors.push(neighbor); 52 | } 53 | } 54 | }); 55 | } 56 | } 57 | 58 | const buildMazeGraph = () => { 59 | const graph = {}; 60 | 61 | for (let i = 0; i < openTiles.length; i++) { 62 | const tile = openTiles[i]; 63 | graph[tile.number] = []; 64 | 65 | for (let j = 0; j < tile.neighbors.length; j++) { 66 | const neighbor = tile.neighbors[j]; 67 | graph[tile.number].push(neighbor.number); 68 | } 69 | } 70 | return graph; 71 | } 72 | 73 | const getRidOfWall = (neighbor, node, color) => { 74 | neR = getRow(neighbor); 75 | noR = getRow(node) 76 | 77 | if (neR - noR == 2) { 78 | visitedAnimation(tiles[node + gridSizeX], color); 79 | return; 80 | } else if (neR - noR == -2) { 81 | visitedAnimation(tiles[node - gridSizeX], color); 82 | return; 83 | } 84 | 85 | neC = getCol(neighbor); 86 | noC = getCol(node) 87 | if (neC - noC == 2) { 88 | visitedAnimation(tiles[node + 1], color); 89 | return; 90 | } else if (neC - noC == -2) { 91 | visitedAnimation(tiles[node - 1], color); 92 | return; 93 | } 94 | } 95 | 96 | 97 | async function dfsMaze() { 98 | selectedColor = ''; 99 | 100 | let delayTime = 1; 101 | const randStartIndex = Math.floor(Math.random() * openTiles.length); 102 | let s = openTiles[randStartIndex].number; 103 | const graph = buildMazeGraph(); 104 | const visited = new Set([s]); 105 | const stack = [s]; 106 | resetVisitedTiles(); 107 | let neighbors = graph[s]; 108 | let neighbor = neighbors[0]; 109 | let node = stack[0]; 110 | 111 | outerLoop : while (stack.length != 0) { 112 | node = stack[stack.length - 1]; 113 | 114 | if (delayTime > 0) { 115 | await delay(delayTime); 116 | } 117 | 118 | neighbors = graph[node]; 119 | 120 | for (let i = 0; i < neighbors.length; i++) { 121 | const randomIndex = Math.floor(Math.random() * neighbors.length); 122 | neighbor = neighbors[randomIndex]; 123 | if (!visited.has(neighbor)) { 124 | neighbors.splice(randomIndex, 1); 125 | break; 126 | } else { 127 | neighbors.splice(randomIndex, 1); 128 | } 129 | } 130 | 131 | if (neighbors.length == 0) { 132 | stack.pop(); 133 | continue; 134 | } 135 | 136 | 137 | if (!visited.has(neighbor)) { 138 | visited.add(neighbor); 139 | stack.push(neighbor); 140 | if (neighbor !== s) { 141 | visitedAnimation(tiles[neighbor], 'white'); 142 | visitedTiles.push(tiles[neighbor]); 143 | } 144 | 145 | getRidOfWall(neighbor, node, 'white'); 146 | 147 | } 148 | 149 | } 150 | 151 | for (let i = 0; i < tiles.length; i++) { 152 | if (tiles[i].element.style.backgroundColor == 'black') { 153 | tiles[i].setTileWall(); 154 | } 155 | } 156 | 157 | pathFindingDone = true; 158 | } -------------------------------------------------------------------------------- /js/tiles.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // TILES CLASS 3 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | let startTile = -1; 5 | let endTile = -1; 6 | let previousStartTile = null; 7 | let previousEndTile = null; 8 | let isMouseDown = false; 9 | let currentAlg = -1; 10 | let pathFindingDone = false; 11 | let clickedOnToMove = -1; 12 | 13 | class Tile { 14 | constructor(number, r, c) { 15 | this.number = number; 16 | this.isWall = false; 17 | this.row = r; 18 | this.col = c; 19 | this.neighbors = []; 20 | } 21 | 22 | getTileRow = () => { 23 | return this.row; 24 | } 25 | 26 | getTileCol = () => { 27 | return this.col; 28 | } 29 | 30 | isTileWall = () => { 31 | return this.isWall; 32 | } 33 | 34 | setTileWall = () => { 35 | this.isWall = true; 36 | } 37 | 38 | setTileNotWall = () => { 39 | this.isWall = false; 40 | } 41 | 42 | getTileNumber = () => { 43 | return this.tileNumber; 44 | } 45 | 46 | appendNeighbors = (neighbor) => { 47 | this.neighbors.push(neighbor); 48 | } 49 | 50 | handleClick = () => { 51 | if (editMode) { 52 | if (selectedColor === 'green') { 53 | if (previousStartTile) { 54 | previousStartTile.element.style.backgroundColor = 'white'; 55 | } 56 | 57 | this.setTileNotWall(); 58 | startTile = this.number; 59 | previousStartTile = this; 60 | } 61 | 62 | if (selectedColor === 'red') { 63 | if (previousEndTile) { 64 | previousEndTile.element.style.backgroundColor = 'white'; 65 | } 66 | 67 | this.setTileNotWall(); 68 | endTile = this.number; 69 | previousEndTile = this; 70 | } 71 | 72 | visitedAnimation(this, selectedColor); 73 | } 74 | 75 | if (selectedColor === 'black') { 76 | if (this.isTileWall()) { 77 | this.setTileNotWall(); 78 | this.element.style.backgroundColor = 'white'; 79 | } 80 | 81 | if (this.number === endTile) { 82 | endTile = -1; 83 | } else if (this.number === startTile) { 84 | startTile = -1; 85 | } 86 | } 87 | 88 | 89 | } 90 | 91 | // Creates a tile as a div 92 | createElement = (size) => { 93 | const tile = document.createElement('div'); 94 | tile.classList.add('tile'); 95 | tile.style.width = size + 'px'; 96 | tile.style.height = size + 'px'; 97 | tile.addEventListener('click', this.handleClick.bind(this)); 98 | 99 | tile.addEventListener('mousedown', () => { 100 | isMouseDown = true; 101 | }); 102 | 103 | tile.addEventListener('mousemove', () => { 104 | if (isMouseDown && this.number === startTile) { 105 | clickedOnToMove = 1; 106 | } else if (isMouseDown && this.number === endTile) { 107 | clickedOnToMove = 0; 108 | } 109 | 110 | if (isMouseDown && editMode && selectedColor === 'black' && !this.isTileWall()) { 111 | this.setTileWall(); 112 | visitedAnimation(this, selectedColor); 113 | 114 | if (this.number === endTile) { 115 | endTile = -1; 116 | } else if (this.number === startTile) { 117 | startTile = -1; 118 | } 119 | } 120 | 121 | if (isMouseDown && pathFindingDone) { 122 | if (clickedOnToMove == 0) { 123 | if (!this.isTileWall() && this.number !== startTile && previousEndTile !== this) { 124 | if (previousEndTile) { 125 | previousEndTile.element.style.backgroundColor = 'white'; 126 | } 127 | 128 | this.setTileNotWall(); 129 | endTile = this.number; 130 | previousEndTile = this; 131 | this.element.style.backgroundColor = 'red'; 132 | } 133 | 134 | } 135 | 136 | if (clickedOnToMove == 1) { 137 | if (!this.isTileWall() && this.number !== endTile && previousStartTile !== this) { 138 | if (previousStartTile) { 139 | previousStartTile.element.style.backgroundColor = 'white'; 140 | } 141 | 142 | this.setTileNotWall(); 143 | startTile = this.number; 144 | previousStartTile = this; 145 | this.element.style.backgroundColor = 'green'; 146 | } 147 | 148 | } 149 | 150 | if (currentAlg == 0) { 151 | bfsTime(0); 152 | } else if (currentAlg == 1) { 153 | dfsTime(0); 154 | } else if (currentAlg == 2) { 155 | dijTime(0, 'euclidean'); 156 | } else if (currentAlg == 3) { 157 | dijTime(0, 'manhattan'); 158 | } else if (currentAlg == 4) { 159 | astarTime(0, 'euclidean'); 160 | } else if (currentAlg == 5) { 161 | astarTime(0, 'manhattan'); 162 | } else if (currentAlg == 6) { 163 | greedyTime(0, 'euclidean'); 164 | } else if (currentAlg == 7) { 165 | greedyTime(0, 'manhattan'); 166 | } 167 | } 168 | 169 | 170 | }); 171 | 172 | document.addEventListener('mouseup', () => { 173 | isMouseDown = false; 174 | }); 175 | 176 | this.element = tile; 177 | return tile; 178 | } 179 | 180 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tile Grid 5 | 6 | 7 | 8 |
9 | 16 | 17 |
18 |
19 |
20 |
21 | 22 | 23 |
24 |
25 | 26 | 27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 |
37 |
38 |
39 |
40 | 41 | 42 | 43 |
44 |
45 | 46 |
47 |
48 |
49 |
50 |
51 |
52 | 53 |
54 |
55 | 56 |
57 |
58 |
59 |
60 | 61 |
62 |
63 | 64 |
65 |
66 |
67 | 70 | 73 |
74 |
75 |
76 |
77 | 78 |
79 |
80 |
81 |
How to Use
82 | 83 |
84 |
85 |
86 |
87 |
88 | To set the starting point, click on the Start Tile button and then click on any tile 89 |
90 | 91 |
92 |
93 |
94 | To set the ending point, click on the End Tile button and then click on any tile 95 |
96 | 97 |
98 |
99 |
100 | To set the walls, click on the Wall Tile button and then click or drag on any tile 101 |
102 | 103 |
104 |
105 | Click any of the algorithms to visualize how they work 106 |
107 |
108 |
109 |
110 | animated GIF 111 |
112 | 115 |
116 |
117 |
118 | 158 | 159 |
160 |
161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pathfinding Visualization 2 | 3 | This project is a web application for visualizing various pathfinding algorithms such as Breadth First Search, Depth First Search, A Star, Dijkstra, and Greedy Best First Search. It allows users to select a start and end tile, add walls to the tile map, and visualize how the pathfinding algorithms find the shortest path from start to end. Users can also create random mazes using depth first search backtracking and set random tilemaps with varying ratios of walls to open tiles. 4 | 5 | ## How to use 6 | Follow the link: https://btschneid.github.io/Pathfinding-and-Sorting-Visualizer/index.html 7 | 8 | 1. Select a start tile by clicking on a tile and then selecting the green button in the tile button container. 9 | 2. Select an end tile by clicking on a tile and then selecting the red button in the tile button container. 10 | 3. Add walls to the tile map by clicking on the tiles. 11 | 4. Click on an algorithm of choice to visualize the pathfinding process. 12 | 5. Once the visualization is complete, click and drag either the start or end node to visualize how the pathfinding algorithm works from other areas. 13 | 6. Create a random maze using depth first search backtracking by clicking on the "Create Maze" button. 14 | 7. Set random tilemaps with differing ratios of walls to open tiles using the range slider 15 | 16 | ## Pathfinding Algorithms 17 | 18 | ### Breadth First Search (BFS) 19 | 20 | BFS is a graph traversal algorithm that explores all the vertices of a graph in breadth-first order. It starts at the root node and explores all the nodes at the current depth before moving on to nodes at the next depth. This algorithm is guaranteed to find the shortest path if it exists, but it can be slow and memory-intensive for large graphs. 21 | 22 | * Time complexity: O(V+E), where V is the number of vertices and E is the number of edges 23 | * Space complexity: O(V), where V is the number of vertices 24 | 25 | ### Depth First Search (DFS) 26 | 27 | DFS is another graph traversal algorithm that explores all the vertices of a graph, but in depth-first order. It starts at the root node and explores as far as possible along each branch before backtracking. Unlike BFS, DFS is not guaranteed to find the shortest path, but it can be faster and use less memory than BFS for certain graphs. 28 | 29 | * Time complexity: O(V+E), where V is the number of vertices and E is the number of edges 30 | * Space complexity: O(V), where V is the number of vertices 31 | 32 | ### Dijkstra 33 | 34 | Dijkstra is a shortest path algorithm that finds the shortest path from a start node to all other nodes in a weighted graph. It works by assigning tentative distances to all nodes and then iteratively selecting the node with the smallest tentative distance and updating the distances of its neighbors. Dijkstra's algorithm is guaranteed to find the shortest path if all edge weights are non-negative, but it can be slow and memory-intensive for large graphs. 35 | 36 | * Time complexity: O((V+E) log V), where V is the number of vertices and E is the number of edges 37 | * Space complexity: O(V), where V is the number of vertices 38 | 39 | ### A Star 40 | 41 | A Star is a heuristic search algorithm that finds the shortest path from a start node to an end node in a weighted graph. It works by using a heuristic function to estimate the distance from each node to the end node and combining this with the actual distance from the start node to calculate a priority for each node. A Star uses a priority queue to select the next node to explore and updates the distances of its neighbors based on the priority. A Star with a Euclidean heuristic function uses the straight-line distance between nodes as the estimate, while A Star with a Manhattan heuristic function uses the sum of the absolute differences in x and y coordinates. 42 | 43 | * Time complexity: O(E log V), where V is the number of vertices and E is the number of edges 44 | * Space complexity: O(V), where V is the number of vertices 45 | 46 | ### Greedy Best First Search 47 | 48 | Greedy Best First Search is a heuristic search algorithm that prioritizes exploring nodes that are closest to the end node in a weighted graph. It works by using a heuristic function to estimate the distance from each node to the end node and selecting the node with the smallest estimate as the next node to explore. Greedy Best First Search can be faster than A Star, but it is not guaranteed to find the shortest path and may get stuck in local optima. 49 | 50 | * Time complexity: O(E log V), where V is the number of vertices and E is the number of edges 51 | * Space complexity: O(V), where V is the number of vertices 52 | 53 | # Sorting Visualization 54 | 55 | This project is a web application for visualizing various sorting algorithms such as Linear, Bubble, Selection, Insertion, Merge, Quick, Heap, and Bogo. It allows users to change the number of bars to sort and the sorting speed, and then visualize how the algorithms sort the bars. 56 | 57 | ## How to use 58 | Follow the link: https://btschneid.github.io/Pathfinding-and-Sorting-Visualizer/sorting.html OR click the Pathfinding Visualizer button on the bottom right of the Pathfinding Visualizer page 59 | 60 | 1. Change the number of bars to sort using the slider. 61 | 2. Change the sorting speed using the speed slider. 62 | 3. Click on any of the sorting algorithm buttons to visualize how they work. 63 | 4. Mute the sound if it is too overbearing. 64 | 65 | ## Sorting Algorithms 66 | 67 | ### Linear 68 | 69 | Linear sort is a simple sorting algorithm that works by iterating through the input array and finding the minimum element, then swapping it with the first element. It then iterates through the remaining elements and finds the minimum of those, swapping it with the second element, and so on. 70 | 71 | * Time complexity: O(n2) 72 | * Space complexity: O(1) 73 | 74 | ### Bubble 75 | 76 | Bubble sort is a simple sorting algorithm that works by repeatedly swapping adjacent elements if they are in the wrong order. It continues to do this until no more swaps are needed. 77 | 78 | * Time complexity: O(n2) 79 | * Space complexity: O(1) 80 | 81 | ### Selection 82 | 83 | Selection sort is a simple sorting algorithm that works by finding the minimum element in the input array and swapping it with the first element. It then finds the minimum element of the remaining elements and swaps it with the second element, and so on. 84 | 85 | * Time complexity: O(n2) 86 | * Space complexity: O(1) 87 | 88 | ### Insertion 89 | 90 | Insertion sort is a simple sorting algorithm that works by iterating through the input array and inserting each element into its proper place in a sorted subarray. It does this by comparing each element with the previous elements in the subarray and swapping them if they are in the wrong order. 91 | 92 | * Time complexity: O(n2) 93 | * Space complexity: O(1) 94 | 95 | ### Merge 96 | 97 | Merge sort is a divide-and-conquer sorting algorithm that works by recursively dividing the input array into two halves, sorting each half, and then merging the two sorted halves into a single sorted array. 98 | 99 | * Time complexity: O(n log n) 100 | * Space complexity: O(n) 101 | 102 | ### Quick 103 | 104 | Quick sort is a divide-and-conquer sorting algorithm that works by selecting a pivot element, partitioning the array into two subarrays based on the pivot, and then recursively sorting the two subarrays. 105 | 106 | * Time complexity: O(n log n) on average. Worst case: O(n2) 107 | * Space complexity: O(log n) 108 | 109 | ### Heap 110 | 111 | Heap sort is a sorting algorithm that works by constructing a binary heap from the input array, repeatedly extracting the maximum element from the heap and inserting it into the sorted portion of the array. 112 | 113 | * Time complexity: O(n log n) 114 | * Space complexity: O(1) 115 | 116 | ### Bogo (for jokes) 117 | Bogo sort is a joke sorting algorithm that works by repeatedly shuffling the input array until it is sorted. 118 | 119 | * Time complexity: O((n+1)!) on average. Worst case: O(infinity) 120 | * Space complexity: O(1) 121 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --grid-size: 37; 3 | } 4 | 5 | body.path { 6 | font-family: Inter,-apple-system,system-ui,"Segoe UI",Helvetica,Arial,sans-serif; 7 | background-color: rgb(239, 239, 239); 8 | min-width: 100%; 9 | } 10 | 11 | .grid-container { 12 | align-items: center; 13 | justify-content: center; 14 | width: 98%; 15 | margin: 8px 10px; 16 | padding: 10px; 17 | box-sizing: border-box; 18 | display: grid; 19 | grid-template-columns: repeat(var(--grid-size), 1fr); 20 | grid-template-rows: repeat(var(--grid-size), 1fr); 21 | gap: 1px; 22 | 23 | } 24 | 25 | .tile { 26 | display: inline-block; 27 | box-sizing: border-box; 28 | margin: 0; 29 | padding: 0; 30 | border: 1px solid #ccc; 31 | background-color: #fff; 32 | } 33 | 34 | .algorithm-container { 35 | display: grid; 36 | grid-template-columns: repeat(4, 1fr); 37 | grid-gap: 10px; 38 | padding: 10px; 39 | } 40 | 41 | .algorithm-container > .button-container { 42 | display: grid; 43 | grid-template-rows: repeat(2, 1fr); 44 | grid-gap: 10px; 45 | } 46 | 47 | .alg-buttons { 48 | appearance: none; 49 | backface-visibility: hidden; 50 | background-color: #488fec; 51 | border-radius: 10px; 52 | border-style: none; 53 | box-shadow: none; 54 | box-sizing: border-box; 55 | color: #fff; 56 | cursor: pointer; 57 | font-family: Inter,-apple-system,system-ui,"Segoe UI",Helvetica,Arial,sans-serif; 58 | font-size: 15px; 59 | font-weight: 500; 60 | letter-spacing: normal; 61 | outline: none; 62 | overflow: hidden; 63 | padding: 10px 25px; 64 | text-align: center; 65 | text-decoration: none; 66 | transform: translate3d(0, 0, 0); 67 | transition: all .3s; 68 | user-select: none; 69 | -webkit-user-select: none; 70 | touch-action: manipulation; 71 | vertical-align: top; 72 | white-space: normal; 73 | 74 | display: inline-flex; 75 | align-items: center; 76 | justify-content: center; 77 | width: auto; 78 | } 79 | 80 | .alg-buttons:hover { 81 | background-color: #1366d6; 82 | box-shadow: rgba(0, 0, 0, .05) 0 5px 30px, rgba(0, 0, 0, .05) 0 1px 4px; 83 | opacity: 1; 84 | transform: translateY(0); 85 | transition-duration: .35s; 86 | } 87 | 88 | .alg-buttons:hover:after { 89 | opacity: .5; 90 | } 91 | 92 | .alg-buttons:active { 93 | box-shadow: rgba(0, 0, 0, .1) 0 3px 6px 0, rgba(0, 0, 0, .1) 0 0 10px 0, rgba(0, 0, 0, .1) 0 1px 4px -1px; 94 | transform: translateY(2px); 95 | transition-duration: .35s; 96 | } 97 | 98 | .alg-buttons:active:after { 99 | opacity: 1; 100 | } 101 | 102 | .main-button-container { 103 | padding: 10px; 104 | display: grid; 105 | grid-gap: 20px; 106 | } 107 | 108 | .tile-button-container { 109 | padding: 5px; 110 | display: grid; 111 | grid-template-rows: repeat(2, 1fr); 112 | grid-template-columns: 100%; 113 | grid-gap: 0px; 114 | justify-content: center; 115 | align-items: center; 116 | height: 100%; 117 | } 118 | 119 | .info-stuff { 120 | display: flex; 121 | justify-content: center; 122 | align-items: center; 123 | } 124 | 125 | 126 | .tile-button-icons, 127 | .tile-buttons { 128 | display: grid; 129 | grid-template-columns: repeat(3, 1fr); 130 | grid-gap: 10px; 131 | justify-content: center; 132 | align-items: center; 133 | height: 100%; 134 | } 135 | 136 | .tilebut1, 137 | .tilebut2, 138 | .tilebut3 { 139 | appearance: none; 140 | backface-visibility: hidden; 141 | background-color: #488fec; 142 | border-radius: 10px; 143 | border-style: none; 144 | box-shadow: none; 145 | box-sizing: border-box; 146 | color: #fff; 147 | cursor: pointer; 148 | font-family: Inter,-apple-system,system-ui,"Segoe UI",Helvetica,Arial,sans-serif; 149 | font-size: 15px; 150 | font-weight: 500; 151 | letter-spacing: normal; 152 | outline: none; 153 | overflow: hidden; 154 | padding: 20px 25px; 155 | text-align: center; 156 | text-decoration: none; 157 | transform: translate3d(0, 0, 0); 158 | transition: all .3s; 159 | user-select: none; 160 | -webkit-user-select: none; 161 | touch-action: manipulation; 162 | vertical-align: top; 163 | white-space: normal; 164 | 165 | display: inline-flex; 166 | align-items: center; 167 | justify-content: center; 168 | width: auto; 169 | 170 | border: none; 171 | color: white; 172 | font-size: 15px; 173 | font-weight: 500; 174 | letter-spacing: normal; 175 | } 176 | 177 | .tilebut1:hover { 178 | background-color: rgb(1, 111, 1); 179 | box-shadow: rgba(0, 0, 0, .05) 0 5px 30px, rgba(0, 0, 0, .05) 0 1px 4px; 180 | opacity: 1; 181 | transform: translateY(0); 182 | transition-duration: .35s; 183 | } 184 | 185 | .tilebut2:hover { 186 | background-color: rgb(182, 1, 1); 187 | box-shadow: rgba(0, 0, 0, .05) 0 5px 30px, rgba(0, 0, 0, .05) 0 1px 4px; 188 | opacity: 1; 189 | transform: translateY(0); 190 | transition-duration: .35s; 191 | } 192 | 193 | .tilebut3:hover { 194 | background-color: rgb(28, 28, 28); 195 | box-shadow: rgba(0, 0, 0, .05) 0 5px 30px, rgba(0, 0, 0, .05) 0 1px 4px; 196 | opacity: 1; 197 | transform: translateY(0); 198 | transition-duration: .35s; 199 | } 200 | 201 | .tilebut1:hover:after, 202 | .tilebut2:hover:after, 203 | .tilebut3:hover:after { 204 | opacity: .5; 205 | } 206 | 207 | .tilebut1:active, 208 | .tilebut2:active, 209 | .tilebut3:active { 210 | box-shadow: rgba(0, 0, 0, .1) 0 3px 6px 0, rgba(0, 0, 0, .1) 0 0 10px 0, rgba(0, 0, 0, .1) 0 1px 4px -1px; 211 | transform: translateY(2px); 212 | transition-duration: .35s; 213 | } 214 | 215 | .tilebut1:active:after, 216 | .tilebut2:active:after, 217 | .tilebut3:active:after { 218 | opacity: 1; 219 | } 220 | 221 | .tilebut1 { 222 | background-color: green; 223 | } 224 | 225 | .tilebut2 { 226 | background-color: red; 227 | } 228 | 229 | .tilebut3 { 230 | background-color: black; 231 | } 232 | 233 | .start-button { 234 | display: flex; 235 | justify-content: center; 236 | align-items: center; 237 | } 238 | 239 | .start-button .alg-buttons { 240 | font-size: 40px; 241 | padding: 40px 40px; 242 | } 243 | 244 | .reset-button, 245 | .maze-button, 246 | .input-container { 247 | display: flex; 248 | justify-content: center; 249 | align-items: center; 250 | } 251 | 252 | .text-slider label { 253 | display: inline-block; 254 | padding: 0.5rem 1rem; 255 | background-color: rgb(239, 239, 239); 256 | border-radius: 0.5rem; 257 | cursor: pointer; 258 | user-select: none; 259 | font-size: 1rem; 260 | font-weight: bold; 261 | color: #333; 262 | text-align: center; 263 | text-transform: uppercase; 264 | } 265 | 266 | .text-slider label:hover { 267 | background-color: #dddddd; 268 | } 269 | 270 | .text-slider label:focus { 271 | outline: none; 272 | box-shadow: 0 0 0 2px #999; 273 | } 274 | 275 | .bottom-container { 276 | width: 100%; 277 | display: flex; 278 | justify-content: center; 279 | overflow: scroll; 280 | } 281 | 282 | .container { 283 | display: grid; 284 | grid-template-columns: repeat(3, 1fr); 285 | grid-template-rows: auto; 286 | gap: 10px; 287 | margin: 0 auto; 288 | } 289 | 290 | .maze-reset { 291 | display: grid; 292 | grid-template-rows: repeat(2, 1fr); 293 | grid-template-columns: auto; 294 | } 295 | 296 | .maze-random-container { 297 | display: grid; 298 | grid-template-columns: repeat(3, 1fr); 299 | grid-template-rows: auto; 300 | gap: 10px; 301 | } 302 | 303 | .sorting-button { 304 | display: grid; 305 | grid-template-rows: repeat(2, 1fr); 306 | grid-template-columns: auto; 307 | gap: 5px; 308 | align-items: center; 309 | justify-content: center; 310 | } 311 | 312 | #source-code { 313 | width: 75%; 314 | margin: 0 auto; 315 | } 316 | 317 | .input-container { 318 | display: flex; 319 | flex-direction: column; 320 | align-items: center; 321 | justify-content: center; 322 | } 323 | 324 | .text-slider { 325 | font-size: 20px; 326 | margin-bottom: 10px; 327 | display: flex; 328 | justify-content: center; 329 | align-items: center; 330 | } 331 | 332 | .slider-container { 333 | width: 100%; 334 | display: flex; 335 | justify-content: center; 336 | align-items: center; 337 | } 338 | 339 | /* Info Stuff */ 340 | 341 | .info { 342 | position: fixed; 343 | top: 40%; 344 | left: 50%; 345 | transform: translate(-50%, -50%) scale(0); 346 | transition: 200ms ease-in-out; 347 | border: 1px solid black; 348 | border-radius: 10px; 349 | z-index: 10; 350 | background-color: white; 351 | width: 1000px; 352 | max-width: 80%; 353 | } 354 | 355 | .info.active { 356 | transform: translate(-50%, -50%) scale(1); 357 | } 358 | 359 | .info-header { 360 | display: flex; 361 | justify-content: space-between; 362 | align-items: center; 363 | padding: 10px 15px; 364 | border-bottom: 1px solid black; 365 | } 366 | 367 | .info-title { 368 | font-size: 2rem; 369 | font-weight: bold; 370 | flex: 1; 371 | display: flex; 372 | justify-content: center; 373 | align-items: center; 374 | } 375 | 376 | 377 | .info-header .info-title { 378 | align-items: center; 379 | font-size: 1.25rem; 380 | font-weight: bold; 381 | } 382 | 383 | .info-header .close-button { 384 | cursor: pointer; 385 | border: none; 386 | outline: none; 387 | background: none; 388 | font-size: 1.25rem; 389 | font-weight: bold; 390 | } 391 | 392 | .info-body { 393 | padding: 10px 15px; 394 | } 395 | 396 | #overlay { 397 | position: fixed; 398 | opacity: 0; 399 | transition: 200ms ease-in-out; 400 | top: 0; 401 | left: 0; 402 | right: 0; 403 | bottom: 0; 404 | background-color: rgba(0, 0, 0, .5); 405 | pointer-events: none; 406 | } 407 | 408 | #overlay.active { 409 | opacity: 1; 410 | pointer-events: all; 411 | } 412 | 413 | .part1-gif { 414 | width: 100%; 415 | height: 100%; 416 | } 417 | 418 | .part2-gif { 419 | width: 100%; 420 | height: 100%; 421 | } 422 | 423 | .info-body { 424 | display: grid; 425 | grid-template-columns: repeat(2, 1fr); 426 | grid-template-rows: auto; 427 | gap: 10px; 428 | } 429 | 430 | .info-body-text { 431 | display: grid; 432 | grid-template-rows: repeat(4, 1fr); 433 | grid-template-columns: auto; 434 | gap: 10px; 435 | } 436 | 437 | .info-body-text-page2 { 438 | display: grid; 439 | grid-template-rows: repeat(3, 1fr); 440 | grid-template-columns: auto; 441 | gap: 10px; 442 | } 443 | 444 | .info-footer-page1, 445 | .info-footer-page2 { 446 | padding: 10px 15px; 447 | display: flex; 448 | align-items: center; 449 | } 450 | 451 | .info-footer-page1 { 452 | justify-content: flex-start; 453 | } 454 | 455 | .info-footer-page2 { 456 | justify-content: flex-end; 457 | } 458 | 459 | .start-info, 460 | .end-info, 461 | .wall-info, 462 | .maze-info, 463 | .random-info { 464 | display: grid; 465 | padding: 20px; 466 | grid-template-columns: 75% 25%; 467 | grid-template-rows: auto; 468 | gap: 10px; 469 | } 470 | 471 | .info-sub-text { 472 | display: flex; 473 | align-items: center; 474 | text-align: center; 475 | } 476 | 477 | .start-alg-info, 478 | .drag-info { 479 | display: grid; 480 | align-items: center; 481 | text-align: center; 482 | } 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | /* SORTING CSS */ 494 | body.sorting { 495 | margin: 3px 10px; 496 | font-family: Inter,-apple-system,system-ui,"Segoe UI",Helvetica,Arial,sans-serif; 497 | background-color: rgb(239, 239, 239); 498 | } 499 | 500 | .sorting-container { 501 | display: flex; 502 | align-items: flex-end; 503 | justify-content: center; 504 | height: calc(80vh - 5px); 505 | } 506 | 507 | .bar { 508 | width: 10px; 509 | margin: 0 2px; 510 | background-color: #488fec; 511 | } 512 | 513 | .bottom-container-sorting { 514 | display: grid; 515 | grid-template-columns: 27% 27% 21% 25%; 516 | grid-template-rows: auto; 517 | gap: 10px; 518 | height: calc(20vh - 20px); 519 | } 520 | 521 | .sorting-settings-container, 522 | .sorting-buttons-container, 523 | .html-nav-container, 524 | .text-container { 525 | gap: 10px; 526 | padding: 25px; 527 | justify-content: center; 528 | } 529 | 530 | .sorting-buttons-container { 531 | display: flex; 532 | } 533 | 534 | .html-nav-container { 535 | display: grid; 536 | grid-template-columns: 10% 60%; 537 | grid-template-rows: auto; 538 | gap: 60px; 539 | padding: 20px; 540 | } 541 | 542 | .sliderstuff { 543 | justify-content: center; 544 | text-align: center; 545 | display: grid; 546 | grid-template-rows: 50% 50%; 547 | grid-template-columns: 100%; 548 | gap: 0px; 549 | } 550 | 551 | 552 | .checkboxstuff { 553 | justify-content: center; 554 | text-align: center; 555 | display: grid; 556 | grid-template-rows: 20% 60%; 557 | grid-template-columns: auto; 558 | gap: 0px; 559 | padding: 15px; 560 | } 561 | 562 | .sorting-settings-container { 563 | display: flex; 564 | flex-direction: column; 565 | padding: 10px; 566 | margin: 0; 567 | } 568 | 569 | .sort-alg-container { 570 | display: grid; 571 | grid-template-rows: repeat(2, 1fr); 572 | grid-template-columns: auto; 573 | gap: 25px; 574 | padding: 5px; 575 | } 576 | 577 | .text-container { 578 | text-align: center; 579 | display: flex; 580 | flex-direction: column; 581 | padding: 10px; 582 | margin: 0; 583 | } 584 | 585 | .text-container > p { 586 | margin: 10px; 587 | padding: 0; 588 | } 589 | -------------------------------------------------------------------------------- /js/sorting.js: -------------------------------------------------------------------------------- 1 | const delay = (time) => { 2 | return new Promise(resolve => setTimeout(resolve, time)); 3 | } 4 | 5 | 6 | ///////////////////////////////////////////////////////////////////////////////////////////// 7 | // AUDIO 8 | ///////////////////////////////////////////////////////////////////////////////////////////// 9 | let audioCtx = null; 10 | let audioOn = true; 11 | let currentAlgOn = false; 12 | 13 | function playNote(freq) { 14 | if (audioCtx == null) { 15 | audioCtx = new (AudioContext || webkitAudioContext || window.webkitAudioContext)(); 16 | } 17 | const gainNode = audioCtx.createGain(); 18 | gainNode.gain.value = 0.008; 19 | gainNode.connect(audioCtx.destination); 20 | const dur = 0.1; 21 | const osc = audioCtx.createOscillator(); 22 | osc.type = "triangle"; // change the oscillator type 23 | osc.frequency.value = freq; 24 | osc.connect(gainNode); 25 | osc.start(); 26 | osc.stop(audioCtx.currentTime + dur); 27 | } 28 | 29 | const audioToggle = document.getElementById('audioToggle'); 30 | audioToggle.addEventListener('change', () => { 31 | toggleAudio(); 32 | }); 33 | 34 | function toggleAudio() { 35 | audioOn = !audioOn; 36 | } 37 | 38 | 39 | 40 | 41 | ///////////////////////////////////////////////////////////////////////////////////////////// 42 | // Creating Bars 43 | ///////////////////////////////////////////////////////////////////////////////////////////// 44 | const container = document.querySelector('.sorting-container'); 45 | const containerHeight = container.clientHeight ; 46 | const slider = document.getElementById("mySlider"); 47 | const defaultValue = slider.defaultValue; 48 | const BARCOLOR = "#488fec"; 49 | const DONEBARCOLOR = "#3fcf1b"; 50 | const CURRENTBARCOLOR = "red"; 51 | 52 | let notDone = true; 53 | 54 | let numOfBars = defaultValue; 55 | 56 | function updateRandom() { 57 | numOfBars = slider.value; 58 | console.log(numOfBars); 59 | randomize(); 60 | } 61 | 62 | const speedSlider = document.getElementById("mySlider2"); 63 | const defaultSpeedSlider = speedSlider.defaultValue; 64 | const sliderMax = speedSlider.max; 65 | let speed = ((sliderMax - defaultSpeedSlider) * 10) / sliderMax; 66 | 67 | function updateSpeed() { 68 | speed = ((sliderMax - speedSlider.value) * 10) / sliderMax; 69 | console.log(speed); 70 | } 71 | 72 | let values = []; 73 | let barHeightValues = []; 74 | 75 | let numOfComparisons = 0; 76 | const outputP = document.getElementById("comparison-count"); 77 | outputP.textContent = "Comparisons: "; 78 | 79 | function updateComparisonNum() { 80 | numOfComparisons++; 81 | outputP.textContent = "Comparisons: " + numOfComparisons; 82 | } 83 | 84 | let numSwaps = 0; 85 | const outputSwap = document.getElementById("swap-count"); 86 | outputSwap.textContent = "Swaps: "; 87 | 88 | function updateSwapNum() { 89 | numSwaps++; 90 | outputSwap.textContent = "Swaps: " + numSwaps; 91 | } 92 | 93 | const timeComplex = document.getElementById("time-complexity"); 94 | timeComplex.textContent = "Time Complexity: "; 95 | 96 | function updateTimeComplexity(input) { 97 | // Change the bars' colors back to BARCOLOR 98 | setTimeout(() => { 99 | timeComplex.textContent = "Time Complexity: " + input; 100 | }, 80); 101 | } 102 | 103 | function randomize() { 104 | notDone = true; 105 | numOfComparisons = 0; 106 | numSwaps = 0; 107 | timeComplex.textContent = "Time Complexity: "; 108 | outputSwap.textContent = "Swaps: "; 109 | outputP.textContent = "Comparisons: "; 110 | currentAlgOn = false; 111 | let barsArray = []; 112 | barHeightValues = []; 113 | container.innerHTML = ''; 114 | 115 | // Calculate the maximum possible bar height based on the height of the container and the number of bars 116 | const maxBarHeight = containerHeight; 117 | 118 | // Calculate the step size for the height of each bar 119 | const stepSize = Math.floor(maxBarHeight / numOfBars); 120 | const barWidth = (container.clientWidth - 2 * (numOfBars - 1)) / numOfBars; // subtracting margins 121 | 122 | // Create an array with values from 1 to numOfBars 123 | values = Array.from({length: numOfBars}, (_, i) => i + 1); 124 | 125 | // Create a bar for each value and set its height to a value based on the step size and the index of the bar 126 | for (let i = 0; i < values.length; i++) { 127 | const bar = document.createElement('div'); 128 | bar.classList.add('bar'); 129 | bar.style.height = `${stepSize * (i + 1)}px`; 130 | bar.style.width = `${barWidth}px`; 131 | barsArray.push(bar); 132 | } 133 | 134 | // Shuffle the values randomly 135 | for (let i = barsArray.length - 1; i > 0; i--) { 136 | const j = Math.floor(Math.random() * (i + 1)); 137 | [barsArray[i], barsArray[j]] = [barsArray[j], barsArray[i]]; 138 | barHeightValues.push(parseInt(barsArray[i].style.height)); 139 | container.appendChild(barsArray[i]); 140 | } 141 | } 142 | 143 | window.onload = function() { 144 | values = [] 145 | randomize(); 146 | } 147 | 148 | 149 | 150 | 151 | 152 | ///////////////////////////////////////////////////////////////////////////////////////////// 153 | // Algorithms 154 | ///////////////////////////////////////////////////////////////////////////////////////////// 155 | 156 | async function colorFromRange(start, end) { 157 | notDone = false; 158 | for (let n = start; n < end; n++) { 159 | if (!currentAlgOn) { 160 | return; 161 | } 162 | container.children[n].style.backgroundColor = DONEBARCOLOR; 163 | if (audioOn) { 164 | playNote(200 + values[n] * 10); 165 | } 166 | await delay(speed); 167 | } 168 | } 169 | 170 | const swapBars = (indexOne, indexTwo) => { 171 | updateSwapNum(); 172 | // Highlight the bars in CURRENTBARCOLOR 173 | const bar1 = container.children[indexOne]; 174 | const bar2 = container.children[indexTwo]; 175 | bar1.style.backgroundColor = CURRENTBARCOLOR; 176 | bar2.style.backgroundColor = CURRENTBARCOLOR; 177 | 178 | // Swap the values in the array 179 | const temp = barHeightValues[indexOne]; 180 | barHeightValues[indexOne] = barHeightValues[indexTwo]; 181 | barHeightValues[indexTwo] = temp; 182 | 183 | // Update the corresponding bars' heights 184 | const tempHeight = bar1.style.height; 185 | bar1.style.height = bar2.style.height; 186 | bar2.style.height = tempHeight; 187 | 188 | // Change the bars' colors back to BARCOLOR 189 | setTimeout(() => { 190 | if (notDone) { 191 | bar1.style.backgroundColor = BARCOLOR; 192 | bar2.style.backgroundColor = BARCOLOR; 193 | } 194 | }, speed * 100); 195 | } 196 | 197 | const playAudio = (indexOne, indexTwo) => { 198 | if (audioOn) { 199 | playNote(200 + values[indexTwo] * 10); 200 | playNote(200 + values[indexOne] * 10); 201 | } 202 | } 203 | 204 | async function checkToStart() { 205 | if (currentAlgOn) { 206 | currentAlgOn = false; 207 | await delay(75); 208 | randomize(); 209 | } 210 | currentAlgOn = true; 211 | } 212 | 213 | 214 | 215 | // LINEAR SORT 216 | async function linearSort() { 217 | updateTimeComplexity('O(n)'); 218 | await checkToStart(); 219 | for (let i = 0; i < barHeightValues.length; i++) { 220 | for (let j = i + 1; j < barHeightValues.length; j++) { 221 | if (!currentAlgOn) { 222 | return; 223 | } 224 | updateComparisonNum(); 225 | if (barHeightValues[j] < barHeightValues[i]) { 226 | 227 | // Swap 228 | swapBars(i, j); 229 | 230 | // Play audio 231 | playAudio(i, j); 232 | 233 | // Add a delay to visualize the swap 234 | await delay(speed * 5); 235 | } 236 | } 237 | } 238 | 239 | await delay(200); 240 | 241 | colorFromRange(0, barHeightValues.length); 242 | currentAlgOn = true; 243 | } 244 | 245 | 246 | 247 | 248 | 249 | // BUBBLE SORT 250 | async function bubbleSort() { 251 | updateTimeComplexity('O(n' + '\u00B2' + ')'); 252 | await checkToStart(); 253 | for (let i = 0; i < barHeightValues.length; i++) { 254 | for (let j = 0; j < barHeightValues.length - i; j++) { 255 | if (!currentAlgOn) { 256 | return; 257 | } 258 | updateComparisonNum(); 259 | if (barHeightValues[j] > barHeightValues[j + 1]) { 260 | // Swap 261 | swapBars(j, j + 1); 262 | 263 | // Play audio 264 | playAudio(j, j + 1); 265 | 266 | // Add a delay to visualize the swap 267 | await delay(speed * 5); 268 | } 269 | } 270 | } 271 | 272 | await delay(200); 273 | 274 | colorFromRange(0, barHeightValues.length); 275 | currentAlgOn = true; 276 | } 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | // SELECTION SORT 286 | async function selectionSort() { 287 | updateTimeComplexity('O(n' + '\u00B2' + ')'); 288 | await checkToStart(); 289 | for (let i = 0; i < barHeightValues.length; i++) { 290 | let currentMin = i; 291 | for (let j = i + 1; j < barHeightValues.length; j++) { 292 | if (!currentAlgOn) { 293 | return; 294 | } 295 | updateComparisonNum(); 296 | if (barHeightValues[j] < barHeightValues[currentMin]) { 297 | currentMin = j; 298 | // Add a delay to visualize the swap 299 | await delay(speed * 8); 300 | } 301 | } 302 | 303 | if (currentMin !== i) { 304 | swapBars(currentMin, i); 305 | 306 | playAudio(currentMin, i); 307 | } 308 | 309 | } 310 | 311 | await delay(200); 312 | 313 | colorFromRange(0, barHeightValues.length); 314 | currentAlgOn = true; 315 | } 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | // INSERTION SORT 326 | async function insertionSort() { 327 | updateTimeComplexity('O(n' + '\u00B2' + ')'); 328 | await checkToStart(); 329 | 330 | for (let i = 1; i < barHeightValues.length; i++) { 331 | let j = i; 332 | while (j > 0 && barHeightValues[j - 1] > barHeightValues[j]) { 333 | if (!currentAlgOn) { 334 | return; 335 | } 336 | updateComparisonNum(); 337 | swapBars(j, j - 1); 338 | 339 | playAudio(j - 1, j); 340 | 341 | await delay(speed * 5); 342 | 343 | j -= 1; 344 | } 345 | } 346 | 347 | await delay(200); 348 | 349 | colorFromRange(0, barHeightValues.length); 350 | currentAlgOn = true; 351 | } 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | // MERGE SORT 364 | async function mergeSort() { 365 | updateTimeComplexity('O(n log n)'); 366 | await checkToStart(); 367 | await mergeSortHelper1(barHeightValues); 368 | 369 | if (!currentAlgOn) { 370 | return; 371 | } 372 | 373 | await delay(200); 374 | 375 | if (!currentAlgOn) { 376 | return; 377 | } 378 | 379 | colorFromRange(0, barHeightValues.length); 380 | currentAlgOn = true; 381 | } 382 | 383 | async function mergeSortHelper1(a) { 384 | if (!currentAlgOn) { 385 | return; 386 | } 387 | if (a.length == 1) { 388 | return a; 389 | } 390 | 391 | let midpoint = Math.ceil(a.length / 2); 392 | let firstHalf = a.slice(0, midpoint); 393 | let secondHalf = a.slice(-midpoint); 394 | 395 | 396 | firstHalf = await mergeSortHelper1(firstHalf); 397 | secondHalf = await mergeSortHelper1(secondHalf); 398 | 399 | 400 | return await mergeSortHelper2(firstHalf, secondHalf); 401 | } 402 | 403 | async function mergeSortHelper2(a, b) { 404 | if (!currentAlgOn) { 405 | return; 406 | } 407 | 408 | let c = []; 409 | 410 | while (a.length > 0 && b.length > 0) { 411 | if (!currentAlgOn) { 412 | return; 413 | } 414 | 415 | if (a[0] > b[0]) { 416 | if (!c.includes(b[0])) { 417 | c.push(b[0]); 418 | } 419 | b.shift(); 420 | } else { 421 | if (!c.includes(a[0])) { 422 | c.push(a[0]); 423 | } 424 | a.shift(); 425 | } 426 | } 427 | 428 | while (a.length > 0) { 429 | if (!currentAlgOn) { 430 | return; 431 | } 432 | 433 | if (!c.includes(a[0])) { 434 | c.push(a[0]); 435 | } 436 | a.shift(); 437 | } 438 | 439 | while (b.length > 0) { 440 | if (!currentAlgOn) { 441 | return; 442 | } 443 | 444 | if (!c.includes(b[0])) { 445 | c.push(b[0]); 446 | } 447 | b.shift(); 448 | } 449 | 450 | // Swap elements and update bars' heights 451 | if (barHeightValues.length > 0) { 452 | for (let i = 0; i < c.length; i++) { 453 | if (!currentAlgOn) { 454 | return; 455 | } 456 | updateComparisonNum(); 457 | if (barHeightValues[i] !== c[i]) { 458 | let j = barHeightValues.indexOf(c[i]); 459 | swapBars(i, j); 460 | playAudio(j, i); 461 | await delay(speed * 5); 462 | } 463 | } 464 | } 465 | 466 | return c; 467 | } 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | // QUICK SORT 479 | async function quickSort() { 480 | updateTimeComplexity('O(n log n) average case, O(n^2) worst case'); 481 | await checkToStart(); 482 | await quickSortHelper(barHeightValues, 0, barHeightValues.length - 1); 483 | if (!currentAlgOn) { 484 | return; 485 | } 486 | colorFromRange(0, barHeightValues.length); 487 | currentAlgOn = true; 488 | } 489 | 490 | async function quickSortHelper(a, low, high) { 491 | if (!currentAlgOn) { 492 | return; 493 | } 494 | if (low < high) { 495 | const pivotIndex = await quickSortHelper2(a, low, high); 496 | await quickSortHelper(a, low, pivotIndex - 1); 497 | await quickSortHelper(a, pivotIndex + 1, high); 498 | } 499 | } 500 | 501 | async function quickSortHelper2(a, low, high) { 502 | if (!currentAlgOn) { 503 | return; 504 | } 505 | const pivot = a[low]; 506 | let leftWall = low; 507 | 508 | for (let i = low + 1; i <= high; i++) { 509 | if (!currentAlgOn) { 510 | return; 511 | } 512 | updateComparisonNum(); 513 | if (a[i] < pivot) { 514 | leftWall++; 515 | swapBars(i, leftWall); 516 | playAudio(leftWall, i); 517 | await delay(speed * 5); 518 | } 519 | } 520 | 521 | 522 | swapBars(low, leftWall); 523 | playAudio(leftWall, low); 524 | await delay(speed * 5); 525 | 526 | if (!currentAlgOn) { 527 | return; 528 | } 529 | 530 | return leftWall; 531 | } 532 | 533 | 534 | 535 | 536 | 537 | 538 | // HEAP SORT 539 | async function heapSort() { 540 | updateTimeComplexity('O(n log n)'); 541 | await checkToStart(); 542 | 543 | await heapSortMain(barHeightValues); 544 | if (!currentAlgOn) { 545 | return; 546 | } 547 | 548 | colorFromRange(0, barHeightValues.length); 549 | currentAlgOn = true; 550 | } 551 | 552 | async function heapSortMain(a) { 553 | await buildMaxHeap(a); 554 | let n = a.length; 555 | for (let i = n - 1; i > 0; i--) { 556 | if (!currentAlgOn) { 557 | return; 558 | } 559 | updateComparisonNum(); 560 | swapBars(0, i); 561 | playAudio(i, 0); 562 | await delay(speed * 5); 563 | n--; 564 | await heapify(a, 0, n); 565 | } 566 | 567 | return a; 568 | } 569 | 570 | async function buildMaxHeap(a) { 571 | let n = a.length; 572 | 573 | for (let i = Math.floor(n / 2); i >= 0; i--) { 574 | if (!currentAlgOn) { 575 | return; 576 | } 577 | await heapify(a, i, n); 578 | } 579 | } 580 | 581 | async function heapify(a, i, n) { 582 | if (!currentAlgOn) { 583 | return; 584 | } 585 | let left = 2 * i + 1; 586 | let right = 2 * i + 2; 587 | let max = i; 588 | 589 | if (left < n && a[left] > a[max]) { 590 | max = left; 591 | } 592 | 593 | if (right < n && a[right] > a[max]) { 594 | max = right; 595 | } 596 | 597 | if (max !== i) { 598 | if (!currentAlgOn) { 599 | return; 600 | } 601 | updateComparisonNum(); 602 | swapBars(max, i); 603 | playAudio(i, max); 604 | await delay(speed * 5); 605 | await heapify(a, max, n); 606 | } 607 | } 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | // BOGO SORT 630 | async function bogoSort() { 631 | updateTimeComplexity('O((n+1)!) on average, O(infinity) worst case'); 632 | await checkToStart(); 633 | while (!checkIfDone()) { 634 | for (let i = 0; i < barHeightValues.length; i++) { 635 | if (!currentAlgOn) { 636 | return; 637 | } 638 | updateComparisonNum(); 639 | const j = Math.floor(Math.random() * barHeightValues.length); 640 | playAudio(j, i); 641 | swapBars(i, j); 642 | } 643 | 644 | 645 | await delay(speed * 5); 646 | } 647 | 648 | colorFromRange(0, barHeightValues.length); 649 | currentAlgOn = true; 650 | } 651 | 652 | const checkIfDone = () => { 653 | 654 | for (let i = 1; i < barHeightValues.length; i++) { 655 | if (barHeightValues[i - 1] > barHeightValues[i]) { 656 | return false; 657 | } 658 | } 659 | 660 | return true; 661 | } -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | const delay = (time) => { 2 | return new Promise(resolve => setTimeout(resolve, time)); 3 | } 4 | 5 | const visitedAnimation = (t, color) => { 6 | t.element.style.opacity = 0; 7 | t.element.style.backgroundColor = color; 8 | t.element.offsetWidth; 9 | t.element.style.opacity = 1; 10 | t.element.style.transition = 'opacity 0.4s ease-in-out'; 11 | 12 | t.element.addEventListener('transitionend', function() { 13 | t.element.style.transition = ''; 14 | }); 15 | } 16 | 17 | const wallColor = 'black'; 18 | const emptyTileColor = 'white'; 19 | const visitedTileColor = '#75a7e6'; 20 | const finalPathTileColor = '#f5ff5e' 21 | const startTileColor = 'green'; 22 | const finalTileColor = 'red'; 23 | 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 25 | // TILES CLASS 26 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 27 | let startTile = -1; 28 | let endTile = -1; 29 | let previousStartTile = null; 30 | let previousEndTile = null; 31 | let isMouseDown = false; 32 | let currentAlg = -1; 33 | let pathFindingDone = false; 34 | let clickedOnToMove = -1; 35 | 36 | class Tile { 37 | constructor(number, r, c) { 38 | this.number = number; 39 | this.isWall = false; 40 | this.row = r; 41 | this.col = c; 42 | this.neighbors = []; 43 | } 44 | 45 | getTileRow = () => { 46 | return this.row; 47 | } 48 | 49 | getTileCol = () => { 50 | return this.col; 51 | } 52 | 53 | isTileWall = () => { 54 | return this.isWall; 55 | } 56 | 57 | setTileWall = () => { 58 | this.isWall = true; 59 | } 60 | 61 | setTileNotWall = () => { 62 | this.isWall = false; 63 | } 64 | 65 | getTileNumber = () => { 66 | return this.tileNumber; 67 | } 68 | 69 | appendNeighbors = (neighbor) => { 70 | this.neighbors.push(neighbor); 71 | } 72 | 73 | handleClick = () => { 74 | if (editMode) { 75 | if (selectedColor === startTileColor) { 76 | if (previousStartTile) { 77 | previousStartTile.element.style.backgroundColor = emptyTileColor; 78 | } 79 | 80 | this.setTileNotWall(); 81 | startTile = this.number; 82 | previousStartTile = this; 83 | } 84 | 85 | if (selectedColor === finalTileColor) { 86 | if (previousEndTile) { 87 | previousEndTile.element.style.backgroundColor = emptyTileColor; 88 | } 89 | 90 | this.setTileNotWall(); 91 | endTile = this.number; 92 | previousEndTile = this; 93 | } 94 | 95 | visitedAnimation(this, selectedColor); 96 | } 97 | 98 | if (selectedColor === wallColor) { 99 | if (this.isTileWall()) { 100 | this.setTileNotWall(); 101 | this.element.style.backgroundColor = emptyTileColor; 102 | } 103 | 104 | if (this.number === endTile) { 105 | endTile = -1; 106 | } else if (this.number === startTile) { 107 | startTile = -1; 108 | } 109 | } 110 | 111 | 112 | } 113 | 114 | // Creates a tile as a div 115 | createElement = (size) => { 116 | const tile = document.createElement('div'); 117 | tile.classList.add('tile'); 118 | tile.style.width = size + 'px'; 119 | tile.style.height = size + 'px'; 120 | tile.addEventListener('click', this.handleClick.bind(this)); 121 | 122 | tile.addEventListener('mousedown', () => { 123 | isMouseDown = true; 124 | }); 125 | 126 | tile.addEventListener('mousemove', () => { 127 | if (isMouseDown && this.number === startTile) { 128 | clickedOnToMove = 1; 129 | } else if (isMouseDown && this.number === endTile) { 130 | clickedOnToMove = 0; 131 | } 132 | 133 | if (isMouseDown && editMode && selectedColor === wallColor && !this.isTileWall()) { 134 | this.setTileWall(); 135 | visitedAnimation(this, selectedColor); 136 | 137 | if (this.number === endTile) { 138 | endTile = -1; 139 | } else if (this.number === startTile) { 140 | startTile = -1; 141 | } 142 | } 143 | 144 | if (isMouseDown && pathFindingDone) { 145 | if (clickedOnToMove == 0) { 146 | if (!this.isTileWall() && this.number !== startTile && previousEndTile !== this) { 147 | if (previousEndTile) { 148 | previousEndTile.element.style.backgroundColor = emptyTileColor; 149 | } 150 | 151 | this.setTileNotWall(); 152 | endTile = this.number; 153 | previousEndTile = this; 154 | this.element.style.backgroundColor = finalTileColor; 155 | } 156 | 157 | } 158 | 159 | if (clickedOnToMove == 1) { 160 | if (!this.isTileWall() && this.number !== endTile && previousStartTile !== this) { 161 | if (previousStartTile) { 162 | previousStartTile.element.style.backgroundColor = emptyTileColor; 163 | } 164 | 165 | this.setTileNotWall(); 166 | startTile = this.number; 167 | previousStartTile = this; 168 | this.element.style.backgroundColor = startTileColor; 169 | } 170 | 171 | } 172 | 173 | if (currentAlg == 0) { 174 | bfsTime(0); 175 | } else if (currentAlg == 1) { 176 | dfsTime(0); 177 | } else if (currentAlg == 2) { 178 | dijTime(0, 'euclidean'); 179 | } else if (currentAlg == 3) { 180 | dijTime(0, 'manhattan'); 181 | } else if (currentAlg == 4) { 182 | astarTime(0, 'euclidean'); 183 | } else if (currentAlg == 5) { 184 | astarTime(0, 'manhattan'); 185 | } else if (currentAlg == 6) { 186 | greedyTime(0, 'euclidean'); 187 | } else if (currentAlg == 7) { 188 | greedyTime(0, 'manhattan'); 189 | } 190 | } 191 | 192 | 193 | }); 194 | 195 | document.addEventListener('mouseup', () => { 196 | isMouseDown = false; 197 | }); 198 | 199 | this.element = tile; 200 | return tile; 201 | } 202 | 203 | } 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 213 | // CREATING GRID 214 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 215 | 216 | // Grid Size 217 | const gridSizeX = 37; 218 | const gridSizeY = 15; 219 | let tileNumber = gridSizeX * gridSizeY; 220 | let tiles = []; 221 | let editMode = true; 222 | let resetOn = false; 223 | let tileSize = updateTileSize(); 224 | 225 | // Listen for changes in the width of the container 226 | window.addEventListener("resize", () => { 227 | const gridContainer = document.querySelector(".grid-container"); 228 | let width = gridContainer.offsetWidth; 229 | let height = width * 15 / 37; // Set height to be 15/37 of the width 230 | 231 | gridContainer.style.height = `${height}px`; // Set the height of the container using the style property 232 | tileSize = width / 37 - 2; 233 | updateTileSize2(); 234 | }); 235 | 236 | function updateTileSize2() { 237 | for (let i = 0; i < tileElementArray.length; i++) { 238 | tileElementArray[i].style.width = tileSize + 'px'; 239 | tileElementArray[i].style.height = tileSize + 'px'; 240 | } 241 | } 242 | 243 | function updateTileSize() { 244 | const gridContainer = document.querySelector(".grid-container"); 245 | const width = gridContainer.offsetWidth; 246 | const tileSize = width / 37 - 2; 247 | return tileSize; 248 | } 249 | 250 | let tileElementArray = []; 251 | 252 | // Creates the tile map 253 | const createTiles = () => { 254 | tileElementArray = []; 255 | tiles = []; 256 | const gridContainer = document.querySelector(".grid-container"); 257 | gridContainer.innerHTML = ""; 258 | 259 | // Appends the tiles to the grid Container 260 | for (let i = 0; i < tileNumber; i++) { 261 | row = Math.floor(i / gridSizeX); 262 | col = i % gridSizeX; 263 | const tile = new Tile(i, row, col); 264 | const tileElement = tile.createElement(tileSize); 265 | tileElementArray.push(tileElement); 266 | tiles.push(tile); 267 | gridContainer.appendChild(tileElement); 268 | } 269 | } 270 | 271 | const updateNeighbors = (tile, allTiles) => { 272 | // Calculate the indices of the neighboring tiles 273 | const indices = [ 274 | tile.number - gridSizeX, // Top 275 | tile.number + gridSizeX, // Bottom 276 | tile.number - 1, // Left 277 | tile.number + 1, // Right 278 | ]; 279 | 280 | // Clear the existing neighbors array 281 | tile.neighbors = []; 282 | 283 | // Add the neighboring tiles to the neighbors array 284 | indices.forEach((index) => { 285 | // Check if the index is within the bounds of the array 286 | if (index >= 0 && index < tileNumber) { 287 | const neighbor = allTiles[index]; 288 | 289 | // Check if the neighbor is in the same row or column as the tile 290 | if (neighbor.row === tile.row || neighbor.col === tile.col) { 291 | if (!neighbor.isTileWall()) tile.neighbors.push(neighbor); 292 | } 293 | } 294 | }); 295 | } 296 | 297 | const updateTileNeighbors = () => { 298 | for (let j = 0; j < tiles.length; j++) { 299 | updateNeighbors(tiles[j], tiles) 300 | } 301 | } 302 | 303 | // Resets the tile map 304 | async function reset() { 305 | openTiles = []; 306 | clickedOnToMove = -1; 307 | startTile = -1; 308 | endTile = -1; 309 | previousStartTile = null; 310 | previousEndTile = null; 311 | isMouseDown = false; 312 | resetOn = true; 313 | editMode = true; 314 | currentAlg = -1; 315 | pathFindingDone = false; 316 | //await delay(25); 317 | visitedTiles = [] 318 | selectedColor = ''; 319 | createTiles(); 320 | } 321 | 322 | // Changes the color of the tile 323 | let selectedColor = ''; 324 | const changeColor = (color) => { 325 | selectedColor = color; 326 | } 327 | 328 | window.onload = function() { 329 | createTiles(); 330 | } 331 | 332 | const slider = document.getElementById("mySlider"); 333 | const defaultValue = slider.defaultValue; 334 | 335 | function updateRandom() { 336 | const value = slider.value; 337 | random(value); 338 | } 339 | 340 | async function random(ratio) { 341 | reset(); 342 | await delay(25); 343 | for (let i = 0; i < tiles.length; i++) { 344 | let randomNumber = Math.random(); 345 | if (randomNumber < ratio) { 346 | tiles[i].setTileWall(); 347 | visitedAnimation(tiles[i], wallColor); 348 | } 349 | } 350 | let tiles2 = tiles; 351 | 352 | let startIndex = Math.floor(Math.random() * tiles2.length); 353 | tiles2.slice(startIndex, 1); 354 | let endIndex = Math.floor(Math.random() * tiles2.length); 355 | 356 | startTile = startIndex; 357 | visitedAnimation(tiles[startIndex], startTileColor); 358 | tiles[startIndex].setTileNotWall() 359 | previousStartTile = tiles[startIndex]; 360 | 361 | endTile = endIndex; 362 | visitedAnimation(tiles[endIndex], finalTileColor); 363 | tiles[endIndex].setTileNotWall() 364 | previousEndTile = tiles[endIndex]; 365 | } 366 | 367 | 368 | 369 | ///////////////////////////////////////////// 370 | // Info Stuff 371 | ///////////////////////////////////////////// 372 | const openInfoButtons = document.querySelectorAll('[data-info-target]'); 373 | const closeInfoButtons = document.querySelectorAll('[data-close-button]'); 374 | const page1 = document.querySelector('.page1'); 375 | const page2 = document.querySelector('.page2'); 376 | const overlay = document.getElementById('overlay'); 377 | 378 | openInfoButtons.forEach(button => { 379 | button.addEventListener('click', () => { 380 | const info = document.querySelector(button.dataset.infoTarget); 381 | openInfo(info); 382 | }); 383 | }); 384 | 385 | overlay.addEventListener('click', () => { 386 | const infos = document.querySelectorAll('.info.active'); 387 | infos.forEach(info => { 388 | closeInfo(info); 389 | }); 390 | }); 391 | 392 | closeInfoButtons.forEach(button => { 393 | button.addEventListener('click', () => { 394 | const info = button.closest('.info'); 395 | closeInfo(info); 396 | }); 397 | }); 398 | 399 | function openInfo (info) { 400 | if (info == null) return; 401 | info.classList.add('active'); 402 | overlay.classList.add('active'); 403 | } 404 | 405 | function closeInfo (info) { 406 | if (info == null) return; 407 | info.classList.remove('active'); 408 | overlay.classList.remove('active'); 409 | setTimeout(() => { 410 | resetPages(); 411 | }, 100); 412 | } 413 | 414 | function togglePages() { 415 | page1.style.display = 'none'; 416 | page2.style.display = 'block'; 417 | } 418 | 419 | function resetPages() { 420 | page2.style.display = 'none'; 421 | page1.style.display = 'block'; 422 | } 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 436 | // Algorithms 437 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 438 | let visitedTiles = [] 439 | 440 | const buildGraph = () => { 441 | const graph = {}; 442 | 443 | for (let i = 0; i < tiles.length; i++) { 444 | const tile = tiles[i]; 445 | graph[tile.number] = []; 446 | 447 | for (let j = 0; j < tile.neighbors.length; j++) { 448 | const neighbor = tile.neighbors[j]; 449 | graph[tile.number].push(neighbor.number); 450 | } 451 | } 452 | return graph; 453 | } 454 | 455 | const resetVisitedTiles = () => { 456 | for (let i = 0; i < visitedTiles.length; i++) { 457 | if (visitedTiles[i].number !== startTile && visitedTiles[i].number !== endTile) { 458 | visitedTiles[i].element.style.backgroundColor = emptyTileColor; 459 | } 460 | } 461 | 462 | visitedTiles = []; 463 | } 464 | 465 | 466 | const checkToStart = () => { 467 | return (endTile > -1 && startTile > -1); 468 | } 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | /////////////////////// 478 | // BFS 479 | /////////////////////// 480 | 481 | const bfs = () => { 482 | resetOn = true; 483 | pathFindingDone = false; 484 | currentAlg = 0; 485 | bfsTime(30) 486 | } 487 | 488 | async function bfsTime(delayTime) { 489 | await delay(50); 490 | selectedColor = ''; 491 | if (!checkToStart()) { 492 | return; 493 | } 494 | 495 | editMode = false; 496 | resetOn = false; 497 | updateTileNeighbors(); 498 | const graph = buildGraph(); 499 | const visited = new Set([startTile]); 500 | const queue = [[startTile, 0]]; 501 | const prev = {}; 502 | let endNode = null; 503 | resetVisitedTiles(); 504 | 505 | outerLoop : while (queue.length > 0) { 506 | if (resetOn) { 507 | return; 508 | } 509 | if (delayTime > 0) { 510 | await delay(delayTime); 511 | } 512 | const [node, distance] = queue.shift(); 513 | 514 | if (node == endTile) { 515 | endNode = node; 516 | break; 517 | } 518 | 519 | for (let neighbor of graph[node]) { 520 | if (!visited.has(neighbor)) { 521 | visited.add(neighbor); 522 | queue.push([neighbor, distance + 1]); 523 | if (neighbor !== startTile && neighbor !== endTile) { 524 | if (delayTime == 0) { 525 | tiles[neighbor].element.style.backgroundColor = visitedTileColor; 526 | } else { 527 | visitedAnimation(tiles[neighbor], visitedTileColor); 528 | } 529 | visitedTiles.push(tiles[neighbor]); 530 | } 531 | prev[neighbor] = node; 532 | if (endTile == neighbor) { 533 | endNode = neighbor; 534 | break outerLoop; 535 | } 536 | } 537 | } 538 | } 539 | 540 | // Color the path from end to start 541 | let node = endNode; 542 | while (node != startTile && !resetOn) { 543 | if (node !== endNode) { 544 | 545 | if (delayTime == 0) { 546 | tiles[node].element.style.backgroundColor = finalPathTileColor; 547 | } else { 548 | visitedAnimation(tiles[node], finalPathTileColor); 549 | await delay(delayTime); 550 | } 551 | } 552 | node = prev[node]; 553 | } 554 | 555 | pathFindingDone = true; 556 | 557 | } 558 | 559 | 560 | 561 | 562 | 563 | 564 | /////////////////////// 565 | // DFS 566 | /////////////////////// 567 | 568 | const dfs = () => { 569 | resetOn = true; 570 | pathFindingDone = false; 571 | currentAlg = 1; 572 | dfsTime(30) 573 | } 574 | 575 | async function dfsTime(delayTime) { 576 | await delay(50); 577 | selectedColor = ''; 578 | if (!checkToStart()) { 579 | return; 580 | } 581 | 582 | editMode = false; 583 | resetOn = false; 584 | updateTileNeighbors(); 585 | const graph = buildGraph(); 586 | const visited = new Set([startTile]); 587 | const array = [[startTile, 0]]; 588 | const prev = {}; 589 | let endNode = null; 590 | resetVisitedTiles(); 591 | 592 | outerLoop : while (array.length > 0) { 593 | if (resetOn) { 594 | return; 595 | } 596 | if (delayTime > 0) { 597 | await delay(delayTime); 598 | } 599 | const [node, distance] = array.pop(); 600 | 601 | if (node == endTile) { 602 | endNode = node; 603 | break; 604 | } 605 | 606 | for (let neighbor of graph[node]) { 607 | if (!visited.has(neighbor)) { 608 | visited.add(neighbor); 609 | array.push([neighbor, distance + 1]); 610 | if (neighbor !== startTile && neighbor !== endTile) { 611 | if (delayTime == 0) { 612 | tiles[neighbor].element.style.backgroundColor = visitedTileColor; 613 | } else { 614 | visitedAnimation(tiles[neighbor], visitedTileColor); 615 | } 616 | visitedTiles.push(tiles[neighbor]); 617 | } 618 | prev[neighbor] = node; 619 | if (endTile == neighbor) { 620 | endNode = neighbor; 621 | break outerLoop; 622 | } 623 | } 624 | } 625 | } 626 | 627 | // Color the path from end to start 628 | let node = endNode; 629 | while (node != startTile && !resetOn) { 630 | if (node !== endNode) { 631 | if (delayTime == 0) { 632 | tiles[node].element.style.backgroundColor = finalPathTileColor; 633 | } else { 634 | visitedAnimation(tiles[node], finalPathTileColor); 635 | await delay(delayTime); 636 | } 637 | } 638 | node = prev[node]; 639 | } 640 | 641 | pathFindingDone = true; 642 | 643 | } 644 | 645 | 646 | 647 | 648 | 649 | 650 | /////////////////////// 651 | // Dijkstra 652 | /////////////////////// 653 | 654 | const addToQueueDij = (queue, tile, type) => { 655 | let startR = getRow(startTile); 656 | let startC = getCol(startTile); 657 | let dis = -1; 658 | 659 | if (type === 'euclidean') { 660 | dis = Math.sqrt(Math.pow(getCol(tile) - startC, 2) + Math.pow(getRow(tile) - startR, 2)); 661 | } else if (type === 'manhattan') { 662 | dis = Math.abs(getCol(tile) - startC) + Math.abs(getRow(tile) - startR); 663 | } 664 | 665 | const newItem = [tile, dis]; 666 | 667 | let i = 0; 668 | 669 | // Iterate through the queue and find the index where the new item should be inserted based on its fCost 670 | while (i < queue.length && queue[i][1] <= dis) { 671 | i++; 672 | } 673 | 674 | // Insert the new item at the correct index 675 | queue.splice(i, 0, newItem); 676 | 677 | return queue; 678 | } 679 | 680 | const dijkstra = (type) => { 681 | resetOn = true; 682 | pathFindingDone = false; 683 | if (type === 'euclidean') { 684 | currentAlg = 2; 685 | } else { 686 | currentAlg = 3; 687 | } 688 | dijTime(30, type) 689 | } 690 | 691 | async function dijTime(delayTime, type) { 692 | await delay(50); 693 | selectedColor = ''; 694 | if (!checkToStart()) { 695 | return; 696 | } 697 | 698 | editMode = false; 699 | updateTileNeighbors(); 700 | const graph = buildGraph(); 701 | const visited = new Set([startTile]); 702 | let prioQueue = [[startTile, 0]]; 703 | const prev = {}; 704 | let endNode = null; 705 | resetVisitedTiles(); 706 | resetOn = false; 707 | 708 | outerLoop : while (prioQueue.length > 0) { 709 | if (resetOn) { 710 | return; 711 | } 712 | if (delayTime > 0) { 713 | await delay(delayTime); 714 | } 715 | 716 | const [node, distance] = prioQueue.shift(); 717 | 718 | if (node == endTile) { 719 | endNode = node; 720 | break; 721 | } 722 | 723 | for (let neighbor of graph[node]) { 724 | if (!visited.has(neighbor)) { 725 | visited.add(neighbor); 726 | prioQueue = addToQueueDij(prioQueue, neighbor, type); 727 | 728 | if (neighbor !== startTile && neighbor !== endTile) { 729 | if (delayTime == 0) { 730 | tiles[neighbor].element.style.backgroundColor = visitedTileColor; 731 | } else { 732 | visitedAnimation(tiles[neighbor], visitedTileColor); 733 | } 734 | visitedTiles.push(tiles[neighbor]); 735 | } 736 | prev[neighbor] = node; 737 | if (endTile == neighbor) { 738 | endNode = neighbor; 739 | break outerLoop; 740 | } 741 | } 742 | } 743 | 744 | 745 | } 746 | 747 | // Color the path from end to start 748 | let node = endNode; 749 | while (node != startTile && !resetOn) { 750 | if (node !== endNode) { 751 | if (delayTime == 0) { 752 | tiles[node].element.style.backgroundColor = finalPathTileColor; 753 | } else { 754 | visitedAnimation(tiles[node], finalPathTileColor); 755 | await delay(delayTime); 756 | } 757 | } 758 | node = prev[node]; 759 | } 760 | 761 | // Color the path from end to start 762 | 763 | pathFindingDone = true; 764 | } 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | /////////////////////// 774 | // AStar 775 | /////////////////////// 776 | 777 | const getRow = (num) => { 778 | return Math.floor(num / gridSizeX); 779 | } 780 | 781 | const getCol = (num) => { 782 | return num % gridSizeX; 783 | } 784 | 785 | const calculateFCost = (tile, type) => { 786 | let gCost, hCost = -1; 787 | let endR = getRow(endTile); 788 | let endC = getCol(endTile); 789 | let startR = getRow(startTile); 790 | let startC = getCol(startTile); 791 | 792 | if (type == 'euclidean') { 793 | gCost = Math.sqrt(Math.pow(getCol(tile) - startC, 2) + Math.pow(getRow(tile) - startR, 2)); 794 | hCost = Math.sqrt(Math.pow(getCol(tile) - endC, 2) + Math.pow(getRow(tile) - endR, 2)); 795 | } else if (type == 'manhattan') { 796 | gCost = Math.abs(getCol(tile) - startC) + Math.abs(getRow(tile) - startR); 797 | hCost = Math.abs(getCol(tile) - endC) + Math.abs(getRow(tile) - endR); 798 | } else { 799 | gCost = Math.abs(getCol(tile) - startC) + Math.abs(getRow(tile) - startR); 800 | hCost = Math.abs(getCol(tile) - endC) + Math.abs(getRow(tile) - endR); 801 | } 802 | 803 | return gCost + hCost; 804 | } 805 | 806 | const addToQueue = (queue, tile, type) => { 807 | const fCost = calculateFCost(tile, type); 808 | const newItem = [tile, fCost]; 809 | 810 | let i = 0; 811 | 812 | // Iterate through the queue and find the index where the new item should be inserted based on its fCost 813 | while (i < queue.length && queue[i][1] <= fCost) { 814 | i++; 815 | } 816 | 817 | // Insert the new item at the correct index 818 | queue.splice(i, 0, newItem); 819 | 820 | return queue; 821 | } 822 | 823 | 824 | const aStar = (type) => { 825 | resetOn = true; 826 | pathFindingDone = false; 827 | if (type === 'euclidean') { 828 | currentAlg = 4; 829 | } else { 830 | currentAlg = 5; 831 | } 832 | astarTime(30, type) 833 | } 834 | 835 | async function astarTime(delayTime, type) { 836 | await delay(50); 837 | selectedColor = ''; 838 | if (!checkToStart()) { 839 | return; 840 | } 841 | 842 | editMode = false; 843 | resetOn = false; 844 | updateTileNeighbors(); 845 | const graph = buildGraph(); 846 | const visited = new Set([startTile]); 847 | let prioQueue = [[startTile, calculateFCost(startTile, type)]]; 848 | const prev = {}; 849 | let endNode = null; 850 | resetVisitedTiles(); 851 | 852 | outerLoop : while (prioQueue.length > 0) { 853 | if (resetOn) { 854 | return; 855 | } 856 | if (delayTime > 0) { 857 | await delay(delayTime); 858 | } 859 | 860 | const [node, fCost] = prioQueue.shift(); 861 | 862 | if (node == endTile) { 863 | endNode = node; 864 | break; 865 | } 866 | 867 | for (let neighbor of graph[node]) { 868 | if (!visited.has(neighbor)) { 869 | visited.add(neighbor); 870 | prioQueue = addToQueue(prioQueue, neighbor, type); 871 | 872 | if (neighbor !== startTile && neighbor !== endTile) { 873 | if (delayTime == 0) { 874 | tiles[neighbor].element.style.backgroundColor = visitedTileColor; 875 | } else { 876 | visitedAnimation(tiles[neighbor], visitedTileColor); 877 | } 878 | visitedTiles.push(tiles[neighbor]); 879 | } 880 | prev[neighbor] = node; 881 | if (endTile == neighbor) { 882 | endNode = neighbor; 883 | break outerLoop; 884 | } 885 | } 886 | } 887 | 888 | 889 | } 890 | 891 | // Color the path from end to start 892 | let node = endNode; 893 | while (node != startTile && !resetOn) { 894 | if (node !== endNode) { 895 | if (delayTime == 0) { 896 | tiles[node].element.style.backgroundColor = finalPathTileColor; 897 | } else { 898 | visitedAnimation(tiles[node], finalPathTileColor); 899 | await delay(delayTime); 900 | } 901 | } 902 | node = prev[node]; 903 | } 904 | 905 | // Color the path from end to start 906 | 907 | pathFindingDone = true; 908 | } 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | /////////////////////// 918 | // Greedy BFS 919 | /////////////////////// 920 | 921 | const addToQueueGreed = (queue, tile, type) => { 922 | let endR = getRow(endTile); 923 | let endC = getCol(endTile); 924 | let dis = -1; 925 | 926 | if (type === 'euclidean') { 927 | dis = Math.sqrt(Math.pow(getCol(tile) - endC, 2) + Math.pow(getRow(tile) - endR, 2)); 928 | } else if (type === 'manhattan') { 929 | dis = Math.abs(getCol(tile) - endC) + Math.abs(getRow(tile) - endR); 930 | } 931 | 932 | const newItem = [tile, dis]; 933 | 934 | let i = 0; 935 | 936 | // Iterate through the queue and find the index where the new item should be inserted based on its fCost 937 | while (i < queue.length && queue[i][1] <= dis) { 938 | i++; 939 | } 940 | 941 | // Insert the new item at the correct index 942 | queue.splice(i, 0, newItem); 943 | 944 | return queue; 945 | } 946 | 947 | const greedyBFS = (type) => { 948 | resetOn = true; 949 | pathFindingDone = false; 950 | if (type === 'euclidean') { 951 | currentAlg = 6; 952 | } else { 953 | currentAlg = 7; 954 | } 955 | greedyTime(30, type) 956 | } 957 | 958 | async function greedyTime(delayTime, type) { 959 | await delay(50); 960 | selectedColor = ''; 961 | if (!checkToStart()) { 962 | return; 963 | } 964 | 965 | editMode = false; 966 | resetOn = false; 967 | updateTileNeighbors(); 968 | const graph = buildGraph(); 969 | const visited = new Set([startTile]); 970 | let prioQueue = [[startTile, 0]]; 971 | const prev = {}; 972 | let endNode = null; 973 | resetVisitedTiles(); 974 | 975 | outerLoop : while (prioQueue.length > 0) { 976 | if (resetOn) { 977 | return; 978 | } 979 | if (delayTime > 0) { 980 | await delay(delayTime); 981 | } 982 | 983 | const [node, distance] = prioQueue.shift(); 984 | 985 | if (node == endTile) { 986 | endNode = node; 987 | break; 988 | } 989 | 990 | for (let neighbor of graph[node]) { 991 | if (!visited.has(neighbor)) { 992 | visited.add(neighbor); 993 | prioQueue = addToQueueGreed(prioQueue, neighbor, type); 994 | 995 | if (neighbor !== startTile && neighbor !== endTile) { 996 | if (delayTime == 0) { 997 | tiles[neighbor].element.style.backgroundColor = visitedTileColor; 998 | } else { 999 | visitedAnimation(tiles[neighbor], visitedTileColor); 1000 | } 1001 | visitedTiles.push(tiles[neighbor]); 1002 | } 1003 | prev[neighbor] = node; 1004 | if (endTile == neighbor) { 1005 | endNode = neighbor; 1006 | break outerLoop; 1007 | } 1008 | } 1009 | } 1010 | 1011 | 1012 | } 1013 | 1014 | // Color the path from end to start 1015 | let node = endNode; 1016 | while (node != startTile && !resetOn) { 1017 | if (node !== endNode) { 1018 | if (delayTime == 0) { 1019 | tiles[node].element.style.backgroundColor = finalPathTileColor; 1020 | } else { 1021 | visitedAnimation(tiles[node], finalPathTileColor); 1022 | await delay(delayTime); 1023 | } 1024 | } 1025 | node = prev[node]; 1026 | } 1027 | 1028 | // Color the path from end to start 1029 | 1030 | pathFindingDone = true; 1031 | } 1032 | 1033 | 1034 | 1035 | 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1043 | // Maze 1044 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1045 | let openTiles = []; 1046 | 1047 | async function maze() { 1048 | reset(); 1049 | await delay(25); 1050 | pathFindingDone = false; 1051 | for (let i = 0; i < tiles.length; i++) { 1052 | let r = getRow(tiles[i].number); 1053 | let c = getCol(tiles[i].number); 1054 | if (r % 2 == 0 && c % 2 == 0) { 1055 | tiles[i].setTileWall(); 1056 | visitedAnimation(tiles[i], wallColor); 1057 | } 1058 | else if (r % 2 != 1 || c % 2 != 1) { 1059 | visitedAnimation(tiles[i], wallColor); 1060 | if (r == 0 || r == gridSizeY - 1 || c == 0 || c == gridSizeX - 1) { 1061 | tiles[i].setTileWall(); 1062 | } 1063 | } else { 1064 | openTiles.push(tiles[i]); 1065 | } 1066 | 1067 | } 1068 | createMazeNeighbors(); 1069 | dfsMaze(); 1070 | } 1071 | 1072 | const createMazeNeighbors = () => { 1073 | for (let i = 0; i < openTiles.length; i++) { 1074 | let tile = openTiles[i]; 1075 | // Calculate the indices of the neighboring tiles 1076 | const indices = [ 1077 | tile.number - (gridSizeX * 2), // Top 1078 | tile.number + (gridSizeX * 2), // Bottom 1079 | tile.number - 2, // Left 1080 | tile.number + 2, // Right 1081 | ]; 1082 | 1083 | // Clear the existing neighbors array 1084 | tile.neighbors = []; 1085 | 1086 | // Add the neighboring tiles to the neighbors array 1087 | indices.forEach((index) => { 1088 | // Check if the index is within the bounds of the array 1089 | if (index >= 0 && index < ((gridSizeX) * (gridSizeY))) { 1090 | const neighbor = tiles[index]; 1091 | 1092 | // Check if the neighbor is in the same row or column as the tile 1093 | if (neighbor.row === tile.row || neighbor.col === tile.col) { 1094 | if (!neighbor.isTileWall()) tile.neighbors.push(neighbor); 1095 | } 1096 | } 1097 | }); 1098 | } 1099 | } 1100 | 1101 | const buildMazeGraph = () => { 1102 | const graph = {}; 1103 | 1104 | for (let i = 0; i < openTiles.length; i++) { 1105 | const tile = openTiles[i]; 1106 | graph[tile.number] = []; 1107 | 1108 | for (let j = 0; j < tile.neighbors.length; j++) { 1109 | const neighbor = tile.neighbors[j]; 1110 | graph[tile.number].push(neighbor.number); 1111 | } 1112 | } 1113 | return graph; 1114 | } 1115 | 1116 | const getRidOfWall = (neighbor, node, color) => { 1117 | neR = getRow(neighbor); 1118 | noR = getRow(node) 1119 | 1120 | if (neR - noR == 2) { 1121 | visitedAnimation(tiles[node + gridSizeX], color); 1122 | return; 1123 | } else if (neR - noR == -2) { 1124 | visitedAnimation(tiles[node - gridSizeX], color); 1125 | return; 1126 | } 1127 | 1128 | neC = getCol(neighbor); 1129 | noC = getCol(node) 1130 | if (neC - noC == 2) { 1131 | visitedAnimation(tiles[node + 1], color); 1132 | return; 1133 | } else if (neC - noC == -2) { 1134 | visitedAnimation(tiles[node - 1], color); 1135 | return; 1136 | } 1137 | } 1138 | 1139 | 1140 | async function dfsMaze() { 1141 | selectedColor = ''; 1142 | 1143 | let delayTime = 1; 1144 | const randStartIndex = Math.floor(Math.random() * openTiles.length); 1145 | let s = openTiles[randStartIndex].number; 1146 | const graph = buildMazeGraph(); 1147 | const visited = new Set([s]); 1148 | const stack = [s]; 1149 | resetVisitedTiles(); 1150 | let neighbors = graph[s]; 1151 | let neighbor = neighbors[0]; 1152 | let node = stack[0]; 1153 | 1154 | outerLoop : while (stack.length != 0) { 1155 | node = stack[stack.length - 1]; 1156 | 1157 | if (delayTime > 0) { 1158 | await delay(delayTime); 1159 | } 1160 | 1161 | neighbors = graph[node]; 1162 | 1163 | for (let i = 0; i < neighbors.length; i++) { 1164 | const randomIndex = Math.floor(Math.random() * neighbors.length); 1165 | neighbor = neighbors[randomIndex]; 1166 | if (!visited.has(neighbor)) { 1167 | neighbors.splice(randomIndex, 1); 1168 | break; 1169 | } else { 1170 | neighbors.splice(randomIndex, 1); 1171 | } 1172 | } 1173 | 1174 | if (neighbors.length == 0) { 1175 | stack.pop(); 1176 | continue; 1177 | } 1178 | 1179 | 1180 | if (!visited.has(neighbor)) { 1181 | visited.add(neighbor); 1182 | stack.push(neighbor); 1183 | if (neighbor !== s) { 1184 | visitedAnimation(tiles[neighbor], emptyTileColor); 1185 | visitedTiles.push(tiles[neighbor]); 1186 | } 1187 | 1188 | getRidOfWall(neighbor, node, emptyTileColor); 1189 | 1190 | } 1191 | 1192 | } 1193 | 1194 | for (let i = 0; i < tiles.length; i++) { 1195 | if (tiles[i].element.style.backgroundColor == wallColor) { 1196 | tiles[i].setTileWall(); 1197 | } 1198 | } 1199 | 1200 | pathFindingDone = true; 1201 | } --------------------------------------------------------------------------------