├── LICENSE ├── README.md ├── build ├── asset-manifest.json ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── precache-manifest.753fd06515cb4bfffa418f4a4b35d820.js ├── robots.txt ├── service-worker.js └── static │ ├── css │ ├── main.3944e690.chunk.css │ └── main.3944e690.chunk.css.map │ └── js │ ├── 2.f0583ba6.chunk.js │ ├── 2.f0583ba6.chunk.js.map │ ├── main.d7aa8aac.chunk.js │ ├── main.d7aa8aac.chunk.js.map │ ├── runtime-main.0f354abe.js │ └── runtime-main.0f354abe.js.map ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.css ├── App.js ├── App.test.js ├── PathfindingVisualizer ├── Node │ ├── Node.css │ └── Node.jsx ├── PathfindingVisualizer.css └── PathfindingVisualizer.jsx ├── algorithms ├── aStar.js ├── bfs.js ├── dfs.js └── dijkstra.js ├── index.css ├── index.js ├── logo.svg └── serviceWorker.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Prudhvi Garapati 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PathFinding-Visualizer: - A REACT APPLICATION 2 | [![NodeJS](https://img.shields.io/badge/node-12.14.1-important)](https://img.shields.io/badge/node-12.14.1-important) 3 | [![NPM](https://img.shields.io/badge/npm-6.13.7-blueviolet)](https://img.shields.io/badge/npm-6.13.7-blueviolet) 4 | [![Made With React](https://img.shields.io/badge/made%20with-react-61DAFB)](https://img.shields.io/badge/npm-6.13.7-blueviolet) 5 | [![Build Status](http://img.shields.io/travis/badges/badgerbadgerbadger.svg?style=flat-square)](https://travis-ci.org/badges/badgerbadgerbadger) 6 | 7 | --------------- 8 | ### check out live demo [HERE](https://prudhvignv.github.io/pathFinderVisualizer/) 9 | 10 | ##### DFS visualization demo: 11 | 12 | ![dfs](https://user-images.githubusercontent.com/39909903/91169511-5723df00-e68c-11ea-87ed-896412c347b2.PNG) 13 | 14 | 15 | 16 | ## Overview: 17 | This is a fun project on visualizing path finding algorithms i.e BFS, DFS, Dikstra’s , A* algorithm. 18 | This app is entirely built in react and below is the how the interface looks like.. 19 | Green box is the starting node and Red box is the end node. 20 | You can see the various algorithms below to visualize. 21 | Here design of grid is done using tables and set first node and second node colors using CSS properties. 22 | ![grid](https://user-images.githubusercontent.com/39909903/91166796-c519d780-e687-11ea-9b16-ac0504aa3a49.PNG) 23 | 24 | 25 | ## Intro .. 26 | 27 | Path finding algorithms plays a vital role in our daily life such as packets in computer networks , google maps uses a path finding algorthm to find the shortest path. 28 | So this project main idea is to visualize this path finding algorthms using react with the help of some css animations. 29 | 30 | You can also follow my [Medium BLOG](https://medium.com/@prudhvi.gnv/path-finding-visualizer-using-react-from-creating-to-building-and-deploying-bd1e2bc64696) for more understanding and details :) 31 | ------ 32 | 33 | Let's check out some intuition behind this algorithms for better insights. 34 | ### Breadth First Search 35 | * Breadth First Search explores equally in all directions. 36 | * This is an incredibly useful algorithm, not only for regular traversal, but also for procedural map generation, flow field pathfinding, distance maps, and other types of map analysis. 37 | * This may be the algorithm of choice to identify nearby places of interest in GPS. 38 | * BFS guarantees the shortest path. 39 | Below is the result for Breadth first search: 40 | ![bfs](https://user-images.githubusercontent.com/39909903/91166761-b7645200-e687-11ea-90ab-ef04daeda21e.PNG) 41 | 42 | ### Depth First Search 43 | - Traverses by exploring as far as possible down each path before backtracking. 44 | - As useful as the BFS: DFS can be used to generate a topological ordering, to generate mazes, to traverse trees, to build decision trees, to discover a solution path with hierarchical choices… 45 | - DFS does not guarantee the shortest path. 46 | Below is how the DFS works 47 | ![dfs](https://user-images.githubusercontent.com/39909903/91169511-5723df00-e68c-11ea-87ed-896412c347b2.PNG) 48 | ### Dijkstra 49 | - Dijkstra's Algorithm lets us prioritize which paths to explore. Instead of exploring all possible paths equally, it favors lower cost paths. 50 | - We can assign lower cost to encourage moving on roads while assigning high cost on highway to avoid them. 51 | - It is the algorithm of choice for finding the shortest path paths with multiple destinations. 52 | Below is the demo 53 | ![dikstra](https://user-images.githubusercontent.com/39909903/91166789-c0552380-e687-11ea-9e87-e023e381eb06.PNG) 54 | 55 | ### A* (A-Star) 56 | - A* is a modification of Dijkstra's Algorithm that is optimized for a single destination. 57 | - Dijkstra's Algorithm can find paths to all locations; A* finds paths to one location. It prioritizes paths that seem to be leading closer to a goal. 58 | - In a game, we could set costs to be attracted or discouraged in going near some objects : how useful it is for an AI. 59 | - It is more or less the golden ticket or industry standard algorithm for all applications finding directions between two locations. 60 | Below is the demo of application: 61 | ![a](https://user-images.githubusercontent.com/39909903/91166759-b59a8e80-e687-11ea-8ed7-faa0d453fe71.PNG) 62 | 63 | Now Let's drive into the code .. 64 | ------------------ 65 | ## Getting Started .. 66 | First install node.js which comes with a bundle(npm+npx) to run javascript in local system. 67 | type following commands in the shell to create a react app 68 | 69 | ``` 70 | npx create-react-app my-app 71 | cd my-app 72 | npm start 73 | ``` 74 | 75 | ### npm start 76 | Runs the app in the development mode. 77 | Open http://localhost:3000 to view it in the browser. 78 | The page will reload if you make edits. 79 | You will also see any lint errors in the console. 80 | Below is the App.js component which is the root component for the react applications. 81 | 82 | ```js 83 | 84 | import React from 'react'; 85 | import './App.css'; 86 | import PathfindingVisualizer from './PathfindingVisualizer/PathfindingVisualizer'; 87 | 88 | function App() { 89 | return ( 90 |
91 | 92 |
93 | ); 94 | } 95 | 96 | export default App; 97 | ``` 98 | 99 | The PathfindingVisualizer component is imported and rendered in App.js. 100 | 101 | I write everything related to the project in the pathfindingVisualizer component. 102 | ## pathfindingVisualizer: 103 | Here we jammed everything we need into this component such as GUI logic, animations logic , node objects structure and its properties, Some boolean values (isRunning, isStartNode, isFinishNode, isWallNode ), css properties , walls logic, implementing algorithms, mousehandlers 104 | Then the pathfinding algorithms are written in another folder and is imported into pathfindingVisualizer component. 105 | 106 | ```js 107 | import React, {Component} from 'react'; 108 | import Node from './Node/Node'; 109 | import {dijkstra} from '../algorithms/dijkstra'; 110 | import {AStar} from '../algorithms/aStar'; 111 | import {dfs} from '../algorithms/dfs'; 112 | import {bfs} from '../algorithms/bfs'; 113 | 114 | import './PathfindingVisualizer.css'; 115 | ``` 116 | Then handle mouse events and some reusable components to make this application more friendly. 117 | 118 | There are functionalities in pathfindingVisualizer component for creating initialgrid(), clearwalls(), CreateNode(), isGridClear(), clearGrid() etc .. 119 | 120 | ***Below is the driver code to implement algorithms in the component!!*** 121 | 122 | ```js 123 | 124 | visualize(algo) { 125 | if (!this.state.isRunning) { 126 | this.clearGrid(); 127 | this.toggleIsRunning(); 128 | const {grid} = this.state; 129 | const startNode = 130 | grid[this.state.START_NODE_ROW][this.state.START_NODE_COL]; 131 | const finishNode = 132 | grid[this.state.FINISH_NODE_ROW][this.state.FINISH_NODE_COL]; 133 | let visitedNodesInOrder; 134 | switch (algo) { 135 | case 'Dijkstra': 136 | visitedNodesInOrder = dijkstra(grid, startNode, finishNode); 137 | break; 138 | case 'AStar': 139 | visitedNodesInOrder = AStar(grid, startNode, finishNode); 140 | break; 141 | case 'BFS': 142 | visitedNodesInOrder = bfs(grid, startNode, finishNode); 143 | break; 144 | case 'DFS': 145 | visitedNodesInOrder = dfs(grid, startNode, finishNode); 146 | break; 147 | default: 148 | // should never get here 149 | break; 150 | } 151 | 152 | ``` 153 | 154 | 155 | 156 | 157 | 158 | 159 | If you want to work off of this base to create your own Pathfinding Visualizer, feel free to fork this project or to just copy-paste code. 160 | 161 | Feel free to check out my other projects by going to [My Portfolio Site](https://prudhvignv.github.io). 162 | 163 | Everything below this line was automatically generated by Create React App. 164 | 165 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 166 | 167 | 168 | ## Deployment 169 | 170 | ### `npm run build` 171 | 172 | Builds the app for production to the `build` folder.
173 | It correctly bundles React in production mode and optimizes the build for the best performance. 174 | 175 | The build is minified and the filenames include the hashes.
176 | Your app is ready to be deployed! 177 | 178 | 179 | I deploy this app in github pages. 180 | You can check out here : [https://prudhvignv.github.io/pathFinderVisualizer/](https://prudhvignv.github.io/pathFinderVisualizer/) 181 | 182 | 183 | 184 | ### LICENSE 185 | [MIT](https://github.com/PrudhviGNV/pathFinderVisualizer/blob/master/LICENSE) 186 | 187 | 188 | 189 | 190 | 191 | ------------------------------- 192 | 193 | ---------------------------- 194 | 195 | 196 | 197 | ## For more documentation and understanding.. check out following links.. 198 | 199 | 200 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 201 | 202 | To learn React, check out the [React documentation](https://reactjs.org/). 203 | 204 | ### Code Splitting 205 | 206 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 207 | 208 | ### Analyzing the Bundle Size 209 | 210 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 211 | 212 | ### Making a Progressive Web App 213 | 214 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 215 | 216 | ### Advanced Configuration 217 | 218 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 219 | 220 | ### Deployment 221 | 222 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 223 | 224 | ### `npm run build` fails to minify 225 | 226 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 227 | -------------------------------------------------------------------------------- /build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/pathFinderVisualizer/static/css/main.3944e690.chunk.css", 4 | "main.js": "/pathFinderVisualizer/static/js/main.d7aa8aac.chunk.js", 5 | "main.js.map": "/pathFinderVisualizer/static/js/main.d7aa8aac.chunk.js.map", 6 | "runtime-main.js": "/pathFinderVisualizer/static/js/runtime-main.0f354abe.js", 7 | "runtime-main.js.map": "/pathFinderVisualizer/static/js/runtime-main.0f354abe.js.map", 8 | "static/js/2.f0583ba6.chunk.js": "/pathFinderVisualizer/static/js/2.f0583ba6.chunk.js", 9 | "static/js/2.f0583ba6.chunk.js.map": "/pathFinderVisualizer/static/js/2.f0583ba6.chunk.js.map", 10 | "index.html": "/pathFinderVisualizer/index.html", 11 | "precache-manifest.753fd06515cb4bfffa418f4a4b35d820.js": "/pathFinderVisualizer/precache-manifest.753fd06515cb4bfffa418f4a4b35d820.js", 12 | "service-worker.js": "/pathFinderVisualizer/service-worker.js", 13 | "static/css/main.3944e690.chunk.css.map": "/pathFinderVisualizer/static/css/main.3944e690.chunk.css.map" 14 | } 15 | } -------------------------------------------------------------------------------- /build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrudhviGNV/pathFinderVisualizer/178c7d4660600c2b8684e38d3a3df554f82c222d/build/favicon.ico -------------------------------------------------------------------------------- /build/index.html: -------------------------------------------------------------------------------- 1 | PathFinder Visualizer - React App



Made with Prudhvi GNV

-------------------------------------------------------------------------------- /build/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrudhviGNV/pathFinderVisualizer/178c7d4660600c2b8684e38d3a3df554f82c222d/build/logo192.png -------------------------------------------------------------------------------- /build/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrudhviGNV/pathFinderVisualizer/178c7d4660600c2b8684e38d3a3df554f82c222d/build/logo512.png -------------------------------------------------------------------------------- /build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /build/precache-manifest.753fd06515cb4bfffa418f4a4b35d820.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = (self.__precacheManifest || []).concat([ 2 | { 3 | "revision": "cd76c10519a55a2a272b80c96c9d0af2", 4 | "url": "/pathFinderVisualizer/index.html" 5 | }, 6 | { 7 | "revision": "7883538111ce8282e9ad", 8 | "url": "/pathFinderVisualizer/static/css/main.3944e690.chunk.css" 9 | }, 10 | { 11 | "revision": "3f51ea05862ffd4db38d", 12 | "url": "/pathFinderVisualizer/static/js/2.f0583ba6.chunk.js" 13 | }, 14 | { 15 | "revision": "7883538111ce8282e9ad", 16 | "url": "/pathFinderVisualizer/static/js/main.d7aa8aac.chunk.js" 17 | }, 18 | { 19 | "revision": "8420aba34769908039a4", 20 | "url": "/pathFinderVisualizer/static/js/runtime-main.0f354abe.js" 21 | } 22 | ]); -------------------------------------------------------------------------------- /build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /build/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); 15 | 16 | importScripts( 17 | "/pathFinderVisualizer/precache-manifest.753fd06515cb4bfffa418f4a4b35d820.js" 18 | ); 19 | 20 | self.addEventListener('message', (event) => { 21 | if (event.data && event.data.type === 'SKIP_WAITING') { 22 | self.skipWaiting(); 23 | } 24 | }); 25 | 26 | workbox.core.clientsClaim(); 27 | 28 | /** 29 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 30 | * requests for URLs in the manifest. 31 | * See https://goo.gl/S9QRab 32 | */ 33 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 34 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 35 | 36 | workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/pathFinderVisualizer/index.html"), { 37 | 38 | blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/], 39 | }); 40 | -------------------------------------------------------------------------------- /build/static/css/main.3944e690.chunk.css: -------------------------------------------------------------------------------- 1 | body{margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.App{text-align:center}.App-logo{height:40vmin}.App-header{background-color:#282c34;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:calc(10px + 2vmin);color:#fff}.App-link{color:#09d3ac}.node{width:25px;height:25px;outline:1px solid #afd8f8;display:inline-block}.node-finish{background-color:red}.node-start{background-color:green}.node-visited{-webkit-animation-name:visitedAnimation;animation-name:visitedAnimation;-webkit-animation-duration:1.5s;animation-duration:1.5s;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-animation-delay:0;animation-delay:0;-webkit-animation-direction:alternate;animation-direction:alternate;-webkit-animation-iteration-count:1;animation-iteration-count:1;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-play-state:running;animation-play-state:running}@-webkit-keyframes visitedAnimation{0%{-webkit-transform:scale(.3);transform:scale(.3);background-color:rgba(0,0,66,.75);border-radius:100%}50%{background-color:rgba(217,17,187,.75)}75%{-webkit-transform:scale(1.2);transform:scale(1.2);background-color:rgba(34,17,217,.75)}to{-webkit-transform:scale(1);transform:scale(1);background-color:rgba(0,218,69,.75)}}@keyframes visitedAnimation{0%{-webkit-transform:scale(.3);transform:scale(.3);background-color:rgba(0,0,66,.75);border-radius:100%}50%{background-color:rgba(217,17,187,.75)}75%{-webkit-transform:scale(1.2);transform:scale(1.2);background-color:rgba(34,17,217,.75)}to{-webkit-transform:scale(1);transform:scale(1);background-color:rgba(0,218,69,.75)}}.node-wall{background-color:#0c3547}.node-shortest-path{-webkit-animation-name:shortestPath;animation-name:shortestPath;-webkit-animation-duration:1.5s;animation-duration:1.5s;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out;-webkit-animation-delay:0;animation-delay:0;-webkit-animation-direction:alternate;animation-direction:alternate;-webkit-animation-iteration-count:1;animation-iteration-count:1;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-play-state:running;animation-play-state:running}@-webkit-keyframes shortestPath{0%{-webkit-transform:scale(.6);transform:scale(.6);background-color:#fffe6a}50%{-webkit-transform:scale(1.2);transform:scale(1.2);background-color:#fffe6a}to{-webkit-transform:scale(1);transform:scale(1);background-color:#fffe6a}}@keyframes shortestPath{0%{-webkit-transform:scale(.6);transform:scale(.6);background-color:#fffe6a}50%{-webkit-transform:scale(1.2);transform:scale(1.2);background-color:#fffe6a}to{-webkit-transform:scale(1);transform:scale(1);background-color:#fffe6a}}.grid-container{margin:10% auto}.grid{white-space:pre}button{margin:2px} 2 | /*# sourceMappingURL=main.3944e690.chunk.css.map */ -------------------------------------------------------------------------------- /build/static/css/main.3944e690.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["index.css","App.css","Node.css","PathfindingVisualizer.css"],"names":[],"mappings":"AAAA,KACE,QAAS,CACT,mIAEY,CACZ,kCAAmC,CACnC,iCACF,CAEA,KACE,uEAEF,CCZA,KACE,iBACF,CAEA,UACE,aACF,CAEA,YACE,wBAAyB,CACzB,gBAAiB,CACjB,YAAa,CACb,qBAAsB,CACtB,kBAAmB,CACnB,sBAAuB,CACvB,4BAA6B,CAC7B,UACF,CAEA,UACE,aACF,CCrBA,MACE,UAAW,CACX,WAAY,CACZ,yBAAqC,CACrC,oBACF,CAEA,aACE,oBACF,CAEA,YACE,sBACF,CAEA,cACE,uCAAgC,CAAhC,+BAAgC,CAChC,+BAAwB,CAAxB,uBAAwB,CACxB,6CAAsC,CAAtC,qCAAsC,CACtC,yBAAkB,CAAlB,iBAAkB,CAClB,qCAA8B,CAA9B,6BAA8B,CAC9B,mCAA4B,CAA5B,2BAA4B,CAC5B,oCAA6B,CAA7B,4BAA6B,CAC7B,oCAA6B,CAA7B,4BACF,CAEA,oCACE,GACE,2BAAqB,CAArB,mBAAqB,CACrB,iCAAsC,CACtC,kBACF,CAEA,IACE,qCACF,CAEA,IACE,4BAAqB,CAArB,oBAAqB,CACrB,oCACF,CAEA,GACE,0BAAmB,CAAnB,kBAAmB,CACnB,mCACF,CACF,CApBA,4BACE,GACE,2BAAqB,CAArB,mBAAqB,CACrB,iCAAsC,CACtC,kBACF,CAEA,IACE,qCACF,CAEA,IACE,4BAAqB,CAArB,oBAAqB,CACrB,oCACF,CAEA,GACE,0BAAmB,CAAnB,kBAAmB,CACnB,mCACF,CACF,CAEA,WACE,wBACF,CAEA,oBACE,mCAA4B,CAA5B,2BAA4B,CAC5B,+BAAwB,CAAxB,uBAAwB,CACxB,0CAAmC,CAAnC,kCAAmC,CACnC,yBAAkB,CAAlB,iBAAkB,CAClB,qCAA8B,CAA9B,6BAA8B,CAC9B,mCAA4B,CAA5B,2BAA4B,CAC5B,oCAA6B,CAA7B,4BAA6B,CAC7B,oCAA6B,CAA7B,4BACF,CAEA,gCACE,GACE,2BAAqB,CAArB,mBAAqB,CACrB,wBACF,CAEA,IACE,4BAAqB,CAArB,oBAAqB,CACrB,wBACF,CAEA,GACE,0BAAmB,CAAnB,kBAAmB,CACnB,wBACF,CACF,CAfA,wBACE,GACE,2BAAqB,CAArB,mBAAqB,CACrB,wBACF,CAEA,IACE,4BAAqB,CAArB,oBAAqB,CACrB,wBACF,CAEA,GACE,0BAAmB,CAAnB,kBAAmB,CACnB,wBACF,CACF,CC9EA,gBACE,eACF,CAEA,MACE,eACF,CAEA,OACE,UACF","file":"main.3944e690.chunk.css","sourcesContent":["body {\r\n margin: 0;\r\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\r\n \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\r\n sans-serif;\r\n -webkit-font-smoothing: antialiased;\r\n -moz-osx-font-smoothing: grayscale;\r\n}\r\n\r\ncode {\r\n font-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\",\r\n monospace;\r\n}\r\n",".App {\r\n text-align: center;\r\n}\r\n\r\n.App-logo {\r\n height: 40vmin;\r\n}\r\n\r\n.App-header {\r\n background-color: #282c34;\r\n min-height: 100vh;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: calc(10px + 2vmin);\r\n color: white;\r\n}\r\n\r\n.App-link {\r\n color: #09d3ac;\r\n}\r\n",".node {\r\n width: 25px;\r\n height: 25px;\r\n outline: 1px solid rgb(175, 216, 248);\r\n display: inline-block;\r\n}\r\n\r\n.node-finish {\r\n background-color: red;\r\n}\r\n\r\n.node-start {\r\n background-color: green;\r\n}\r\n\r\n.node-visited {\r\n animation-name: visitedAnimation;\r\n animation-duration: 1.5s;\r\n animation-timing-function: ease-in-out;\r\n animation-delay: 0;\r\n animation-direction: alternate;\r\n animation-iteration-count: 1;\r\n animation-fill-mode: forwards;\r\n animation-play-state: running;\r\n}\r\n\r\n@keyframes visitedAnimation {\r\n 0% {\r\n transform: scale(0.3);\r\n background-color: rgba(0, 0, 66, 0.75);\r\n border-radius: 100%;\r\n }\r\n\r\n 50% {\r\n background-color: rgba(217, 17, 187, 0.75);\r\n }\r\n\r\n 75% {\r\n transform: scale(1.2);\r\n background-color: rgba(34, 17, 217, 0.75);\r\n }\r\n\r\n 100% {\r\n transform: scale(1);\r\n background-color: rgba(0, 218, 69, 0.75);\r\n }\r\n}\r\n\r\n.node-wall {\r\n background-color: rgb(12, 53, 71);\r\n}\r\n\r\n.node-shortest-path {\r\n animation-name: shortestPath;\r\n animation-duration: 1.5s;\r\n animation-timing-function: ease-out;\r\n animation-delay: 0;\r\n animation-direction: alternate;\r\n animation-iteration-count: 1;\r\n animation-fill-mode: forwards;\r\n animation-play-state: running;\r\n}\r\n\r\n@keyframes shortestPath {\r\n 0% {\r\n transform: scale(0.6);\r\n background-color: rgb(255, 254, 106);\r\n }\r\n\r\n 50% {\r\n transform: scale(1.2);\r\n background-color: rgb(255, 254, 106);\r\n }\r\n\r\n 100% {\r\n transform: scale(1);\r\n background-color: rgb(255, 254, 106);\r\n }\r\n}\r\n",".grid-container {\r\n margin: 10% auto;\r\n}\r\n\r\n.grid {\r\n white-space: pre;\r\n}\r\n\r\nbutton {\r\n margin: 2px;\r\n}\r\n"]} -------------------------------------------------------------------------------- /build/static/js/main.d7aa8aac.chunk.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonpmy-app"]=window["webpackJsonpmy-app"]||[]).push([[0],[,,,,,,,,,,function(t,e,n){t.exports=n(19)},,,,,function(t,e,n){},function(t,e,n){},function(t,e,n){},function(t,e,n){},function(t,e,n){"use strict";n.r(e);var i=n(0),a=n.n(i),s=n(8),r=n.n(s),o=(n(15),n(16),n(9)),l=n(2),c=n(3),u=n(5),d=n(4),h=n(1),f=n(6),v=(n(17),function(t){function e(){return Object(l.a)(this,e),Object(u.a)(this,Object(d.a)(e).apply(this,arguments))}return Object(f.a)(e,t),Object(c.a)(e,[{key:"render",value:function(){var t=this.props,e=t.col,n=t.isFinish,i=t.isStart,s=t.isWall,r=t.onMouseDown,o=t.onMouseEnter,l=t.onMouseUp,c=t.row,u=n?"node-finish":i?"node-start":s?"node-wall":"";return a.a.createElement("td",{id:"node-".concat(c,"-").concat(e),className:"node ".concat(u),onMouseDown:function(){return r(c,e)},onMouseEnter:function(){return o(c,e)},onMouseUp:function(){return l()}})}}]),e}(i.Component));function N(t,e,n){var i=[];e.distance=0;for(var a=function(t){var e=[],n=!0,i=!1,a=void 0;try{for(var s,r=t[Symbol.iterator]();!(n=(s=r.next()).done);n=!0){var o=s.value,l=!0,c=!1,u=void 0;try{for(var d,h=o[Symbol.iterator]();!(l=(d=h.next()).done);l=!0){var f=d.value;e.push(f)}}catch(v){c=!0,u=v}finally{try{l||null==h.return||h.return()}finally{if(c)throw u}}}}catch(v){i=!0,a=v}finally{try{n||null==r.return||r.return()}finally{if(i)throw a}}return e}(t);a.length;){m(a);var s=a.shift();if(!s.isWall){if(s.distance===1/0)return i;if(s.isVisited=!0,i.push(s),s===n)return i;O(s,t)}}}function m(t){t.sort((function(t,e){return t.distance-e.distance}))}function O(t,e){var n=function(t,e){var n=[],i=t.col,a=t.row;a>0&&n.push(e[a-1][i]);a0&&n.push(e[a][i-1]);i0&&n.push(e[a-1][i]);a0&&n.push(e[a][i-1]);i0&&void 0!==arguments[0]?arguments[0]:t.state.ROW_COUNT,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:t.state.COLUMN_COUNT,i=[],a=0;athis.state.MOBILE_ROW_COUNT||this.state.FINISH_NODE_ROW>this.state.MOBILE_ROW_COUNT||this.state.START_NODE_COL>this.state.MOBILE_COLUMN_COUNT||this.state.FINISH_NODE_COL>this.state.MOBILE_COLUMN_COUNT?alert("Start & Finish Nodes Must Be within 10 Rows x 20 Columns"):(t=this.getInitialGrid(this.state.MOBILE_ROW_COUNT,this.state.MOBILE_COLUMN_COUNT),this.setState({isDesktopView:e,grid:t}))}}},{key:"handleMouseDown",value:function(t,e){if(!this.state.isRunning)if(this.isGridClear())if("node node-start"===document.getElementById("node-".concat(t,"-").concat(e)).className)this.setState({mouseIsPressed:!0,isStartNode:!0,currRow:t,currCol:e});else if("node node-finish"===document.getElementById("node-".concat(t,"-").concat(e)).className)this.setState({mouseIsPressed:!0,isFinishNode:!0,currRow:t,currCol:e});else{var n=E(this.state.grid,t,e);this.setState({grid:n,mouseIsPressed:!0,isWallNode:!0,currRow:t,currCol:e})}else this.clearGrid()}},{key:"isGridClear",value:function(){var t=!0,e=!1,n=void 0;try{for(var i,a=this.state.grid[Symbol.iterator]();!(t=(i=a.next()).done);t=!0){var s=i.value,r=!0,o=!1,l=void 0;try{for(var c,u=s[Symbol.iterator]();!(r=(c=u.next()).done);r=!0){var d=c.value,h=document.getElementById("node-".concat(d.row,"-").concat(d.col)).className;if("node node-visited"===h||"node node-shortest-path"===h)return!1}}catch(f){o=!0,l=f}finally{try{r||null==u.return||u.return()}finally{if(o)throw l}}}}catch(f){e=!0,n=f}finally{try{t||null==a.return||a.return()}finally{if(e)throw n}}return!0}},{key:"handleMouseEnter",value:function(t,e){if(!this.state.isRunning&&this.state.mouseIsPressed){var n=document.getElementById("node-".concat(t,"-").concat(e)).className;if(this.state.isStartNode){if("node node-wall"!==n)this.state.grid[this.state.currRow][this.state.currCol].isStart=!1,document.getElementById("node-".concat(this.state.currRow,"-").concat(this.state.currCol)).className="node",this.setState({currRow:t,currCol:e}),this.state.grid[t][e].isStart=!0,document.getElementById("node-".concat(t,"-").concat(e)).className="node node-start";this.setState({START_NODE_ROW:t,START_NODE_COL:e})}else if(this.state.isFinishNode){if("node node-wall"!==n)this.state.grid[this.state.currRow][this.state.currCol].isFinish=!1,document.getElementById("node-".concat(this.state.currRow,"-").concat(this.state.currCol)).className="node",this.setState({currRow:t,currCol:e}),this.state.grid[t][e].isFinish=!0,document.getElementById("node-".concat(t,"-").concat(e)).className="node node-finish";this.setState({FINISH_NODE_ROW:t,FINISH_NODE_COL:e})}else if(this.state.isWallNode){var i=E(this.state.grid,t,e);this.setState({grid:i})}}}},{key:"handleMouseUp",value:function(t,e){if(!this.state.isRunning){if(this.setState({mouseIsPressed:!1}),this.state.isStartNode){var n=!this.state.isStartNode;this.setState({isStartNode:n,START_NODE_ROW:t,START_NODE_COL:e})}else if(this.state.isFinishNode){var i=!this.state.isFinishNode;this.setState({isFinishNode:i,FINISH_NODE_ROW:t,FINISH_NODE_COL:e})}this.getInitialGrid()}}},{key:"handleMouseLeave",value:function(){if(this.state.isStartNode){var t=!this.state.isStartNode;this.setState({isStartNode:t,mouseIsPressed:!1})}else if(this.state.isFinishNode){var e=!this.state.isFinishNode;this.setState({isFinishNode:e,mouseIsPressed:!1})}else if(this.state.isWallNode){var n=!this.state.isWallNode;this.setState({isWallNode:n,mouseIsPressed:!1}),this.getInitialGrid()}}},{key:"clearGrid",value:function(){if(!this.state.isRunning){var t=this.state.grid.slice(),e=!0,n=!1,i=void 0;try{for(var a,s=t[Symbol.iterator]();!(e=(a=s.next()).done);e=!0){var r=a.value,o=!0,l=!1,c=void 0;try{for(var u,d=r[Symbol.iterator]();!(o=(u=d.next()).done);o=!0){var h=u.value,f=document.getElementById("node-".concat(h.row,"-").concat(h.col)).className;"node node-start"!==f&&"node node-finish"!==f&&"node node-wall"!==f&&(document.getElementById("node-".concat(h.row,"-").concat(h.col)).className="node",h.isVisited=!1,h.distance=1/0,h.distanceToFinishNode=Math.abs(this.state.FINISH_NODE_ROW-h.row)+Math.abs(this.state.FINISH_NODE_COL-h.col)),"node node-finish"===f&&(h.isVisited=!1,h.distance=1/0,h.distanceToFinishNode=0),"node node-start"===f&&(h.isVisited=!1,h.distance=1/0,h.distanceToFinishNode=Math.abs(this.state.FINISH_NODE_ROW-h.row)+Math.abs(this.state.FINISH_NODE_COL-h.col),h.isStart=!0,h.isWall=!1,h.previousNode=null,h.isNode=!0)}}catch(v){l=!0,c=v}finally{try{o||null==d.return||d.return()}finally{if(l)throw c}}}}catch(v){n=!0,i=v}finally{try{e||null==s.return||s.return()}finally{if(n)throw i}}}}},{key:"clearWalls",value:function(){if(!this.state.isRunning){var t=this.state.grid.slice(),e=!0,n=!1,i=void 0;try{for(var a,s=t[Symbol.iterator]();!(e=(a=s.next()).done);e=!0){var r=a.value,o=!0,l=!1,c=void 0;try{for(var u,d=r[Symbol.iterator]();!(o=(u=d.next()).done);o=!0){var h=u.value;"node node-wall"===document.getElementById("node-".concat(h.row,"-").concat(h.col)).className&&(document.getElementById("node-".concat(h.row,"-").concat(h.col)).className="node",h.isWall=!1)}}catch(f){l=!0,c=f}finally{try{o||null==d.return||d.return()}finally{if(l)throw c}}}}catch(f){n=!0,i=f}finally{try{e||null==s.return||s.return()}finally{if(n)throw i}}}}},{key:"visualize",value:function(t){if(!this.state.isRunning){this.clearGrid(),this.toggleIsRunning();var e,n=this.state.grid,i=n[this.state.START_NODE_ROW][this.state.START_NODE_COL],a=n[this.state.FINISH_NODE_ROW][this.state.FINISH_NODE_COL];switch(t){case"Dijkstra":e=N(n,i,a);break;case"AStar":e=y(n,i,a);break;case"BFS":e=function(t,e,n){for(var i=[],a=[e];a.length;){var s=a.shift();if(s===n)return i;if(!s.isWall&&(s.isStart||!s.isVisited)){s.isVisited=!0,i.push(s);var r=s.col,o=s.row,l=void 0;o>0&&((l=t[o-1][r]).isVisited||(l.previousNode=s,a.push(l))),o0&&((l=t[o][r-1]).isVisited||(l.previousNode=s,a.push(l))),r0&&((l=t[o-1][r]).isVisited||(l.previousNode=s,a.push(l))),o0&&((l=t[o][r-1]).isVisited||(l.previousNode=s,a.push(l))),r onMouseDown(row, col)}\r\n onMouseEnter={() => onMouseEnter(row, col)}\r\n onMouseUp={() => onMouseUp()}>\r\n );\r\n }\r\n}\r\n","// Returns all nodes in the order in which they were visited.\r\n// Make nodes point back to their previous node so that we can compute the shortest path\r\n// by backtracking from the finish node.\r\n\r\nexport function dijkstra(grid, startNode, finishNode) {\r\n const visitedNodesInOrder = [];\r\n startNode.distance = 0;\r\n const unvisitedNodes = getAllNodes(grid); // Q: different from using grid or slice of grid???\r\n\r\n while (unvisitedNodes.length) {\r\n sortNodesByDistance(unvisitedNodes);\r\n const closestNode = unvisitedNodes.shift();\r\n // If we encounter a wall, we skip it.\r\n if (!closestNode.isWall) {\r\n // If the closest node is at a distance of infinity,\r\n // we must be trapped and should stop.\r\n if (closestNode.distance === Infinity) return visitedNodesInOrder;\r\n closestNode.isVisited = true;\r\n visitedNodesInOrder.push(closestNode);\r\n if (closestNode === finishNode) return visitedNodesInOrder;\r\n updateUnvisitedNeighbors(closestNode, grid);\r\n }\r\n }\r\n}\r\n\r\nfunction getAllNodes(grid) {\r\n const nodes = [];\r\n for (const row of grid) {\r\n for (const node of row) {\r\n nodes.push(node);\r\n }\r\n }\r\n return nodes;\r\n}\r\n\r\nfunction sortNodesByDistance(unvisitedNodes) {\r\n unvisitedNodes.sort((nodeA, nodeB) => nodeA.distance - nodeB.distance);\r\n}\r\n\r\nfunction updateUnvisitedNeighbors(node, grid) {\r\n const unvisitedNeighbors = getUnvisitedNeighbors(node, grid);\r\n for (const neighbor of unvisitedNeighbors) {\r\n neighbor.distance = node.distance + 1;\r\n neighbor.previousNode = node;\r\n }\r\n}\r\n\r\nfunction getUnvisitedNeighbors(node, grid) {\r\n const neighbors = [];\r\n const {col, row} = node;\r\n if (row > 0) neighbors.push(grid[row - 1][col]);\r\n if (row < grid.length - 1) neighbors.push(grid[row + 1][col]);\r\n if (col > 0) neighbors.push(grid[row][col - 1]);\r\n if (col < grid[0].length - 1) neighbors.push(grid[row][col + 1]);\r\n return neighbors.filter(neighbor => !neighbor.isVisited);\r\n}\r\n","// Returns all nodes in the order in which they were visited.\r\n// Make nodes point back to their previous node so that we can compute the shortest path\r\n// by backtracking from the finish node.\r\n\r\nexport function AStar(grid, startNode, finishNode) {\r\n const visitedNodesInOrder = [];\r\n startNode.distance = 0;\r\n const unvisitedNodes = getAllNodes(grid); // Q: different from using grid or slice of grid???\r\n\r\n while (unvisitedNodes.length) {\r\n sortByDistance(unvisitedNodes);\r\n const closestNode = unvisitedNodes.shift();\r\n // If we encounter a wall, we skip it.\r\n if (!closestNode.isWall) {\r\n // If the closest node is at a distance of infinity,\r\n // we must be trapped and should stop.\r\n if (closestNode.distance === Infinity) return visitedNodesInOrder;\r\n closestNode.isVisited = true;\r\n visitedNodesInOrder.push(closestNode);\r\n if (closestNode === finishNode) return visitedNodesInOrder;\r\n updateUnvisitedNeighbors(closestNode, grid);\r\n }\r\n }\r\n}\r\n\r\nfunction getAllNodes(grid) {\r\n const nodes = [];\r\n for (const row of grid) {\r\n for (const node of row) {\r\n nodes.push(node);\r\n }\r\n }\r\n return nodes;\r\n}\r\n\r\nfunction sortByDistance(unvisitedNodes) {\r\n unvisitedNodes.sort((nodeA, nodeB) => nodeA.distance - nodeB.distance);\r\n}\r\n\r\nfunction updateUnvisitedNeighbors(node, grid) {\r\n const unvisitedNeighbors = getUnvisitedNeighbors(node, grid);\r\n for (const neighbor of unvisitedNeighbors) {\r\n neighbor.distance = node.distance + 1 + neighbor.distanceToFinishNode;\r\n neighbor.previousNode = node;\r\n }\r\n}\r\n\r\nfunction getUnvisitedNeighbors(node, grid) {\r\n const neighbors = [];\r\n const {col, row} = node;\r\n if (row > 0) neighbors.push(grid[row - 1][col]);\r\n if (row < grid.length - 1) neighbors.push(grid[row + 1][col]);\r\n if (col > 0) neighbors.push(grid[row][col - 1]);\r\n if (col < grid[0].length - 1) neighbors.push(grid[row][col + 1]);\r\n return neighbors.filter(neighbor => !neighbor.isVisited);\r\n}\r\n","import React, {Component} from 'react';\r\nimport Node from './Node/Node';\r\nimport {dijkstra} from '../algorithms/dijkstra';\r\nimport {AStar} from '../algorithms/aStar';\r\nimport {dfs} from '../algorithms/dfs';\r\nimport {bfs} from '../algorithms/bfs';\r\n\r\nimport './PathfindingVisualizer.css';\r\n\r\nexport default class PathfindingVisualizer extends Component {\r\n constructor() {\r\n super();\r\n this.state = {\r\n grid: [],\r\n START_NODE_ROW: 5,\r\n FINISH_NODE_ROW: 5,\r\n START_NODE_COL: 5,\r\n FINISH_NODE_COL: 15,\r\n mouseIsPressed: false,\r\n ROW_COUNT: 25,\r\n COLUMN_COUNT: 35,\r\n MOBILE_ROW_COUNT: 10,\r\n MOBILE_COLUMN_COUNT: 20,\r\n isRunning: false,\r\n isStartNode: false,\r\n isFinishNode: false,\r\n isWallNode: false, // xxxxxxx\r\n currRow: 0,\r\n currCol: 0,\r\n isDesktopView: true,\r\n };\r\n\r\n this.handleMouseDown = this.handleMouseDown.bind(this);\r\n this.handleMouseLeave = this.handleMouseLeave.bind(this);\r\n this.toggleIsRunning = this.toggleIsRunning.bind(this);\r\n }\r\n\r\n componentDidMount() {\r\n const grid = this.getInitialGrid();\r\n this.setState({grid});\r\n }\r\n\r\n toggleIsRunning() {\r\n this.setState({isRunning: !this.state.isRunning});\r\n }\r\n\r\n toggleView() {\r\n if (!this.state.isRunning) {\r\n this.clearGrid();\r\n this.clearWalls();\r\n const isDesktopView = !this.state.isDesktopView;\r\n let grid;\r\n if (isDesktopView) {\r\n grid = this.getInitialGrid(\r\n this.state.ROW_COUNT,\r\n this.state.COLUMN_COUNT,\r\n );\r\n this.setState({isDesktopView, grid});\r\n } else {\r\n if (\r\n this.state.START_NODE_ROW > this.state.MOBILE_ROW_COUNT ||\r\n this.state.FINISH_NODE_ROW > this.state.MOBILE_ROW_COUNT ||\r\n this.state.START_NODE_COL > this.state.MOBILE_COLUMN_COUNT ||\r\n this.state.FINISH_NODE_COL > this.state.MOBILE_COLUMN_COUNT\r\n ) {\r\n alert('Start & Finish Nodes Must Be within 10 Rows x 20 Columns');\r\n } else {\r\n grid = this.getInitialGrid(\r\n this.state.MOBILE_ROW_COUNT,\r\n this.state.MOBILE_COLUMN_COUNT,\r\n );\r\n this.setState({isDesktopView, grid});\r\n }\r\n }\r\n }\r\n }\r\n\r\n /******************** Set up the initial grid ********************/\r\n getInitialGrid = (\r\n rowCount = this.state.ROW_COUNT,\r\n colCount = this.state.COLUMN_COUNT,\r\n ) => {\r\n const initialGrid = [];\r\n for (let row = 0; row < rowCount; row++) {\r\n const currentRow = [];\r\n for (let col = 0; col < colCount; col++) {\r\n currentRow.push(this.createNode(row, col));\r\n }\r\n initialGrid.push(currentRow);\r\n }\r\n return initialGrid;\r\n };\r\n\r\n createNode = (row, col) => {\r\n return {\r\n row,\r\n col,\r\n isStart:\r\n row === this.state.START_NODE_ROW && col === this.state.START_NODE_COL,\r\n isFinish:\r\n row === this.state.FINISH_NODE_ROW &&\r\n col === this.state.FINISH_NODE_COL,\r\n distance: Infinity,\r\n distanceToFinishNode:\r\n Math.abs(this.state.FINISH_NODE_ROW - row) +\r\n Math.abs(this.state.FINISH_NODE_COL - col),\r\n isVisited: false,\r\n isWall: false,\r\n previousNode: null,\r\n isNode: true,\r\n };\r\n };\r\n\r\n /******************** Control mouse events ********************/\r\n handleMouseDown(row, col) {\r\n if (!this.state.isRunning) {\r\n if (this.isGridClear()) {\r\n if (\r\n document.getElementById(`node-${row}-${col}`).className ===\r\n 'node node-start'\r\n ) {\r\n this.setState({\r\n mouseIsPressed: true,\r\n isStartNode: true,\r\n currRow: row,\r\n currCol: col,\r\n });\r\n } else if (\r\n document.getElementById(`node-${row}-${col}`).className ===\r\n 'node node-finish'\r\n ) {\r\n this.setState({\r\n mouseIsPressed: true,\r\n isFinishNode: true,\r\n currRow: row,\r\n currCol: col,\r\n });\r\n } else {\r\n const newGrid = getNewGridWithWallToggled(this.state.grid, row, col);\r\n this.setState({\r\n grid: newGrid,\r\n mouseIsPressed: true,\r\n isWallNode: true,\r\n currRow: row,\r\n currCol: col,\r\n });\r\n }\r\n } else {\r\n this.clearGrid();\r\n }\r\n }\r\n }\r\n\r\n isGridClear() {\r\n for (const row of this.state.grid) {\r\n for (const node of row) {\r\n const nodeClassName = document.getElementById(\r\n `node-${node.row}-${node.col}`,\r\n ).className;\r\n if (\r\n nodeClassName === 'node node-visited' ||\r\n nodeClassName === 'node node-shortest-path'\r\n ) {\r\n return false;\r\n }\r\n }\r\n }\r\n return true;\r\n }\r\n\r\n handleMouseEnter(row, col) {\r\n if (!this.state.isRunning) {\r\n if (this.state.mouseIsPressed) {\r\n const nodeClassName = document.getElementById(`node-${row}-${col}`)\r\n .className;\r\n if (this.state.isStartNode) {\r\n if (nodeClassName !== 'node node-wall') {\r\n const prevStartNode = this.state.grid[this.state.currRow][\r\n this.state.currCol\r\n ];\r\n prevStartNode.isStart = false;\r\n document.getElementById(\r\n `node-${this.state.currRow}-${this.state.currCol}`,\r\n ).className = 'node';\r\n\r\n this.setState({currRow: row, currCol: col});\r\n const currStartNode = this.state.grid[row][col];\r\n currStartNode.isStart = true;\r\n document.getElementById(`node-${row}-${col}`).className =\r\n 'node node-start';\r\n }\r\n this.setState({START_NODE_ROW: row, START_NODE_COL: col});\r\n } else if (this.state.isFinishNode) {\r\n if (nodeClassName !== 'node node-wall') {\r\n const prevFinishNode = this.state.grid[this.state.currRow][\r\n this.state.currCol\r\n ];\r\n prevFinishNode.isFinish = false;\r\n document.getElementById(\r\n `node-${this.state.currRow}-${this.state.currCol}`,\r\n ).className = 'node';\r\n\r\n this.setState({currRow: row, currCol: col});\r\n const currFinishNode = this.state.grid[row][col];\r\n currFinishNode.isFinish = true;\r\n document.getElementById(`node-${row}-${col}`).className =\r\n 'node node-finish';\r\n }\r\n this.setState({FINISH_NODE_ROW: row, FINISH_NODE_COL: col});\r\n } else if (this.state.isWallNode) {\r\n const newGrid = getNewGridWithWallToggled(this.state.grid, row, col);\r\n this.setState({grid: newGrid});\r\n }\r\n }\r\n }\r\n }\r\n\r\n handleMouseUp(row, col) {\r\n if (!this.state.isRunning) {\r\n this.setState({mouseIsPressed: false});\r\n if (this.state.isStartNode) {\r\n const isStartNode = !this.state.isStartNode;\r\n this.setState({isStartNode, START_NODE_ROW: row, START_NODE_COL: col});\r\n } else if (this.state.isFinishNode) {\r\n const isFinishNode = !this.state.isFinishNode;\r\n this.setState({\r\n isFinishNode,\r\n FINISH_NODE_ROW: row,\r\n FINISH_NODE_COL: col,\r\n });\r\n }\r\n this.getInitialGrid();\r\n }\r\n }\r\n\r\n handleMouseLeave() {\r\n if (this.state.isStartNode) {\r\n const isStartNode = !this.state.isStartNode;\r\n this.setState({isStartNode, mouseIsPressed: false});\r\n } else if (this.state.isFinishNode) {\r\n const isFinishNode = !this.state.isFinishNode;\r\n this.setState({isFinishNode, mouseIsPressed: false});\r\n } else if (this.state.isWallNode) {\r\n const isWallNode = !this.state.isWallNode;\r\n this.setState({isWallNode, mouseIsPressed: false});\r\n this.getInitialGrid();\r\n }\r\n }\r\n\r\n /******************** Clear Board/Walls ********************/\r\n\r\n clearGrid() {\r\n if (!this.state.isRunning) {\r\n const newGrid = this.state.grid.slice();\r\n for (const row of newGrid) {\r\n for (const node of row) {\r\n let nodeClassName = document.getElementById(\r\n `node-${node.row}-${node.col}`,\r\n ).className;\r\n if (\r\n nodeClassName !== 'node node-start' &&\r\n nodeClassName !== 'node node-finish' &&\r\n nodeClassName !== 'node node-wall'\r\n ) {\r\n document.getElementById(`node-${node.row}-${node.col}`).className =\r\n 'node';\r\n node.isVisited = false;\r\n node.distance = Infinity;\r\n node.distanceToFinishNode =\r\n Math.abs(this.state.FINISH_NODE_ROW - node.row) +\r\n Math.abs(this.state.FINISH_NODE_COL - node.col);\r\n }\r\n if (nodeClassName === 'node node-finish') {\r\n node.isVisited = false;\r\n node.distance = Infinity;\r\n node.distanceToFinishNode = 0;\r\n }\r\n if (nodeClassName === 'node node-start') {\r\n node.isVisited = false;\r\n node.distance = Infinity;\r\n node.distanceToFinishNode =\r\n Math.abs(this.state.FINISH_NODE_ROW - node.row) +\r\n Math.abs(this.state.FINISH_NODE_COL - node.col);\r\n node.isStart = true;\r\n node.isWall = false;\r\n node.previousNode = null;\r\n node.isNode = true;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n clearWalls() {\r\n if (!this.state.isRunning) {\r\n const newGrid = this.state.grid.slice();\r\n for (const row of newGrid) {\r\n for (const node of row) {\r\n let nodeClassName = document.getElementById(\r\n `node-${node.row}-${node.col}`,\r\n ).className;\r\n if (nodeClassName === 'node node-wall') {\r\n document.getElementById(`node-${node.row}-${node.col}`).className =\r\n 'node';\r\n node.isWall = false;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n /******************** Create Animations ********************/\r\n visualize(algo) {\r\n if (!this.state.isRunning) {\r\n this.clearGrid();\r\n this.toggleIsRunning();\r\n const {grid} = this.state;\r\n const startNode =\r\n grid[this.state.START_NODE_ROW][this.state.START_NODE_COL];\r\n const finishNode =\r\n grid[this.state.FINISH_NODE_ROW][this.state.FINISH_NODE_COL];\r\n let visitedNodesInOrder;\r\n switch (algo) {\r\n case 'Dijkstra':\r\n visitedNodesInOrder = dijkstra(grid, startNode, finishNode);\r\n break;\r\n case 'AStar':\r\n visitedNodesInOrder = AStar(grid, startNode, finishNode);\r\n break;\r\n case 'BFS':\r\n visitedNodesInOrder = bfs(grid, startNode, finishNode);\r\n break;\r\n case 'DFS':\r\n visitedNodesInOrder = dfs(grid, startNode, finishNode);\r\n break;\r\n default:\r\n // should never get here\r\n break;\r\n }\r\n const nodesInShortestPathOrder = getNodesInShortestPathOrder(finishNode);\r\n nodesInShortestPathOrder.push('end');\r\n this.animate(visitedNodesInOrder, nodesInShortestPathOrder);\r\n }\r\n }\r\n\r\n animate(visitedNodesInOrder, nodesInShortestPathOrder) {\r\n for (let i = 0; i <= visitedNodesInOrder.length; i++) {\r\n if (i === visitedNodesInOrder.length) {\r\n setTimeout(() => {\r\n this.animateShortestPath(nodesInShortestPathOrder);\r\n }, 10 * i);\r\n return;\r\n }\r\n setTimeout(() => {\r\n const node = visitedNodesInOrder[i];\r\n const nodeClassName = document.getElementById(\r\n `node-${node.row}-${node.col}`,\r\n ).className;\r\n if (\r\n nodeClassName !== 'node node-start' &&\r\n nodeClassName !== 'node node-finish'\r\n ) {\r\n document.getElementById(`node-${node.row}-${node.col}`).className =\r\n 'node node-visited';\r\n }\r\n }, 10 * i);\r\n }\r\n }\r\n\r\n /******************** Create path from start to finish ********************/\r\n animateShortestPath(nodesInShortestPathOrder) {\r\n for (let i = 0; i < nodesInShortestPathOrder.length; i++) {\r\n if (nodesInShortestPathOrder[i] === 'end') {\r\n setTimeout(() => {\r\n this.toggleIsRunning();\r\n }, i * 50);\r\n } else {\r\n setTimeout(() => {\r\n const node = nodesInShortestPathOrder[i];\r\n const nodeClassName = document.getElementById(\r\n `node-${node.row}-${node.col}`,\r\n ).className;\r\n if (\r\n nodeClassName !== 'node node-start' &&\r\n nodeClassName !== 'node node-finish'\r\n ) {\r\n document.getElementById(`node-${node.row}-${node.col}`).className =\r\n 'node node-shortest-path';\r\n }\r\n }, i * 40);\r\n }\r\n }\r\n }\r\n\r\n render() {\r\n const {grid, mouseIsPressed} = this.state;\r\n return (\r\n
\r\n \r\n\r\n this.handleMouseLeave()}>\r\n \r\n {grid.map((row, rowIdx) => {\r\n return (\r\n \r\n {row.map((node, nodeIdx) => {\r\n const {row, col, isFinish, isStart, isWall} = node;\r\n return (\r\n \r\n this.handleMouseDown(row, col)\r\n }\r\n onMouseEnter={(row, col) =>\r\n this.handleMouseEnter(row, col)\r\n }\r\n onMouseUp={() => this.handleMouseUp(row, col)}\r\n row={row}>\r\n );\r\n })}\r\n \r\n );\r\n })}\r\n \r\n \r\n this.clearGrid()}>\r\n Clear Grid\r\n \r\n this.clearWalls()}>\r\n Clear Walls\r\n \r\n this.visualize('Dijkstra')}>\r\n Dijkstra's\r\n \r\n this.visualize('AStar')}>\r\n A*\r\n \r\n this.visualize('BFS')}>\r\n Bread First Search\r\n \r\n this.visualize('DFS')}>\r\n Depth First Search\r\n \r\n {this.state.isDesktopView ? (\r\n this.toggleView()}>\r\n Mobile View\r\n \r\n ) : (\r\n this.toggleView()}>\r\n Desktop View\r\n \r\n )}\r\n
\r\n );\r\n }\r\n}\r\n\r\n/******************** Create Walls ********************/\r\nconst getNewGridWithWallToggled = (grid, row, col) => {\r\n // mouseDown starts to act strange if I don't make newGrid and work off of grid instead.\r\n const newGrid = grid.slice();\r\n const node = newGrid[row][col];\r\n if (!node.isStart && !node.isFinish && node.isNode) {\r\n const newNode = {\r\n ...node,\r\n isWall: !node.isWall,\r\n };\r\n newGrid[row][col] = newNode;\r\n }\r\n return newGrid;\r\n};\r\n\r\n// Backtracks from the finishNode to find the shortest path.\r\n// Only works when called after the pathfinding methods.\r\nfunction getNodesInShortestPathOrder(finishNode) {\r\n const nodesInShortestPathOrder = [];\r\n let currentNode = finishNode;\r\n while (currentNode !== null) {\r\n nodesInShortestPathOrder.unshift(currentNode);\r\n currentNode = currentNode.previousNode;\r\n }\r\n return nodesInShortestPathOrder;\r\n}\r\n","// Returns all nodes in the order in which they were visited.\r\n// Make nodes point back to their previous node so that we can compute the shortest path\r\n// by backtracking from the finish node.\r\n\r\nexport function bfs(grid, startNode, finishNode) {\r\n const visitedNodesInOrder = [];\r\n let nextNodesStack = [startNode];\r\n while (nextNodesStack.length) {\r\n const currentNode = nextNodesStack.shift();\r\n if (currentNode === finishNode) return visitedNodesInOrder;\r\n\r\n if (\r\n !currentNode.isWall &&\r\n (currentNode.isStart || !currentNode.isVisited)\r\n ) {\r\n currentNode.isVisited = true;\r\n visitedNodesInOrder.push(currentNode);\r\n const {col, row} = currentNode;\r\n let nextNode;\r\n if (row > 0) {\r\n nextNode = grid[row - 1][col];\r\n if (!nextNode.isVisited) {\r\n nextNode.previousNode = currentNode;\r\n nextNodesStack.push(nextNode);\r\n }\r\n }\r\n if (row < grid.length - 1) {\r\n nextNode = grid[row + 1][col];\r\n if (!nextNode.isVisited) {\r\n nextNode.previousNode = currentNode;\r\n nextNodesStack.push(nextNode);\r\n }\r\n }\r\n if (col > 0) {\r\n nextNode = grid[row][col - 1];\r\n if (!nextNode.isVisited) {\r\n nextNode.previousNode = currentNode;\r\n nextNodesStack.push(nextNode);\r\n }\r\n }\r\n if (col < grid[0].length - 1) {\r\n nextNode = grid[row][col + 1];\r\n if (!nextNode.isVisited) {\r\n nextNode.previousNode = currentNode;\r\n nextNodesStack.push(nextNode);\r\n }\r\n }\r\n }\r\n }\r\n // return visitedNodesInOrder;\r\n}\r\n","// Returns all nodes in the order in which they were visited.\r\n// Make nodes point back to their previous node so that we can compute the shortest path\r\n// by backtracking from the finish node.\r\n\r\nexport function dfs(grid, startNode, finishNode) {\r\n const visitedNodesInOrder = [];\r\n const nextNodesStack = [];\r\n nextNodesStack.push(startNode);\r\n while (nextNodesStack.length) {\r\n const currentNode = nextNodesStack.pop();\r\n\r\n if (currentNode === finishNode) {\r\n return visitedNodesInOrder;\r\n }\r\n\r\n if (\r\n !currentNode.isWall &&\r\n (currentNode.isStart || !currentNode.isVisited)\r\n ) {\r\n currentNode.isVisited = true;\r\n visitedNodesInOrder.push(currentNode);\r\n\r\n const {col, row} = currentNode;\r\n let nextNode;\r\n if (row > 0) {\r\n nextNode = grid[row - 1][col];\r\n if (!nextNode.isVisited) {\r\n nextNode.previousNode = currentNode;\r\n nextNodesStack.push(nextNode);\r\n }\r\n }\r\n if (row < grid.length - 1) {\r\n nextNode = grid[row + 1][col];\r\n if (!nextNode.isVisited) {\r\n nextNode.previousNode = currentNode;\r\n nextNodesStack.push(nextNode);\r\n }\r\n }\r\n if (col > 0) {\r\n nextNode = grid[row][col - 1];\r\n if (!nextNode.isVisited) {\r\n nextNode.previousNode = currentNode;\r\n nextNodesStack.push(nextNode);\r\n }\r\n }\r\n if (col < grid[0].length - 1) {\r\n nextNode = grid[row][col + 1];\r\n if (!nextNode.isVisited) {\r\n nextNode.previousNode = currentNode;\r\n nextNodesStack.push(nextNode);\r\n }\r\n }\r\n }\r\n }\r\n}\r\n","import React from 'react';\r\nimport './App.css';\r\nimport PathfindingVisualizer from './PathfindingVisualizer/PathfindingVisualizer';\r\n\r\nfunction App() {\r\n return (\r\n
\r\n \r\n
\r\n );\r\n}\r\n\r\nexport default App;\r\n","// This optional code is used to register a service worker.\r\n// register() is not called by default.\r\n\r\n// This lets the app load faster on subsequent visits in production, and gives\r\n// it offline capabilities. However, it also means that developers (and users)\r\n// will only see deployed updates on subsequent visits to a page, after all the\r\n// existing tabs open on the page have been closed, since previously cached\r\n// resources are updated in the background.\r\n\r\n// To learn more about the benefits of this model and instructions on how to\r\n// opt-in, read https://bit.ly/CRA-PWA\r\n\r\nconst isLocalhost = Boolean(\r\n window.location.hostname === 'localhost' ||\r\n // [::1] is the IPv6 localhost address.\r\n window.location.hostname === '[::1]' ||\r\n // 127.0.0.1/8 is considered localhost for IPv4.\r\n window.location.hostname.match(\r\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\r\n )\r\n);\r\n\r\nexport function register(config) {\r\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\r\n // The URL constructor is available in all browsers that support SW.\r\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);\r\n if (publicUrl.origin !== window.location.origin) {\r\n // Our service worker won't work if PUBLIC_URL is on a different origin\r\n // from what our page is served on. This might happen if a CDN is used to\r\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\r\n return;\r\n }\r\n\r\n window.addEventListener('load', () => {\r\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\r\n\r\n if (isLocalhost) {\r\n // This is running on localhost. Let's check if a service worker still exists or not.\r\n checkValidServiceWorker(swUrl, config);\r\n\r\n // Add some additional logging to localhost, pointing developers to the\r\n // service worker/PWA documentation.\r\n navigator.serviceWorker.ready.then(() => {\r\n console.log(\r\n 'This web app is being served cache-first by a service ' +\r\n 'worker. To learn more, visit https://bit.ly/CRA-PWA'\r\n );\r\n });\r\n } else {\r\n // Is not localhost. Just register service worker\r\n registerValidSW(swUrl, config);\r\n }\r\n });\r\n }\r\n}\r\n\r\nfunction registerValidSW(swUrl, config) {\r\n navigator.serviceWorker\r\n .register(swUrl)\r\n .then(registration => {\r\n registration.onupdatefound = () => {\r\n const installingWorker = registration.installing;\r\n if (installingWorker == null) {\r\n return;\r\n }\r\n installingWorker.onstatechange = () => {\r\n if (installingWorker.state === 'installed') {\r\n if (navigator.serviceWorker.controller) {\r\n // At this point, the updated precached content has been fetched,\r\n // but the previous service worker will still serve the older\r\n // content until all client tabs are closed.\r\n console.log(\r\n 'New content is available and will be used when all ' +\r\n 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'\r\n );\r\n\r\n // Execute callback\r\n if (config && config.onUpdate) {\r\n config.onUpdate(registration);\r\n }\r\n } else {\r\n // At this point, everything has been precached.\r\n // It's the perfect time to display a\r\n // \"Content is cached for offline use.\" message.\r\n console.log('Content is cached for offline use.');\r\n\r\n // Execute callback\r\n if (config && config.onSuccess) {\r\n config.onSuccess(registration);\r\n }\r\n }\r\n }\r\n };\r\n };\r\n })\r\n .catch(error => {\r\n console.error('Error during service worker registration:', error);\r\n });\r\n}\r\n\r\nfunction checkValidServiceWorker(swUrl, config) {\r\n // Check if the service worker can be found. If it can't reload the page.\r\n fetch(swUrl)\r\n .then(response => {\r\n // Ensure service worker exists, and that we really are getting a JS file.\r\n const contentType = response.headers.get('content-type');\r\n if (\r\n response.status === 404 ||\r\n (contentType != null && contentType.indexOf('javascript') === -1)\r\n ) {\r\n // No service worker found. Probably a different app. Reload the page.\r\n navigator.serviceWorker.ready.then(registration => {\r\n registration.unregister().then(() => {\r\n window.location.reload();\r\n });\r\n });\r\n } else {\r\n // Service worker found. Proceed as normal.\r\n registerValidSW(swUrl, config);\r\n }\r\n })\r\n .catch(() => {\r\n console.log(\r\n 'No internet connection found. App is running in offline mode.'\r\n );\r\n });\r\n}\r\n\r\nexport function unregister() {\r\n if ('serviceWorker' in navigator) {\r\n navigator.serviceWorker.ready.then(registration => {\r\n registration.unregister();\r\n });\r\n }\r\n}\r\n","import React from 'react';\r\nimport ReactDOM from 'react-dom';\r\nimport './index.css';\r\nimport App from './App';\r\nimport * as serviceWorker from './serviceWorker';\r\n\r\nReactDOM.render(, document.getElementById('root'));\r\n\r\n// If you want your app to work offline and load faster, you can change\r\n// unregister() to register() below. Note this comes with some pitfalls.\r\n// Learn more about service workers: https://bit.ly/CRA-PWA\r\nserviceWorker.unregister();\r\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /build/static/js/runtime-main.0f354abe.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,p,a=r[0],i=r[1],l=r[2],c=0,s=[];c0.2%", 25 | "not dead", 26 | "not op_mini all" 27 | ], 28 | "development": [ 29 | "last 1 chrome version", 30 | "last 1 firefox version", 31 | "last 1 safari version" 32 | ] 33 | }, 34 | "devDependencies": { 35 | "gh-pages": "^3.1.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrudhviGNV/pathFinderVisualizer/178c7d4660600c2b8684e38d3a3df554f82c222d/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 20 | 21 | 30 | PathFinder Visualizer - React App 31 | 32 | 38 | 39 | 40 | 100 | 101 |
102 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 |


