diff --git a/index.html b/index.html index 2bb761f..f2357ac 100644 --- a/index.html +++ b/index.html @@ -10,6 +10,10 @@ + + + + @@ -34,6 +38,24 @@ color: #222; text-shadow: 0px 0px 5px green, 0px 0px 2px white, 2px 2px 1px white; } + .reveal .hidden { + display: none + } + .reveal .pathfinder-rerun { + z-index: 1000; + position: fixed; + bottom: 10px; + left: 11px; + background-color: rgba(200, 200, 200, 0.3); + border-radius: 10px; + padding: 10px; + cursor: pointer; + } + .reveal .pathfinder-rerun.disabled { + background-color: rgba(139, 106, 106, 0.3); + color: rgba(39, 27, 27, 0.3); + cursor: not-allowed; + } @@ -108,6 +130,37 @@ plugins: [RevealMarkdown, RevealHighlight, RevealNotes] }); + + + \ No newline at end of file diff --git a/module/pathfinder/README.md b/module/pathfinder/README.md new file mode 100644 index 0000000..f594525 --- /dev/null +++ b/module/pathfinder/README.md @@ -0,0 +1,20 @@ +

+

Pathfinding visualizer

+ + +

+ GitHub last commit +

+ + +![Gif of Pathfinding](https://honzaap.github.io/Pathfinding/animation.gif) + +### Available algorithms: + * A* + * Djikstra's + * BFS + * DFS + * Greedy + +### Installation +Just clone the repository and open index.html, no compilation is needed. diff --git a/module/pathfinder/animation.gif b/module/pathfinder/animation.gif new file mode 100644 index 0000000..c2c3381 Binary files /dev/null and b/module/pathfinder/animation.gif differ diff --git a/module/pathfinder/grid.css b/module/pathfinder/grid.css new file mode 100644 index 0000000..59d7533 --- /dev/null +++ b/module/pathfinder/grid.css @@ -0,0 +1,109 @@ + +.main-grid-container{ + height: 100%; + display: flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; +} +.grid-container{ + display: inline-block; +} +.grid{ + display: flex; + flex-flow: column nowrap; + justify-content: center; + align-items: center; + background: #1e1f29; + border: 2px solid var(--closed-path); + overflow: hidden; +} +.row{ + display: flex; + flex-flow: row nowrap; + height: 31px; +} +.row.odd{ + margin-left: 37px; +} +.cell{ + margin: 0 1px 0 0; + width: 36px; + height: 40px; + overflow: visible; + position: relative; +} +.cell::after{ + content: ""; + display: block; + position: absolute; + background-color: transparent; + width: 40px; + height: 40px; + transition: background-color 0.15s linear; + clip-path: polygon(20px 0, 38px 10px, 38px 30px, 20px 40px, 2px 30px, 2px 10px);; + top: 0; + left: 0; +} + +.cell::before{ + content: ""; + display: block; + position: absolute; + background-color: rgba(255, 255, 255, 0.03); + width: 40px; + height: 40px; + clip-path: polygon(20px 0, 38px 10px, 38px 30px, 20px 40px, 2px 30px, 2px 10px);; + top: 0px; + left: 0px; +} + +/* Cell States */ +.cell.start::after{ + background-color: var(--start); +} +.cell.end::after{ + background-color: var(--end); +} +.cell.null::after{ + background-color: transparent; +} +.cell.null::before{ + /*transform: scale(1.05);*/ +} +.cell.open::after{ + background-color: var(--open-path); +} +.cell.closed::after{ + background-color: var(--closed-path); +} +.cell.path::after{ + background-color: var(--path); +} +.cell.wall::after{ + background: linear-gradient(var(--wall) 0px, var(--open-path) 1400px); + background-attachment: fixed; +} +.cell.wall::before{ + background: linear-gradient(var(--wall) 0px, var(--open-path) 1400px); + background-attachment: fixed; +} +.cell.animate{ + animation-name: cell-anim; + animation-duration: 0.4s; +} + +@keyframes cell-anim { + 0%{ + transform: scale(1.0); + z-index: 2; + } + 50%{ + transform: scale(1.25); + } + 100%{ + transform: scale(1.0); + z-index: 1; + } +} + \ No newline at end of file diff --git a/module/pathfinder/index.html b/module/pathfinder/index.html new file mode 100644 index 0000000..915d944 --- /dev/null +++ b/module/pathfinder/index.html @@ -0,0 +1,74 @@ + + + + + + + + + Pathfinding Visualizer + + + + + + + + + +
+
+ +
+
+
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/module/pathfinder/main.css b/module/pathfinder/main.css new file mode 100644 index 0000000..31f8fdf --- /dev/null +++ b/module/pathfinder/main.css @@ -0,0 +1,195 @@ +*{ + margin: 0; + padding: 0; + outline: none; + border: none; + text-decoration: none; + box-sizing: border-box; +} + +:root{ + --start: #9faf20; + --end: #207094; + --open-path: #885771; + --closed-path: #a6819b; + --path: #ff6be6; + --wall: #001834; +} + +.pathfinder-page{ + width: 100%; + height: 100%; + display: flex; + flex-flow:column nowrap; + justify-content: flex-start; + align-items: stretch; +} +.navbar{ + width: 100%; + display: flex; + flex-flow: row wrap; + justify-content: center; + align-items: stretch; + font-size: 16px; + font-weight: bold; + padding: 15px 0; +} +.navbar-item{ + text-align: center; + display: flex; + align-items: center; + margin: 0 10px; +} +.navbar-item-caption{ + text-align: center; + background-color: var(--closed-path); +} +.general-button{ + color: #fff; + font-weight: inherit; + font-size: inherit; + padding: 0.5em 1em; + background-color: transparent; + transition: background-color 0.3s linear, transform 0.15s ease-out; +} +.general-button:hover{ + cursor: pointer; +} +.general-button:active{ + transform: scale(1.1); +} +.options-button:hover{ + color: var(--path); +} +.start-button{ + background-color: var(--start); + border-radius: 50px; + color: #fff; + padding: 20px 40px; +} +.start-button:hover{ + background-color: var(--end); +} + +.speed-container{ + margin-top: 0.2em; + padding: 0.2em 0.5em; +} + +.range-caption{ + font-size: 1em; + display: block; + text-align: center; +} + +.custom-select { + position: relative; + font-size: 1em; + background-color: transparent; + height: 100%; +} +.custom-select:hover{ +} +.custom-select select { + font-size: inherit; +} + +.select-selected:after { + position: absolute; + content: ""; + top: 40%; + right: 5px; + width: 0; + height: 0; + border: 6px solid transparent; + border-color: #fff transparent transparent transparent; +} + +.select-selected.select-arrow-active:after { + border-color: transparent transparent #fff transparent; + top: 30%; +} + +.select-items div,.select-selected { + padding: 0.5em 0 0.5em 0.3em; + border: none; + cursor: pointer; +} + +/* Style items (options): */ +.select-items { + position: absolute; + top: 100%; + left: 0; + right: 0; + z-index: 99; + text-align:left; + border-radius: 8px; + overflow: hidden; + width: 220px; +} +.select-items div{ + background-color: var(--open-path); + transition: background-color 0.3s linear; + padding: 12px 20px; +} +/* Hide the items when the select box is closed: */ +.select-hide { + display: none; +} + +.select-items div:hover, .same-as-selected { + background-color: var(--closed-path); +} + +.select-selected { + background-color: transparent; + border: none; + height: 100%; + padding: 1em 1em 1em 0.3em; +} + +.slidecontainer { + width: 100%; +} +.slider { + -webkit-appearance: none; + overflow: hidden; + appearance: none; + width: 100%; + height: 18px; + margin-top: 5px; + outline: none; + border-radius: 15px; +} + +.slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 18px; + height: 18px; + border-radius: 15px; + cursor: pointer; + background: var(--wall); + box-shadow: -85px 0 0 80px var(--path); +} +.slider::-moz-range-thumb { + width: 1em; + height: 1em; + background: #000; + cursor: pointer; +} +/** FF*/ +input[type="range"]::-moz-range-progress { + background-color: var(--start); +} +input[type="range"]::-moz-range-track { + background-color: #fff; +} +/* IE*/ +input[type="range"]::-ms-fill-lower { + background-color: var(--start); +} +input[type="range"]::-ms-fill-upper { + background-color: #fff; +} \ No newline at end of file diff --git a/module/pathfinder/main.js b/module/pathfinder/main.js new file mode 100644 index 0000000..437883f --- /dev/null +++ b/module/pathfinder/main.js @@ -0,0 +1,1027 @@ +function spawnPathfinder(root) { + + class Cell + { + is_state(state){return this.State == state} + reset(){this.make_state("null")} + make_state(state){this.State = state;} + draw(grid){ + grid.children[this.Y].children[this.X].classList.remove("animate") + grid.children[this.Y].children[this.X].classList = "cell " + this.State + ` x${this.X}-y${this.Y}`; + grid.children[this.Y].children[this.X].style.backgroundPosition = `${this.X * horizontal_cells * 0}px ${this.Y * vertical_cells / 4}px`; + if(this.State != "null") grid.children[this.Y].children[this.X].classList.add("animate"); + + } + update_neighbours(grid, diagonal=true) + { + this.neighbours = [] + let space_top = this.Y > 0 && grid[this.Y-1][this.X]; + let space_down = this.Y < vertical_cells - 1 && grid[this.Y+1][this.X]; + let space_left = this.X > 0 && grid[this.Y][this.X-1]; + let space_right = this.X < horizontal_cells - 1 && grid[this.Y][this.X+1]; + + let shiftXR = this.Y % 2 === 0 ? 0 : 1; + let shiftXL = this.Y % 2 === 0 ? 1 : 0; + + let left = space_left && !grid[this.Y][this.X - 1].is_state("wall"); + let right = space_right && !grid[this.Y][this.X + 1].is_state("wall"); + + let top_right = space_top && grid[this.Y-1][this.X + 1 - shiftXR] && !grid[this.Y-1][this.X + 1 - shiftXR].is_state("wall"); + let top_left = space_top && grid[this.Y-1][this.X - 1 + shiftXL] && !grid[this.Y-1][this.X - 1 + shiftXL].is_state("wall"); + let down_right = space_down && grid[this.Y+1][this.X + 1 - shiftXR] && !grid[this.Y+1][this.X + 1 - shiftXR].is_state("wall"); + let down_left = space_down && grid[this.Y+1][this.X - 1 + shiftXL] && !grid[this.Y+1][this.X - 1 + shiftXL].is_state("wall"); + + + if(top_right) this.neighbours.push(grid[this.Y-1][this.X + 1 - shiftXR]); + + if(top_left) this.neighbours.push(grid[this.Y-1][this.X - 1 + shiftXL]); + + if(down_right) this.neighbours.push(grid[this.Y+1][this.X + 1 - shiftXR]); + + if(down_left) this.neighbours.push(grid[this.Y+1][this.X - 1 + shiftXL]); + + if(right) this.neighbours.push(grid[this.Y][this.X+1]); + + if(left) this.neighbours.push(grid[this.Y][this.X-1]); + } + + update_wall_neighbours(grid,space=1) + { + this.wall_neighbours = [] + + let space_top = this.Y > space; + let space_down = this.Y < vertical_cells - 1 - space; + let space_left = this.X > space; + let space_right = this.X < horizontal_cells - 1 - space; + + if(space_top) this.wall_neighbours.push(grid[this.Y-1-space][this.X]);// UP + + if(space_down) this.wall_neighbours.push(grid[this.Y+1+space][this.X]);// DOWN + + if(space_right) this.wall_neighbours.push(grid[this.Y][this.X+1+space]);// RIGHT + + if(space_left) this.wall_neighbours.push(grid[this.Y][this.X-1-space]);// LEFT + } + + constructor(X,Y) + { + this.X = X; + this.Y = Y; + this.State = "null" + this.neighbours = [] + this.wall_neighbours = [] + } + } + // Main grid + let grid = []; + + // Grid element + let body = root; + let grid_HTML = root.getElementsByClassName("grid")[0]; + let navbar_HTML = root.getElementsByClassName("navbar")[0]; + let speed_range = root.getElementsByClassName("range-speed")[0]; + let algo_select = root.getElementsByClassName("algo-select")[0]; + let maze_algo_select = root.getElementsByClassName("maze-algo-select")[0]; + + const window_y = body.scrollHeight - navbar_HTML.scrollHeight; + const window_x = body.scrollWidth; + + let horizontal_cells; + let vertical_cells; + let tile_size ; + let placing_tiles = false; + let erasing_tiles = false; + let dragging_tile = false; + let dragged_tile = ""; + let is_running = false; + + + tile_size = "big" + horizontal_cells = Math.floor(window_x / 35); + vertical_cells = Math.floor(window_y / 35); + + horizontal_cells = Math.floor( (window_x - horizontal_cells) /35); + vertical_cells = Math.floor( (window_y - vertical_cells) / 35); + + grid_HTML.style.width = `${horizontal_cells * 35}px`; + grid_HTML.style.height = `${vertical_cells * 30}px`; + + + // Start and End nodes + let start_node = [Math.floor(horizontal_cells/3) , Math.floor(vertical_cells/2)]; + let end_node = [Math.floor(horizontal_cells/3*2) , Math.floor(vertical_cells/2)]; + + let start_node_initial = [Math.floor(horizontal_cells/3) , Math.floor(vertical_cells/2)] + let end_node_initial = [Math.floor(horizontal_cells/3*2) , Math.floor(vertical_cells/2)] + + // Populating grid + for(var i = 0; i < vertical_cells; i++) + { + let row = []; + let odd = i % 2 === 0; + var row_HTML = document.createElement("div"); + row_HTML.classList = "row " + tile_size + (odd ? " odd" : ""); + for(var j = 0; j < horizontal_cells; j++) + { + let cell = new Cell(j,i); + row.push(cell); + + var cell_HTML = document.createElement("div"); + cell_HTML.classList.add("cell"); + row_HTML.appendChild(cell_HTML); + } + grid.push(row); + + grid_HTML.appendChild(row_HTML); + } + // Setting tile placing + for(var i = 0; i < vertical_cells; i++) + { + for(var j = 0; j < horizontal_cells; j++) + { + grid[i][j].draw(grid_HTML); + grid_HTML.children[i].children[j].X=j; + grid_HTML.children[i].children[j].Y=i; + + grid_HTML.children[i].children[j].onmouseover = function() + { + if(placing_tiles) PlaceTile(this.X,this.Y); + else if(erasing_tiles) ResetTile(this.X,this.Y); + + if(dragging_tile) this.classList = "cell "+dragged_tile + } ; + grid_HTML.children[i].children[j].onmouseleave = function() + { + if(dragged_tile) grid[this.Y][this.X].draw(grid_HTML); + } + grid_HTML.children[i].children[j].onmousedown = function() { + if(this.X == start_node[0] && this.Y == start_node[1] || this.X == end_node[0] && this.Y == end_node[1]){ + dragging_tile = true; + dragged_tile = grid[this.Y][this.X].State; + } + else{ + if(grid[this.Y][this.X].is_state("wall")) erasing_tiles = true; + else placing_tiles = true; + + if(placing_tiles) PlaceTile(this.X,this.Y); + if(erasing_tiles) ResetTile(this.X,this.Y) + } + } + grid_HTML.children[i].children[j].onmouseup = function() { + if(dragging_tile) + { + dragging_tile = false; + PlaceTile(this.X,this.Y,dragged_tile) + } + } + grid_HTML.children[i].children[j].ondragstart = function(){return false}; + grid_HTML.children[i].children[j].ondrop = function(){return false}; + } + } + + grid_HTML.onmouseup = function() { + placing_tiles = false + erasing_tiles = false + } + + PlaceTile(start_node_initial[0],start_node_initial[1],"start") + PlaceTile(end_node_initial[0],end_node_initial[1],"end") + + async function SelectChange(select) + { + if(select == "maze-algo-select") + { + await RunMaze(); + } + } + + function PlaceTile(x,y,tile = "wall") + { + if(is_running) return; + let is_start = start_node.length != 0 ? x==start_node[0] && y==start_node[1] : false; + let is_end = end_node.length != 0 ? x==end_node[0] && y==end_node[1] : false; + + if(start_node.length == 0 || tile == "start"){ + if(!is_end){ + if(start_node.length != 0) ResetTile(start_node[0],start_node[1], true); + + grid[y][x].make_state("start"); + start_node = [x,y]; + } + } + else if(end_node.length == 0 || tile == "end"){ + if(!is_start){ + if(end_node.length != 0) ResetTile(end_node[0],end_node[1], true); + + grid[y][x].make_state("end"); + end_node = [x,y] + } + } + else{ + if(!is_start && !is_end){ + grid[y][x].make_state("wall"); + } + } + grid[y][x].draw(grid_HTML); + } + + function ResetTile(x,y,full=false) + { + if(is_running) return + if(full){ + grid[y][x].reset(); + grid[y][x].draw(grid_HTML); + return; + } + if(grid[y][x].is_state("wall")) + { + grid[y][x].reset(); + } + grid[y][x].draw(grid_HTML); + } + + async function ReconstructPath(came_from, current) + { + while(came_from[""+current.X+"y"+current.Y] != undefined){ + current = came_from[""+current.X+"y"+current.Y] + current.make_state("path") + current.draw(grid_HTML); + await sleep(25) + } + } + + function sleep (time) { + return new Promise((resolve) => setTimeout(resolve, time)); + } + + function ResetPath() + { + if(is_running) return + for(var i = 0; i < vertical_cells; i++){ + for(var j = 0; j < horizontal_cells; j++){ + let cell = grid[i][j]; + if(!cell.is_state("wall") && !cell.is_state("start") && !cell.is_state("end")){ + grid[i][j].reset(); + grid[i][j].draw(grid_HTML); + } + } + } + } + + function ClearWalls() + { + if(is_running) return + for(var i = 0; i < vertical_cells; i++){ + for(var j = 0; j < horizontal_cells; j++){ + let cell = grid[i][j]; + if(cell.is_state("wall")){ + grid[i][j].reset(); + grid[i][j].draw(grid_HTML); + } + } + } + } + + function ClearGrid() + { + if(is_running) return + start_node = []; + end_node = []; + for(var i = 0; i < vertical_cells; i++){ + for(var j = 0; j < horizontal_cells; j++){ + grid[i][j].reset(); + grid[i][j].draw(grid_HTML); + } + } + PlaceTile(start_node_initial[0],start_node_initial[1]) + PlaceTile(end_node_initial[0],end_node_initial[1]) + } + + async function Run() + { + let algo = algo_select.value; + switch(algo){ + case "AStar": + await RunAStar(); + break; + case "Dijkstra": + await RunDijkstra(); + break; + case "BFS": + await RunBFS(); + break; + case "DFS": + await RunDFS(); + break; + case "Greedy": + await RunGreedy(); + break; + } + } + + async function RunAStar() + { + if(is_running) return; + ResetPath(); + is_running = true; + let start = grid[start_node[1]][start_node[0]]; + let end = grid[end_node[1]][end_node[0]]; + for (var row of grid) + { + for (var cell of row) + { + cell.update_neighbours(grid) + } + } + let count = 0; + let open_set = []; + open_set.push([0,count,start]); + let came_from = {} + + let g_score = {} + for(let row of grid){ + for(let cell of row){ + g_score[""+cell.X+"y"+cell.Y] = Number.MAX_VALUE + } + } + g_score[""+start.X+"y"+start.Y] = 0; + let f_score = {} + for(let row of grid){ + for(let cell of row){ + f_score[""+cell.X+"y"+cell.Y] = Number.MAX_VALUE + } + } + f_score[""+start.X+"y"+start.Y] = H(start_node[0],start_node[1],end_node[0],end_node[1]); + + let open_set_hash = new Set(); + open_set_hash.add(start); + + while(!open_set.length == 0) + { + let current = open_set[0][2] + let current_smallest = Number.MAX_VALUE + let temp_index = 0; + for(let cell = 0; cell < open_set.length; cell ++) + { + if(open_set[cell][0] < current_smallest) + { + temp_index = cell; + current = open_set[cell][2] + current_smallest = open_set[cell][0] + } + } + open_set.splice(temp_index, 1); + open_set_hash.delete(current) + if(current == end) + { + // make path + end.make_state("end") + start.make_state("start") + end.draw(grid_HTML); + start.draw(grid_HTML) + await ReconstructPath(came_from,end) + end.make_state("end") + start.make_state("start") + end.draw(grid_HTML); + start.draw(grid_HTML) + is_running = false; + return + } + + for(var neighbour of current.neighbours){ + let temp_g_score = g_score[""+current.X+"y"+current.Y]+1 + if(temp_g_score < g_score[""+neighbour.X+"y"+neighbour.Y] || g_score[""+neighbour.X+"y"+neighbour.Y] == undefined){ + came_from[""+neighbour.X+"y"+neighbour.Y] = current + g_score[""+neighbour.X+"y"+neighbour.Y] = temp_g_score + f_score[""+neighbour.X+"y"+neighbour.Y] = temp_g_score + H(neighbour.X,neighbour.Y, end.X,end.Y) + if(!open_set_hash.has(neighbour)){ + count++; + open_set.push([f_score[""+neighbour.X+"y"+neighbour.Y],count,neighbour]) + open_set_hash.add(neighbour) + neighbour.make_state("open") + } + neighbour.draw(grid_HTML) + } + } + if(current != start) + { + current.make_state("closed"); + current.draw(grid_HTML) + } + + await sleep(200-speed_range.value) + } + is_running = false; + } + + function H(x1,y1,x2,y2){ + return Math.abs(x1-x2)+Math.abs(y1-y2); + } + + async function RunDijkstra() + { + if(is_running) return; + ResetPath(); + is_running = true; + let start = grid[start_node[1]][start_node[0]]; + let end = grid[end_node[1]][end_node[0]]; + + let dist = {}; + let prev = {}; + let Q = [] + + + for (var row of grid) + { + for (var cell of row) + { + cell.update_neighbours(grid) + } + } + + for(var row of grid){ + for(var cell of row){ + dist[""+cell.X+"y"+cell.Y] = Number.MAX_VALUE; + prev[""+cell.X+"y"+cell.Y] = null; + Q.push(cell) + } + } + dist[""+start.X+"y"+start.Y] = 0; + + while (Q.length > 0) + { + let u = Q[0]; + let current_smallest = Number.MAX_VALUE; + let temp_index = 0; + for(let cell = 0; cell < Q.length; cell ++) + { + if(parseFloat(dist[""+Q[cell].X+"y"+Q[cell].Y]) < current_smallest) + { + temp_index = cell; + current_smallest = parseFloat(dist[""+ Q[cell].X+"y"+ Q[cell].Y]); + u = Q[cell]; + } + } + Q.splice(temp_index, 1); + + if(current_smallest == Number.MAX_VALUE) + { + is_running = false; + return; + } + for(let v of u.neighbours) + { + if(Q.indexOf(v) != -1) + { + let alt = parseFloat(dist[""+u.X+"y"+u.Y]) + H(u.X,u.Y,v.X,v.Y); + if(alt < parseFloat(dist[""+v.X+"y"+v.Y])){ + dist[""+v.X+"y"+v.Y] = alt; + prev[""+v.X+"y"+v.Y] = u; + } + v.make_state("open"); + v.draw(grid_HTML); + } + if(v == end) + { + end.make_state("end") + start.make_state("start") + end.draw(grid_HTML); + start.draw(grid_HTML) + await ReconstructPath(prev,end) + end.make_state("end") + start.make_state("start") + end.draw(grid_HTML); + start.draw(grid_HTML) + is_running = false; + return + } + } + + if(u != start && u != end) + { + u.make_state("closed"); + u.draw(grid_HTML); + } + await sleep(200-speed_range.value) + } + is_running = false; + } + + async function RunBFS() + { + if(is_running) return; + ResetPath(); + is_running = true; + let start = grid[start_node[1]][start_node[0]]; + let end = grid[end_node[1]][end_node[0]]; + + let disc = []; + let prev = {}; + let Q = [] + + disc.push(start) + Q.push(start) + + for (var row of grid) + { + for (var cell of row) + { + cell.update_neighbours(grid) + } + } + + while(Q.length > 0) + { + let v = Q[0]; + Q.splice(0,1); + + for(var n of v.neighbours) + { + if(disc.indexOf(n) == -1) + { + disc.push(n); + Q.push(n); + n.make_state("open"); + n.draw(grid_HTML); + prev[""+n.X+"y"+n.Y] = v; + if(n == end) + { + end.make_state("end") + start.make_state("start") + end.draw(grid_HTML); + start.draw(grid_HTML) + await ReconstructPath(prev,end) + end.make_state("end") + start.make_state("start") + end.draw(grid_HTML); + start.draw(grid_HTML) + is_running = false; + return + } + } + } + if(v != start && v != end) + { + v.make_state("closed"); + v.draw(grid_HTML); + } + await sleep(200-speed_range.value) + } + is_running = false; + } + + async function RunDFS() + { + if(is_running) return; + ResetPath(); + is_running = true; + let start = grid[start_node[1]][start_node[0]]; + let end = grid[end_node[1]][end_node[0]]; + + let disc = []; + let prev = {}; + let S = [] + + S.push(start) + + for (var row of grid) + { + for (var cell of row) + { + cell.update_neighbours(grid,false) + } + } + + while(S.length > 0) + { + let v = S.pop(); + disc.push(v); + + for(var i = v.neighbours.length-1;i>=0;i--) + { + let n = v.neighbours[i]; + if(disc.indexOf(n) == -1) + { + n.make_state("open"); + n.draw(grid_HTML); + prev[""+n.X+"y"+n.Y] = v; + if(n == end) + { + end.make_state("end") + start.make_state("start") + end.draw(grid_HTML); + start.draw(grid_HTML) + await ReconstructPath(prev,end) + end.make_state("end") + start.make_state("start") + end.draw(grid_HTML); + start.draw(grid_HTML) + is_running = false; + return + } + else{ + S.push(n) + } + } + } + if(v != start && v != end) + { + v.make_state("closed"); + v.draw(grid_HTML); + } + await sleep(200-speed_range.value) + } + is_running = false; + } + + async function RunGreedy() + { + if(is_running) return; + ResetPath(); + is_running = true; + let start = grid[start_node[1]][start_node[0]]; + let end = grid[end_node[1]][end_node[0]]; + + let dist = {} + let disc = [] + let prev = {}; + let Q = [] + + for (var row of grid) + { + for (var cell of row) + { + cell.update_neighbours(grid) + } + } + + for(let row of grid){ + for(let cell of row){ + dist[""+cell.X+"y"+cell.Y] = Number.MAX_VALUE + } + } + dist[""+start.X+"y"+start.Y] = H(start.X,start.Y,end.X,end.Y); + Q.push(start); + + while(Q.length > 0){ + let u = Q[0]; + let current_smallest = Number.MAX_VALUE; + let temp_index = 0; + for(let cell = 0; cell < Q.length; cell ++) + { + if(parseFloat(dist[""+Q[cell].X+"y"+Q[cell].Y]) < current_smallest) + { + temp_index = cell; + current_smallest = parseFloat(dist[""+ Q[cell].X+"y"+ Q[cell].Y]); + u = Q[cell]; + } + } + Q.splice(temp_index, 1); + disc.push(u); + + if(u == end) + { + end.make_state("end") + start.make_state("start") + end.draw(grid_HTML); + start.draw(grid_HTML) + await ReconstructPath(prev,end) + end.make_state("end") + start.make_state("start") + end.draw(grid_HTML); + start.draw(grid_HTML) + is_running = false; + return + } + + for(var n of u.neighbours) + { + if(disc.indexOf(n) == -1) + { + dist[""+n.X+"y"+n.Y] = H(n.X,n.Y,end.X,end.Y); + disc.push(n); + Q.push(n); + n.make_state("open"); + n.draw(grid_HTML); + prev[""+n.X+"y"+n.Y] = u; + } + } + if(u != start && u != end) + { + u.make_state("closed"); + u.draw(grid_HTML); + } + await sleep(200-speed_range.value) + } + is_running = false; + } + + + + // MAZES + async function RunMaze() + { + ClearGrid(); + is_running = true; + for (var row of grid) + { + for (var cell of row) + { + cell.update_wall_neighbours(grid); + cell.update_neighbours(grid,false); + cell.visited_fill = null; + cell.visited = null; + cell.connect_visited = null; + } + } + + + + let ctX = Math.floor(horizontal_cells/2); + let ctY = Math.floor(vertical_cells/2); + + if(grid[ctY][ctX].is_state("start") || grid[ctY][ctX].is_state("end") && !grid[ctY+1][ctX].is_state("start") && !grid[ctY+1][ctX].is_state("end")) + { + ctY++; + } + else if(grid[ctY+1][ctX].is_state("start") || grid[ctY+1][ctX].is_state("end") && !grid[ctY][ctX+1].is_state("start") && !grid[ctY][ctX+1].is_state("end")){ + ctX++; + } + + + + let algo = maze_algo_select.value; + switch(algo){ + case "DFS": + FillWithWalls(); + await RecursiveMazeDFS(ctY,ctX); + break; + case "Prim": + FillWithWalls(); + await MazePrim(grid[ctY][ctX]); + break; + case "Random": + FillWithWalls(); + await RecursiveMazeRandom(grid[ctY][ctX]); + break; + } + + is_running = false; + + + } + + + function FillWithWalls() + { + if(!grid[0][0].is_state("start") && !grid[0][0].is_state("end")) + { + RecursiveFillWithWalls(grid[0][0]); + } + else if(!grid[0][1].is_state("start") && !grid[0][1].is_state("end")) + { + RecursiveFillWithWalls(grid[0][1]); + } + else{ + RecursiveFillWithWalls(grid[1][0]); + } + + } + + function RecursiveFillWithWalls(current) + { + if(current.is_state("start") || current.is_state("end")) return; + current.make_state("wall"); + current.draw(grid_HTML); + current.visited_fill = true; + for(let n of current.neighbours) + { + if(n.visited_fill != true)RecursiveFillWithWalls(n); + } + } + + async function RecursiveMazeRandom(current) + { + current.visited = true; + let neighbours = current.wall_neighbours; + while(neighbours.length > 0) + { + let idx = Math.floor(Math.random() * neighbours.length); + let n = neighbours[idx]; + neighbours.splice(idx,1); + + if(n.visited != true) + { + n.visited = true; + let wall = []; + if(n.X == current.X) wall = [n.X, n.Y>current.Y ? current.Y+1 : n.Y+1 ]; + else wall = [n.X>current.X ? current.X+1 : n.X+1, n.Y ]; + + let wall_cell = grid[wall[1]][wall[0]] + if(!wall_cell.is_state("start") && !wall_cell.is_state("end")) + { + wall_cell.make_state("null"); + wall_cell.draw(grid_HTML); + } + //await sleep(1); + await RecursiveMazeRandom(wall_cell); + + } + } + } + + async function RecursiveMazeDFS(r,c) + { + let randDirs = [1,2,3,4]; + shuffle(randDirs); + for (var i = 0; i < randDirs.length; i++) { + + switch(randDirs[i]){ + case 1: + if (r - 2 <= 0 ) + continue; + if (grid[r - 2][c].State != "null") { + if(!grid[r-2][c].is_state("start") && !grid[r-2][c].is_state("end")) grid[r-2][c].make_state("null"); + if(!grid[r-1][c].is_state("start") && !grid[r-1][c].is_state("end")) grid[r-1][c].make_state("null"); + + grid[r-2][c].draw(grid_HTML); + grid[r-1][c].draw(grid_HTML); + + await sleep(1); + RecursiveMazeDFS(r - 2, c); + } + break; + case 2: + if (c + 2 >= horizontal_cells - 1) + continue; + if (grid[r][c + 2].State != "null") { + if(!grid[r][c + 2].is_state("start") && !grid[r][c + 2].is_state("end")) grid[r][c + 2].make_state("null"); + if(!grid[r][c + 1].is_state("start") && !grid[r][c + 1].is_state("end")) grid[r][c + 1].make_state("null"); + + grid[r][c + 2].draw(grid_HTML); + grid[r][c + 1].draw(grid_HTML); + + await sleep(1); + RecursiveMazeDFS(r, c + 2); + } + break; + case 3: + if (r + 2 >= vertical_cells - 1) + continue; + if (grid[r + 2][c].State != "null") { + if(!grid[r+2][c].is_state("start") && !grid[r+2][c].is_state("end")) grid[r+2][c].make_state("null"); + if(!grid[r+1][c].is_state("start") && !grid[r+1][c].is_state("end")) grid[r+1][c].make_state("null"); + + grid[r+2][c].draw(grid_HTML); + grid[r+1][c].draw(grid_HTML); + + await sleep(1); + RecursiveMazeDFS(r + 2, c); + } + break; + case 4: + if (c - 2 <= 0) + continue; + if (grid[r][c - 2].State != "null") { + if(!grid[r][c - 2].is_state("start") && !grid[r][c - 2].is_state("end")) grid[r][c - 2].make_state("null"); + if(!grid[r][c - 1].is_state("start") && !grid[r][c - 1].is_state("end")) grid[r][c - 1].make_state("null"); + + grid[r][c - 2].draw(grid_HTML); + grid[r][c - 1].draw(grid_HTML); + + await sleep(1); + RecursiveMazeDFS(r, c - 2); + } + break; + } + } + + } + + async function MazePrim(start) + { + + for(var row of grid){ + for(var cell of row){ + cell.update_wall_neighbours(grid,0); + } + } + + start.make_state("null"); + start.draw(grid_HTML); + start.visited = true; + + let walls = []; + + for(let n of start.wall_neighbours){ + if(n.is_state("wall")) walls.push(n); + } + + while(walls.length > 0) + { + let idx = Math.floor(Math.random() * walls.length); + let r = walls[idx]; + walls.splice(idx,1); + + if( !(r.X <= 0 || r.X >= horizontal_cells-1) ){ + + let prev = grid[r.Y][r.X-1]; + let next = grid[r.Y][r.X+1]; + + if(prev.visited != true && next.visited == true){ + if(!r.is_state("start") && !r.is_state("end")){ + r.make_state("null"); + r.draw(grid_HTML); + } + + prev.visited = true; + if(!prev.is_state("start") && !prev.is_state("end")){ + prev.make_state("null"); + prev.draw(grid_HTML); + } + + for(var n of prev.wall_neighbours){ + if(n.is_state("wall")) walls.push(n); + } + } + else if(prev.visited == true && next.visited != true){ + if(!r.is_state("start") && !r.is_state("end")){ + r.make_state("null"); + r.draw(grid_HTML); + } + + next.visited = true; + if(!next.is_state("start") && !next.is_state("end")){ + next.make_state("null"); + next.draw(grid_HTML); + } + + for(var n of next.wall_neighbours){ + if(n.is_state("wall")) walls.push(n); + } + } + } + + if( !(r.Y <= 0 || r.Y >= vertical_cells-1) ){ + let prev = grid[r.Y-1][r.X]; + let next = grid[r.Y+1][r.X]; + + if(prev.visited != true && next.visited == true){ + if(!r.is_state("start") && !r.is_state("end")){ + r.make_state("null"); + r.draw(grid_HTML); + } + + prev.visited = true; + if(!prev.is_state("start") && !prev.is_state("end")){ + prev.make_state("null"); + prev.draw(grid_HTML); + } + + for(var n of prev.wall_neighbours){ + if(n.is_state("wall")) walls.push(n); + } + } + else if(prev.visited == true && next.visited != true){ + if(!r.is_state("start") && !r.is_state("end")){ + r.make_state("null"); + r.draw(grid_HTML); + } + + next.visited = true; + if(!next.is_state("start") && !next.is_state("end")){ + next.make_state("null"); + next.draw(grid_HTML); + } + + for(var n of next.wall_neighbours){ + if(n.is_state("wall")) walls.push(n); + } + } + } + await sleep(0); + } + + } + + function RecursiveAreConnected(start,end) + { + if(start == end) return true; + start.connect_visited = true; + let connected = false; + start.update_neighbours(grid,false); + for(var n of start.neighbours) + { + if(n == end) return true; + if(n.connect_visited != true) connected = connected || RecursiveAreConnected(n,end); + } + return connected; + + } + + function shuffle(array) { + array.sort(() => Math.random() - 0.5); + } + + return {Run, RunMaze}; +} diff --git a/module/pathfinder/neural.svg b/module/pathfinder/neural.svg new file mode 100644 index 0000000..86d6770 --- /dev/null +++ b/module/pathfinder/neural.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/module/pathfinder/select.js b/module/pathfinder/select.js new file mode 100644 index 0000000..b52d9f8 --- /dev/null +++ b/module/pathfinder/select.js @@ -0,0 +1,89 @@ +var x, i, j, l, ll, selElmnt, a, b, c; +/* Look for any elements with the class "custom-select": */ +x = document.getElementsByClassName("custom-select"); +for(let c of x) +{ + c.style.width = (c.children[0].scrollWidth + 35).toString() + "px"; + c.children[0].style.display = "none"; +} +l = x.length; +for (i = 0; i < l; i++) { + selElmnt = x[i].getElementsByTagName("select")[0]; + ll = selElmnt.length; + /* For each element, create a new DIV that will act as the selected item: */ + a = document.createElement("DIV"); + a.setAttribute("class", "select-selected"); + a.innerHTML = selElmnt.options[selElmnt.selectedIndex].innerHTML; + x[i].appendChild(a); + /* For each element, create a new DIV that will contain the option list: */ + b = document.createElement("DIV"); + b.setAttribute("class", "select-items select-hide"); + for (j = 0; j < ll; j++) { + /* For each option in the original select element, + create a new DIV that will act as an option item: */ + c = document.createElement("DIV"); + c.innerHTML = selElmnt.options[j].innerHTML; + c.addEventListener("click", function(e) { + /* When an item is clicked, update the original select box, + and the selected item: */ + var y, i, k, s, h, sl, yl; + s = this.parentNode.parentNode.getElementsByTagName("select")[0]; + sl = s.length; + h = this.parentNode.previousSibling; + for (i = 0; i < sl; i++) { + + if (s.options[i].innerHTML == this.innerHTML) { + s.selectedIndex = i; + h.innerHTML = this.innerHTML; + y = this.parentNode.getElementsByClassName("same-as-selected"); + yl = y.length; + for (k = 0; k < yl; k++) { + y[k].removeAttribute("class"); + } + + this.setAttribute("class", "same-as-selected"); + break; + } + } + SelectChange(s.id); + + h.click(); + }); + b.appendChild(c); + } + x[i].appendChild(b); + a.addEventListener("click", function(e) { + /* When the select box is clicked, close any other select boxes, + and open/close the current select box: */ + e.stopPropagation(); + closeAllSelect(this); + this.nextSibling.classList.toggle("select-hide"); + this.classList.toggle("select-arrow-active"); + }); +} + +function closeAllSelect(elmnt) { + /* A function that will close all select boxes in the document, + except the current select box: */ + var x, y, i, xl, yl, arrNo = []; + x = document.getElementsByClassName("select-items"); + y = document.getElementsByClassName("select-selected"); + xl = x.length; + yl = y.length; + for (i = 0; i < yl; i++) { + if (elmnt == y[i]) { + arrNo.push(i) + } else { + y[i].classList.remove("select-arrow-active"); + } + } + for (i = 0; i < xl; i++) { + if (arrNo.indexOf(i)) { + x[i].classList.add("select-hide"); + } + } +} + +/* If the user clicks anywhere outside the select box, +then close all select boxes: */ +document.addEventListener("click", closeAllSelect); \ No newline at end of file diff --git a/module/pathfinder/typo.css b/module/pathfinder/typo.css new file mode 100644 index 0000000..a174bac --- /dev/null +++ b/module/pathfinder/typo.css @@ -0,0 +1,15 @@ +html{ + font-family: "Roboto"; + font-size: 1.3em; +} +a,p,span,h1,h2,h3,h4,h5,button,select,option{ + font-family: "Roboto"; +} +a{ + text-decoration: none; + color: inherit; +} +select,option,select::after,select::before{ + border: none; + outline: none; +} \ No newline at end of file diff --git a/slides/intro.md b/slides/intro.md index fbb805e..0fb9335 100644 --- a/slides/intro.md +++ b/slides/intro.md @@ -13,9 +13,123 @@ - you feel miserable and tired. --- - + ### ...but why? +--- + +I don't feel like it. + +I'm too tired now. + +I don't know how to do it. + +I'm too busy now. + +I'll do it, but first [☕]. + +--- + +#### Trying it all + + +
+
+
+ +
Restart
+ +-V- + +#### Wandering around + + +
+
+
+ +
Restart
+ +-V- + +#### Having a goal + + +
+
+
+ +
Restart
+ --- #### Decision Paralysis @@ -24,7 +138,7 @@ - What if I choose the wrong one? - Should I try the other one? -Deciding on anything hurts. Let's not do it *right now*. +Deciding on anything hurts. Let's not do it *now*. ---