-
Notifications
You must be signed in to change notification settings - Fork 0
/
pathfinder.min.js
1 lines (1 loc) · 17.2 KB
/
pathfinder.min.js
1
let weights,start=null,end=null,N=10,M=10,gridClear=!0,ongoing=!1,wall=!1,dr=[0,-1,1,0,1,-1,1,-1],dc=[1,0,0,-1,1,1,-1,-1],rightClicked=!1,found=!1,pre=[],algo="",weighted=!1,totalVisited=0,leftInQueue=0,dis=0,timeTaken=0;async function Main(){customSelect(),makegrid(),weights=new Array(N*M).fill(1),document.addEventListener("click",closeAllSelect),document.querySelectorAll(".btn").forEach(e=>e.onclick=handleButtons);let e=uniqueRandInts(0,N*M,2);addStart("col-"+e[0]),addEnd("col-"+e[1]),await sleep(20),toast.fire({title:"Click to add Start node",text:"Right Click to add end node",timer:1500,icon:"info"})}function clearGrid(e=!1){if(weighted&&e)for(let e=0;e<weights.length;e++)weights[e]=1;start=end=null,document.querySelectorAll(".cell").forEach(e=>{e.removeEventListener("click",handleClick),e.classList.contains("weight")&&e.removeEventListener("click",handleCtrlClick),e.removeEventListener("contextmenu",handleRightClick),e.className="cell",e.innerText="",e.removeAttribute("title"),e.addEventListener("click",handleClick),e.addEventListener("contextmenu",handleRightClick)}),weighted=!1,gridClear=!0}function recreateGrid({readdWalls:e=!0,readdWghts:t=!1}={}){let i=start,l=end,n=document.querySelectorAll(".cell.wall");if(clearGrid(!t),t){weighted=!0;for(let e=0;e<weights.length;e++)addWeight(e)}null!=i&&addStart(i),null!=l&&addEnd(l),e&&n.forEach(e=>addWall(e.id))}const toast=Swal.mixin({icon:"error",iconColor:"white",customClass:{popup:"colored-toast"},showConfirmButton:!1,showCloseButton:!0,title:"Please wait for the algorithm to finish",toast:!0,position:"bottom-right",timer:1e3,timerProgressBar:!1});function cntr(){window.scrollTo({top:document.querySelector("header").offsetHeight,left:0,behavior:"smooth"})}function handleButtons(e){if(ongoing)return void toast.fire();Swal.close();let t=e.target.id;(!wall&&"wallbtn"!=t||(wallswitch(),"wallbtn"!=t))&&("startbtn"==t?startAlgo():"weightbtn"==t?randomWeights():"randwalls"==t?randomWalls():"randmaze"==t?createMaze():"removewalls"==t?removeAllWalls():"reset"==t&&clearGrid(!0))}function msToTime(e){let t=(e/1e3).toFixed(2),i=(e/6e4).toFixed(1),l=(e/36e5).toFixed(1),n=(e/864e5).toFixed(1);return t<60?t+" Sec":i<60?i+" Min":l<24?l+" Hrs":n+" Days"}async function startAlgo(){if(ongoing)return void toast.fire();if(null==start||null==end)return void toast.fire({title:"Please select Start and End nodes first"});if(!algo.length)return toast.fire({title:"Select an algorithm first",timer:1500}),await sleep(50),void document.querySelector(".select-selected").click();cntr(),ongoing=!0,gridClear||recreateGrid({readdWghts:weighted}),found=!1,totalVisited=0,leftInQueue=0,pre=new Array(N*M);const e=+start.slice(4),t=end.slice(4);let i=~~(e/M),l=e%M,n=~~(t/M),a=t%M,r=performance.now();if("bellman"==algo?(await bellmanford(i,l,n,a),timeTaken="unmeasured"):("bfs"==algo?await bfs(i,l,n,a):"dfs"==algo?await dfsUtil(i,l,n,a):"dijkstra"==algo?await dijkstra(i,l,n,a):"bestfs"==algo&&await bestfirstsearch(i,l,n,a),timeTaken=msToTime(performance.now()-r)),gridClear=!1,found){toast.fire({title:"FOUND",icon:"success",timer:1500,timerProgressBar:!0});let i=reconstructPath(e,t);dis=i.length-1;let l=-weights[e],n=dis<100?450:0;for(;i.length;){let e=i.pop();l+=weights[e],finalpath(e),await sleep(12)}let a="dfs"==algo?"Current path":"Shortest path",r="distance";weighted&&(a="Minimum",r="cost",dis=l),await sleep(n),toast.fire({title:`${a} ${r} : ${dis}`,text:"Click the path for more info.",icon:"info",timer:2e3})}else Swal.fire({title:"<strong>Sorry</strong>",icon:"error",text:"There exists no path to the destination",showDenyButton:!0,confirmButtonText:"Try Again?",denyButtonText:"Later"}).then(e=>e.isConfirmed?recreateGrid({readdWalls:!1,readdWghts:weighted}):null);ongoing=!1}async function wallswitch(){wall?disbaleWallMode():(enableWallMode(),cntr(),toast.fire({icon:"info",title:"Click on cells to add walls",text:"Right Click to add multiple walls",timer:2e3})),wall=!wall}function enableWallMode(){document.querySelector("#wallbtn").classList.replace("addWallBtn","wallMode"),document.querySelectorAll(".cell").forEach(e=>e.id!=start&&e.id!=end?e.classList.add("wallHover"):null)}function disbaleWallMode(){document.querySelector("#wallbtn").classList.replace("wallMode","addWallBtn"),rightClicked&&disableWallHoverMode(),document.querySelectorAll(".cell").forEach(e=>e.classList.remove("wallHover"))}function addWall(e){start!=e&&end!=e&&document.querySelector("#"+e).classList.add("wall")}function removeAllWalls(){recreateGrid({readdWalls:!1,readdWghts:weighted})}function removeAllWeights(){recreateGrid({readdWghts:!1})}function removeWall(e){document.querySelector("#"+e).classList.remove("wall")}const sleep=e=>new Promise(t=>setTimeout(t,e));function addWeight(e){let t=document.querySelector("#col-"+e);t.classList.contains("wall")||(t.classList.add("weight"),t.innerText=weights[e],t.addEventListener("click",handleCtrlClick))}async function handleCtrlClick(e){if(e.preventDefault(),ongoing)toast.fire();else if(window.event.ctrlKey){if(e.target.id===start)return void toast.fire({icon:"info",title:"Weight of starting node is always 0"});let t;const{value:i}=await Swal.fire({title:"Change Weight",input:"text",inputLabel:"Enter number between 1 and 99",showCloseButton:!0,showCancelButton:!0,inputValidator:e=>e?isNaN(e)?"Enter a valid number!":(t=+e)<=0?"Weight cannot be 0 or negative":t>99?"Weight must be less than 100 (for display reasons)":void 0:"You need to write something!"});if(i){e.target.innerText=t;let i=+e.target.id.slice(4);weights[i]=t}}}const isWall=(e,t)=>document.querySelector("#col-"+(e*M+t)).classList.contains("wall");function randomWalls(){cntr(),recreateGrid({readdWalls:!1,readdWghts:weighted});let e=.2*Math.random()+.1;uniqueRandInts(0,N*M,~~(N*M*e)).forEach(e=>addWall("col-"+e))}async function randomWeights(){ongoing=!0,cntr(),clearGrid(),weighted=!0,toast.fire({icon:"info",title:"Adding Random Weights",timer:10*(N+M)});for(let e=0;e<M;e++){for(let t=0;t<N;t++){let i=t*M+e;weights[i]=1+~~(98*Math.random()),addWeight(i)}await sleep(10)}toast.fire({icon:"success",title:"Random Weights Added",text:"CTRL+Click to change",timer:2500}),ongoing=!1}class queue{constructor(e=10){this.len=0,this.q=new Array(e),this.front=0,this.rear=0}isEmpty(){return this.front===this.rear}size(){return this.len}push(e){this.len++,this.q[this.rear++]=e}pop(){return this.len--,this.q[this.front++]}}let _top=0;class PriorityQueue{parent(e){return(e+1>>>1)-1}left(e){return 1+(e<<1)}right(e){return e+1<<1}constructor(e=((e,t)=>e[0]!=t[0]?e[0]<t[0]:e[1]<t[1])){this._heap=[],this._comparator=e}size(){return this._heap.length}isEmpty(){return 0==this.size()}peek(){return this._heap[_top]}push(...e){return e.forEach(e=>{this._heap.push(e),this._siftUp()}),this.size()}pop(){const e=this.peek(),t=this.size()-1;return t>_top&&this._swap(_top,t),this._heap.pop(),this._siftDown(),e}replace(e){const t=this.peek();return this._heap[_top]=e,this._siftDown(),t}_greater(e,t){return this._comparator(this._heap[e],this._heap[t])}_swap(e,t){[this._heap[e],this._heap[t]]=[this._heap[t],this._heap[e]]}_siftUp(){let e=this.size()-1;for(;e>_top&&this._greater(e,this.parent(e));)this._swap(e,this.parent(e)),e=this.parent(e)}_siftDown(){let e=_top;for(;this.left(e)<this.size()&&this._greater(this.left(e),e)||this.right(e)<this.size()&&this._greater(this.right(e),e);){let t=this.right(e)<this.size()&&this._greater(this.right(e),this.left(e))?this.right(e):this.left(e);this._swap(e,t),e=t}}}async function bestfirstsearch(e,t,i,l){let n=e*M+t,a=i*M+l,r=N*M,s=new Array(r),o=new PriorityQueue((e,t)=>s[e[1]]<s[t[1]]),d=new Array(r);for(s[n]=(Math.abs(i-e)+Math.abs(l-t))*weights[n]+weights[a],o.push([0,n]),color(n);!o.isEmpty();){let e=o.pop()[1],t=~~(e/M),n=e%M;if(!d[e]){if(d[e]=!0,markVis(e),await sleep(10),totalVisited++,e==a){found=!0,leftInQueue=o.size();break}for(let a=0;a<4;a++){let r=t+dr[a],c=n+dc[a],h=r*M+c;r<0||c<0||r>=N||c>=M||isWall(r,c)||d[h]||(s[h]=(Math.abs(r-i)+Math.abs(c-l))*weights[h]+weights[e],pre[h]=e,o.push([weights[h],h]),color(h),await sleep(1))}}}}async function dijkstra(e,t,i,l){let n=N*M,a=0,r=e*M+t,s=i*M+l,o=new PriorityQueue,d=new Array(n),c=new Array(n);for(let e=0;e<n;e++)d[e]=1e9;for(d[r]=0,o.push([0,r]),color(r);!o.isEmpty();){let e=o.pop()[1];if(c[e])continue;c[e]=!0,a++;let t=~~(e/M),i=e%M;if(markVis(e),await sleep(10),totalVisited++,e==s){found=!0,leftInQueue=o.size();break}for(let l=0;l<4;l++){let n=t+dr[l],a=i+dc[l],r=n*M+a,s=weights[r];n<0||a<0||n>=N||a>=M||isWall(n,a)||c[r]||d[e]+s<d[r]&&(d[r]=d[e]+s,pre[r]=e,o.push([d[r],r]),color(r),await sleep(1))}}return found||(a=-1),a}async function dfsUtil(e,t,i,l){weighted&&(await Swal.fire({title:"Removing All Weights...",text:"DFS cannot find shortest path on weighted graph",icon:"error",timer:1500}),removeAllWeights()),await dfs(e,t,i,l,new Array(N*M))}async function dfs(e,t,i,l,n,a=[[-1,0],[1,0],[0,1],[0,-1]]){if(found)return;let r=e*M+t;if(!n[r]){n[r]=!0,color(r),await sleep(10),e===i&&t===l&&(found=!0),markVis(r),await sleep(1),totalVisited++;for(let s=0;s<4;s++){let o=e+a[s][0],d=t+a[s][1],c=o*M+d;o<0||d<0||o>=N||d>=M||isWall(o,d)||n[c]||(await dfs(o,d,i,l,n),pre[c]=r)}}}async function bfs(e,t,i,l){weighted&&(await Swal.fire({title:"Removing All Weights...",text:"BFS cannot find shortest path on weighted graph",icon:"error",timer:1e3}),removeAllWeights());let n=new queue(N*M),a=new Array(N*M);for(n.push([e,t]),a[e*M+t]=1,color(e*M+t);!n.isEmpty();){let e=n.pop(),t=e[0],r=e[1];if(markVis(t*M+r),await sleep(10),totalVisited++,t==i&&r==l)return found=!0,void(leftInQueue=n.size());for(let e=0;e<4;e++){let i=t+dr[e],l=r+dc[e],s=i*M+l;i<0||l<0||i>=N||l>=M||a[s]||(isWall(i,l)||(n.push([i,l]),a[s]=!0,pre[s]=t*M+r,color(s),await sleep(1)))}}return-1}async function bellmanford(e,t,i,l){"2".sup();await Swal.fire({title:"Note",html:"This is a very slow algorithm with O(N*M) time complexity.<br> Only changes will be visualized",icon:"warning",timer:1e3});const n=N*M;let a=new Array(n),r=e*M+t,s=i*M+l,o=[];for(let e=0;e<N;e++){for(let t=0,i=e*M;t<M;t++)if(!isWall(e,t))for(let l=0,n=i+t;l<4;l++){let i=e+dr[l],a=t+dc[l],r=i*M+a;i<0||a<0||i>=N||a>=M||isWall(i,a)||(o.push([n,r,weights[r]]),document.querySelector("#col-"+r).classList.add("visited"))}await sleep(1)}for(let e=0;e<n;e++)a[e]=1/0;a[r]=0;for(let e=1;e<n;e++){let e=!1;for(let t of o){totalVisited++;let[i,l,n]=t;if(a[i]+n<a[l]){e=!0,a[l]=a[i]+n,pre[l]=i;let t=document.querySelector("#col-"+i).classList,r=document.querySelector("#col-"+l).classList;t.add("bellmanford"),r.add("bellmanford"),await sleep(10),t.remove("bellmanford"),r.remove("bellmanford"),await sleep(7),l==s&&(found=!0)}}if(!e)break}}function reconstructPath(e,t){let i=[];for(let e=t;null!=e;e=pre[e])i.push(e);return i[i.length-1]!=e?["NO PATH FOUND"]:i}function finalpath(e){let t=document.querySelector("#col-"+e);t.classList.replace("visited","finalpath"),t.title="Final Path"}function markVis(e){let t="#col-"+e;document.querySelector(t).classList.replace("visiting","visited")}function color(e){let t="#col-"+e;document.querySelector(t).classList.add("visiting")}function handleClick(e){if(e.preventDefault(),ongoing)return void toast.fire();if(weighted&&window.event.ctrlKey)return;if(rightClicked)return void disableWallHoverMode();let t=e.target,i=e.target.id;if(!t.classList.contains("finalpath")||wall){if(gridClear||(recreateGrid({readdWghts:weighted}),wall&&enableWallMode()),wall)return start===i||end===i?void toast.fire({title:"Start and End Node cannot contain walls",timer:2e3}):void(t.classList.contains("wall")?removeWall(i):addWall(i));if(t.classList.contains("wall"))toast.fire({title:"Start Node cannot be a wall"});else if(e.target.id!==end){if(null!=start){let t=document.querySelector(".cell.start");if(t.classList.remove("start"),t.removeAttribute("title"),t.innerText=weighted?weights[+start.slice(4)]:"",start===e.target.id)return void(start=null)}addStart(i)}else toast.fire({title:"Start and End Nodes cannot be same",icon:"warning"})}else{let e=weighted?"Cost":"Distance";Swal.fire({title:"INFO",html:`\n <div>\n <div class="cn">\n <p class="t infoCell grn"></p>\n <p class="t" id="tt">Total Visited: ${totalVisited}</p>\n </div>\n <div class="cn">\n <p class="t infoCell ylw"></p>\n <p class="t" id="tt"> Left In Queue: ${leftInQueue} </p>\n </div>\n <div class="cn">\n <p class="t infoCell orng"></p>\n <p class="t" id="tt">${e}: ${dis}</p>\n </div>\n <div class="cn">\n <p class="t" id="tt">Time Taken: ${timeTaken}</p>\n </div>\n </div>`,showCloseButton:!0,icon:"question"})}}function handleRightClick(e){if(e.preventDefault(),closeAllSelect(),ongoing)return void toast.fire();if(wall)return void handleRightHover(e);gridClear||recreateGrid({readdWghts:weighted});let t=e.target.id,i=e.target;if(start!==t)if(i.classList.contains("wall"))toast.fire({title:"End Node cannot be a wall"});else{if(null!=end){let t=document.querySelector(".cell.end");if(t.classList.remove("end"),t.removeAttribute("title"),weighted||(t.innerText=""),end===e.target.id)return void(end=null)}addEnd(t)}else toast.fire({title:"Start and End Nodes cannot be same",icon:"warning"})}function disableWallHoverMode(){rightClicked=!1,document.querySelectorAll(".cell").forEach(e=>{e.removeEventListener("mouseover",hoverAddWall)})}function handleRightHover(e){e.preventDefault(),start!==e.target.id&&end!==e.target.id?(gridClear||(recreateGrid({readdWghts:weighted}),enableWallMode()),rightClicked?disableWallHoverMode():(rightClicked=!0,document.querySelectorAll(".cell").forEach(e=>{e.addEventListener("mouseover",hoverAddWall)}))):toast.fire({title:"Start and End Node cannot contain walls",timer:2e3})}function hoverAddWall(e){addWall(e.target.id)}function addEnd(e){end=e;let t=document.querySelector(`div[id=${end}]`);t.classList.add("end"),t.title="End Node",weighted||(t.innerText="E")}function addStart(e){start=e;let t=document.querySelector(`div[id=${start}]`);t.classList.add("start"),t.title="Start Node",t.innerText=weighted?0:"S"}const randInt=(e,t)=>~~(Math.random()*(t-e))+e;function uniqueRandInts(e,t,i=0){t=Math.max(t,i+e),0==i&&(i=t-e);let l=[],n=new Array(i),a=0,r=randInt(e,t);for(;a<i;){for(;n[r];)r=randInt(e,t);n[r]=!0,a++,l.push(r)}return l}async function createMaze(){ongoing=!0,cntr(),toast.fire({icon:"info",title:"Generating Random Maze",timer:N*M*4}),clearGrid(!0);for(let e=0;e<N;e++){for(let t=0;t<M;t++)addWall("col-"+(e*M+t));await sleep(10)}let e=randInt(0,N),t=randInt(0,M);await recursiveMaze(e,t),ongoing=!1,toast.fire({icon:"success",title:"Done"})}async function recursiveMaze(e,t){removeWall("col-"+(e*M+t)),await sleep(10);let i=uniqueRandInts(0,4);for(let l of i){let i=e+2*dr[l],n=t+2*dc[l];if(i<0||n<0||i>=N||n>=M)continue;if(!isWall(i,n))continue;let a=e+dr[l],r=t+dc[l];document.querySelector("#col-"+(a*M+r)),removeWall("col-"+(a*M+r)),await sleep(5),await recursiveMaze(i,n)}}function makegrid(){let e=window.innerHeight,t=window.innerWidth;N=~~(.98*e/30),M=~~((t-.028*t)/30),N=Math.max(1,N),M=Math.max(1,M);let i=document.querySelector(".nodes");for(let e=0;e<N;e++){let t=document.createElement("div");t.id="row-"+e;for(let i=0;i<M;i++){let l=document.createElement("div");l.id="col-"+(e*M+i),t.appendChild(l).className="cell"}i.appendChild(t).className="node-row"}gridClear=!0,document.querySelectorAll(".cell").forEach(e=>{e.addEventListener("click",handleClick),e.addEventListener("contextmenu",handleRightClick)})}function changeAlgo(e){let t={bestfs:"Best",dfs:"Depth"};"bestfs"!=e&&"dfs"!=e||Swal.fire("",t[e]+" First Search does not guarantee the shortest path","info"),algo=e}function customSelect(){let e,t,i,l,n,a,r,s,o;for(l=(e=document.getElementsByClassName("custom-select")).length,t=0;t<l;t++){for(n=(a=e[t].getElementsByTagName("select")[0]).length,(r=document.createElement("DIV")).setAttribute("class","select-selected"),r.innerHTML=a.options[a.selectedIndex].innerHTML,e[t].appendChild(r),(s=document.createElement("DIV")).setAttribute("class","select-items select-hide"),i=1;i<n;i++)(o=document.createElement("DIV")).innerHTML=a.options[i].innerHTML,o.id=a.options[i].value,o.addEventListener("click",updateSelectBox),s.appendChild(o);e[t].appendChild(s),r.addEventListener("click",function(e){e.preventDefault(),e.stopPropagation(),closeAllSelect(this),this.nextSibling.classList.toggle("select-hide"),this.classList.toggle("select-arrow-active")})}}function updateSelectBox(e){let t,i,l,n,a,r,s;for(e.preventDefault(),changeAlgo(e.target.id),r=(n=this.parentNode.parentNode.getElementsByTagName("select")[0]).length,a=this.parentNode.previousSibling,i=0;i<r;i++)if(n.options[i].innerHTML==this.innerHTML){for(n.selectedIndex=i,a.innerHTML=this.innerHTML,s=(t=this.parentNode.getElementsByClassName("same-as-selected")).length,l=0;l<s;l++)t[l].removeAttribute("class");this.setAttribute("class","same-as-selected");break}a.click()}function closeAllSelect(e){let t,i,l,n,a,r=[];t=document.getElementsByClassName("select-items"),i=document.getElementsByClassName("select-selected"),n=t.length,a=i.length;for(let t=0;t<a;t++)e==i[t]?r.push(t):i[t].classList.remove("select-arrow-active");for(l=0;l<n;l++)r.indexOf(l)&&t[l].classList.add("select-hide")}document.addEventListener("DOMContentLoaded",Main);