121 |
Made with Prudhvi GNV
122 |
123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrudhviGNV/pathFinderVisualizer/178c7d4660600c2b8684e38d3a3df554f82c222d/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrudhviGNV/pathFinderVisualizer/178c7d4660600c2b8684e38d3a3df554f82c222d/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | } 8 | 9 | .App-header { 10 | background-color: #282c34; 11 | min-height: 100vh; 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: center; 16 | font-size: calc(10px + 2vmin); 17 | color: white; 18 | } 19 | 20 | .App-link { 21 | color: #09d3ac; 22 | } 23 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.css'; 3 | import PathfindingVisualizer from './PathfindingVisualizer/PathfindingVisualizer'; 4 | 5 | function App() { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | 13 | export default App; 14 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/PathfindingVisualizer/Node/Node.css: -------------------------------------------------------------------------------- 1 | .node { 2 | width: 25px; 3 | height: 25px; 4 | outline: 1px solid rgb(175, 216, 248); 5 | display: inline-block; 6 | } 7 | 8 | .node-finish { 9 | background-color: red; 10 | } 11 | 12 | .node-start { 13 | background-color: green; 14 | } 15 | 16 | .node-visited { 17 | animation-name: visitedAnimation; 18 | animation-duration: 1.5s; 19 | animation-timing-function: ease-in-out; 20 | animation-delay: 0; 21 | animation-direction: alternate; 22 | animation-iteration-count: 1; 23 | animation-fill-mode: forwards; 24 | animation-play-state: running; 25 | } 26 | 27 | @keyframes visitedAnimation { 28 | 0% { 29 | transform: scale(0.3); 30 | background-color: rgba(0, 0, 66, 0.75); 31 | border-radius: 100%; 32 | } 33 | 34 | 50% { 35 | background-color: rgba(217, 17, 187, 0.75); 36 | } 37 | 38 | 75% { 39 | transform: scale(1.2); 40 | background-color: rgba(34, 17, 217, 0.75); 41 | } 42 | 43 | 100% { 44 | transform: scale(1); 45 | background-color: rgba(0, 218, 69, 0.75); 46 | } 47 | } 48 | 49 | .node-wall { 50 | background-color: rgb(12, 53, 71); 51 | } 52 | 53 | .node-shortest-path { 54 | animation-name: shortestPath; 55 | animation-duration: 1.5s; 56 | animation-timing-function: ease-out; 57 | animation-delay: 0; 58 | animation-direction: alternate; 59 | animation-iteration-count: 1; 60 | animation-fill-mode: forwards; 61 | animation-play-state: running; 62 | } 63 | 64 | @keyframes shortestPath { 65 | 0% { 66 | transform: scale(0.6); 67 | background-color: rgb(255, 254, 106); 68 | } 69 | 70 | 50% { 71 | transform: scale(1.2); 72 | background-color: rgb(255, 254, 106); 73 | } 74 | 75 | 100% { 76 | transform: scale(1); 77 | background-color: rgb(255, 254, 106); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/PathfindingVisualizer/Node/Node.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | import './Node.css'; 4 | 5 | export default class Node extends Component { 6 | render() { 7 | const { 8 | col, 9 | isFinish, 10 | isStart, 11 | isWall, 12 | onMouseDown, 13 | onMouseEnter, 14 | onMouseUp, 15 | row, 16 | } = this.props; 17 | 18 | const extraClassName = isFinish 19 | ? 'node-finish' 20 | : isStart 21 | ? 'node-start' 22 | : isWall 23 | ? 'node-wall' 24 | : ''; 25 | 26 | return ( 27 | onMouseDown(row, col)} 31 | onMouseEnter={() => onMouseEnter(row, col)} 32 | onMouseUp={() => onMouseUp()}> 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/PathfindingVisualizer/PathfindingVisualizer.css: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | margin: 10% auto; 3 | } 4 | 5 | .grid { 6 | white-space: pre; 7 | } 8 | 9 | button { 10 | margin: 2px; 11 | } 12 | -------------------------------------------------------------------------------- /src/PathfindingVisualizer/PathfindingVisualizer.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import Node from './Node/Node'; 3 | import {dijkstra} from '../algorithms/dijkstra'; 4 | import {AStar} from '../algorithms/aStar'; 5 | import {dfs} from '../algorithms/dfs'; 6 | import {bfs} from '../algorithms/bfs'; 7 | 8 | import './PathfindingVisualizer.css'; 9 | 10 | export default class PathfindingVisualizer extends Component { 11 | constructor() { 12 | super(); 13 | this.state = { 14 | grid: [], 15 | START_NODE_ROW: 5, 16 | FINISH_NODE_ROW: 5, 17 | START_NODE_COL: 5, 18 | FINISH_NODE_COL: 15, 19 | mouseIsPressed: false, 20 | ROW_COUNT: 25, 21 | COLUMN_COUNT: 35, 22 | MOBILE_ROW_COUNT: 10, 23 | MOBILE_COLUMN_COUNT: 20, 24 | isRunning: false, 25 | isStartNode: false, 26 | isFinishNode: false, 27 | isWallNode: false, // xxxxxxx 28 | currRow: 0, 29 | currCol: 0, 30 | isDesktopView: true, 31 | }; 32 | 33 | this.handleMouseDown = this.handleMouseDown.bind(this); 34 | this.handleMouseLeave = this.handleMouseLeave.bind(this); 35 | this.toggleIsRunning = this.toggleIsRunning.bind(this); 36 | } 37 | 38 | componentDidMount() { 39 | const grid = this.getInitialGrid(); 40 | this.setState({grid}); 41 | } 42 | 43 | toggleIsRunning() { 44 | this.setState({isRunning: !this.state.isRunning}); 45 | } 46 | 47 | toggleView() { 48 | if (!this.state.isRunning) { 49 | this.clearGrid(); 50 | this.clearWalls(); 51 | const isDesktopView = !this.state.isDesktopView; 52 | let grid; 53 | if (isDesktopView) { 54 | grid = this.getInitialGrid( 55 | this.state.ROW_COUNT, 56 | this.state.COLUMN_COUNT, 57 | ); 58 | this.setState({isDesktopView, grid}); 59 | } else { 60 | if ( 61 | this.state.START_NODE_ROW > this.state.MOBILE_ROW_COUNT || 62 | this.state.FINISH_NODE_ROW > this.state.MOBILE_ROW_COUNT || 63 | this.state.START_NODE_COL > this.state.MOBILE_COLUMN_COUNT || 64 | this.state.FINISH_NODE_COL > this.state.MOBILE_COLUMN_COUNT 65 | ) { 66 | alert('Start & Finish Nodes Must Be within 10 Rows x 20 Columns'); 67 | } else { 68 | grid = this.getInitialGrid( 69 | this.state.MOBILE_ROW_COUNT, 70 | this.state.MOBILE_COLUMN_COUNT, 71 | ); 72 | this.setState({isDesktopView, grid}); 73 | } 74 | } 75 | } 76 | } 77 | 78 | /******************** Set up the initial grid ********************/ 79 | getInitialGrid = ( 80 | rowCount = this.state.ROW_COUNT, 81 | colCount = this.state.COLUMN_COUNT, 82 | ) => { 83 | const initialGrid = []; 84 | for (let row = 0; row < rowCount; row++) { 85 | const currentRow = []; 86 | for (let col = 0; col < colCount; col++) { 87 | currentRow.push(this.createNode(row, col)); 88 | } 89 | initialGrid.push(currentRow); 90 | } 91 | return initialGrid; 92 | }; 93 | 94 | createNode = (row, col) => { 95 | return { 96 | row, 97 | col, 98 | isStart: 99 | row === this.state.START_NODE_ROW && col === this.state.START_NODE_COL, 100 | isFinish: 101 | row === this.state.FINISH_NODE_ROW && 102 | col === this.state.FINISH_NODE_COL, 103 | distance: Infinity, 104 | distanceToFinishNode: 105 | Math.abs(this.state.FINISH_NODE_ROW - row) + 106 | Math.abs(this.state.FINISH_NODE_COL - col), 107 | isVisited: false, 108 | isWall: false, 109 | previousNode: null, 110 | isNode: true, 111 | }; 112 | }; 113 | 114 | /******************** Control mouse events ********************/ 115 | handleMouseDown(row, col) { 116 | if (!this.state.isRunning) { 117 | if (this.isGridClear()) { 118 | if ( 119 | document.getElementById(`node-${row}-${col}`).className === 120 | 'node node-start' 121 | ) { 122 | this.setState({ 123 | mouseIsPressed: true, 124 | isStartNode: true, 125 | currRow: row, 126 | currCol: col, 127 | }); 128 | } else if ( 129 | document.getElementById(`node-${row}-${col}`).className === 130 | 'node node-finish' 131 | ) { 132 | this.setState({ 133 | mouseIsPressed: true, 134 | isFinishNode: true, 135 | currRow: row, 136 | currCol: col, 137 | }); 138 | } else { 139 | const newGrid = getNewGridWithWallToggled(this.state.grid, row, col); 140 | this.setState({ 141 | grid: newGrid, 142 | mouseIsPressed: true, 143 | isWallNode: true, 144 | currRow: row, 145 | currCol: col, 146 | }); 147 | } 148 | } else { 149 | this.clearGrid(); 150 | } 151 | } 152 | } 153 | 154 | isGridClear() { 155 | for (const row of this.state.grid) { 156 | for (const node of row) { 157 | const nodeClassName = document.getElementById( 158 | `node-${node.row}-${node.col}`, 159 | ).className; 160 | if ( 161 | nodeClassName === 'node node-visited' || 162 | nodeClassName === 'node node-shortest-path' 163 | ) { 164 | return false; 165 | } 166 | } 167 | } 168 | return true; 169 | } 170 | 171 | handleMouseEnter(row, col) { 172 | if (!this.state.isRunning) { 173 | if (this.state.mouseIsPressed) { 174 | const nodeClassName = document.getElementById(`node-${row}-${col}`) 175 | .className; 176 | if (this.state.isStartNode) { 177 | if (nodeClassName !== 'node node-wall') { 178 | const prevStartNode = this.state.grid[this.state.currRow][ 179 | this.state.currCol 180 | ]; 181 | prevStartNode.isStart = false; 182 | document.getElementById( 183 | `node-${this.state.currRow}-${this.state.currCol}`, 184 | ).className = 'node'; 185 | 186 | this.setState({currRow: row, currCol: col}); 187 | const currStartNode = this.state.grid[row][col]; 188 | currStartNode.isStart = true; 189 | document.getElementById(`node-${row}-${col}`).className = 190 | 'node node-start'; 191 | } 192 | this.setState({START_NODE_ROW: row, START_NODE_COL: col}); 193 | } else if (this.state.isFinishNode) { 194 | if (nodeClassName !== 'node node-wall') { 195 | const prevFinishNode = this.state.grid[this.state.currRow][ 196 | this.state.currCol 197 | ]; 198 | prevFinishNode.isFinish = false; 199 | document.getElementById( 200 | `node-${this.state.currRow}-${this.state.currCol}`, 201 | ).className = 'node'; 202 | 203 | this.setState({currRow: row, currCol: col}); 204 | const currFinishNode = this.state.grid[row][col]; 205 | currFinishNode.isFinish = true; 206 | document.getElementById(`node-${row}-${col}`).className = 207 | 'node node-finish'; 208 | } 209 | this.setState({FINISH_NODE_ROW: row, FINISH_NODE_COL: col}); 210 | } else if (this.state.isWallNode) { 211 | const newGrid = getNewGridWithWallToggled(this.state.grid, row, col); 212 | this.setState({grid: newGrid}); 213 | } 214 | } 215 | } 216 | } 217 | 218 | handleMouseUp(row, col) { 219 | if (!this.state.isRunning) { 220 | this.setState({mouseIsPressed: false}); 221 | if (this.state.isStartNode) { 222 | const isStartNode = !this.state.isStartNode; 223 | this.setState({isStartNode, START_NODE_ROW: row, START_NODE_COL: col}); 224 | } else if (this.state.isFinishNode) { 225 | const isFinishNode = !this.state.isFinishNode; 226 | this.setState({ 227 | isFinishNode, 228 | FINISH_NODE_ROW: row, 229 | FINISH_NODE_COL: col, 230 | }); 231 | } 232 | this.getInitialGrid(); 233 | } 234 | } 235 | 236 | handleMouseLeave() { 237 | if (this.state.isStartNode) { 238 | const isStartNode = !this.state.isStartNode; 239 | this.setState({isStartNode, mouseIsPressed: false}); 240 | } else if (this.state.isFinishNode) { 241 | const isFinishNode = !this.state.isFinishNode; 242 | this.setState({isFinishNode, mouseIsPressed: false}); 243 | } else if (this.state.isWallNode) { 244 | const isWallNode = !this.state.isWallNode; 245 | this.setState({isWallNode, mouseIsPressed: false}); 246 | this.getInitialGrid(); 247 | } 248 | } 249 | 250 | /******************** Clear Board/Walls ********************/ 251 | 252 | clearGrid() { 253 | if (!this.state.isRunning) { 254 | const newGrid = this.state.grid.slice(); 255 | for (const row of newGrid) { 256 | for (const node of row) { 257 | let nodeClassName = document.getElementById( 258 | `node-${node.row}-${node.col}`, 259 | ).className; 260 | if ( 261 | nodeClassName !== 'node node-start' && 262 | nodeClassName !== 'node node-finish' && 263 | nodeClassName !== 'node node-wall' 264 | ) { 265 | document.getElementById(`node-${node.row}-${node.col}`).className = 266 | 'node'; 267 | node.isVisited = false; 268 | node.distance = Infinity; 269 | node.distanceToFinishNode = 270 | Math.abs(this.state.FINISH_NODE_ROW - node.row) + 271 | Math.abs(this.state.FINISH_NODE_COL - node.col); 272 | } 273 | if (nodeClassName === 'node node-finish') { 274 | node.isVisited = false; 275 | node.distance = Infinity; 276 | node.distanceToFinishNode = 0; 277 | } 278 | if (nodeClassName === 'node node-start') { 279 | node.isVisited = false; 280 | node.distance = Infinity; 281 | node.distanceToFinishNode = 282 | Math.abs(this.state.FINISH_NODE_ROW - node.row) + 283 | Math.abs(this.state.FINISH_NODE_COL - node.col); 284 | node.isStart = true; 285 | node.isWall = false; 286 | node.previousNode = null; 287 | node.isNode = true; 288 | } 289 | } 290 | } 291 | } 292 | } 293 | 294 | clearWalls() { 295 | if (!this.state.isRunning) { 296 | const newGrid = this.state.grid.slice(); 297 | for (const row of newGrid) { 298 | for (const node of row) { 299 | let nodeClassName = document.getElementById( 300 | `node-${node.row}-${node.col}`, 301 | ).className; 302 | if (nodeClassName === 'node node-wall') { 303 | document.getElementById(`node-${node.row}-${node.col}`).className = 304 | 'node'; 305 | node.isWall = false; 306 | } 307 | } 308 | } 309 | } 310 | } 311 | 312 | /******************** Create Animations ********************/ 313 | visualize(algo) { 314 | if (!this.state.isRunning) { 315 | this.clearGrid(); 316 | this.toggleIsRunning(); 317 | const {grid} = this.state; 318 | const startNode = 319 | grid[this.state.START_NODE_ROW][this.state.START_NODE_COL]; 320 | const finishNode = 321 | grid[this.state.FINISH_NODE_ROW][this.state.FINISH_NODE_COL]; 322 | let visitedNodesInOrder; 323 | switch (algo) { 324 | case 'Dijkstra': 325 | visitedNodesInOrder = dijkstra(grid, startNode, finishNode); 326 | break; 327 | case 'AStar': 328 | visitedNodesInOrder = AStar(grid, startNode, finishNode); 329 | break; 330 | case 'BFS': 331 | visitedNodesInOrder = bfs(grid, startNode, finishNode); 332 | break; 333 | case 'DFS': 334 | visitedNodesInOrder = dfs(grid, startNode, finishNode); 335 | break; 336 | default: 337 | // should never get here 338 | break; 339 | } 340 | const nodesInShortestPathOrder = getNodesInShortestPathOrder(finishNode); 341 | nodesInShortestPathOrder.push('end'); 342 | this.animate(visitedNodesInOrder, nodesInShortestPathOrder); 343 | } 344 | } 345 | 346 | animate(visitedNodesInOrder, nodesInShortestPathOrder) { 347 | for (let i = 0; i <= visitedNodesInOrder.length; i++) { 348 | if (i === visitedNodesInOrder.length) { 349 | setTimeout(() => { 350 | this.animateShortestPath(nodesInShortestPathOrder); 351 | }, 10 * i); 352 | return; 353 | } 354 | setTimeout(() => { 355 | const node = visitedNodesInOrder[i]; 356 | const nodeClassName = document.getElementById( 357 | `node-${node.row}-${node.col}`, 358 | ).className; 359 | if ( 360 | nodeClassName !== 'node node-start' && 361 | nodeClassName !== 'node node-finish' 362 | ) { 363 | document.getElementById(`node-${node.row}-${node.col}`).className = 364 | 'node node-visited'; 365 | } 366 | }, 10 * i); 367 | } 368 | } 369 | 370 | /******************** Create path from start to finish ********************/ 371 | animateShortestPath(nodesInShortestPathOrder) { 372 | for (let i = 0; i < nodesInShortestPathOrder.length; i++) { 373 | if (nodesInShortestPathOrder[i] === 'end') { 374 | setTimeout(() => { 375 | this.toggleIsRunning(); 376 | }, i * 50); 377 | } else { 378 | setTimeout(() => { 379 | const node = nodesInShortestPathOrder[i]; 380 | const nodeClassName = document.getElementById( 381 | `node-${node.row}-${node.col}`, 382 | ).className; 383 | if ( 384 | nodeClassName !== 'node node-start' && 385 | nodeClassName !== 'node node-finish' 386 | ) { 387 | document.getElementById(`node-${node.row}-${node.col}`).className = 388 | 'node node-shortest-path'; 389 | } 390 | }, i * 40); 391 | } 392 | } 393 | } 394 | 395 | render() { 396 | const {grid, mouseIsPressed} = this.state; 397 | return ( 398 |
399 | 431 | 432 | this.handleMouseLeave()}> 435 | 436 | {grid.map((row, rowIdx) => { 437 | return ( 438 | 439 | {row.map((node, nodeIdx) => { 440 | const {row, col, isFinish, isStart, isWall} = node; 441 | return ( 442 | 450 | this.handleMouseDown(row, col) 451 | } 452 | onMouseEnter={(row, col) => 453 | this.handleMouseEnter(row, col) 454 | } 455 | onMouseUp={() => this.handleMouseUp(row, col)} 456 | row={row}> 457 | ); 458 | })} 459 | 460 | ); 461 | })} 462 | 463 |
464 | 470 | 476 | 482 | 488 | 494 | 500 | {this.state.isDesktopView ? ( 501 | 507 | ) : ( 508 | 514 | )} 515 |
516 | ); 517 | } 518 | } 519 | 520 | /******************** Create Walls ********************/ 521 | const getNewGridWithWallToggled = (grid, row, col) => { 522 | // mouseDown starts to act strange if I don't make newGrid and work off of grid instead. 523 | const newGrid = grid.slice(); 524 | const node = newGrid[row][col]; 525 | if (!node.isStart && !node.isFinish && node.isNode) { 526 | const newNode = { 527 | ...node, 528 | isWall: !node.isWall, 529 | }; 530 | newGrid[row][col] = newNode; 531 | } 532 | return newGrid; 533 | }; 534 | 535 | // Backtracks from the finishNode to find the shortest path. 536 | // Only works when called after the pathfinding methods. 537 | function getNodesInShortestPathOrder(finishNode) { 538 | const nodesInShortestPathOrder = []; 539 | let currentNode = finishNode; 540 | while (currentNode !== null) { 541 | nodesInShortestPathOrder.unshift(currentNode); 542 | currentNode = currentNode.previousNode; 543 | } 544 | return nodesInShortestPathOrder; 545 | } 546 | -------------------------------------------------------------------------------- /src/algorithms/aStar.js: -------------------------------------------------------------------------------- 1 | // Returns all nodes in the order in which they were visited. 2 | // Make nodes point back to their previous node so that we can compute the shortest path 3 | // by backtracking from the finish node. 4 | 5 | export function AStar(grid, startNode, finishNode) { 6 | const visitedNodesInOrder = []; 7 | startNode.distance = 0; 8 | const unvisitedNodes = getAllNodes(grid); // Q: different from using grid or slice of grid??? 9 | 10 | while (unvisitedNodes.length) { 11 | sortByDistance(unvisitedNodes); 12 | const closestNode = unvisitedNodes.shift(); 13 | // If we encounter a wall, we skip it. 14 | if (!closestNode.isWall) { 15 | // If the closest node is at a distance of infinity, 16 | // we must be trapped and should stop. 17 | if (closestNode.distance === Infinity) return visitedNodesInOrder; 18 | closestNode.isVisited = true; 19 | visitedNodesInOrder.push(closestNode); 20 | if (closestNode === finishNode) return visitedNodesInOrder; 21 | updateUnvisitedNeighbors(closestNode, grid); 22 | } 23 | } 24 | } 25 | 26 | function getAllNodes(grid) { 27 | const nodes = []; 28 | for (const row of grid) { 29 | for (const node of row) { 30 | nodes.push(node); 31 | } 32 | } 33 | return nodes; 34 | } 35 | 36 | function sortByDistance(unvisitedNodes) { 37 | unvisitedNodes.sort((nodeA, nodeB) => nodeA.distance - nodeB.distance); 38 | } 39 | 40 | function updateUnvisitedNeighbors(node, grid) { 41 | const unvisitedNeighbors = getUnvisitedNeighbors(node, grid); 42 | for (const neighbor of unvisitedNeighbors) { 43 | neighbor.distance = node.distance + 1 + neighbor.distanceToFinishNode; 44 | neighbor.previousNode = node; 45 | } 46 | } 47 | 48 | function getUnvisitedNeighbors(node, grid) { 49 | const neighbors = []; 50 | const {col, row} = node; 51 | if (row > 0) neighbors.push(grid[row - 1][col]); 52 | if (row < grid.length - 1) neighbors.push(grid[row + 1][col]); 53 | if (col > 0) neighbors.push(grid[row][col - 1]); 54 | if (col < grid[0].length - 1) neighbors.push(grid[row][col + 1]); 55 | return neighbors.filter(neighbor => !neighbor.isVisited); 56 | } 57 | -------------------------------------------------------------------------------- /src/algorithms/bfs.js: -------------------------------------------------------------------------------- 1 | // Returns all nodes in the order in which they were visited. 2 | // Make nodes point back to their previous node so that we can compute the shortest path 3 | // by backtracking from the finish node. 4 | 5 | export function bfs(grid, startNode, finishNode) { 6 | const visitedNodesInOrder = []; 7 | let nextNodesStack = [startNode]; 8 | while (nextNodesStack.length) { 9 | const currentNode = nextNodesStack.shift(); 10 | if (currentNode === finishNode) return visitedNodesInOrder; 11 | 12 | if ( 13 | !currentNode.isWall && 14 | (currentNode.isStart || !currentNode.isVisited) 15 | ) { 16 | currentNode.isVisited = true; 17 | visitedNodesInOrder.push(currentNode); 18 | const {col, row} = currentNode; 19 | let nextNode; 20 | if (row > 0) { 21 | nextNode = grid[row - 1][col]; 22 | if (!nextNode.isVisited) { 23 | nextNode.previousNode = currentNode; 24 | nextNodesStack.push(nextNode); 25 | } 26 | } 27 | if (row < grid.length - 1) { 28 | nextNode = grid[row + 1][col]; 29 | if (!nextNode.isVisited) { 30 | nextNode.previousNode = currentNode; 31 | nextNodesStack.push(nextNode); 32 | } 33 | } 34 | if (col > 0) { 35 | nextNode = grid[row][col - 1]; 36 | if (!nextNode.isVisited) { 37 | nextNode.previousNode = currentNode; 38 | nextNodesStack.push(nextNode); 39 | } 40 | } 41 | if (col < grid[0].length - 1) { 42 | nextNode = grid[row][col + 1]; 43 | if (!nextNode.isVisited) { 44 | nextNode.previousNode = currentNode; 45 | nextNodesStack.push(nextNode); 46 | } 47 | } 48 | } 49 | } 50 | // return visitedNodesInOrder; 51 | } 52 | -------------------------------------------------------------------------------- /src/algorithms/dfs.js: -------------------------------------------------------------------------------- 1 | // Returns all nodes in the order in which they were visited. 2 | // Make nodes point back to their previous node so that we can compute the shortest path 3 | // by backtracking from the finish node. 4 | 5 | export function dfs(grid, startNode, finishNode) { 6 | const visitedNodesInOrder = []; 7 | const nextNodesStack = []; 8 | nextNodesStack.push(startNode); 9 | while (nextNodesStack.length) { 10 | const currentNode = nextNodesStack.pop(); 11 | 12 | if (currentNode === finishNode) { 13 | return visitedNodesInOrder; 14 | } 15 | 16 | if ( 17 | !currentNode.isWall && 18 | (currentNode.isStart || !currentNode.isVisited) 19 | ) { 20 | currentNode.isVisited = true; 21 | visitedNodesInOrder.push(currentNode); 22 | 23 | const {col, row} = currentNode; 24 | let nextNode; 25 | if (row > 0) { 26 | nextNode = grid[row - 1][col]; 27 | if (!nextNode.isVisited) { 28 | nextNode.previousNode = currentNode; 29 | nextNodesStack.push(nextNode); 30 | } 31 | } 32 | if (row < grid.length - 1) { 33 | nextNode = grid[row + 1][col]; 34 | if (!nextNode.isVisited) { 35 | nextNode.previousNode = currentNode; 36 | nextNodesStack.push(nextNode); 37 | } 38 | } 39 | if (col > 0) { 40 | nextNode = grid[row][col - 1]; 41 | if (!nextNode.isVisited) { 42 | nextNode.previousNode = currentNode; 43 | nextNodesStack.push(nextNode); 44 | } 45 | } 46 | if (col < grid[0].length - 1) { 47 | nextNode = grid[row][col + 1]; 48 | if (!nextNode.isVisited) { 49 | nextNode.previousNode = currentNode; 50 | nextNodesStack.push(nextNode); 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/algorithms/dijkstra.js: -------------------------------------------------------------------------------- 1 | // Returns all nodes in the order in which they were visited. 2 | // Make nodes point back to their previous node so that we can compute the shortest path 3 | // by backtracking from the finish node. 4 | 5 | export function dijkstra(grid, startNode, finishNode) { 6 | const visitedNodesInOrder = []; 7 | startNode.distance = 0; 8 | const unvisitedNodes = getAllNodes(grid); // Q: different from using grid or slice of grid??? 9 | 10 | while (unvisitedNodes.length) { 11 | sortNodesByDistance(unvisitedNodes); 12 | const closestNode = unvisitedNodes.shift(); 13 | // If we encounter a wall, we skip it. 14 | if (!closestNode.isWall) { 15 | // If the closest node is at a distance of infinity, 16 | // we must be trapped and should stop. 17 | if (closestNode.distance === Infinity) return visitedNodesInOrder; 18 | closestNode.isVisited = true; 19 | visitedNodesInOrder.push(closestNode); 20 | if (closestNode === finishNode) return visitedNodesInOrder; 21 | updateUnvisitedNeighbors(closestNode, grid); 22 | } 23 | } 24 | } 25 | 26 | function getAllNodes(grid) { 27 | const nodes = []; 28 | for (const row of grid) { 29 | for (const node of row) { 30 | nodes.push(node); 31 | } 32 | } 33 | return nodes; 34 | } 35 | 36 | function sortNodesByDistance(unvisitedNodes) { 37 | unvisitedNodes.sort((nodeA, nodeB) => nodeA.distance - nodeB.distance); 38 | } 39 | 40 | function updateUnvisitedNeighbors(node, grid) { 41 | const unvisitedNeighbors = getUnvisitedNeighbors(node, grid); 42 | for (const neighbor of unvisitedNeighbors) { 43 | neighbor.distance = node.distance + 1; 44 | neighbor.previousNode = node; 45 | } 46 | } 47 | 48 | function getUnvisitedNeighbors(node, grid) { 49 | const neighbors = []; 50 | const {col, row} = node; 51 | if (row > 0) neighbors.push(grid[row - 1][col]); 52 | if (row < grid.length - 1) neighbors.push(grid[row + 1][col]); 53 | if (col > 0) neighbors.push(grid[row][col - 1]); 54 | if (col < grid[0].length - 1) neighbors.push(grid[row][col + 1]); 55 | return neighbors.filter(neighbor => !neighbor.isVisited); 56 | } 57 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | --------------------------------------------------------------------------------