├── .gitignore ├── .vscode └── settings.json ├── README.md ├── debug.log ├── package-lock.json ├── package.json ├── public ├── index.html ├── location.png ├── manifest.json └── robots.txt ├── src ├── App.css ├── App.test.tsx ├── App.tsx ├── components │ ├── Controller │ │ ├── Controller.module.css │ │ └── Controller.tsx │ ├── MobileController │ │ ├── MobileController.module.css │ │ └── MobileController.tsx │ └── UI │ │ ├── Dropdown │ │ ├── Dropdown.module.css │ │ └── Dropdown.tsx │ │ └── Navbar │ │ ├── Navbar.module.css │ │ └── Navbar.tsx ├── containers │ └── PathfindingVisualizer │ │ ├── 4913 │ │ ├── PathfindingVisualizer.module.css │ │ ├── PathfindingVisualizer.tsx │ │ └── Vertices │ │ ├── Vertex │ │ ├── Vertex.module.css │ │ └── Vertex.tsx │ │ ├── VertexRow │ │ ├── VertexRow.module.css │ │ └── VertexRow.tsx │ │ ├── Vertices.module.css │ │ └── Vertices.tsx ├── fonts │ ├── Avengers │ │ └── Avengers.ttf │ ├── Lakers │ │ └── lakers.ttf │ ├── Montserrat │ │ ├── Montserrat-Black.ttf │ │ ├── Montserrat-BlackItalic.ttf │ │ ├── Montserrat-Bold.ttf │ │ ├── Montserrat-BoldItalic.ttf │ │ ├── Montserrat-ExtraBold.ttf │ │ ├── Montserrat-ExtraBoldItalic.ttf │ │ ├── Montserrat-ExtraLight.ttf │ │ ├── Montserrat-ExtraLightItalic.ttf │ │ ├── Montserrat-Italic.ttf │ │ ├── Montserrat-Light.ttf │ │ ├── Montserrat-LightItalic.ttf │ │ ├── Montserrat-Medium.ttf │ │ ├── Montserrat-MediumItalic.ttf │ │ ├── Montserrat-Regular.ttf │ │ ├── Montserrat-SemiBold.ttf │ │ ├── Montserrat-SemiBoldItalic.ttf │ │ ├── Montserrat-Thin.ttf │ │ ├── Montserrat-ThinItalic.ttf │ │ └── OFL.txt │ ├── OnePiece │ │ └── OnePiece.ttf │ ├── Pokemon │ │ ├── PokemonHollow.ttf │ │ └── PokemonSolid.ttf │ ├── TheOffice │ │ ├── TheOffice.ttf │ │ └── readme.html │ ├── avengers.css │ ├── lakers.css │ ├── montserrat.css │ ├── onePiece.css │ ├── pokemon.css │ └── theOffice.css ├── index.css ├── index.tsx ├── logo.svg ├── react-app-env.d.ts ├── serviceWorker.ts ├── setupTests.ts ├── store │ ├── actions │ │ ├── drag.ts │ │ ├── graph.ts │ │ ├── index.ts │ │ └── types.ts │ └── reducers │ │ ├── drag.ts │ │ ├── graph.ts │ │ └── index.ts └── utils │ ├── colors.ts │ ├── pathfinding │ ├── algorithms │ │ ├── aStar.ts │ │ ├── breadthFirstSearch.ts │ │ ├── buildPath.ts │ │ ├── depthFirstSearch.ts │ │ ├── dijkstra.ts │ │ ├── graph.ts │ │ ├── graphTypes.ts │ │ ├── greedyBestFirstSearch.ts │ │ ├── index.ts │ │ └── priorityQueue.ts │ ├── pathfindingAlgorithms.ts │ ├── pathfindingOptions.ts │ └── pathfindingStates.ts │ ├── position.ts │ └── themes │ ├── avengers.ts │ ├── car.ts │ ├── hunterxhunter.ts │ ├── img │ ├── badge.png │ ├── bigmom.png │ ├── bigmomCursor.png │ ├── blackbeard.png │ ├── blackbeardCursor.png │ ├── building.png │ ├── buildingCursor.png │ ├── captainAmerica.png │ ├── captainMarvel.png │ ├── car.png │ ├── carObstacle.png │ ├── carObstacleCursor.png │ ├── celtics.png │ ├── celticsCursor.png │ ├── chineseFood.png │ ├── chineseFoodCursor.png │ ├── clippers.png │ ├── clippersCursor.png │ ├── cone.png │ ├── coneCursor.png │ ├── gasStation.png │ ├── gasStationCursor.png │ ├── gauntlet.png │ ├── ging.png │ ├── gon.png │ ├── groudon.png │ ├── groudonCursor.png │ ├── hill.png │ ├── hillCursor.png │ ├── hisoka.png │ ├── hisokaCursor.png │ ├── holly.jpg │ ├── ironMan.png │ ├── kaido.png │ ├── kaidoCursor.png │ ├── kyogre.png │ ├── kyogreCursor.png │ ├── lakers.png │ ├── latias.png │ ├── latiasCursor.png │ ├── location.png │ ├── meruem.png │ ├── meruemCursor.png │ ├── michael.png │ ├── phone.png │ ├── phoneCursor.png │ ├── pistons.png │ ├── pistonsCursor.png │ ├── pokemonPlayer.png │ ├── rain.png │ ├── rainCursor.png │ ├── rayquaza.png │ ├── rayquazaCursor.png │ ├── razor.png │ ├── razorCursor.png │ ├── road-horizontal.jpg │ ├── road-vertical.jpg │ ├── shanks.png │ ├── shanksCursor.png │ ├── spurs.png │ ├── strawhat.png │ ├── sunny.png │ ├── thanos.png │ ├── thor.png │ ├── treasure.png │ ├── tree.png │ ├── trophy.png │ ├── troupe.png │ └── troupeCursor.png │ ├── index.ts │ ├── lakers.ts │ ├── onePiece.ts │ ├── pokemon.ts │ └── theOffice.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "'s", 4 | "Choo", 5 | "Choo's", 6 | "Choo\\'s", 7 | "DARKBLUE", 8 | "Dropdowns", 9 | "Groudon", 10 | "Kyogre", 11 | "LIGHTBLUE", 12 | "Latias", 13 | "Meruem", 14 | "THEOFFICE", 15 | "bigmom", 16 | "browserslist", 17 | "darr", 18 | "focusable", 19 | "ging", 20 | "hunterxhunter", 21 | "kaido", 22 | "larr", 23 | "pathfinding", 24 | "rarr", 25 | "rayquaza", 26 | "thanos", 27 | "uarr" 28 | ] 29 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pathfinding Visualizer 2 | 3 | This is my version of the pathfinding visualization project. Like my sorting visualization project, this project was also inspired by [Clement Mihailescu](https://github.com/clementmihailescu) (make sure you checkout his company AlgoExpert on [algoexpert.io](https://algoexpert.io)) to practice for coding interviews). 4 | 5 | The pathfinding algorithms implemented were Dijkstra's algorithm, A\*, Greedy Best-First Search, Breadth-First Search, and Depth-First Search. 6 | 7 | For this project I wanted to make fun themes on some of my favorite things including One Piece, Avengers, Lakers, The Office (U.S. of course), and more. 8 | 9 | [Live Demo](https://princhcanal.github.io/pathfinding-visualizer) 10 | 11 | The rest of this README was automatically generated by Create React App. 12 | 13 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 14 | 15 | ## Available Scripts 16 | 17 | In the project directory, you can run: 18 | 19 | ### `npm start` 20 | 21 | Runs the app in the development mode.
22 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 23 | 24 | The page will reload if you make edits.
25 | You will also see any lint errors in the console. 26 | 27 | ### `npm test` 28 | 29 | Launches the test runner in the interactive watch mode.
30 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 31 | 32 | ### `npm run build` 33 | 34 | Builds the app for production to the `build` folder.
35 | It correctly bundles React in production mode and optimizes the build for the best performance. 36 | 37 | The build is minified and the filenames include the hashes.
38 | Your app is ready to be deployed! 39 | 40 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 41 | 42 | ### `npm run eject` 43 | 44 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 45 | 46 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 47 | 48 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 49 | 50 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 51 | 52 | ## Learn More 53 | 54 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 55 | 56 | To learn React, check out the [React documentation](https://reactjs.org/). 57 | -------------------------------------------------------------------------------- /debug.log: -------------------------------------------------------------------------------- 1 | [0721/160747.768:ERROR:http_transport_win.cc(276)] WinHttpSendRequest: The operation completed successfully. (0x0) 2 | [0721/162552.354:ERROR:process_reader_win.cc(151)] SuspendThread: Access is denied. (0x5) 3 | [0721/162552.356:ERROR:process_reader_win.cc(151)] SuspendThread: Access is denied. (0x5) 4 | [0721/162552.357:ERROR:process_reader_win.cc(151)] SuspendThread: Access is denied. (0x5) 5 | [0721/162552.357:ERROR:process_reader_win.cc(151)] SuspendThread: Access is denied. (0x5) 6 | [0721/162552.357:ERROR:process_reader_win.cc(151)] SuspendThread: Access is denied. (0x5) 7 | [0721/162552.357:ERROR:process_reader_win.cc(151)] SuspendThread: Access is denied. (0x5) 8 | [0721/162552.358:ERROR:process_reader_win.cc(151)] SuspendThread: Access is denied. (0x5) 9 | [0721/162552.360:ERROR:process_reader_win.cc(151)] SuspendThread: Access is denied. (0x5) 10 | [0721/162552.360:ERROR:process_reader_win.cc(151)] SuspendThread: Access is denied. (0x5) 11 | [0721/162552.360:ERROR:process_reader_win.cc(151)] SuspendThread: Access is denied. (0x5) 12 | [0721/162552.360:ERROR:process_reader_win.cc(151)] SuspendThread: Access is denied. (0x5) 13 | [0721/162552.360:ERROR:process_reader_win.cc(151)] SuspendThread: Access is denied. (0x5) 14 | [0721/162552.361:ERROR:process_reader_win.cc(151)] SuspendThread: Access is denied. (0x5) 15 | [0721/162552.361:ERROR:process_reader_win.cc(123)] NtOpenThread: {Access Denied} A process has requested access to an object, but has not been granted those access rights. (0xc0000022) 16 | [0721/162552.362:ERROR:exception_snapshot_win.cc(98)] thread ID 20420 not found in process 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pathfinding-visualizer", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.5.0", 8 | "@testing-library/user-event": "^7.2.1", 9 | "@types/jest": "^24.9.1", 10 | "@types/node": "^12.12.47", 11 | "@types/react": "^16.9.41", 12 | "@types/react-dom": "^16.9.8", 13 | "@types/react-redux": "^7.1.9", 14 | "@types/redux": "^3.6.0", 15 | "react": "^16.13.1", 16 | "react-dom": "^16.13.1", 17 | "react-redux": "^7.2.0", 18 | "react-scripts": "3.4.1", 19 | "redux": "^4.0.5", 20 | "redux-thunk": "^2.3.0", 21 | "typescript": "^3.7.5" 22 | }, 23 | "scripts": { 24 | "start": "react-scripts start", 25 | "build": "react-scripts build", 26 | "test": "react-scripts test", 27 | "eject": "react-scripts eject", 28 | "deploy": "npm run build && gh-pages -d build", 29 | "deployy": "gh-pages -d build" 30 | }, 31 | "eslintConfig": { 32 | "extends": "react-app" 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "homepage": "https://princhcanal.github.io/pathfinding-visualizer", 47 | "devDependencies": { 48 | "gh-pages": "^3.1.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | 28 | Pathfinding Visualizer 29 | 30 | 31 | 32 |
33 | 43 | 44 | -------------------------------------------------------------------------------- /public/location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/public/location.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "theme_color": "#000000", 7 | "background_color": "#ffffff" 8 | } 9 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/App.css -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useLayoutEffect, useEffect, useCallback } from 'react'; 2 | import './App.css'; 3 | 4 | import PathfindingVisualizer from './containers/PathfindingVisualizer/PathfindingVisualizer'; 5 | import { useDispatch } from 'react-redux'; 6 | import * as Actions from './store/actions'; 7 | 8 | const App = () => { 9 | const dispatch = useDispatch(); 10 | 11 | const updateSize = useCallback(() => { 12 | if (window.innerWidth <= 500) { 13 | dispatch(Actions.setNumRows(11)); 14 | dispatch(Actions.setNumCols(9)); 15 | } else if (window.innerWidth <= 600) { 16 | dispatch(Actions.setNumRows(13)); 17 | dispatch(Actions.setNumCols(13)); 18 | } else if (window.innerWidth <= 820) { 19 | dispatch(Actions.setNumRows(13)); 20 | dispatch(Actions.setNumCols(17)); 21 | } else if (window.innerWidth <= 960) { 22 | dispatch(Actions.setNumRows(13)); 23 | dispatch(Actions.setNumCols(21)); 24 | } else if (window.innerWidth <= 1040) { 25 | dispatch(Actions.setNumRows(15)); 26 | dispatch(Actions.setNumCols(25)); 27 | } else { 28 | dispatch(Actions.setNumRows(17)); 29 | dispatch(Actions.setNumCols(33)); 30 | } 31 | }, [dispatch]); 32 | 33 | useLayoutEffect(() => { 34 | window.addEventListener('resize', updateSize); 35 | 36 | return () => window.removeEventListener('resize', updateSize); 37 | }, [updateSize]); 38 | 39 | useEffect(() => { 40 | updateSize(); 41 | }, [updateSize]); 42 | 43 | return ( 44 |
45 | 46 |
47 | ); 48 | }; 49 | 50 | export default App; 51 | -------------------------------------------------------------------------------- /src/components/Controller/Controller.module.css: -------------------------------------------------------------------------------- 1 | .Controller { 2 | color: var(--color-4); 3 | width: 90%; 4 | margin: 0 auto; 5 | border-radius: 0.3rem; 6 | } 7 | 8 | .Buttons { 9 | display: flex; 10 | justify-content: space-around; 11 | padding: 1rem 0; 12 | } 13 | 14 | .Button { 15 | font-size: inherit; 16 | background-color: transparent; 17 | border: none; 18 | transition: all 0.2s; 19 | color: inherit; 20 | padding: 0.5rem 1rem; 21 | border-radius: 0.3rem; 22 | } 23 | 24 | .Button:hover { 25 | background-color: var(--color-4); 26 | color: var(--color-3); 27 | } 28 | 29 | .Button:active { 30 | color: red; 31 | } 32 | 33 | @media (hover: none) { 34 | .Button:hover { 35 | background-color: transparent; 36 | color: inherit; 37 | } 38 | } 39 | 40 | .Dropdowns { 41 | display: flex; 42 | justify-content: space-around; 43 | align-items: center; 44 | flex-wrap: wrap; 45 | padding: 1rem 0; 46 | /* border: 1px solid var(--color-1); */ 47 | } 48 | 49 | .Dropdown { 50 | background-color: var(--color-4); 51 | color: var(--color-3); 52 | border: none; 53 | text-align-last: center; 54 | transition: all 0.2s; 55 | border-radius: 0.3rem; 56 | padding: 0.5rem 1rem; 57 | /* margin-left: 1rem; */ 58 | } 59 | 60 | .Dropdown:active { 61 | outline: none; 62 | } 63 | 64 | .Dropdown option { 65 | background-color: var(--color-1); 66 | } 67 | -------------------------------------------------------------------------------- /src/components/Controller/Controller.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | RefObject, 3 | MouseEvent, 4 | ForwardRefExoticComponent, 5 | RefAttributes, 6 | useState, 7 | } from 'react'; 8 | import { useDispatch, useSelector } from 'react-redux'; 9 | 10 | import styles from './Controller.module.css'; 11 | import * as Actions from '../../store/actions'; 12 | import * as Position from '../../utils/position'; 13 | import { StoreState } from '../../store/reducers'; 14 | import { Graph } from '../../utils/pathfinding/algorithms/graph'; 15 | import { Vertex } from '../../store/reducers/graph'; 16 | import { GraphTheme } from '../../utils/themes'; 17 | import { PathfindingStates } from '../../utils/pathfinding/pathfindingStates'; 18 | 19 | import Dropdown, { DropdownProps, DropdownRef } from '../UI/Dropdown/Dropdown'; 20 | import { pathfindingOptions } from '../../utils/pathfinding/pathfindingOptions'; 21 | import { pathfindingAlgorithms } from '../../utils/pathfinding/pathfindingAlgorithms'; 22 | import { themes, themeOptions, obstacleOptions } from '../../utils/themes/'; 23 | 24 | interface ControllerProps { 25 | classNames?: string[]; 26 | verticesRef: RefObject; 27 | } 28 | 29 | export type Handle = T extends ForwardRefExoticComponent< 30 | DropdownProps & RefAttributes 31 | > 32 | ? T2 33 | : never; 34 | 35 | const Controller = (props: ControllerProps) => { 36 | let classNames = [styles.Controller]; 37 | if (props.classNames) { 38 | classNames.push(...props.classNames); 39 | } 40 | const dispatch = useDispatch(); 41 | 42 | const [obstacles, setObstacles] = useState(obstacleOptions['car']); 43 | const [speed, setSpeed] = useState(25); 44 | const graph = useSelector((state) => state.graph.graph); 45 | const numRows = useSelector( 46 | (state) => state.graph.numRows 47 | ); 48 | const numCols = useSelector( 49 | (state) => state.graph.numCols 50 | ); 51 | const startVertex = useSelector( 52 | (state) => state.graph.startVertex 53 | ); 54 | const endVertex = useSelector( 55 | (state) => state.graph.endVertex 56 | ); 57 | const setTimeouts = useSelector( 58 | (state) => state.graph.setTimeouts 59 | ); 60 | const currentAlgorithm = useSelector( 61 | (state) => state.graph.currentAlgorithm 62 | ); 63 | const theme = useSelector( 64 | (state) => state.graph.theme 65 | ); 66 | let obstacleDropdown: Handle; 67 | 68 | const handleAnimation = (start: number, end: number) => { 69 | dispatch(Actions.clearPath(props.verticesRef)); 70 | dispatch(Actions.setIsAnimating(true)); 71 | 72 | let animations = currentAlgorithm(graph, start, end); 73 | 74 | for (let i = 0; i < animations.length; i++) { 75 | let animation = animations[i]; 76 | let row = Math.floor(animation.index / numCols); 77 | let column = animation.index % numCols; 78 | let vertex: Vertex; 79 | 80 | vertex = Position.getVertex( 81 | row, 82 | column, 83 | numRows, 84 | numCols, 85 | props.verticesRef 86 | ); 87 | 88 | setTimeouts.push( 89 | setTimeout(() => { 90 | if (animation.state === PathfindingStates.VISITING) { 91 | theme.visiting(vertex.element); 92 | if (i > 0) { 93 | let prevAnimation = animations[i - 1]; 94 | let prevVertex = Position.getVertexAbsolute( 95 | prevAnimation.index, 96 | numRows, 97 | numCols, 98 | props.verticesRef 99 | ); 100 | theme.visited(prevVertex.element); 101 | } 102 | } else if ( 103 | animation.state === 104 | PathfindingStates.PATH_HORIZONTAL_START 105 | ) { 106 | theme.pathHorizontalStart(vertex.element); 107 | } else if ( 108 | animation.state === 109 | PathfindingStates.PATH_VERTICAL_START 110 | ) { 111 | theme.pathVerticalStart(vertex.element); 112 | } else if ( 113 | animation.state === 114 | PathfindingStates.PATH_HORIZONTAL_END 115 | ) { 116 | theme.pathHorizontalEnd(vertex.element); 117 | } else if ( 118 | animation.state === PathfindingStates.PATH_VERTICAL_END 119 | ) { 120 | theme.pathVerticalEnd(vertex.element); 121 | } else if ( 122 | animation.state === PathfindingStates.PATH_HORIZONTAL 123 | ) { 124 | theme.pathHorizontal(vertex.element); 125 | } else if ( 126 | animation.state === PathfindingStates.PATH_VERTICAL 127 | ) { 128 | theme.pathVertical(vertex.element); 129 | } else if ( 130 | animation.state === PathfindingStates.DONE || 131 | animation.state === PathfindingStates.NO_PATH 132 | ) { 133 | dispatch(Actions.setIsAnimating(false)); 134 | dispatch(Actions.setIsDoneAnimating(true)); 135 | } 136 | }, speed * i) 137 | ); 138 | } 139 | }; 140 | 141 | const handleFindPath = () => 142 | handleAnimation( 143 | startVertex?.absoluteIndex ? startVertex.absoluteIndex : 0, 144 | endVertex?.absoluteIndex ? endVertex?.absoluteIndex : 0 145 | ); 146 | 147 | const handleClearPath = () => { 148 | dispatch(Actions.setIsAnimating(false)); 149 | dispatch(Actions.setIsDoneAnimating(false)); 150 | dispatch(Actions.clearPath(props.verticesRef)); 151 | }; 152 | const handleClearObstacles = () => { 153 | dispatch(Actions.onClearWalls(props.verticesRef)); 154 | if (obstacleDropdown.heading) { 155 | obstacleDropdown.heading.children[0].innerHTML = obstacles['wall']; 156 | } 157 | }; 158 | const handleReset = () => { 159 | dispatch(Actions.setIsAnimating(false)); 160 | dispatch(Actions.setIsDoneAnimating(false)); 161 | dispatch(Actions.clearPath(props.verticesRef)); 162 | handleClearObstacles(); 163 | dispatch( 164 | Actions.setStartVertex( 165 | Math.floor(numRows / 2), 166 | Math.floor(numCols / 4), 167 | props.verticesRef 168 | ) 169 | ); 170 | dispatch( 171 | Actions.setEndVertex( 172 | Math.floor(numRows / 2), 173 | Math.floor(numCols / 4) * 3, 174 | props.verticesRef 175 | ) 176 | ); 177 | dispatch(Actions.initGraph(props.verticesRef)); 178 | dispatch(Actions.clearPath(props.verticesRef)); 179 | }; 180 | 181 | const handleAlgorithmChanged = (e: MouseEvent) => { 182 | dispatch( 183 | Actions.setCurrentAlgorithm( 184 | pathfindingAlgorithms[ 185 | e.currentTarget.getAttribute('value') as string 186 | ] 187 | ) 188 | ); 189 | }; 190 | 191 | const handleObstacleChanged = (e: MouseEvent) => { 192 | const obstacle = e.currentTarget.getAttribute('value') as string; 193 | dispatch(Actions.onSetObstacleRef(obstacle)); 194 | 195 | for (let i = 0; i < numRows; i++) { 196 | for (let j = 0; j < numCols; j++) { 197 | let vertex = Position.getVertex( 198 | i, 199 | j, 200 | numRows, 201 | numCols, 202 | props.verticesRef 203 | ); 204 | 205 | if ( 206 | vertex.absoluteIndex !== startVertex?.absoluteIndex && 207 | vertex.absoluteIndex !== endVertex?.absoluteIndex 208 | ) { 209 | if (obstacle === 'wall') { 210 | theme.cursorWall(vertex.element); 211 | } else if (obstacle === 'obstacle1') { 212 | theme.cursorObstacle1(vertex.element); 213 | } else if (obstacle === 'obstacle2') { 214 | theme.cursorObstacle2(vertex.element); 215 | } else if (obstacle === 'obstacle3') { 216 | theme.cursorObstacle3(vertex.element); 217 | } 218 | } 219 | } 220 | } 221 | }; 222 | 223 | const handleThemeChanged = (e: MouseEvent) => { 224 | const theme = e.currentTarget.getAttribute('value') as string; 225 | dispatch(Actions.setTheme(themes[theme])); 226 | setObstacles(obstacleOptions[theme]); 227 | themes[theme].bodyBackground( 228 | document.querySelector('body') as HTMLBodyElement 229 | ); 230 | themes[theme].header( 231 | document.getElementById('Header') as HTMLDivElement 232 | ); 233 | themes[theme].heading( 234 | document.getElementById('Heading') as HTMLHeadingElement 235 | ); 236 | themes[theme].controller( 237 | document.getElementById('Controller') as HTMLDivElement 238 | ); 239 | const buttons = document.querySelectorAll('.' + styles.Button); 240 | for (let i = 0; i < buttons.length; i++) { 241 | themes[theme].button(buttons[i] as HTMLButtonElement); 242 | } 243 | let dropdown: HTMLCollection; 244 | let dropdowns = document.querySelector('.' + styles.Dropdowns); 245 | if (dropdowns) { 246 | dropdown = dropdowns.children; 247 | for (let i = 0; i < dropdown.length; i++) { 248 | themes[theme].dropdown( 249 | dropdown[i].children[1].children[0] as HTMLHeadingElement 250 | ); 251 | themes[theme].options( 252 | dropdown[i].children[1].children[1] as HTMLDivElement 253 | ); 254 | let options = dropdown[i].children[1].children[1].children; 255 | for (let j = 0; j < options.length; j++) { 256 | themes[theme].option( 257 | options[j].children[0] as HTMLLIElement 258 | ); 259 | } 260 | } 261 | } 262 | handleReset(); 263 | }; 264 | 265 | const handleSpeedChanged = (e: MouseEvent) => { 266 | setSpeed(parseInt(e.currentTarget.getAttribute('value') as string)); 267 | }; 268 | 269 | const speedOptions = { 270 | 10: 'Very Fast', 271 | 25: 'Fast', 272 | 100: 'Medium', 273 | 500: 'Slow', 274 | 2000: 'Very Slow', 275 | }; 276 | 277 | return ( 278 |
279 |
280 | 289 | 298 | (obstacleDropdown = o as DropdownRef)} 300 | name='obstacles' 301 | default={obstacles['wall']} 302 | options={obstacles} 303 | onChange={handleObstacleChanged} 304 | classNames={[styles.Dropdown]} 305 | label='Obstacle:' 306 | width='15rem' 307 | > 308 | 317 |
318 |
319 | 322 | 325 | 331 | 334 |
335 |
336 | ); 337 | }; 338 | 339 | export default Controller; 340 | -------------------------------------------------------------------------------- /src/components/MobileController/MobileController.module.css: -------------------------------------------------------------------------------- 1 | .MobileController { 2 | display: none; 3 | /* position: fixed; 4 | bottom: 5%; 5 | left: 50%; 6 | transform: translateX(-50%); */ 7 | justify-content: space-between; 8 | align-items: center; 9 | width: 90%; 10 | margin: 0 auto 3rem; 11 | background-color: rgba(0, 0, 0, 0.2); 12 | border-radius: 0.3rem; 13 | padding: 2rem 1rem; 14 | touch-action: manipulation; 15 | } 16 | 17 | .MobileController button { 18 | border: none; 19 | border-radius: 0.3rem; 20 | padding: 0.7rem 1rem; 21 | background-color: var(--color-1); 22 | color: var(--color-4); 23 | } 24 | 25 | .MobileController button:active { 26 | color: red; 27 | } 28 | 29 | .MobileController button.Selected { 30 | border: 2px solid var(--color-4); 31 | } 32 | 33 | @media (hover: none) { 34 | .MobileController { 35 | display: flex; 36 | } 37 | } 38 | 39 | .StartEndContainer { 40 | display: flex; 41 | justify-content: center; 42 | align-items: center; 43 | flex-basis: 30%; 44 | } 45 | 46 | .StartEnd { 47 | display: flex; 48 | flex-direction: column; 49 | justify-content: center; 50 | align-items: center; 51 | flex-basis: 50%; 52 | } 53 | 54 | .StartEnd button { 55 | margin: 1rem 0; 56 | } 57 | 58 | .DPadContainer { 59 | display: flex; 60 | justify-content: center; 61 | align-items: center; 62 | flex-basis: 30%; 63 | } 64 | 65 | .DPad { 66 | display: flex; 67 | flex-direction: column; 68 | flex-basis: 40%; 69 | } 70 | 71 | .Row_1, 72 | .Row_3 { 73 | display: flex; 74 | justify-content: center; 75 | align-items: center; 76 | } 77 | 78 | .Row_1 { 79 | padding-bottom: 0.5rem; 80 | } 81 | 82 | .Row_2 { 83 | display: flex; 84 | justify-content: space-between; 85 | align-items: center; 86 | } 87 | 88 | .Row_3 { 89 | padding-top: 0.5rem; 90 | } 91 | 92 | .Shake { 93 | animation: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; 94 | transform: translate3d(0, 0, 0); 95 | backface-visibility: hidden; 96 | perspective: 1000px; 97 | } 98 | 99 | @keyframes shake { 100 | 10%, 101 | 90% { 102 | transform: translate3d(-1px, 0, 0); 103 | } 104 | 105 | 20%, 106 | 80% { 107 | transform: translate3d(2px, 0, 0); 108 | } 109 | 110 | 30%, 111 | 50%, 112 | 70% { 113 | transform: translate3d(-4px, 0, 0); 114 | } 115 | 116 | 40%, 117 | 60% { 118 | transform: translate3d(4px, 0, 0); 119 | } 120 | } 121 | 122 | @media only screen and (max-width: 1000px) { 123 | .DPad { 124 | flex-basis: 60%; 125 | } 126 | } 127 | 128 | @media only screen and (max-width: 800px) { 129 | .Row_1 { 130 | padding-bottom: 0.7rem; 131 | } 132 | 133 | .Row_3 { 134 | padding-top: 0.7rem; 135 | } 136 | .DPad { 137 | flex-basis: 75%; 138 | } 139 | } 140 | 141 | @media only screen and (max-width: 700px) { 142 | .DPad { 143 | flex-basis: 85%; 144 | } 145 | } 146 | 147 | @media only screen and (max-width: 600px) { 148 | .DPad { 149 | flex-basis: 95%; 150 | } 151 | } 152 | 153 | @media only screen and (max-width: 500px) { 154 | .DPad { 155 | flex-basis: 100%; 156 | } 157 | .DPadContainer { 158 | flex-basis: 35%; 159 | } 160 | } 161 | 162 | @media only screen and (max-width: 400px) { 163 | .DPadContainer { 164 | flex-basis: 40%; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/components/MobileController/MobileController.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, RefObject } from 'react'; 2 | 3 | import styles from './MobileController.module.css'; 4 | import { 5 | getNeighbors, 6 | getVertexAbsolute, 7 | Neighbors, 8 | } from '../../utils/position'; 9 | import { StoreState } from '../../store/reducers'; 10 | import { Vertex } from '../../store/reducers/graph'; 11 | import { useSelector, useDispatch } from 'react-redux'; 12 | import * as Actions from '../../store/actions'; 13 | import { GraphTheme } from '../../utils/themes'; 14 | 15 | interface MobileControllerProps { 16 | classNames?: string[]; 17 | verticesRef: RefObject; 18 | } 19 | 20 | // FIXME: moves over walls 21 | // FIXME: responsive issues 22 | const MobileController = (props: MobileControllerProps) => { 23 | let classNames = [styles.MobileController]; 24 | if (props.classNames) { 25 | classNames.push(...props.classNames); 26 | } 27 | 28 | const startVertex = useSelector( 29 | (state) => state.graph.startVertex 30 | ); 31 | const endVertex = useSelector( 32 | (state) => state.graph.endVertex 33 | ); 34 | const numRows = useSelector( 35 | (state) => state.graph.numRows 36 | ); 37 | const numCols = useSelector( 38 | (state) => state.graph.numCols 39 | ); 40 | const theme = useSelector( 41 | (state) => state.graph.theme 42 | ); 43 | const isDoneAnimating = useSelector( 44 | (state) => state.drag.isDoneAnimating 45 | ); 46 | const obstacleIndices = useSelector( 47 | (state) => state.drag.obstacleIndices 48 | ); 49 | const controllerRef = useRef(null); 50 | const startEndRef = useRef(null); 51 | const dispatch = useDispatch(); 52 | let start: Element; 53 | let startNeighbors: Neighbors; 54 | let startNeighborsTopVertex: Vertex; 55 | let startNeighborsBottomVertex: Vertex; 56 | let startNeighborsLeftVertex: Vertex; 57 | let startNeighborsRightVertex: Vertex; 58 | 59 | let end: Element; 60 | let endNeighbors: Neighbors; 61 | let endNeighborsTopVertex: Vertex; 62 | let endNeighborsBottomVertex: Vertex; 63 | let endNeighborsLeftVertex: Vertex; 64 | let endNeighborsRightVertex: Vertex; 65 | 66 | if (startEndRef.current) { 67 | start = startEndRef.current.children[0]; 68 | end = startEndRef.current.children[1]; 69 | startNeighbors = getNeighbors( 70 | startVertex?.absoluteIndex as number, 71 | startVertex?.row as number, 72 | numRows, 73 | numCols 74 | ); 75 | endNeighbors = getNeighbors( 76 | endVertex?.absoluteIndex as number, 77 | endVertex?.row as number, 78 | numRows, 79 | numCols 80 | ); 81 | if ( 82 | startNeighbors.top !== -1 && 83 | startNeighbors.top < numRows * numCols && 84 | !obstacleIndices.includes(startNeighbors.top) 85 | ) { 86 | startNeighborsTopVertex = getVertexAbsolute( 87 | startNeighbors.top, 88 | numRows, 89 | numCols, 90 | props.verticesRef 91 | ); 92 | } 93 | if ( 94 | endNeighbors.top !== -1 && 95 | endNeighbors.top < numRows * numCols && 96 | !obstacleIndices.includes(endNeighbors.top) 97 | ) { 98 | endNeighborsTopVertex = getVertexAbsolute( 99 | endNeighbors.top, 100 | numRows, 101 | numCols, 102 | props.verticesRef 103 | ); 104 | } 105 | if ( 106 | startNeighbors.bottom !== -1 && 107 | startNeighbors.bottom < numRows * numCols && 108 | !obstacleIndices.includes(startNeighbors.bottom) 109 | ) { 110 | startNeighborsBottomVertex = getVertexAbsolute( 111 | startNeighbors.bottom, 112 | numRows, 113 | numCols, 114 | props.verticesRef 115 | ); 116 | } 117 | if ( 118 | endNeighbors.bottom !== -1 && 119 | endNeighbors.bottom < numRows * numCols && 120 | !obstacleIndices.includes(endNeighbors.bottom) 121 | ) { 122 | endNeighborsBottomVertex = getVertexAbsolute( 123 | endNeighbors.bottom, 124 | numRows, 125 | numCols, 126 | props.verticesRef 127 | ); 128 | } 129 | if ( 130 | startNeighbors.left !== -1 && 131 | startNeighbors.left < numRows * numCols && 132 | !obstacleIndices.includes(startNeighbors.left) 133 | ) { 134 | startNeighborsLeftVertex = getVertexAbsolute( 135 | startNeighbors.left, 136 | numRows, 137 | numCols, 138 | props.verticesRef 139 | ); 140 | } 141 | if ( 142 | endNeighbors.left !== -1 && 143 | endNeighbors.left < numRows * numCols && 144 | !obstacleIndices.includes(endNeighbors.left) 145 | ) { 146 | endNeighborsLeftVertex = getVertexAbsolute( 147 | endNeighbors.left, 148 | numRows, 149 | numCols, 150 | props.verticesRef 151 | ); 152 | } 153 | if ( 154 | startNeighbors.right !== -1 && 155 | startNeighbors.right < numRows * numCols && 156 | !obstacleIndices.includes(startNeighbors.right) 157 | ) { 158 | startNeighborsRightVertex = getVertexAbsolute( 159 | startNeighbors.right, 160 | numRows, 161 | numCols, 162 | props.verticesRef 163 | ); 164 | } 165 | if ( 166 | endNeighbors.right !== -1 && 167 | endNeighbors.right < numRows * numCols && 168 | !obstacleIndices.includes(endNeighbors.right) 169 | ) { 170 | endNeighborsRightVertex = getVertexAbsolute( 171 | endNeighbors.right, 172 | numRows, 173 | numCols, 174 | props.verticesRef 175 | ); 176 | } 177 | } 178 | 179 | const handleStart = () => { 180 | if (startEndRef.current) { 181 | const start = startEndRef.current.children[0]; 182 | const end = startEndRef.current.children[1]; 183 | if (end.classList.contains(styles.Selected)) { 184 | end.classList.remove(styles.Selected); 185 | start.classList.add(styles.Selected); 186 | } 187 | } 188 | }; 189 | 190 | const handleEnd = () => { 191 | if (startEndRef.current) { 192 | const start = startEndRef.current.children[0]; 193 | const end = startEndRef.current.children[1]; 194 | if (start.classList.contains(styles.Selected)) { 195 | start.classList.remove(styles.Selected); 196 | end.classList.add(styles.Selected); 197 | } 198 | } 199 | }; 200 | 201 | const handleMove = ( 202 | element: Element, 203 | elementNeighbor: number, 204 | vertex: Vertex 205 | ) => { 206 | if (startEndRef.current && startVertex && endVertex) { 207 | if ( 208 | elementNeighbor !== -1 && 209 | elementNeighbor !== endVertex.absoluteIndex && 210 | elementNeighbor !== startVertex.absoluteIndex && 211 | !obstacleIndices.includes(elementNeighbor) 212 | ) { 213 | if (element === start) { 214 | theme.start(vertex.element); 215 | theme.unvisited(startVertex?.element); 216 | dispatch( 217 | Actions.setStartVertex( 218 | vertex.row, 219 | vertex.column, 220 | props.verticesRef 221 | ) 222 | ); 223 | if (isDoneAnimating) { 224 | dispatch(Actions.clearPath(props.verticesRef)); 225 | dispatch( 226 | Actions.recalculatePath( 227 | vertex.absoluteIndex, 228 | endVertex.absoluteIndex, 229 | props.verticesRef 230 | ) 231 | ); 232 | } 233 | } else if (element === end) { 234 | theme.end(vertex.element); 235 | theme.unvisited(endVertex?.element); 236 | dispatch( 237 | Actions.setEndVertex( 238 | vertex.row, 239 | vertex.column, 240 | props.verticesRef 241 | ) 242 | ); 243 | if (isDoneAnimating) { 244 | dispatch(Actions.clearPath(props.verticesRef)); 245 | dispatch( 246 | Actions.recalculatePath( 247 | startVertex.absoluteIndex, 248 | vertex.absoluteIndex, 249 | props.verticesRef 250 | ) 251 | ); 252 | } 253 | } 254 | } else if (controllerRef.current) { 255 | controllerRef.current.classList.remove(styles.Shake); 256 | setTimeout(() => { 257 | if (controllerRef.current) { 258 | controllerRef.current.classList.add(styles.Shake); 259 | } 260 | }, 10); 261 | } 262 | } 263 | }; 264 | 265 | const handleUp = () => { 266 | if (start.classList.contains(styles.Selected)) { 267 | handleMove(start, startNeighbors.top, startNeighborsTopVertex); 268 | } else if (end.classList.contains(styles.Selected)) { 269 | handleMove(end, endNeighbors.top, endNeighborsTopVertex); 270 | } 271 | }; 272 | const handleDown = () => { 273 | if (start.classList.contains(styles.Selected)) { 274 | handleMove( 275 | start, 276 | startNeighbors.bottom, 277 | startNeighborsBottomVertex 278 | ); 279 | } else if (end.classList.contains(styles.Selected)) { 280 | handleMove(end, endNeighbors.bottom, endNeighborsBottomVertex); 281 | } 282 | }; 283 | const handleLeft = () => { 284 | if (start.classList.contains(styles.Selected)) { 285 | handleMove(start, startNeighbors.left, startNeighborsLeftVertex); 286 | } else if (end.classList.contains(styles.Selected)) { 287 | handleMove(end, endNeighbors.left, endNeighborsLeftVertex); 288 | } 289 | }; 290 | const handleRight = () => { 291 | if (start.classList.contains(styles.Selected)) { 292 | handleMove(start, startNeighbors.right, startNeighborsRightVertex); 293 | } else if (end.classList.contains(styles.Selected)) { 294 | handleMove(end, endNeighbors.right, endNeighborsRightVertex); 295 | } 296 | }; 297 | 298 | return ( 299 |
300 |
301 |
302 | 305 | 306 |
307 |
308 |
309 |
310 |
311 | 312 |
313 |
314 | 315 | 316 |
317 |
318 | 319 |
320 |
321 |
322 |
323 | ); 324 | }; 325 | 326 | export default MobileController; 327 | -------------------------------------------------------------------------------- /src/components/UI/Dropdown/Dropdown.module.css: -------------------------------------------------------------------------------- 1 | .Dropdown { 2 | display: flex; 3 | align-items: center; 4 | width: 70%; 5 | justify-content: space-between; 6 | } 7 | 8 | @media only screen and (max-width: 700px) { 9 | .Dropdown { 10 | width: 90%; 11 | } 12 | } 13 | 14 | @media only screen and (max-width: 550px) { 15 | .Dropdown { 16 | width: 100%; 17 | } 18 | } 19 | 20 | .Label { 21 | font-size: 1rem; 22 | } 23 | 24 | .Heading { 25 | background-color: var(--color-4); 26 | color: var(--color-3); 27 | border-radius: 0.3rem; 28 | text-align: center; 29 | font-size: 1.3rem; 30 | font-weight: 400; 31 | cursor: pointer; 32 | position: relative; 33 | padding: 0.3rem 0; 34 | } 35 | 36 | .Caret { 37 | position: absolute; 38 | right: 3%; 39 | top: 50%; 40 | font-size: 2.5rem; 41 | transform: translateY(-50%) rotate(90deg); 42 | transform-origin: center; 43 | transition: transform 0.3s; 44 | } 45 | 46 | .CaretRotate { 47 | transform: translateY(-50%) rotate(270deg); 48 | } 49 | 50 | .List { 51 | list-style: none; 52 | padding: 0; 53 | /* margin: 0 0 0 1rem; */ 54 | margin: 0; 55 | position: relative; 56 | width: 21rem; 57 | } 58 | 59 | .Options { 60 | position: absolute; 61 | width: 100%; 62 | display: flex; 63 | flex-direction: column; 64 | background-color: var(--color-1); 65 | border-radius: 0.3rem; 66 | padding: 0.3rem; 67 | height: 14rem; 68 | transition: height 0.3s, padding 0.3s; 69 | z-index: 10; 70 | } 71 | 72 | .OptionsHidden { 73 | height: 0; 74 | padding: 0; 75 | } 76 | 77 | .OptionsZIndex { 78 | z-index: -10; 79 | } 80 | 81 | .Option { 82 | cursor: pointer; 83 | padding: 0.3rem 0.5rem; 84 | transition: opacity 0.3s, display 0.3s 0.3s, background-color 0.2s, 85 | color 0.2s; 86 | display: block; 87 | opacity: 1; 88 | } 89 | 90 | .OptionHidden { 91 | opacity: 0; 92 | } 93 | 94 | .Option:hover { 95 | background-color: var(--color-4); 96 | color: var(--color-3); 97 | } 98 | 99 | .Option:first-of-type { 100 | border-top-left-radius: 0.3rem; 101 | border-top-right-radius: 0.3rem; 102 | } 103 | 104 | .Option:last-of-type { 105 | border-bottom-left-radius: 0.3rem; 106 | border-bottom-right-radius: 0.3rem; 107 | } 108 | -------------------------------------------------------------------------------- /src/components/UI/Dropdown/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | useRef, 3 | useImperativeHandle, 4 | forwardRef, 5 | MouseEvent, 6 | Ref, 7 | useEffect, 8 | } from 'react'; 9 | 10 | import styles from './Dropdown.module.css'; 11 | 12 | type Option = { [key: string]: string }; 13 | 14 | export interface DropdownProps { 15 | name: string; 16 | options: Option; 17 | default: string; 18 | classNames?: string[]; 19 | label?: string; 20 | width: string; 21 | onChange: (event: MouseEvent) => void; 22 | } 23 | 24 | export interface DropdownRef { 25 | heading: HTMLHeadingElement | null; 26 | } 27 | 28 | const Dropdown = forwardRef((props: DropdownProps, ref: Ref) => { 29 | let classNames = [styles.Dropdown]; 30 | if (props.classNames) { 31 | classNames.push(...props.classNames); 32 | } 33 | 34 | const optionsRef = useRef(null); 35 | const headingRef = useRef(null); 36 | 37 | useImperativeHandle( 38 | ref, 39 | (): DropdownRef => ({ 40 | heading: headingRef.current, 41 | }) 42 | ); 43 | 44 | useEffect(() => { 45 | document.addEventListener('click', (e: any) => { 46 | if ( 47 | e.target.parentElement && 48 | e.target.parentElement.parentElement && 49 | !e.target.parentElement.parentElement.classList.contains( 50 | styles.Dropdown 51 | ) && 52 | !e.target.parentElement.parentElement.classList.contains( 53 | styles.List 54 | ) 55 | ) { 56 | if (optionsRef.current && headingRef.current) { 57 | optionsRef.current.classList.add(styles.OptionsHidden); 58 | setTimeout(() => { 59 | if (optionsRef.current) { 60 | optionsRef.current.classList.add( 61 | styles.OptionsZIndex 62 | ); 63 | } 64 | }, 200); 65 | let options = optionsRef.current.children; 66 | for (let i = 0; i < options.length; i++) { 67 | options[i].children[0].classList.add( 68 | styles.OptionHidden 69 | ); 70 | } 71 | headingRef.current.children[1].classList.remove( 72 | styles.CaretRotate 73 | ); 74 | } 75 | } 76 | }); 77 | }, []); 78 | 79 | let optionsStyles = [ 80 | styles.Options, 81 | styles.OptionsHidden, 82 | styles.OptionsZIndex, 83 | ]; 84 | 85 | let optionStyles = [styles.Option, styles.OptionHidden]; 86 | 87 | const handleDropdownToggle = () => { 88 | if (optionsRef.current && headingRef.current) { 89 | optionsRef.current.classList.toggle(styles.OptionsHidden); 90 | if (optionsRef.current.classList.contains(styles.OptionsZIndex)) { 91 | setTimeout(() => { 92 | if (optionsRef.current) 93 | optionsRef.current.classList.toggle( 94 | styles.OptionsZIndex 95 | ); 96 | }, 3); 97 | } else { 98 | setTimeout(() => { 99 | if (optionsRef.current) 100 | optionsRef.current.classList.toggle( 101 | styles.OptionsZIndex 102 | ); 103 | }, 100); 104 | } 105 | 106 | let options = optionsRef.current.children; 107 | for (let i = 0; i < options.length; i++) { 108 | options[i].children[0].classList.toggle(styles.OptionHidden); 109 | } 110 | headingRef.current.children[1].classList.toggle(styles.CaretRotate); 111 | } 112 | }; 113 | 114 | const handleOptionClicked = (e: MouseEvent) => { 115 | if (headingRef.current) { 116 | props.onChange(e); 117 | headingRef.current.children[0].innerHTML = 118 | e.currentTarget.innerHTML; 119 | headingRef.current.click(); 120 | } 121 | }; 122 | 123 | let options = Object.keys(props.options).map((option) => { 124 | return ( 125 | 126 |
  • 132 | {props.options[option]} 133 |
  • 134 |
    135 | ); 136 | }); 137 | 138 | return ( 139 |
    140 |

    {props.label && props.label}

    141 |
      142 |

      147 | {props.default} 148 | 149 |

      150 |
      155 | {options} 156 |
      157 |
    158 |
    159 | ); 160 | }); 161 | 162 | export default Dropdown; 163 | -------------------------------------------------------------------------------- /src/components/UI/Navbar/Navbar.module.css: -------------------------------------------------------------------------------- 1 | .Navbar { 2 | width: 100%; 3 | background-color: var(--color-1); 4 | color: var(--color-4); 5 | display: flex; 6 | align-items: center; 7 | justify-content: space-between; 8 | } 9 | 10 | .Heading { 11 | width: 100%; 12 | text-align: center; 13 | } 14 | -------------------------------------------------------------------------------- /src/components/UI/Navbar/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import React, { RefObject } from 'react'; 2 | import styles from './Navbar.module.css'; 3 | 4 | export interface NavbarProps { 5 | classNames?: string[]; 6 | verticesRef: RefObject; 7 | } 8 | 9 | const Navbar = (props: NavbarProps) => { 10 | let classNames = [styles.Navbar]; 11 | if (props.classNames) { 12 | classNames.push(...props.classNames); 13 | } 14 | 15 | return ( 16 | 21 | ); 22 | }; 23 | 24 | export default Navbar; 25 | -------------------------------------------------------------------------------- /src/containers/PathfindingVisualizer/4913: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/containers/PathfindingVisualizer/4913 -------------------------------------------------------------------------------- /src/containers/PathfindingVisualizer/PathfindingVisualizer.module.css: -------------------------------------------------------------------------------- 1 | .PathfindingVisualizer { 2 | /* display: flex; 3 | flex-direction: column; 4 | justify-content: space-between; 5 | align-items: center; */ 6 | min-height: 100vh; 7 | } 8 | 9 | .buttons { 10 | display: flex; 11 | width: 100%; 12 | justify-content: center; 13 | } 14 | -------------------------------------------------------------------------------- /src/containers/PathfindingVisualizer/PathfindingVisualizer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useLayoutEffect, useEffect } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | 4 | import Vertices from './Vertices/Vertices'; 5 | import Navbar from '../../components/UI/Navbar/Navbar'; 6 | import Controller from '../../components/Controller/Controller'; 7 | import MobileController from '../../components/MobileController/MobileController'; 8 | 9 | import * as Actions from '../../store/actions'; 10 | 11 | import styles from './PathfindingVisualizer.module.css'; 12 | import { StoreState } from '../../store/reducers'; 13 | 14 | interface PathfindingVisualizerProps {} 15 | 16 | const PathfindingVisualizer = (props: PathfindingVisualizerProps) => { 17 | const dispatch = useDispatch(); 18 | const numRows = useSelector( 19 | (state) => state.graph.numRows 20 | ); 21 | const numCols = useSelector( 22 | (state) => state.graph.numCols 23 | ); 24 | const dragWallIndices = useSelector( 25 | (state) => state.drag.wallIndices 26 | ); 27 | 28 | const verticesRef = useRef(null); 29 | 30 | useLayoutEffect(() => { 31 | dispatch(Actions.setVerticesRef(verticesRef)); 32 | dispatch(Actions.setObstacleRef(dragWallIndices)); 33 | }, [dispatch, dragWallIndices]); 34 | 35 | useLayoutEffect(() => { 36 | dispatch( 37 | Actions.setStartVertex( 38 | Math.floor(numRows / 2), 39 | Math.floor(numCols / 4), 40 | verticesRef 41 | ) 42 | ); 43 | dispatch( 44 | Actions.setEndVertex( 45 | Math.floor(numRows / 2), 46 | Math.floor(numCols / 4) * 3, 47 | verticesRef 48 | ) 49 | ); 50 | }, [dispatch, verticesRef, numRows, numCols]); 51 | 52 | useEffect(() => { 53 | dispatch(Actions.initGraph(verticesRef)); 54 | dispatch(Actions.clearPath(verticesRef)); 55 | dispatch(Actions.setIsDoneAnimating(false)); 56 | dispatch(Actions.onClearWalls(verticesRef)); 57 | }, [dispatch, numRows, numCols]); 58 | 59 | // const handleTest = () => { 60 | // console.log(dragWallIndices, graphWallIndices); 61 | // console.log(dragObstacle1Indices, graphObstacle1Indices); 62 | // console.log(dragObstacle2Indices, graphObstacle2Indices); 63 | // console.log(dragObstacle3Indices, graphObstacle3Indices); 64 | // }; 65 | 66 | return ( 67 |
    68 | 69 | 70 | 71 | 72 | {/*
    73 | 74 |
    */} 75 |
    76 | ); 77 | }; 78 | 79 | export default PathfindingVisualizer; 80 | -------------------------------------------------------------------------------- /src/containers/PathfindingVisualizer/Vertices/Vertex/Vertex.module.css: -------------------------------------------------------------------------------- 1 | .Vertex { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | width: 100%; 6 | height: 100%; 7 | font-size: 50%; 8 | cursor: default; 9 | /* border-left: 0.1px solid var(--color-2); */ 10 | } 11 | 12 | .Vertex:last-of-type { 13 | /* border-right: 0.1px solid var(--color-2); */ 14 | } 15 | 16 | .Vertex > * { 17 | z-index: -10; 18 | } 19 | 20 | .Vertex p { 21 | text-align: center; 22 | font-weight: bold; 23 | font-size: large; 24 | margin: 0; 25 | padding: 0; 26 | display: flex; 27 | justify-content: center; 28 | align-items: center; 29 | } 30 | 31 | .Start:hover { 32 | cursor: grab; 33 | } 34 | 35 | .Vertex img { 36 | width: 100%; 37 | } 38 | -------------------------------------------------------------------------------- /src/containers/PathfindingVisualizer/Vertices/Vertex/Vertex.tsx: -------------------------------------------------------------------------------- 1 | import React, { RefObject } from 'react'; 2 | import { useDispatch } from 'react-redux'; 3 | import * as actions from '../../../../store/actions'; 4 | 5 | import styles from './Vertex.module.css'; 6 | 7 | interface VertexRef { 8 | verticesRef: RefObject; 9 | absoluteIndex: number; 10 | } 11 | 12 | const Vertex = (props: VertexRef) => { 13 | const dispatch = useDispatch(); 14 | 15 | return ( 16 |
    { 19 | e.preventDefault(); 20 | dispatch(actions.onMouseDown(e)); 21 | }} 22 | onMouseOver={(e: any) => { 23 | e.preventDefault(); 24 | dispatch(actions.onMouseOver(e, props.verticesRef)); 25 | }} 26 | onMouseOut={(e: any) => { 27 | e.preventDefault(); 28 | dispatch(actions.onMouseOut(e, props.verticesRef)); 29 | }} 30 | onMouseUp={(e: any) => { 31 | e.preventDefault(); 32 | dispatch(actions.onMouseUp(e, props.verticesRef)); 33 | }} 34 | > 35 |

    36 | {/* {props.absoluteIndex} */} 37 |
    38 | ); 39 | }; 40 | 41 | export default Vertex; 42 | -------------------------------------------------------------------------------- /src/containers/PathfindingVisualizer/Vertices/VertexRow/VertexRow.module.css: -------------------------------------------------------------------------------- 1 | .VertexRow { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | /* border-top: 0.1px solid var(--color-2); */ 6 | width: 100%; 7 | height: 10%; 8 | } 9 | 10 | .VertexRow:last-of-type { 11 | /* border-bottom: 0.1px solid var(--color-2); */ 12 | } 13 | -------------------------------------------------------------------------------- /src/containers/PathfindingVisualizer/Vertices/VertexRow/VertexRow.tsx: -------------------------------------------------------------------------------- 1 | import React, { RefObject } from 'react'; 2 | 3 | import Vertex from '../Vertex/Vertex'; 4 | 5 | import styles from './VertexRow.module.css'; 6 | import * as Position from '../../../../utils/position'; 7 | import { StoreState } from '../../../../store/reducers'; 8 | import { useSelector } from 'react-redux'; 9 | 10 | interface VertexRowProps { 11 | rowNumber: number; 12 | columns: number; 13 | verticesRef: RefObject; 14 | } 15 | 16 | const VertexRow = (props: VertexRowProps) => { 17 | let vertexRow: JSX.Element[] = []; 18 | const numRows = useSelector( 19 | (state) => state.graph.numRows 20 | ); 21 | const numCols = useSelector( 22 | (state) => state.graph.numCols 23 | ); 24 | 25 | for (let i = 0; i < props.columns; i++) { 26 | let absoluteIndex = Position.indexToAbsolute( 27 | props.rowNumber, 28 | i, 29 | numRows, 30 | numCols 31 | ); 32 | vertexRow.push( 33 | 38 | ); 39 | } 40 | 41 | return
    {vertexRow}
    ; 42 | }; 43 | 44 | export default VertexRow; 45 | -------------------------------------------------------------------------------- /src/containers/PathfindingVisualizer/Vertices/Vertices.module.css: -------------------------------------------------------------------------------- 1 | .Vertices { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | width: 90%; 7 | margin: 0 auto; 8 | padding: 0 0 1rem; 9 | height: 90vh; 10 | } 11 | -------------------------------------------------------------------------------- /src/containers/PathfindingVisualizer/Vertices/Vertices.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useImperativeHandle, forwardRef } from 'react'; 2 | import { useSelector, useDispatch } from 'react-redux'; 3 | import { StoreState } from '../../../store/reducers'; 4 | import * as Actions from '../../../store/actions'; 5 | 6 | import styles from './Vertices.module.css'; 7 | 8 | import VertexRow from './VertexRow/VertexRow'; 9 | import { Vertex } from '../../../store/reducers/graph'; 10 | 11 | interface VerticesProps {} 12 | 13 | export interface VerticesRef { 14 | vertices: HTMLDivElement | null; 15 | children: HTMLCollection | null; 16 | } 17 | 18 | const Vertices = (props: VerticesProps, ref: any) => { 19 | const dispatch = useDispatch(); 20 | const numRows = useSelector( 21 | (state) => state.graph.numRows 22 | ); 23 | const numCols = useSelector( 24 | (state) => state.graph.numCols 25 | ); 26 | const startVertex = useSelector( 27 | (state) => state.graph.startVertex 28 | ); 29 | const endVertex = useSelector( 30 | (state) => state.graph.endVertex 31 | ); 32 | const wallIndices = useSelector( 33 | (state) => state.drag.wallIndices 34 | ); 35 | const isStartMouseDown = useSelector( 36 | (state) => state.drag.isStartMouseDown 37 | ); 38 | const isEndMouseDown = useSelector( 39 | (state) => state.drag.isEndMouseDown 40 | ); 41 | const isMouseDown = useSelector( 42 | (state) => state.drag.isMouseDown 43 | ); 44 | let verticesRef = useRef(null); 45 | 46 | useImperativeHandle( 47 | ref, 48 | (): VerticesRef => ({ 49 | vertices: verticesRef.current, 50 | children: verticesRef.current ? verticesRef.current.children : null, 51 | }) 52 | ); 53 | 54 | let vertices: JSX.Element[] = []; 55 | 56 | for (let i = 0; i < numRows; i++) { 57 | vertices.push( 58 | 64 | ); 65 | } 66 | 67 | const handleMouseLeave = (e: any) => { 68 | if ( 69 | startVertex && 70 | endVertex && 71 | (isStartMouseDown || isEndMouseDown || isMouseDown) 72 | ) { 73 | dispatch(Actions.mouseUp(e)); 74 | dispatch( 75 | Actions.setStartVertex( 76 | startVertex?.row, 77 | startVertex?.column, 78 | verticesRef 79 | ) 80 | ); 81 | dispatch( 82 | Actions.setEndVertex( 83 | endVertex?.row, 84 | endVertex?.column, 85 | verticesRef 86 | ) 87 | ); 88 | dispatch( 89 | Actions.clearPath( 90 | verticesRef, 91 | false, 92 | startVertex.absoluteIndex, 93 | endVertex.absoluteIndex 94 | ) 95 | ); 96 | dispatch(Actions.onSetWallIndices(wallIndices, verticesRef)); 97 | } 98 | }; 99 | 100 | return ( 101 |
    106 | {vertices} 107 |
    108 | ); 109 | }; 110 | 111 | export default forwardRef(Vertices); 112 | -------------------------------------------------------------------------------- /src/fonts/Avengers/Avengers.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Avengers/Avengers.ttf -------------------------------------------------------------------------------- /src/fonts/Lakers/lakers.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Lakers/lakers.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-Black.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-BlackItalic.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-BoldItalic.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-ExtraBold.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-ExtraLight.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-Italic.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-Light.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-LightItalic.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-Medium.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-MediumItalic.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-SemiBold.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-Thin.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/Montserrat-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Montserrat/Montserrat-ThinItalic.ttf -------------------------------------------------------------------------------- /src/fonts/Montserrat/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2011 The Montserrat Project Authors (https://github.com/JulietaUla/Montserrat) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /src/fonts/OnePiece/OnePiece.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/OnePiece/OnePiece.ttf -------------------------------------------------------------------------------- /src/fonts/Pokemon/PokemonHollow.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Pokemon/PokemonHollow.ttf -------------------------------------------------------------------------------- /src/fonts/Pokemon/PokemonSolid.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/Pokemon/PokemonSolid.ttf -------------------------------------------------------------------------------- /src/fonts/TheOffice/TheOffice.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/fonts/TheOffice/TheOffice.ttf -------------------------------------------------------------------------------- /src/fonts/TheOffice/readme.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | American Typewriter RegularFontsgeek 8 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 182 | 183 | 184 |
    165 | 166 | 167 |
    168 | 169 | 170 | 176 | 177 |
    171 |

    American Typewriter Regular

    172 |

    This font was downloaded from fontsgeek.com . You can visit fontsgeek.com for thousands of free fonts.

    173 |

    View Charmap and other information Browse other free fonts

    174 |

    You will be shortly redirected to fontsgeek.

    175 |
    178 |
    179 | 180 | 181 |
    185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /src/fonts/avengers.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Avengers'; 3 | font-style: normal; 4 | font-weight: 700; 5 | font-display: swap; 6 | src: url(./Avengers/Avengers.ttf); 7 | } 8 | -------------------------------------------------------------------------------- /src/fonts/lakers.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Lakers'; 3 | font-style: normal; 4 | font-weight: 700; 5 | font-display: swap; 6 | src: url(./Lakers//lakers.ttf); 7 | } 8 | -------------------------------------------------------------------------------- /src/fonts/montserrat.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Montserrat'; 3 | font-style: normal; 4 | font-weight: 400; 5 | font-display: swap; 6 | src: url(./Montserrat/Montserrat-Regular.ttf); 7 | } 8 | 9 | @font-face { 10 | font-family: 'Montserrat'; 11 | font-style: bold; 12 | font-weight: 700; 13 | font-display: swap; 14 | src: url(./Montserrat/Montserrat-Bold.ttf); 15 | } 16 | 17 | @font-face { 18 | font-family: 'Montserrat'; 19 | font-style: lighter; 20 | font-weight: 300; 21 | font-display: swap; 22 | src: url(./Montserrat/Montserrat-Light.ttf); 23 | } 24 | -------------------------------------------------------------------------------- /src/fonts/onePiece.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'One Piece'; 3 | font-style: normal; 4 | font-weight: 700; 5 | font-display: swap; 6 | src: url(./OnePiece/OnePiece.ttf); 7 | } 8 | -------------------------------------------------------------------------------- /src/fonts/pokemon.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Pokemon'; 3 | font-style: normal; 4 | font-weight: 700; 5 | font-display: swap; 6 | src: url(./Pokemon/PokemonSolid.ttf); 7 | } 8 | -------------------------------------------------------------------------------- /src/fonts/theOffice.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'The Office'; 3 | font-style: normal; 4 | font-weight: 700; 5 | font-display: swap; 6 | src: url(./TheOffice/TheOffice.ttf); 7 | } 8 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url(./fonts/montserrat.css); 2 | @import url(./fonts/avengers.css); 3 | @import url(./fonts/onePiece.css); 4 | @import url(./fonts/lakers.css); 5 | @import url(./fonts/pokemon.css); 6 | @import url(./fonts/theOffice.css); 7 | 8 | :root { 9 | --color-1: #292f36; 10 | --color-2: #4ecdc4; 11 | --color-3: #ffffff; 12 | --color-4: #ff6b6b; 13 | } 14 | 15 | *, 16 | *::after, 17 | *::before { 18 | font-family: inherit; 19 | box-sizing: border-box; 20 | } 21 | 22 | html { 23 | font-size: 100%; 24 | } 25 | 26 | @media only screen and (max-width: 508px) { 27 | html { 28 | font-size: 80%; 29 | } 30 | } 31 | 32 | @media only screen and (max-width: 406px) { 33 | html { 34 | font-size: 70%; 35 | } 36 | } 37 | 38 | @media only screen and (max-width: 355px) { 39 | html { 40 | font-size: 60%; 41 | } 42 | } 43 | 44 | @media only screen and (max-width: 304px) { 45 | html { 46 | font-size: 50%; 47 | } 48 | } 49 | 50 | body { 51 | margin: 0; 52 | min-height: 100vh; 53 | background-color: var(--color-3); 54 | font-family: 'Montserrat', sans-serif; 55 | } 56 | 57 | ::selection { 58 | background-color: var(--color-4); 59 | color: var(--color-3); 60 | } 61 | 62 | button, 63 | select, 64 | option { 65 | outline: none; 66 | cursor: pointer; 67 | } 68 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 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 | import { createStore, applyMiddleware } from 'redux'; 8 | import { Provider } from 'react-redux'; 9 | import thunk from 'redux-thunk'; 10 | import { reducers } from './store/reducers'; 11 | 12 | export const store = createStore(reducers, applyMiddleware(thunk)); 13 | 14 | ReactDOM.render( 15 | 16 | 17 | 18 | 19 | , 20 | document.getElementById('root') 21 | ); 22 | 23 | // If you want your app to work offline and load faster, you can change 24 | // unregister() to register() below. Note this comes with some pitfalls. 25 | // Learn more about service workers: https://bit.ly/CRA-PWA 26 | serviceWorker.unregister(); 27 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/serviceWorker.ts: -------------------------------------------------------------------------------- 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.0/8 are 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 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL( 32 | process.env.PUBLIC_URL, 33 | window.location.href 34 | ); 35 | if (publicUrl.origin !== window.location.origin) { 36 | // Our service worker won't work if PUBLIC_URL is on a different origin 37 | // from what our page is served on. This might happen if a CDN is used to 38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 39 | return; 40 | } 41 | 42 | window.addEventListener('load', () => { 43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 44 | 45 | if (isLocalhost) { 46 | // This is running on localhost. Let's check if a service worker still exists or not. 47 | checkValidServiceWorker(swUrl, config); 48 | 49 | // Add some additional logging to localhost, pointing developers to the 50 | // service worker/PWA documentation. 51 | navigator.serviceWorker.ready.then(() => { 52 | console.log( 53 | 'This web app is being served cache-first by a service ' + 54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 55 | ); 56 | }); 57 | } else { 58 | // Is not localhost. Just register service worker 59 | registerValidSW(swUrl, config); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | function registerValidSW(swUrl: string, config?: Config) { 66 | navigator.serviceWorker 67 | .register(swUrl) 68 | .then(registration => { 69 | registration.onupdatefound = () => { 70 | const installingWorker = registration.installing; 71 | if (installingWorker == null) { 72 | return; 73 | } 74 | installingWorker.onstatechange = () => { 75 | if (installingWorker.state === 'installed') { 76 | if (navigator.serviceWorker.controller) { 77 | // At this point, the updated precached content has been fetched, 78 | // but the previous service worker will still serve the older 79 | // content until all client tabs are closed. 80 | console.log( 81 | 'New content is available and will be used when all ' + 82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 83 | ); 84 | 85 | // Execute callback 86 | if (config && config.onUpdate) { 87 | config.onUpdate(registration); 88 | } 89 | } else { 90 | // At this point, everything has been precached. 91 | // It's the perfect time to display a 92 | // "Content is cached for offline use." message. 93 | console.log('Content is cached for offline use.'); 94 | 95 | // Execute callback 96 | if (config && config.onSuccess) { 97 | config.onSuccess(registration); 98 | } 99 | } 100 | } 101 | }; 102 | }; 103 | }) 104 | .catch(error => { 105 | console.error('Error during service worker registration:', error); 106 | }); 107 | } 108 | 109 | function checkValidServiceWorker(swUrl: string, config?: Config) { 110 | // Check if the service worker can be found. If it can't reload the page. 111 | fetch(swUrl, { 112 | headers: { 'Service-Worker': 'script' } 113 | }) 114 | .then(response => { 115 | // Ensure service worker exists, and that we really are getting a JS file. 116 | const contentType = response.headers.get('content-type'); 117 | if ( 118 | response.status === 404 || 119 | (contentType != null && contentType.indexOf('javascript') === -1) 120 | ) { 121 | // No service worker found. Probably a different app. Reload the page. 122 | navigator.serviceWorker.ready.then(registration => { 123 | registration.unregister().then(() => { 124 | window.location.reload(); 125 | }); 126 | }); 127 | } else { 128 | // Service worker found. Proceed as normal. 129 | registerValidSW(swUrl, config); 130 | } 131 | }) 132 | .catch(() => { 133 | console.log( 134 | 'No internet connection found. App is running in offline mode.' 135 | ); 136 | }); 137 | } 138 | 139 | export function unregister() { 140 | if ('serviceWorker' in navigator) { 141 | navigator.serviceWorker.ready 142 | .then(registration => { 143 | registration.unregister(); 144 | }) 145 | .catch(error => { 146 | console.error(error.message); 147 | }); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /src/store/actions/drag.ts: -------------------------------------------------------------------------------- 1 | import { RefObject, Dispatch } from 'react'; 2 | import { ActionTypes } from './types'; 3 | import * as actions from '../actions'; 4 | import * as Position from '../../utils/position'; 5 | import { Vertex } from '../reducers/graph'; 6 | import { GraphTheme } from '../../utils/themes'; 7 | import { indexToAbsolute } from '../../utils/position'; 8 | 9 | export const mouseDown = ( 10 | e: any, 11 | isAnimating: boolean, 12 | startVertex: Vertex, 13 | endVertex: Vertex, 14 | theme: GraphTheme 15 | ) => { 16 | return { 17 | type: ActionTypes.MOUSE_DOWN, 18 | e, 19 | isAnimating, 20 | startVertex, 21 | endVertex, 22 | theme, 23 | }; 24 | }; 25 | 26 | export const onMouseDown = (e: any) => { 27 | return (dispatch: Dispatch, getState: any) => { 28 | let graph = getState().graph; 29 | let isAnimating = graph.isAnimating; 30 | let startVertex = graph.startVertex; 31 | let endVertex = graph.endVertex; 32 | let theme = graph.theme; 33 | 34 | dispatch(mouseDown(e, isAnimating, startVertex, endVertex, theme)); 35 | }; 36 | }; 37 | 38 | export const mouseOver = ( 39 | e: any, 40 | verticesRef: RefObject, 41 | startVertex: Vertex, 42 | endVertex: Vertex, 43 | numRows: number, 44 | numCols: number, 45 | startNeighbors: number[], 46 | startNeighborsVertices: Vertex[], 47 | endNeighbors: number[], 48 | endNeighborsVertices: Vertex[], 49 | obstacleNeighbors: number[], 50 | obstacleNeighborsVertices: Vertex[], 51 | theme: GraphTheme 52 | ) => { 53 | return { 54 | type: ActionTypes.MOUSE_OVER, 55 | e, 56 | verticesRef, 57 | startVertex, 58 | endVertex, 59 | numRows, 60 | numCols, 61 | startNeighbors, 62 | startNeighborsVertices, 63 | endNeighbors, 64 | endNeighborsVertices, 65 | obstacleNeighbors, 66 | obstacleNeighborsVertices, 67 | theme, 68 | }; 69 | }; 70 | 71 | export const onMouseOver = (e: any, verticesRef: RefObject) => { 72 | return (dispatch: Dispatch, getState: any) => { 73 | let graph = getState().graph; 74 | let drag = getState().drag; 75 | let vertex = e.target; 76 | let startVertex = graph.startVertex; 77 | let endVertex = graph.endVertex; 78 | let numRows = graph.numRows; 79 | let numCols = graph.numCols; 80 | let theme = graph.theme; 81 | let wallIndices = drag.wallIndices; 82 | let obstacle1Indices = drag.obstacle1Indices; 83 | let obstacle2Indices = drag.obstacle2Indices; 84 | let obstacle3Indices = drag.obstacle3Indices; 85 | let obstacleIndices = [ 86 | ...wallIndices, 87 | ...obstacle1Indices, 88 | ...obstacle2Indices, 89 | obstacle3Indices, 90 | ]; 91 | let startNeighbors = getNeighbors( 92 | startVertex.absoluteIndex, 93 | startVertex.row, 94 | numRows, 95 | numCols 96 | ); 97 | let endNeighbors = getNeighbors( 98 | endVertex.absoluteIndex, 99 | endVertex.row, 100 | numRows, 101 | numCols 102 | ); 103 | let obstacleNeighbors: number[] = []; 104 | for (let obstacleIndex of obstacleIndices) { 105 | let obstacleRow = Position.absoluteToIndex( 106 | obstacleIndex, 107 | numRows, 108 | numCols 109 | ).row; 110 | obstacleNeighbors.push( 111 | ...getNeighbors(obstacleIndex, obstacleRow, numRows, numCols) 112 | ); 113 | } 114 | let startNeighborsVertices: Vertex[] = getNeighborVertices( 115 | startNeighbors, 116 | numRows, 117 | numCols, 118 | verticesRef 119 | ); 120 | let endNeighborsVertices: Vertex[] = getNeighborVertices( 121 | endNeighbors, 122 | numRows, 123 | numCols, 124 | verticesRef 125 | ); 126 | let obstacleNeighborsVertices: Vertex[] = getNeighborVertices( 127 | obstacleNeighbors, 128 | numRows, 129 | numCols, 130 | verticesRef 131 | ); 132 | 133 | dispatch( 134 | mouseOver( 135 | e, 136 | verticesRef, 137 | startVertex, 138 | endVertex, 139 | numRows, 140 | numCols, 141 | startNeighbors, 142 | startNeighborsVertices, 143 | endNeighbors, 144 | endNeighborsVertices, 145 | obstacleNeighbors, 146 | obstacleNeighborsVertices, 147 | theme 148 | ) 149 | ); 150 | 151 | let vertexIndex = parseInt(vertex.getAttribute('absoluteIndex')); 152 | let overEndRow = drag.overEndRow; 153 | let overEndCol = drag.overEndCol; 154 | let overStartRow = drag.overStartRow; 155 | let overStartCol = drag.overStartCol; 156 | let overObstacleRow = drag.overObstacleRow; 157 | let overObstacleCol = drag.overObstacleCol; 158 | 159 | let overEndIndex = indexToAbsolute( 160 | overEndRow, 161 | overEndCol, 162 | numRows, 163 | numCols 164 | ); 165 | let overStartIndex = indexToAbsolute( 166 | overStartRow, 167 | overStartCol, 168 | numRows, 169 | numCols 170 | ); 171 | let overObstacleIndex = indexToAbsolute( 172 | overObstacleRow, 173 | overObstacleCol, 174 | numRows, 175 | numCols 176 | ); 177 | 178 | if (drag.isStartMouseDown && drag.isDoneAnimating) { 179 | dispatch( 180 | actions.onRecalculatePath( 181 | vertexIndex, 182 | endVertex.absoluteIndex, 183 | verticesRef, 184 | overEndIndex, 185 | overStartIndex, 186 | overObstacleIndex 187 | ) 188 | ); 189 | } else if (drag.isEndMouseDown && drag.isDoneAnimating) { 190 | dispatch( 191 | actions.onRecalculatePath( 192 | startVertex.absoluteIndex, 193 | vertexIndex, 194 | verticesRef, 195 | overEndIndex, 196 | overStartIndex, 197 | overObstacleIndex 198 | ) 199 | ); 200 | } 201 | }; 202 | }; 203 | 204 | export const mouseOut = ( 205 | e: any, 206 | verticesRef: RefObject, 207 | startVertex: Vertex, 208 | endVertex: Vertex, 209 | numRows: number, 210 | numCols: number, 211 | startNeighbors: number[], 212 | startNeighborsVertices: Vertex[], 213 | endNeighbors: number[], 214 | endNeighborsVertices: Vertex[], 215 | obstacleNeighbors: number[], 216 | obstacleNeighborsVertices: Vertex[], 217 | theme: GraphTheme 218 | ) => { 219 | return { 220 | type: ActionTypes.MOUSE_OUT, 221 | e, 222 | verticesRef, 223 | startVertex, 224 | endVertex, 225 | numRows, 226 | numCols, 227 | startNeighbors, 228 | startNeighborsVertices, 229 | endNeighbors, 230 | endNeighborsVertices, 231 | obstacleNeighbors, 232 | obstacleNeighborsVertices, 233 | theme, 234 | }; 235 | }; 236 | 237 | export const onMouseOut = (e: any, verticesRef: RefObject) => { 238 | return (dispatch: Dispatch, getState: any) => { 239 | let graph = getState().graph; 240 | let drag = getState().drag; 241 | let startVertex = graph.startVertex; 242 | let endVertex = graph.endVertex; 243 | let numRows = graph.numRows; 244 | let numCols = graph.numCols; 245 | let verticesRef = graph.verticesRef; 246 | let theme = graph.theme; 247 | let wallIndices = drag.wallIndices; 248 | let obstacle1Indices = drag.obstacle1Indices; 249 | let obstacle2Indices = drag.obstacle2Indices; 250 | let obstacle3Indices = drag.obstacle3Indices; 251 | let obstacleIndices = [ 252 | ...wallIndices, 253 | ...obstacle1Indices, 254 | ...obstacle2Indices, 255 | ...obstacle3Indices, 256 | ]; 257 | let startNeighbors = getNeighbors( 258 | startVertex.absoluteIndex, 259 | startVertex.row, 260 | numRows, 261 | numCols 262 | ); 263 | let endNeighbors = getNeighbors( 264 | endVertex.absoluteIndex, 265 | endVertex.row, 266 | numRows, 267 | numCols 268 | ); 269 | let obstacleNeighbors: number[] = []; 270 | for (let obstacleIndex of obstacleIndices) { 271 | let obstacleRow = Position.absoluteToIndex( 272 | obstacleIndex, 273 | numRows, 274 | numCols 275 | ).row; 276 | obstacleNeighbors.push( 277 | ...getNeighbors(obstacleIndex, obstacleRow, numRows, numCols) 278 | ); 279 | } 280 | let startNeighborsVertices: Vertex[] = getNeighborVertices( 281 | startNeighbors, 282 | numRows, 283 | numCols, 284 | verticesRef 285 | ); 286 | let endNeighborsVertices: Vertex[] = getNeighborVertices( 287 | endNeighbors, 288 | numRows, 289 | numCols, 290 | verticesRef 291 | ); 292 | let obstacleNeighborsVertices: Vertex[] = getNeighborVertices( 293 | obstacleNeighbors, 294 | numRows, 295 | numCols, 296 | verticesRef 297 | ); 298 | 299 | dispatch( 300 | mouseOut( 301 | e, 302 | verticesRef, 303 | startVertex, 304 | endVertex, 305 | numRows, 306 | numCols, 307 | startNeighbors, 308 | startNeighborsVertices, 309 | endNeighbors, 310 | endNeighborsVertices, 311 | obstacleNeighbors, 312 | obstacleNeighborsVertices, 313 | theme 314 | ) 315 | ); 316 | }; 317 | }; 318 | 319 | const getNeighbors = ( 320 | absoluteIndex: number, 321 | row: number, 322 | numRows: number, 323 | numCols: number 324 | ): number[] => { 325 | return Object.values( 326 | Position.getNeighbors(absoluteIndex, row, numRows, numCols) 327 | ); 328 | }; 329 | 330 | const getNeighborVertices = ( 331 | neighbors: number[], 332 | numRows: number, 333 | numCols: number, 334 | verticesRef: RefObject 335 | ): Vertex[] => { 336 | let neighborVertices: Vertex[] = []; 337 | 338 | for (let neighbor of neighbors) { 339 | if (neighbor >= 0 && neighbor < numRows * numCols) 340 | neighborVertices.push( 341 | Position.getVertexAbsolute( 342 | neighbor, 343 | numRows, 344 | numCols, 345 | verticesRef 346 | ) 347 | ); 348 | } 349 | 350 | return neighborVertices; 351 | }; 352 | 353 | export const mouseUp = (e: any) => { 354 | return { 355 | type: ActionTypes.MOUSE_UP, 356 | e, 357 | }; 358 | }; 359 | 360 | export const onMouseUp = (e: any, verticesRef: RefObject) => { 361 | return (dispatch: Dispatch, getState: any) => { 362 | let vertex = e.target; 363 | let vertexRow = parseInt(vertex.getAttribute('row')); 364 | let vertexCol = parseInt(vertex.getAttribute('column')); 365 | let drag = getState().drag; 366 | 367 | if (drag.isStartMouseDown) { 368 | if (drag.isOverEnd) { 369 | dispatch( 370 | actions.setStartVertex( 371 | drag.overEndRow, 372 | drag.overEndCol, 373 | verticesRef 374 | ) 375 | ); 376 | } else if (drag.isOverObstacle) { 377 | dispatch( 378 | actions.setStartVertex( 379 | drag.overObstacleRow, 380 | drag.overObstacleCol, 381 | verticesRef 382 | ) 383 | ); 384 | } else { 385 | dispatch( 386 | actions.setStartVertex(vertexRow, vertexCol, verticesRef) 387 | ); 388 | } 389 | } else if (drag.isEndMouseDown) { 390 | if (drag.isOverStart) { 391 | dispatch( 392 | actions.setEndVertex( 393 | drag.overStartRow, 394 | drag.overStartCol, 395 | verticesRef 396 | ) 397 | ); 398 | } else if (drag.isOverObstacle) { 399 | dispatch( 400 | actions.setEndVertex( 401 | drag.overObstacleRow, 402 | drag.overObstacleCol, 403 | verticesRef 404 | ) 405 | ); 406 | } else { 407 | dispatch( 408 | actions.setEndVertex(vertexRow, vertexCol, verticesRef) 409 | ); 410 | } 411 | } else if (drag.isMouseDown) { 412 | dispatch(actions.onSetWallIndices(drag.wallIndices, verticesRef)); 413 | dispatch( 414 | actions.onSetObstacle1Indices( 415 | drag.obstacle1Indices, 416 | verticesRef 417 | ) 418 | ); 419 | dispatch( 420 | actions.onSetObstacle2Indices( 421 | drag.obstacle2Indices, 422 | verticesRef 423 | ) 424 | ); 425 | dispatch( 426 | actions.onSetObstacle3Indices( 427 | drag.obstacle3Indices, 428 | verticesRef 429 | ) 430 | ); 431 | } 432 | 433 | dispatch(mouseUp(e)); 434 | }; 435 | }; 436 | 437 | export const clearDragWalls = () => { 438 | return { 439 | type: ActionTypes.CLEAR_DRAG_WALLS, 440 | }; 441 | }; 442 | 443 | export const setIsDoneAnimating = (isDoneAnimating: boolean) => { 444 | return { 445 | type: ActionTypes.SET_IS_DONE_ANIMATING, 446 | isDoneAnimating, 447 | }; 448 | }; 449 | 450 | export const setIsRecalculating = (isRecalculating: boolean) => { 451 | return { 452 | type: ActionTypes.SET_IS_RECALCULATING, 453 | isRecalculating, 454 | }; 455 | }; 456 | 457 | export const setObstacleRef = (obstacleRef: number[]) => { 458 | return { 459 | type: ActionTypes.SET_OBSTACLE_REF, 460 | obstacleRef, 461 | }; 462 | }; 463 | 464 | export const onSetObstacleRef = (obstacleRef: string) => { 465 | return (dispatch: Dispatch, getState: any) => { 466 | const drag = getState().drag; 467 | const wallIndices = drag.wallIndices; 468 | const obstacle1Indices = drag.obstacle1Indices; 469 | const obstacle2Indices = drag.obstacle2Indices; 470 | const obstacle3Indices = drag.obstacle3Indices; 471 | 472 | type Obstacles = { [key: string]: number[] }; 473 | 474 | const obstacles: Obstacles = { 475 | wall: wallIndices, 476 | obstacle1: obstacle1Indices, 477 | obstacle2: obstacle2Indices, 478 | obstacle3: obstacle3Indices, 479 | }; 480 | 481 | dispatch(setObstacleRef(obstacles[obstacleRef])); 482 | }; 483 | }; 484 | -------------------------------------------------------------------------------- /src/store/actions/graph.ts: -------------------------------------------------------------------------------- 1 | import { RefObject, Dispatch } from 'react'; 2 | import { ActionTypes } from './types'; 3 | import * as actions from '../actions'; 4 | import { GraphTheme } from '../../utils/themes'; 5 | 6 | export const initGraph = ( 7 | verticesRef: RefObject, 8 | isSettingWalls: boolean = false 9 | ) => { 10 | return { 11 | type: ActionTypes.INIT_GRAPH, 12 | verticesRef, 13 | isSettingWalls, 14 | }; 15 | }; 16 | 17 | export const setVerticesRef = (verticesRef: RefObject) => { 18 | return { 19 | type: ActionTypes.SET_VERTICES_REF, 20 | verticesRef, 21 | }; 22 | }; 23 | 24 | export const setStartVertex = ( 25 | startRow: number, 26 | startCol: number, 27 | verticesRef: RefObject 28 | ) => { 29 | return { 30 | type: ActionTypes.SET_START_VERTEX, 31 | startRow, 32 | startCol, 33 | verticesRef, 34 | }; 35 | }; 36 | 37 | export const setEndVertex = ( 38 | endRow: number, 39 | endCol: number, 40 | verticesRef: RefObject 41 | ) => { 42 | return { 43 | type: ActionTypes.SET_END_VERTEX, 44 | endRow, 45 | endCol, 46 | verticesRef, 47 | }; 48 | }; 49 | 50 | export const clearTimeouts = () => { 51 | return { 52 | type: ActionTypes.CLEAR_TIMEOUTS, 53 | }; 54 | }; 55 | 56 | export const clearPath = ( 57 | verticesRef: RefObject, 58 | isRecalculating: boolean = false, 59 | start: number = -1, 60 | end: number = -1 61 | ) => { 62 | return { 63 | type: ActionTypes.CLEAR_PATH, 64 | verticesRef, 65 | isRecalculating, 66 | start, 67 | end, 68 | }; 69 | }; 70 | 71 | export const setWallIndices = ( 72 | wallIndices: number[], 73 | verticesRef: RefObject 74 | ) => { 75 | return { 76 | type: ActionTypes.SET_WALL_INDICES, 77 | wallIndices, 78 | verticesRef, 79 | }; 80 | }; 81 | 82 | export const onSetWallIndices = ( 83 | wallIndices: number[], 84 | verticesRef: RefObject 85 | ) => { 86 | return (dispatch: Dispatch, getState: any) => { 87 | dispatch(setWallIndices(wallIndices, verticesRef)); 88 | dispatch(initGraph(verticesRef, true)); 89 | dispatch( 90 | onRecalculatePath( 91 | getState().graph.start, 92 | getState().graph.end, 93 | verticesRef, 94 | 0, 95 | 0, 96 | 0 97 | ) 98 | ); 99 | }; 100 | }; 101 | 102 | export const setObstacle1Indices = ( 103 | obstacle1Indices: number[], 104 | verticesRef: RefObject 105 | ) => { 106 | return { 107 | type: ActionTypes.SET_OBSTACLE_1_INDICES, 108 | obstacle1Indices, 109 | verticesRef, 110 | }; 111 | }; 112 | 113 | export const onSetObstacle1Indices = ( 114 | obstacle1Indices: number[], 115 | verticesRef: RefObject 116 | ) => { 117 | return (dispatch: Dispatch, getState: any) => { 118 | dispatch(setObstacle1Indices(obstacle1Indices, verticesRef)); 119 | dispatch(initGraph(verticesRef, true)); 120 | dispatch( 121 | onRecalculatePath( 122 | getState().graph.start, 123 | getState().graph.end, 124 | verticesRef, 125 | 0, 126 | 0, 127 | 0 128 | ) 129 | ); 130 | }; 131 | }; 132 | 133 | export const setObstacle2Indices = ( 134 | obstacle2Indices: number[], 135 | verticesRef: RefObject 136 | ) => { 137 | return { 138 | type: ActionTypes.SET_OBSTACLE_2_INDICES, 139 | obstacle2Indices, 140 | verticesRef, 141 | }; 142 | }; 143 | 144 | export const onSetObstacle2Indices = ( 145 | obstacle2Indices: number[], 146 | verticesRef: RefObject 147 | ) => { 148 | return (dispatch: Dispatch, getState: any) => { 149 | dispatch(setObstacle2Indices(obstacle2Indices, verticesRef)); 150 | dispatch(initGraph(verticesRef, true)); 151 | dispatch( 152 | onRecalculatePath( 153 | getState().graph.start, 154 | getState().graph.end, 155 | verticesRef, 156 | 0, 157 | 0, 158 | 0 159 | ) 160 | ); 161 | }; 162 | }; 163 | 164 | export const setObstacle3Indices = ( 165 | obstacle3Indices: number[], 166 | verticesRef: RefObject 167 | ) => { 168 | return { 169 | type: ActionTypes.SET_OBSTACLE_3_INDICES, 170 | obstacle3Indices, 171 | verticesRef, 172 | }; 173 | }; 174 | 175 | export const onSetObstacle3Indices = ( 176 | obstacle3Indices: number[], 177 | verticesRef: RefObject 178 | ) => { 179 | return (dispatch: Dispatch, getState: any) => { 180 | dispatch(setObstacle3Indices(obstacle3Indices, verticesRef)); 181 | dispatch(initGraph(verticesRef, true)); 182 | dispatch( 183 | onRecalculatePath( 184 | getState().graph.start, 185 | getState().graph.end, 186 | verticesRef, 187 | 0, 188 | 0, 189 | 0 190 | ) 191 | ); 192 | }; 193 | }; 194 | 195 | export const clearWalls = (verticesRef: RefObject) => { 196 | return { 197 | type: ActionTypes.CLEAR_WALLS, 198 | verticesRef, 199 | }; 200 | }; 201 | 202 | export const onClearWalls = (verticesRef: RefObject) => { 203 | return (dispatch: Dispatch, getState: any) => { 204 | dispatch(actions.clearDragWalls()); 205 | dispatch(clearWalls(verticesRef)); 206 | dispatch(initGraph(verticesRef, true)); 207 | }; 208 | }; 209 | 210 | export const setIsAnimating = (isAnimating: boolean) => { 211 | return { 212 | type: ActionTypes.SET_IS_ANIMATING, 213 | isAnimating, 214 | }; 215 | }; 216 | 217 | export const recalculatePath = ( 218 | start: number, 219 | end: number, 220 | verticesRef: RefObject 221 | ) => { 222 | return { 223 | type: ActionTypes.RECALCULATE_PATH, 224 | start, 225 | end, 226 | verticesRef, 227 | }; 228 | }; 229 | 230 | export const onRecalculatePath = ( 231 | start: number, 232 | end: number, 233 | verticesRef: RefObject, 234 | overEndIndex: number, 235 | overStartIndex: number, 236 | overObstacleIndex: number 237 | ) => { 238 | return (dispatch: Dispatch, getState: any) => { 239 | let drag = getState().drag; 240 | let isDoneAnimating = drag.isDoneAnimating; 241 | let isStartMouseDown = drag.isStartMouseDown; 242 | let isEndMouseDown = drag.isEndMouseDown; 243 | // TODO: add when new walls to condition 244 | if (isDoneAnimating && (isStartMouseDown || isEndMouseDown)) { 245 | dispatch(actions.setIsRecalculating(true)); 246 | dispatch(clearPath(verticesRef, true, start, end)); 247 | 248 | let startIndex = start; 249 | let endIndex = end; 250 | if (drag.isOverEnd) { 251 | startIndex = overEndIndex; 252 | } else if ( 253 | drag.isOverObstacle && 254 | !drag.isOverStart && 255 | isStartMouseDown 256 | ) { 257 | startIndex = overObstacleIndex; 258 | } else if (drag.isOverStart) { 259 | endIndex = overStartIndex; 260 | } else if ( 261 | drag.isOverObstacle && 262 | !drag.isOverEnd && 263 | isEndMouseDown 264 | ) { 265 | endIndex = overObstacleIndex; 266 | } 267 | 268 | dispatch(recalculatePath(startIndex, endIndex, verticesRef)); 269 | } 270 | }; 271 | }; 272 | 273 | export const setCurrentAlgorithm = (algorithm: Function) => { 274 | return { 275 | type: ActionTypes.SET_CURRENT_ALGORITHM, 276 | currentAlgorithm: algorithm, 277 | }; 278 | }; 279 | 280 | export const setNumRows = (numRows: number) => { 281 | return { 282 | type: ActionTypes.SET_NUM_ROWS, 283 | numRows, 284 | }; 285 | }; 286 | 287 | export const setNumCols = (numCols: number) => { 288 | return { 289 | type: ActionTypes.SET_NUM_COLS, 290 | numCols, 291 | }; 292 | }; 293 | 294 | export const setTheme = (theme: GraphTheme) => { 295 | return { 296 | type: ActionTypes.SET_THEME, 297 | theme, 298 | }; 299 | }; 300 | -------------------------------------------------------------------------------- /src/store/actions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export * from './graph'; 3 | export * from './drag'; 4 | -------------------------------------------------------------------------------- /src/store/actions/types.ts: -------------------------------------------------------------------------------- 1 | export enum ActionTypes { 2 | INIT_GRAPH, 3 | SET_START_ROW, 4 | SET_START_COL, 5 | SET_END_ROW, 6 | SET_END_COL, 7 | MOUSE_DOWN, 8 | MOUSE_UP, 9 | MOUSE_OVER, 10 | MOUSE_OUT, 11 | CLEAR_TIMEOUTS, 12 | CLEAR_PATH, 13 | SET_WALL_INDICES, 14 | CLEAR_WALLS, 15 | CLEAR_DRAG_WALLS, 16 | SET_IS_ANIMATING, 17 | SET_IS_DONE_ANIMATING, 18 | RECALCULATE_PATH, 19 | SET_IS_RECALCULATING, 20 | SET_VERTICES_REF, 21 | SET_START_VERTEX, 22 | SET_END_VERTEX, 23 | SET_CURRENT_ALGORITHM, 24 | SET_NUM_ROWS, 25 | SET_NUM_COLS, 26 | SET_OBSTACLE_REF, 27 | SET_OBSTACLE_1_INDICES, 28 | SET_OBSTACLE_2_INDICES, 29 | SET_OBSTACLE_3_INDICES, 30 | SET_THEME, 31 | } 32 | -------------------------------------------------------------------------------- /src/store/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { graphReducer, GraphState } from './graph'; 3 | import { dragReducer, DragState } from './drag'; 4 | 5 | export interface StoreState { 6 | graph: GraphState; 7 | drag: DragState; 8 | } 9 | 10 | export const reducers = combineReducers({ 11 | graph: graphReducer, 12 | drag: dragReducer, 13 | }); 14 | -------------------------------------------------------------------------------- /src/utils/colors.ts: -------------------------------------------------------------------------------- 1 | export const COLOR_START = 'blue'; 2 | export const COLOR_END = 'red'; 3 | export const COLOR_VISITED = '#8d6b94'; 4 | export const COLOR_VISITING = '#ff6b6b'; 5 | export const COLOR_PATH = '#4ecdc4'; 6 | export const COLOR_WALL = '#292f36'; 7 | 8 | export const COLOR_GRASS = '#ACE4AA'; 9 | export const COLOR_WATER = '#56CBF9'; 10 | export const COLOR_TRAFFIC = '#F9C8D8'; 11 | export const COLOR_MUD = 'brown'; 12 | export const COLOR_CONCRETE = '#57585A'; 13 | 14 | export const COLOR_AVENGERS_PURPLE = '#9789FB'; 15 | export const COLOR_AVENGERS_GOLD = '#E4B835'; 16 | export const COLOR_AVENGERS_BROWN = '#974613'; 17 | export const COLOR_AVENGERS_BLACK = '#03011D'; 18 | 19 | export const COLOR_ONEPIECE_LIGHTBLUE = '#47BBE8'; 20 | export const COLOR_ONEPIECE_DARKBLUE = '#0A2ACE'; 21 | export const COLOR_ONEPIECE_WHITE = '#fff'; 22 | export const COLOR_ONEPIECE_BLACK = '#000'; 23 | export const COLOR_ONEPIECE_RED = '#F80101'; 24 | export const COLOR_ONEPIECE_YELLOW = '#F5CC01'; 25 | 26 | export const COLOR_LAKERS_PURPLE = '#52247F'; 27 | export const COLOR_LAKERS_GOLD = '#F5B326'; 28 | export const COLOR_LAKERS_WHITE = '#F8F8F8'; 29 | 30 | export const COLOR_LAKERS_BLACK = '#000'; 31 | 32 | export const COLOR_POKEMON_EMERALD = '#1BA763'; 33 | export const COLOR_POKEMON_BLUE = '#00479E'; 34 | export const COLOR_POKEMON_YELLOW = '#F8C406'; 35 | 36 | export const COLOR_THEOFFICE_BLACK = '#000'; 37 | export const COLOR_THEOFFICE_WHITE = '#fff'; 38 | export const COLOR_THEOFFICE_BROWN = '#925B4E'; 39 | export const COLOR_THEOFFICE_BLUE = '#36BAEF'; 40 | 41 | export const COLOR_HUNTERXHUNTER_BROWN = '#7F5A52'; 42 | export const COLOR_HUNTERXHUNTER_DARK_BROWN = '#5E413E'; 43 | export const COLOR_HUNTERXHUNTER_GREEN = '#116D35'; 44 | export const COLOR_HUNTERXHUNTER_WHITE = '#fff'; 45 | export const COLOR_HUNTERXHUNTER_LIGHT_ORANGE = '#F7A74D'; 46 | export const COLOR_HUNTERXHUNTER_DARK_ORANGE = '#CE2619'; 47 | export const COLOR_HUNTERXHUNTER_MAROON = '#7F252F'; 48 | -------------------------------------------------------------------------------- /src/utils/pathfinding/algorithms/aStar.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from './graph'; 2 | import { PriorityQueueFast } from './priorityQueue'; 3 | import * as GraphTypes from './graphTypes'; 4 | import * as Position from '../../position'; 5 | import { PathfindingStates } from '../pathfindingStates'; 6 | import { buildPath } from './buildPath'; 7 | 8 | export const aStar = ( 9 | graph: Graph, 10 | start: number, 11 | end: number 12 | ): GraphTypes.PathfindingAnimation[] => { 13 | let nodes = new PriorityQueueFast(graph.numRows, graph.numCols); 14 | let previous: GraphTypes.Previous = {}; 15 | let distances: GraphTypes.Distances = {}; 16 | let pathfindingAnimation: GraphTypes.PathfindingAnimation[] = []; 17 | let path: number[] = []; 18 | let endIndices = Position.absoluteToIndex( 19 | end, 20 | graph.numRows, 21 | graph.numCols 22 | ); 23 | let endVertex: GraphTypes.Vertex = { 24 | node: end, 25 | weight: 1, 26 | x: endIndices.row, 27 | y: endIndices.col, 28 | }; 29 | let foundEnd = false; 30 | 31 | nodes.enqueue(start, 0); 32 | previous[start] = NaN; 33 | distances[start] = 0; 34 | 35 | pathfindingAnimation.push({ 36 | index: start, 37 | state: PathfindingStates.VISITING, 38 | }); 39 | 40 | while (nodes.values.length) { 41 | let current = nodes.dequeue(); 42 | 43 | // pathfindingAnimation.push({ 44 | // index: current.node, 45 | // state: PathfindingStates.VISITING, 46 | // }); 47 | 48 | if (current.node === end) { 49 | buildPath( 50 | pathfindingAnimation, 51 | current.node, 52 | previous, 53 | path, 54 | start, 55 | end, 56 | graph 57 | ); 58 | 59 | break; 60 | } 61 | 62 | for (const neighbor of graph.adjacencyList[current.node]) { 63 | const newCost = 64 | distances[current.node] + 65 | graph.getEdgeWeight(current.node, neighbor.node); 66 | 67 | const priority = newCost + graph.getHeuristic(neighbor, endVertex); 68 | 69 | if (!(neighbor.node in distances) && !foundEnd) { 70 | pathfindingAnimation.push({ 71 | index: neighbor.node, 72 | state: PathfindingStates.VISITING, 73 | }); 74 | if (neighbor.node === end) { 75 | foundEnd = true; 76 | } 77 | } 78 | 79 | if ( 80 | !(neighbor.node in distances) || 81 | newCost < distances[neighbor.node] 82 | ) { 83 | if (neighbor.node in distances) { 84 | nodes.updatePriority(neighbor.node, priority); 85 | } else { 86 | nodes.enqueue(neighbor.node, priority); 87 | } 88 | distances[neighbor.node] = newCost; 89 | previous[neighbor.node] = current.node; 90 | } 91 | } 92 | } 93 | 94 | if (path.length === 0) { 95 | pathfindingAnimation.push({ 96 | index: 0, 97 | state: PathfindingStates.NO_PATH, 98 | }); 99 | } 100 | 101 | return pathfindingAnimation; 102 | }; 103 | -------------------------------------------------------------------------------- /src/utils/pathfinding/algorithms/breadthFirstSearch.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from './graph'; 2 | import * as GraphTypes from './graphTypes'; 3 | import { PathfindingStates } from '../pathfindingStates'; 4 | import { buildPath } from './buildPath'; 5 | 6 | export const breadthFirstSearch = ( 7 | graph: Graph, 8 | start: number, 9 | end: number 10 | ): GraphTypes.PathfindingAnimation[] => { 11 | let nodes: number[] = []; 12 | nodes.push(start); 13 | let previous: GraphTypes.Previous = {}; 14 | previous[start] = NaN; 15 | let path: number[] = []; 16 | let pathfindingAnimation: GraphTypes.PathfindingAnimation[] = []; 17 | 18 | while (nodes.length) { 19 | let current = nodes.shift(); 20 | 21 | pathfindingAnimation.push({ 22 | index: current as number, 23 | state: PathfindingStates.VISITING, 24 | }); 25 | 26 | if (current === end) { 27 | buildPath( 28 | pathfindingAnimation, 29 | current, 30 | previous, 31 | path, 32 | start, 33 | end, 34 | graph 35 | ); 36 | 37 | break; 38 | } 39 | 40 | if (current) { 41 | for (let neighbor of graph.adjacencyList[current]) { 42 | if (!(neighbor.node in previous)) { 43 | nodes.push(neighbor.node); 44 | previous[neighbor.node] = current; 45 | } 46 | } 47 | } 48 | } 49 | 50 | return pathfindingAnimation; 51 | }; 52 | -------------------------------------------------------------------------------- /src/utils/pathfinding/algorithms/buildPath.ts: -------------------------------------------------------------------------------- 1 | import { PathfindingAnimation, Previous } from './graphTypes'; 2 | import { PathfindingStates } from '../pathfindingStates'; 3 | import { Neighbors, getNeighbors, absoluteToIndex } from '../../position'; 4 | import { Graph } from './graph'; 5 | 6 | export const buildPath = ( 7 | pathfindingAnimation: PathfindingAnimation[], 8 | currentNode: number, 9 | previous: Previous, 10 | path: number[], 11 | start: number, 12 | end: number, 13 | graph: Graph 14 | ) => { 15 | while (previous[currentNode] || previous[currentNode] === 0) { 16 | path.push(currentNode); 17 | currentNode = previous[currentNode]; 18 | } 19 | 20 | path.push(currentNode); 21 | path.reverse(); 22 | 23 | for (let i = 0; i < path.length; i++) { 24 | let previousNeighbors: Neighbors = { 25 | left: -1, 26 | right: -1, 27 | top: -1, 28 | bottom: -1, 29 | }; 30 | if (i > 0) { 31 | let previousIndices = absoluteToIndex( 32 | path[i - 1], 33 | graph.numRows, 34 | graph.numCols 35 | ); 36 | previousNeighbors = getNeighbors( 37 | path[i - 1], 38 | previousIndices.row, 39 | graph.numRows, 40 | graph.numCols 41 | ); 42 | } 43 | 44 | let startIndices = absoluteToIndex( 45 | path[i], 46 | graph.numRows, 47 | graph.numCols 48 | ); 49 | let startNeighbors = getNeighbors( 50 | path[i], 51 | startIndices.row, 52 | graph.numRows, 53 | graph.numCols 54 | ); 55 | let endIndices = absoluteToIndex(path[i], graph.numRows, graph.numCols); 56 | let endNeighbors = getNeighbors( 57 | path[i], 58 | endIndices.row, 59 | graph.numRows, 60 | graph.numCols 61 | ); 62 | 63 | if (path[i] === start) { 64 | if ( 65 | path[i + 1] === startNeighbors.left || 66 | path[i + 1] === startNeighbors.right 67 | ) { 68 | pathfindingAnimation.push({ 69 | index: path[i], 70 | state: PathfindingStates.PATH_HORIZONTAL_START, 71 | }); 72 | } else if ( 73 | path[i + 1] === startNeighbors.top || 74 | path[i + 1] === startNeighbors.bottom 75 | ) { 76 | pathfindingAnimation.push({ 77 | index: path[i], 78 | state: PathfindingStates.PATH_VERTICAL_START, 79 | }); 80 | } 81 | } else if (path[i] === end) { 82 | if ( 83 | path[i - 1] === endNeighbors.left || 84 | path[i - 1] === endNeighbors.right 85 | ) { 86 | pathfindingAnimation.push({ 87 | index: path[i], 88 | state: PathfindingStates.PATH_HORIZONTAL_END, 89 | }); 90 | } else if ( 91 | path[i - 1] === endNeighbors.top || 92 | path[i - 1] === endNeighbors.bottom 93 | ) { 94 | pathfindingAnimation.push({ 95 | index: path[i], 96 | state: PathfindingStates.PATH_VERTICAL_END, 97 | }); 98 | } 99 | } else if ( 100 | path[i] === previousNeighbors.top || 101 | path[i] === previousNeighbors.bottom 102 | ) { 103 | pathfindingAnimation.push({ 104 | index: path[i], 105 | state: PathfindingStates.PATH_VERTICAL, 106 | }); 107 | } else if ( 108 | path[i] === previousNeighbors.left || 109 | path[i] === previousNeighbors.right 110 | ) { 111 | pathfindingAnimation.push({ 112 | index: path[i], 113 | state: PathfindingStates.PATH_HORIZONTAL, 114 | }); 115 | } else { 116 | pathfindingAnimation.push({ 117 | index: path[i], 118 | state: PathfindingStates.PATH, 119 | }); 120 | } 121 | } 122 | 123 | pathfindingAnimation.push({ 124 | index: 0, 125 | state: PathfindingStates.DONE, 126 | }); 127 | }; 128 | -------------------------------------------------------------------------------- /src/utils/pathfinding/algorithms/depthFirstSearch.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from './graph'; 2 | import * as GraphTypes from './graphTypes'; 3 | import { PathfindingStates } from '../pathfindingStates'; 4 | import { buildPath } from './buildPath'; 5 | 6 | export const depthFirstSearch = ( 7 | graph: Graph, 8 | start: number, 9 | end: number 10 | ): GraphTypes.PathfindingAnimation[] => { 11 | let visited: number[] = []; 12 | let pathfindingAnimations: GraphTypes.PathfindingAnimation[] = []; 13 | let previous: GraphTypes.Previous = {}; 14 | let path: number[] = []; 15 | 16 | depthFirstSearchUtil( 17 | graph, 18 | start, 19 | end, 20 | visited, 21 | pathfindingAnimations, 22 | previous 23 | ); 24 | 25 | buildPath(pathfindingAnimations, end, previous, path, start, end, graph); 26 | 27 | return pathfindingAnimations; 28 | }; 29 | 30 | const depthFirstSearchUtil = ( 31 | graph: Graph, 32 | start: number, 33 | end: number, 34 | visited: number[], 35 | pathfindingAnimations: GraphTypes.PathfindingAnimation[], 36 | previous: GraphTypes.Previous 37 | ) => { 38 | visited.push(start); 39 | 40 | pathfindingAnimations.push({ 41 | index: start, 42 | state: PathfindingStates.VISITING, 43 | }); 44 | 45 | for (let neighbor of graph.adjacencyList[start]) { 46 | if (!visited.includes(end)) { 47 | if (!visited.includes(neighbor.node)) { 48 | depthFirstSearchUtil( 49 | graph, 50 | neighbor.node, 51 | end, 52 | visited, 53 | pathfindingAnimations, 54 | previous 55 | ); 56 | previous[neighbor.node] = start; 57 | } 58 | } 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /src/utils/pathfinding/algorithms/dijkstra.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from './graph'; 2 | import { PriorityQueue } from './priorityQueue'; 3 | import * as GraphTypes from './graphTypes'; 4 | import { PathfindingStates } from '../pathfindingStates'; 5 | import { buildPath } from './buildPath'; 6 | export const dijkstra = ( 7 | graph: Graph, 8 | start: number, 9 | end: number 10 | ): GraphTypes.PathfindingAnimation[] => { 11 | let nodes = new PriorityQueue(graph.numRows, graph.numCols); 12 | let previous: GraphTypes.Previous = {}; 13 | let distances: GraphTypes.Distances = {}; 14 | let path: number[] = []; 15 | let pathfindingAnimation: GraphTypes.PathfindingAnimation[] = []; 16 | 17 | for (const vertex in graph.adjacencyList) { 18 | const vertexIndex = parseInt(vertex); 19 | distances[vertexIndex] = Infinity; 20 | previous[vertexIndex] = NaN; 21 | nodes.enqueue(vertexIndex, Infinity); 22 | } 23 | distances[start] = 0; 24 | previous[start] = NaN; 25 | nodes.updatePriority(start, 0); 26 | 27 | while (nodes.values.length) { 28 | const current = nodes.dequeue(); 29 | 30 | pathfindingAnimation.push({ 31 | index: current.node, 32 | state: PathfindingStates.VISITING, 33 | }); 34 | 35 | if (current.node === end) { 36 | buildPath( 37 | pathfindingAnimation, 38 | end, 39 | previous, 40 | path, 41 | start, 42 | end, 43 | graph 44 | ); 45 | 46 | break; 47 | } 48 | 49 | for (const neighbor of graph.adjacencyList[current.node]) { 50 | const newCost = 51 | distances[current.node] + 52 | graph.getEdgeWeight(current.node, neighbor.node); 53 | 54 | if (newCost < distances[neighbor.node]) { 55 | distances[neighbor.node] = newCost; 56 | previous[neighbor.node] = current.node; 57 | nodes.updatePriority(neighbor.node, newCost); 58 | } 59 | } 60 | } 61 | 62 | if (path.length === 0) { 63 | pathfindingAnimation.push({ 64 | index: 0, 65 | state: PathfindingStates.NO_PATH, 66 | }); 67 | } 68 | 69 | return pathfindingAnimation; 70 | }; 71 | -------------------------------------------------------------------------------- /src/utils/pathfinding/algorithms/graph.ts: -------------------------------------------------------------------------------- 1 | import * as Position from '../../position'; 2 | import * as GraphTypes from './graphTypes'; 3 | 4 | export class Graph { 5 | adjacencyList: GraphTypes.AdjacencyList; 6 | 7 | constructor(public numRows: number, public numCols: number) { 8 | this.adjacencyList = {}; 9 | } 10 | 11 | addVertex(vertex: number) { 12 | if (!this.adjacencyList[vertex]) this.adjacencyList[vertex] = []; 13 | } 14 | 15 | addEdge(vertex1: number, vertex2: number, weight: number = 1) { 16 | let indices = Position.absoluteToIndex( 17 | vertex2, 18 | this.numRows, 19 | this.numCols 20 | ); 21 | 22 | this.adjacencyList[vertex1].push({ 23 | node: vertex2, 24 | weight, 25 | x: indices.row, 26 | y: indices.col, 27 | }); 28 | } 29 | 30 | getEdgeWeight(vertex1: number, vertex2: number) { 31 | for (const edge of this.adjacencyList[vertex1]) { 32 | if (edge.node === vertex2) { 33 | return edge.weight; 34 | } 35 | } 36 | return Infinity; 37 | } 38 | 39 | getHeuristic(vertex1: GraphTypes.Vertex, vertex2: GraphTypes.Vertex) { 40 | return ( 41 | Math.abs(vertex1.x - vertex2.x) + Math.abs(vertex1.y - vertex2.y) 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/pathfinding/algorithms/graphTypes.ts: -------------------------------------------------------------------------------- 1 | import { PathfindingStates } from '../pathfindingStates'; 2 | 3 | export type Vertex = { node: number; weight: number; x: number; y: number }; 4 | export type Distances = { [key: string]: number }; 5 | export type Previous = { [key: string]: number }; 6 | export type AdjacencyList = { [key: string]: Vertex[] }; 7 | export type PathfindingAnimation = { index: number; state: PathfindingStates }; 8 | -------------------------------------------------------------------------------- /src/utils/pathfinding/algorithms/greedyBestFirstSearch.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from './graph'; 2 | import { PriorityQueue } from './priorityQueue'; 3 | import * as GraphTypes from './graphTypes'; 4 | import * as Position from '../../position'; 5 | import { PathfindingStates } from '../pathfindingStates'; 6 | import { buildPath } from './buildPath'; 7 | 8 | export const greedyBestFirstSearch = ( 9 | graph: Graph, 10 | start: number, 11 | end: number 12 | ): GraphTypes.PathfindingAnimation[] => { 13 | let nodes = new PriorityQueue(graph.numRows, graph.numCols); 14 | let previous: GraphTypes.Previous = {}; 15 | let endIndices = Position.absoluteToIndex( 16 | end, 17 | graph.numRows, 18 | graph.numCols 19 | ); 20 | let endVertex: GraphTypes.Vertex = { 21 | node: end, 22 | weight: 1, 23 | x: endIndices.row, 24 | y: endIndices.col, 25 | }; 26 | let visited: number[] = []; 27 | let pathfindingAnimation: GraphTypes.PathfindingAnimation[] = []; 28 | let path: number[] = []; 29 | let foundEnd = false; 30 | 31 | nodes.enqueue(start, 0); 32 | previous[start] = NaN; 33 | 34 | visited.push(start); 35 | 36 | pathfindingAnimation.push({ 37 | index: start, 38 | state: PathfindingStates.VISITING, 39 | }); 40 | 41 | while (nodes.values.length) { 42 | let current = nodes.dequeue(); 43 | 44 | // pathfindingAnimation.push({ 45 | // index: current.node, 46 | // state: PathfindingStates.VISITING, 47 | // }); 48 | 49 | if (current.node === end) { 50 | buildPath( 51 | pathfindingAnimation, 52 | current.node, 53 | previous, 54 | path, 55 | start, 56 | end, 57 | graph 58 | ); 59 | 60 | break; 61 | } 62 | 63 | for (const neighbor of graph.adjacencyList[current.node]) { 64 | if (!(neighbor.node in previous)) { 65 | if (!foundEnd) { 66 | pathfindingAnimation.push({ 67 | index: neighbor.node, 68 | state: PathfindingStates.VISITING, 69 | }); 70 | if (neighbor.node === end) { 71 | foundEnd = true; 72 | } 73 | } 74 | 75 | const priority = graph.getHeuristic(neighbor, endVertex); 76 | previous[neighbor.node] = current.node; 77 | nodes.enqueue(neighbor.node, priority); 78 | } 79 | } 80 | } 81 | 82 | if (path.length === 0) { 83 | pathfindingAnimation.push({ 84 | index: 0, 85 | state: PathfindingStates.NO_PATH, 86 | }); 87 | } 88 | 89 | return pathfindingAnimation; 90 | }; 91 | -------------------------------------------------------------------------------- /src/utils/pathfinding/algorithms/index.ts: -------------------------------------------------------------------------------- 1 | export { aStar } from './aStar'; 2 | export { dijkstra } from './dijkstra'; 3 | export { greedyBestFirstSearch } from './greedyBestFirstSearch'; 4 | export { breadthFirstSearch } from './breadthFirstSearch'; 5 | export { depthFirstSearch } from './depthFirstSearch'; 6 | -------------------------------------------------------------------------------- /src/utils/pathfinding/algorithms/priorityQueue.ts: -------------------------------------------------------------------------------- 1 | import * as GraphTypes from './graphTypes'; 2 | import * as Position from '../../position'; 3 | 4 | export class PriorityQueueFast { 5 | values: GraphTypes.Vertex[]; 6 | 7 | constructor(public numRows: number, public numCols: number) { 8 | this.values = []; 9 | } 10 | 11 | enqueue(val: number, priority: number) { 12 | let indices = Position.absoluteToIndex(val, this.numRows, this.numCols); 13 | let newNode: GraphTypes.Vertex = { 14 | node: val, 15 | weight: priority, 16 | x: indices.row, 17 | y: indices.col, 18 | }; 19 | this.values.push(newNode); 20 | 21 | let index = this.values.length - 1; 22 | let parentIndex = Math.floor((index - 1) / 2); 23 | 24 | while ( 25 | parentIndex >= 0 && 26 | this.values[index].weight <= this.values[parentIndex].weight 27 | ) { 28 | this.swap(this.values, index, parentIndex); 29 | index = parentIndex; 30 | parentIndex = Math.floor((index - 1) / 2); 31 | } 32 | } 33 | 34 | updatePriority(val: number, priority: number) { 35 | for (const value of this.values) { 36 | if (value.node === val) { 37 | this.values.splice(this.values.indexOf(value), 1); 38 | this.enqueue(val, priority); 39 | } 40 | } 41 | } 42 | 43 | dequeue() { 44 | this.swap(this.values, 0, this.values.length - 1); 45 | let max = this.values[this.values.length - 1]; 46 | this.values.pop(); 47 | 48 | let parentIndex = 0; 49 | while (true) { 50 | let leftChildIndex = 2 * parentIndex + 1; 51 | let rightChildIndex = 2 * parentIndex + 2; 52 | let swapIndex; 53 | if (leftChildIndex < this.values.length) { 54 | if (rightChildIndex < this.values.length) { 55 | swapIndex = 56 | this.values[leftChildIndex].weight < 57 | this.values[rightChildIndex].weight 58 | ? leftChildIndex 59 | : rightChildIndex; 60 | } else { 61 | swapIndex = leftChildIndex; 62 | } 63 | } 64 | if ( 65 | swapIndex && 66 | this.values[parentIndex].weight >= this.values[swapIndex].weight 67 | ) { 68 | this.swap(this.values, parentIndex, swapIndex); 69 | parentIndex = swapIndex; 70 | } else { 71 | break; 72 | } 73 | } 74 | 75 | return max; 76 | } 77 | 78 | swap(arr: GraphTypes.Vertex[], ind1: number, ind2: number) { 79 | [arr[ind1], arr[ind2]] = [arr[ind2], arr[ind1]]; 80 | } 81 | } 82 | 83 | export class PriorityQueue { 84 | values: GraphTypes.Vertex[]; 85 | 86 | constructor(public numRows: number, public numCols: number) { 87 | this.values = []; 88 | } 89 | 90 | enqueue(val: number, priority: number) { 91 | let indices = Position.absoluteToIndex(val, this.numRows, this.numCols); 92 | let newNode: GraphTypes.Vertex = { 93 | node: val, 94 | weight: priority, 95 | x: indices.row, 96 | y: indices.col, 97 | }; 98 | this.values.push(newNode); 99 | this.sort(); 100 | } 101 | 102 | dequeue() { 103 | return this.values.shift() as GraphTypes.Vertex; 104 | } 105 | 106 | updatePriority(val: number, priority: number) { 107 | for (const value of this.values) { 108 | if (value.node === val) { 109 | this.values.splice(this.values.indexOf(value), 1); 110 | this.enqueue(val, priority); 111 | } 112 | } 113 | } 114 | 115 | sort() { 116 | this.values.sort((a, b) => a.weight - b.weight); 117 | } 118 | 119 | swap(arr: GraphTypes.Vertex[], ind1: number, ind2: number) { 120 | [arr[ind1], arr[ind2]] = [arr[ind2], arr[ind1]]; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/utils/pathfinding/pathfindingAlgorithms.ts: -------------------------------------------------------------------------------- 1 | import * as algorithms from './algorithms'; 2 | 3 | type PathfindingAlgorithms = { [key: string]: Function }; 4 | 5 | export const pathfindingAlgorithms: PathfindingAlgorithms = { 6 | BreadthFirstSearch: algorithms.breadthFirstSearch, 7 | Dijkstra: algorithms.dijkstra, 8 | GreedyBestFirstSearch: algorithms.greedyBestFirstSearch, 9 | AStar: algorithms.aStar, 10 | DepthFirstSearch: algorithms.depthFirstSearch, 11 | }; 12 | -------------------------------------------------------------------------------- /src/utils/pathfinding/pathfindingOptions.ts: -------------------------------------------------------------------------------- 1 | export const pathfindingOptions = { 2 | Dijkstra: 'Dijkstra', 3 | AStar: 'A*', 4 | GreedyBestFirstSearch: 'Greedy Best First Search', 5 | BreadthFirstSearch: 'Breadth First Search', 6 | DepthFirstSearch: 'Depth First Search', 7 | }; 8 | -------------------------------------------------------------------------------- /src/utils/pathfinding/pathfindingStates.ts: -------------------------------------------------------------------------------- 1 | export enum PathfindingStates { 2 | VISITING, 3 | PATH, 4 | PATH_HORIZONTAL_START, 5 | PATH_VERTICAL_START, 6 | PATH_HORIZONTAL_END, 7 | PATH_VERTICAL_END, 8 | PATH_VERTICAL, 9 | PATH_HORIZONTAL, 10 | DONE, 11 | NO_PATH, 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/position.ts: -------------------------------------------------------------------------------- 1 | import { RefObject } from 'react'; 2 | import { Vertex } from '../store/reducers/graph'; 3 | 4 | export interface Neighbors { 5 | left: number; 6 | right: number; 7 | top: number; 8 | bottom: number; 9 | } 10 | 11 | export function absoluteToIndex( 12 | index: number, 13 | numRows: number, 14 | numCols: number 15 | ) { 16 | let row = 0; 17 | let col = 0; 18 | 19 | for (let i = 0; i < numRows; i++) { 20 | for (let j = 0; j < numCols; j++) { 21 | if (i * numCols + j === index) { 22 | row = i; 23 | col = j; 24 | } 25 | } 26 | } 27 | return { row, col }; 28 | } 29 | 30 | export function indexToAbsolute( 31 | row: number, 32 | col: number, 33 | numRows: number, 34 | numCols: number 35 | ) { 36 | let index = 0; 37 | 38 | index += row * numCols; 39 | index += col; 40 | 41 | return index; 42 | } 43 | 44 | export const getVertex = ( 45 | row: number, 46 | column: number, 47 | numRows: number, 48 | numCols: number, 49 | verticesRef: RefObject 50 | ): Vertex => { 51 | return { 52 | element: verticesRef.current?.children[row].children[ 53 | column 54 | ] as HTMLDivElement, 55 | row, 56 | column, 57 | absoluteIndex: indexToAbsolute(row, column, numRows, numCols), 58 | }; 59 | }; 60 | 61 | export const getVertexAbsolute = ( 62 | absoluteIndex: number, 63 | numRows: number, 64 | numCols: number, 65 | verticesRef: RefObject 66 | ): Vertex => { 67 | let vertexIndices = absoluteToIndex(absoluteIndex, numRows, numCols); 68 | 69 | return { 70 | element: verticesRef.current?.children[vertexIndices.row].children[ 71 | vertexIndices.col 72 | ] as HTMLDivElement, 73 | row: vertexIndices.row, 74 | column: vertexIndices.col, 75 | absoluteIndex, 76 | }; 77 | }; 78 | 79 | export const getNeighbors = ( 80 | idx: number, 81 | row: number, 82 | numRows: number, 83 | numCols: number 84 | ): Neighbors => { 85 | let leftNeighbor: number = idx - 1; 86 | let rightNeighbor: number = idx + 1; 87 | let topNeighbor: number = idx - numCols; 88 | let bottomNeighbor: number = idx + numCols; 89 | 90 | if (!validLeftNeighbor(leftNeighbor, row, numCols)) leftNeighbor = -1; 91 | if (!validRightNeighbor(rightNeighbor, row, numCols)) rightNeighbor = -1; 92 | if (!validTopNeighbor(topNeighbor)) topNeighbor = -1; 93 | if (!validBottomNeighbor(bottomNeighbor, numRows, numCols)) 94 | bottomNeighbor = -1; 95 | 96 | return { 97 | left: leftNeighbor, 98 | right: rightNeighbor, 99 | top: topNeighbor, 100 | bottom: bottomNeighbor, 101 | }; 102 | }; 103 | 104 | export const validLeftNeighbor = ( 105 | leftNeighbor: number, 106 | row: number, 107 | numCols: number 108 | ): boolean => { 109 | return leftNeighbor !== numCols * row - 1; 110 | }; 111 | 112 | export const validRightNeighbor = ( 113 | rightNeighbor: number, 114 | row: number, 115 | numCols: number 116 | ): boolean => { 117 | return rightNeighbor !== numCols * (row + 1); 118 | }; 119 | 120 | export const validTopNeighbor = (topNeighbor: number): boolean => { 121 | return topNeighbor >= 0; 122 | }; 123 | 124 | export const validBottomNeighbor = ( 125 | bottomNeighbor: number, 126 | numRows: number, 127 | numCols: number 128 | ): boolean => { 129 | return bottomNeighbor < numRows * numCols; 130 | }; 131 | -------------------------------------------------------------------------------- /src/utils/themes/avengers.ts: -------------------------------------------------------------------------------- 1 | import { GraphTheme, Obstacle, is_touch_device } from './index'; 2 | import * as Colors from '../colors'; 3 | import thanos from './img/thanos.png'; 4 | import gauntlet from './img/gauntlet.png'; 5 | import ironMan from './img/ironMan.png'; 6 | import ironManCursor from './img/ironMan.png'; 7 | import captainAmerica from './img/captainAmerica.png'; 8 | import captainAmericaCursor from './img/captainAmerica.png'; 9 | import thor from './img/thor.png'; 10 | import thorCursor from './img/thor.png'; 11 | import captainMarvel from './img/captainMarvel.png'; 12 | import captainMarvelCursor from './img/captainMarvel.png'; 13 | 14 | export const avengersTheme: GraphTheme = { 15 | start: (vertex: HTMLDivElement) => { 16 | vertex.style.backgroundColor = ''; 17 | vertex.style.backgroundImage = `url(${thanos})`; 18 | vertex.style.backgroundRepeat = 'no-repeat'; 19 | vertex.style.backgroundPosition = 'center'; 20 | vertex.style.backgroundSize = '90%'; 21 | vertex.style.cursor = 'grab'; 22 | }, 23 | end: (vertex: HTMLDivElement) => { 24 | vertex.style.backgroundColor = ''; 25 | vertex.style.backgroundImage = `url(${gauntlet})`; 26 | vertex.style.backgroundRepeat = 'no-repeat'; 27 | vertex.style.backgroundPosition = 'center'; 28 | vertex.style.backgroundSize = '40%'; 29 | vertex.style.cursor = 'grab'; 30 | }, 31 | wall: (vertex: HTMLDivElement) => { 32 | vertex.style.backgroundColor = ''; 33 | vertex.style.backgroundImage = `url(${ironMan})`; 34 | vertex.style.backgroundRepeat = 'no-repeat'; 35 | vertex.style.backgroundPosition = 'center'; 36 | vertex.style.backgroundSize = '40%'; 37 | vertex.style.cursor = `url(${ironManCursor}), pointer`; 38 | }, 39 | cursorWall: (vertex: HTMLDivElement) => { 40 | vertex.style.cursor = `url(${ironManCursor}), pointer`; 41 | }, 42 | obstacle1: (vertex: HTMLDivElement) => { 43 | vertex.style.backgroundColor = ''; 44 | vertex.style.backgroundImage = `url(${captainAmerica})`; 45 | vertex.style.backgroundRepeat = 'no-repeat'; 46 | vertex.style.backgroundPosition = 'center'; 47 | vertex.style.backgroundSize = '50%'; 48 | vertex.style.cursor = `url(${captainAmericaCursor}), pointer`; 49 | }, 50 | cursorObstacle1: (vertex: HTMLDivElement) => { 51 | vertex.style.cursor = `url(${captainAmericaCursor}), pointer`; 52 | }, 53 | obstacle2: (vertex: HTMLDivElement) => { 54 | vertex.style.backgroundColor = ''; 55 | vertex.style.backgroundImage = `url(${thor})`; 56 | vertex.style.backgroundRepeat = 'no-repeat'; 57 | vertex.style.backgroundPosition = 'center'; 58 | vertex.style.backgroundSize = '60%'; 59 | vertex.style.cursor = `url(${thorCursor}), pointer`; 60 | }, 61 | cursorObstacle2: (vertex: HTMLDivElement) => { 62 | vertex.style.cursor = `url(${thorCursor}), pointer`; 63 | }, 64 | obstacle3: (vertex: HTMLDivElement) => { 65 | vertex.style.backgroundColor = ''; 66 | vertex.style.backgroundImage = `url(${captainMarvel})`; 67 | vertex.style.backgroundRepeat = 'no-repeat'; 68 | vertex.style.backgroundPosition = 'center'; 69 | vertex.style.backgroundSize = '70%'; 70 | vertex.style.cursor = `url(${captainMarvelCursor}), pointer`; 71 | }, 72 | cursorObstacle3: (vertex: HTMLDivElement) => { 73 | vertex.style.cursor = `url(${captainMarvelCursor}), pointer`; 74 | }, 75 | unvisited: (vertex: HTMLDivElement) => { 76 | vertex.style.backgroundColor = ''; 77 | vertex.style.backgroundImage = ''; 78 | vertex.style.color = Colors.COLOR_AVENGERS_GOLD; 79 | vertex.style.cursor = `url(${ironManCursor}), pointer`; 80 | }, 81 | visited: (vertex: HTMLDivElement) => { 82 | vertex.style.backgroundColor = Colors.COLOR_AVENGERS_PURPLE; 83 | }, 84 | visiting: (vertex: HTMLDivElement) => { 85 | vertex.style.backgroundColor = Colors.COLOR_AVENGERS_GOLD; 86 | }, 87 | pathHorizontalStart: (vertex: HTMLDivElement) => { 88 | vertex.style.backgroundImage = `url(${thanos})`; 89 | vertex.style.backgroundColor = Colors.COLOR_AVENGERS_GOLD; 90 | }, 91 | pathVerticalStart: (vertex: HTMLDivElement) => { 92 | vertex.style.backgroundImage = `url(${thanos})`; 93 | vertex.style.backgroundColor = Colors.COLOR_AVENGERS_GOLD; 94 | }, 95 | pathHorizontalEnd: (vertex: HTMLDivElement) => { 96 | vertex.style.backgroundImage = `url(${gauntlet})`; 97 | vertex.style.backgroundColor = Colors.COLOR_AVENGERS_GOLD; 98 | }, 99 | pathVerticalEnd: (vertex: HTMLDivElement) => { 100 | vertex.style.backgroundImage = `url(${gauntlet})`; 101 | vertex.style.backgroundColor = Colors.COLOR_AVENGERS_GOLD; 102 | }, 103 | pathHorizontal: (vertex: HTMLDivElement) => { 104 | vertex.style.backgroundColor = Colors.COLOR_AVENGERS_GOLD; 105 | }, 106 | pathVertical: (vertex: HTMLDivElement) => { 107 | vertex.style.backgroundColor = Colors.COLOR_AVENGERS_GOLD; 108 | }, 109 | revertObstacle: (vertex: HTMLDivElement) => { 110 | vertex.style.backgroundColor = ''; 111 | vertex.style.backgroundImage = ''; 112 | }, 113 | revertPath: (vertex: HTMLDivElement) => { 114 | vertex.style.backgroundColor = ''; 115 | }, 116 | bodyBackground: (body: HTMLBodyElement) => { 117 | body.style.background = Colors.COLOR_AVENGERS_BLACK; 118 | }, 119 | header: (div: HTMLDivElement) => { 120 | div.style.backgroundColor = Colors.COLOR_AVENGERS_BLACK; 121 | div.style.color = Colors.COLOR_AVENGERS_GOLD; 122 | div.style.fontFamily = 'Avengers, sans-serif'; 123 | div.style.letterSpacing = ''; 124 | div.style.fontSize = '2rem'; 125 | }, 126 | heading: (heading: HTMLHeadingElement) => { 127 | heading.style.background = `linear-gradient(to top, ${Colors.COLOR_AVENGERS_BROWN}, ${Colors.COLOR_AVENGERS_GOLD})`; 128 | heading.style.backgroundClip = 'text'; 129 | heading.style.webkitBackgroundClip = 'text'; 130 | heading.style.webkitTextFillColor = 'transparent'; 131 | heading.style.webkitTextStroke = `4px ${Colors.COLOR_AVENGERS_BROWN}`; 132 | }, 133 | controller: (div: HTMLDivElement) => { 134 | div.style.color = Colors.COLOR_AVENGERS_PURPLE; 135 | }, 136 | button: (button: HTMLButtonElement) => { 137 | button.style.backgroundColor = 'transparent'; 138 | button.style.color = Colors.COLOR_AVENGERS_PURPLE; 139 | 140 | if (!is_touch_device()) { 141 | button.addEventListener('mouseover', () => { 142 | button.style.backgroundColor = Colors.COLOR_AVENGERS_PURPLE; 143 | button.style.color = Colors.COLOR_AVENGERS_GOLD; 144 | }); 145 | 146 | button.addEventListener('mouseout', () => { 147 | button.style.backgroundColor = 'transparent'; 148 | button.style.color = Colors.COLOR_AVENGERS_PURPLE; 149 | }); 150 | } 151 | }, 152 | dropdown: (heading: HTMLHeadingElement) => { 153 | heading.style.backgroundColor = Colors.COLOR_AVENGERS_PURPLE; 154 | heading.style.color = Colors.COLOR_AVENGERS_GOLD; 155 | }, 156 | options: (div: HTMLDivElement) => { 157 | div.style.backgroundColor = Colors.COLOR_AVENGERS_PURPLE; 158 | }, 159 | option: (li: HTMLLIElement) => { 160 | li.style.backgroundColor = 'transparent'; 161 | li.style.color = Colors.COLOR_AVENGERS_GOLD; 162 | 163 | if (!is_touch_device()) { 164 | li.addEventListener('mouseover', () => { 165 | li.style.backgroundColor = Colors.COLOR_AVENGERS_GOLD; 166 | li.style.color = Colors.COLOR_AVENGERS_PURPLE; 167 | }); 168 | 169 | li.addEventListener('mouseout', () => { 170 | li.style.backgroundColor = 'transparent'; 171 | li.style.color = Colors.COLOR_AVENGERS_GOLD; 172 | }); 173 | } 174 | }, 175 | }; 176 | 177 | export const avengersObstacleOptions: Obstacle = { 178 | wall: 'Iron Man (Barrier)', 179 | obstacle1: 'Captain America (Weight: 2)', 180 | obstacle2: 'Thor (Weight: 3)', 181 | obstacle3: 'Captain Marvel (Weight: 4)', 182 | }; 183 | -------------------------------------------------------------------------------- /src/utils/themes/car.ts: -------------------------------------------------------------------------------- 1 | import { GraphTheme, Obstacle, is_touch_device } from './index'; 2 | import * as Colors from '../colors'; 3 | import car from './img/car.png'; 4 | import location from './img/location.png'; 5 | import carObstacle from './img/carObstacle.png'; 6 | import carObstacleCursor from './img/carObstacleCursor.png'; 7 | import rain from './img/rain.png'; 8 | import rainCursor from './img/rainCursor.png'; 9 | import cone from './img/cone.png'; 10 | import coneCursor from './img/coneCursor.png'; 11 | import hill from './img/hill.png'; 12 | import hillCursor from './img/hillCursor.png'; 13 | import roadHorizontal from './img/road-horizontal.jpg'; 14 | import roadVertical from './img/road-vertical.jpg'; 15 | 16 | export const carTheme: GraphTheme = { 17 | start: (vertex: HTMLDivElement) => { 18 | vertex.style.backgroundColor = ''; 19 | vertex.style.backgroundImage = `url(${car})`; 20 | vertex.style.backgroundRepeat = 'no-repeat'; 21 | vertex.style.backgroundPosition = 'center'; 22 | vertex.style.backgroundSize = '90%'; 23 | vertex.style.cursor = 'grab'; 24 | }, 25 | end: (vertex: HTMLDivElement) => { 26 | vertex.style.backgroundColor = ''; 27 | vertex.style.backgroundImage = `url(${location})`; 28 | vertex.style.backgroundRepeat = 'no-repeat'; 29 | vertex.style.backgroundPosition = 'center'; 30 | vertex.style.backgroundSize = '90%'; 31 | vertex.style.cursor = 'grab'; 32 | }, 33 | wall: (vertex: HTMLDivElement) => { 34 | vertex.style.backgroundColor = ''; 35 | vertex.style.backgroundImage = `url(${carObstacle})`; 36 | vertex.style.backgroundRepeat = 'no-repeat'; 37 | vertex.style.backgroundPosition = 'center'; 38 | vertex.style.backgroundSize = '90%'; 39 | vertex.style.cursor = `url(${carObstacleCursor}), pointer`; 40 | }, 41 | cursorWall: (vertex: HTMLDivElement) => { 42 | vertex.style.cursor = `url(${carObstacleCursor}), pointer`; 43 | }, 44 | obstacle1: (vertex: HTMLDivElement) => { 45 | vertex.style.backgroundColor = ''; 46 | vertex.style.backgroundImage = `url(${rain})`; 47 | vertex.style.backgroundRepeat = 'no-repeat'; 48 | vertex.style.backgroundPosition = 'center'; 49 | vertex.style.backgroundSize = '90%'; 50 | vertex.style.cursor = `url(${rainCursor}), pointer`; 51 | }, 52 | cursorObstacle1: (vertex: HTMLDivElement) => { 53 | vertex.style.cursor = `url(${rainCursor}), pointer`; 54 | }, 55 | obstacle2: (vertex: HTMLDivElement) => { 56 | vertex.style.backgroundColor = ''; 57 | vertex.style.backgroundImage = `url(${cone})`; 58 | vertex.style.backgroundRepeat = 'no-repeat'; 59 | vertex.style.backgroundPosition = 'center'; 60 | vertex.style.backgroundSize = '80%'; 61 | vertex.style.cursor = `url(${coneCursor}), pointer`; 62 | }, 63 | cursorObstacle2: (vertex: HTMLDivElement) => { 64 | vertex.style.cursor = `url(${coneCursor}), pointer`; 65 | }, 66 | obstacle3: (vertex: HTMLDivElement) => { 67 | vertex.style.backgroundColor = ''; 68 | vertex.style.backgroundImage = `url(${hill})`; 69 | vertex.style.backgroundRepeat = 'no-repeat'; 70 | vertex.style.backgroundPosition = 'center'; 71 | vertex.style.backgroundSize = '70%'; 72 | vertex.style.cursor = `url(${hillCursor}), pointer`; 73 | }, 74 | cursorObstacle3: (vertex: HTMLDivElement) => { 75 | vertex.style.cursor = `url(${hillCursor}), pointer`; 76 | }, 77 | unvisited: (vertex: HTMLDivElement) => { 78 | vertex.style.backgroundColor = ''; 79 | vertex.style.backgroundImage = ''; 80 | vertex.style.color = Colors.COLOR_WALL; 81 | vertex.style.cursor = `url(${carObstacleCursor}), pointer`; 82 | }, 83 | visited: (vertex: HTMLDivElement) => { 84 | vertex.style.backgroundColor = Colors.COLOR_VISITED; 85 | }, 86 | visiting: (vertex: HTMLDivElement) => { 87 | vertex.style.backgroundColor = Colors.COLOR_VISITING; 88 | }, 89 | pathHorizontalStart: (vertex: HTMLDivElement) => { 90 | vertex.style.backgroundImage = `url(${car}), url(${roadHorizontal})`; 91 | vertex.style.backgroundRepeat = 'no-repeat, no-repeat'; 92 | vertex.style.backgroundPosition = 'center, center'; 93 | vertex.style.backgroundSize = '90%, cover'; 94 | vertex.style.cursor = 'grab'; 95 | }, 96 | pathVerticalStart: (vertex: HTMLDivElement) => { 97 | vertex.style.backgroundImage = `url(${car}), url(${roadVertical})`; 98 | vertex.style.backgroundRepeat = 'no-repeat, no-repeat'; 99 | vertex.style.backgroundPosition = 'center, center'; 100 | vertex.style.backgroundSize = '90%, cover'; 101 | vertex.style.cursor = 'grab'; 102 | }, 103 | pathHorizontalEnd: (vertex: HTMLDivElement) => { 104 | vertex.style.backgroundImage = `url(${location}), url(${roadHorizontal})`; 105 | vertex.style.backgroundRepeat = 'no-repeat, no-repeat'; 106 | vertex.style.backgroundPosition = 'center, center'; 107 | vertex.style.backgroundSize = '90%, cover'; 108 | vertex.style.cursor = 'grab'; 109 | }, 110 | pathVerticalEnd: (vertex: HTMLDivElement) => { 111 | vertex.style.backgroundImage = `url(${location}), url(${roadVertical})`; 112 | vertex.style.backgroundRepeat = 'no-repeat, no-repeat'; 113 | vertex.style.backgroundPosition = 'center, center'; 114 | vertex.style.backgroundSize = '90%, cover'; 115 | vertex.style.cursor = 'grab'; 116 | }, 117 | pathHorizontal: (vertex: HTMLDivElement) => { 118 | vertex.style.backgroundImage = `url(${roadHorizontal})`; 119 | vertex.style.backgroundRepeat = 'no-repeat'; 120 | vertex.style.backgroundPosition = 'center'; 121 | vertex.style.backgroundSize = 'cover'; 122 | }, 123 | pathVertical: (vertex: HTMLDivElement) => { 124 | vertex.style.backgroundImage = `url(${roadVertical})`; 125 | vertex.style.backgroundRepeat = 'no-repeat'; 126 | vertex.style.backgroundPosition = 'center'; 127 | vertex.style.backgroundSize = 'cover'; 128 | }, 129 | revertObstacle: (vertex: HTMLDivElement) => { 130 | vertex.style.backgroundColor = ''; 131 | vertex.style.backgroundImage = ''; 132 | }, 133 | revertPath: (vertex: HTMLDivElement) => { 134 | vertex.style.backgroundImage = ''; 135 | }, 136 | bodyBackground: (body: HTMLBodyElement) => { 137 | body.style.background = '#fff'; 138 | }, 139 | heading: (heading: HTMLHeadingElement) => { 140 | heading.style.background = Colors.COLOR_VISITING; 141 | heading.style.backgroundClip = 'text'; 142 | heading.style.webkitBackgroundClip = 'text'; 143 | heading.style.webkitTextFillColor = 'transparent'; 144 | heading.style.webkitTextStroke = ''; 145 | }, 146 | header: (div: HTMLDivElement) => { 147 | div.style.backgroundColor = Colors.COLOR_WALL; 148 | div.style.color = Colors.COLOR_VISITING; 149 | div.style.fontFamily = 'Montserrat, sans-serif'; 150 | div.style.letterSpacing = ''; 151 | div.style.fontSize = ''; 152 | }, 153 | controller: (div: HTMLDivElement) => { 154 | div.style.color = Colors.COLOR_VISITING; 155 | }, 156 | button: (button: HTMLButtonElement) => { 157 | button.style.backgroundColor = 'transparent'; 158 | button.style.color = Colors.COLOR_VISITING; 159 | 160 | if (!is_touch_device()) { 161 | button.addEventListener('mouseover', () => { 162 | button.style.backgroundColor = Colors.COLOR_VISITING; 163 | button.style.color = '#fff'; 164 | }); 165 | 166 | button.addEventListener('mouseout', () => { 167 | button.style.backgroundColor = 'transparent'; 168 | button.style.color = Colors.COLOR_VISITING; 169 | }); 170 | } 171 | }, 172 | dropdown: (heading: HTMLHeadingElement) => { 173 | heading.style.backgroundColor = Colors.COLOR_VISITING; 174 | heading.style.color = '#fff'; 175 | }, 176 | options: (div: HTMLDivElement) => { 177 | div.style.backgroundColor = Colors.COLOR_WALL; 178 | }, 179 | option: (li: HTMLLIElement) => { 180 | li.style.backgroundColor = 'transparent'; 181 | li.style.color = Colors.COLOR_VISITING; 182 | 183 | if (!is_touch_device()) { 184 | li.addEventListener('mouseover', () => { 185 | li.style.backgroundColor = Colors.COLOR_VISITING; 186 | li.style.color = '#fff'; 187 | }); 188 | 189 | li.addEventListener('mouseout', () => { 190 | li.style.backgroundColor = 'transparent'; 191 | li.style.color = Colors.COLOR_VISITING; 192 | }); 193 | } 194 | }, 195 | }; 196 | 197 | export const carObstacleOptions: Obstacle = { 198 | wall: 'Car (Barrier)', 199 | obstacle1: 'Rain (Weight: 2)', 200 | obstacle2: 'Cone (Weight: 3)', 201 | obstacle3: 'Hill (Weight: 4)', 202 | }; 203 | -------------------------------------------------------------------------------- /src/utils/themes/hunterxhunter.ts: -------------------------------------------------------------------------------- 1 | import { GraphTheme, Obstacle, is_touch_device } from './index'; 2 | import * as Colors from '../colors'; 3 | import gon from './img/gon.png'; 4 | import ging from './img/ging.png'; 5 | import hisoka from './img/hisoka.png'; 6 | import hisokaCursor from './img/hisokaCursor.png'; 7 | import troupe from './img/troupe.png'; 8 | import troupeCursor from './img/troupeCursor.png'; 9 | import razor from './img/razor.png'; 10 | import razorCursor from './img/razorCursor.png'; 11 | import meruem from './img/meruem.png'; 12 | import meruemCursor from './img/meruemCursor.png'; 13 | 14 | export const hunterxhunterTheme: GraphTheme = { 15 | start: (vertex: HTMLDivElement) => { 16 | vertex.style.backgroundColor = ''; 17 | vertex.style.backgroundImage = `url(${gon})`; 18 | vertex.style.backgroundRepeat = 'no-repeat'; 19 | vertex.style.backgroundPosition = 'center'; 20 | vertex.style.backgroundSize = '100%;'; 21 | vertex.style.cursor = 'grab'; 22 | }, 23 | end: (vertex: HTMLDivElement) => { 24 | vertex.style.backgroundColor = ''; 25 | vertex.style.backgroundImage = `url(${ging})`; 26 | vertex.style.backgroundRepeat = 'no-repeat'; 27 | vertex.style.backgroundPosition = 'center'; 28 | vertex.style.backgroundSize = '80%'; 29 | vertex.style.cursor = 'grab'; 30 | }, 31 | wall: (vertex: HTMLDivElement) => { 32 | vertex.style.backgroundColor = ''; 33 | vertex.style.backgroundImage = `url(${hisoka})`; 34 | vertex.style.backgroundRepeat = 'no-repeat'; 35 | vertex.style.backgroundPosition = 'center'; 36 | vertex.style.backgroundSize = '80%'; 37 | vertex.style.cursor = `url(${hisokaCursor}), pointer`; 38 | }, 39 | cursorWall: (vertex: HTMLDivElement) => { 40 | vertex.style.cursor = `url(${hisokaCursor}), pointer`; 41 | }, 42 | obstacle1: (vertex: HTMLDivElement) => { 43 | vertex.style.backgroundColor = ''; 44 | vertex.style.backgroundImage = `url(${troupe})`; 45 | vertex.style.backgroundRepeat = 'no-repeat'; 46 | vertex.style.backgroundPosition = 'center'; 47 | vertex.style.backgroundSize = '90%'; 48 | vertex.style.cursor = `url(${troupeCursor}), pointer`; 49 | }, 50 | cursorObstacle1: (vertex: HTMLDivElement) => { 51 | vertex.style.cursor = `url(${troupeCursor}), pointer`; 52 | }, 53 | obstacle2: (vertex: HTMLDivElement) => { 54 | vertex.style.backgroundColor = ''; 55 | vertex.style.backgroundImage = `url(${razor})`; 56 | vertex.style.backgroundRepeat = 'no-repeat'; 57 | vertex.style.backgroundPosition = 'center'; 58 | vertex.style.backgroundSize = '40%'; 59 | vertex.style.cursor = `url(${razorCursor}), pointer`; 60 | }, 61 | cursorObstacle2: (vertex: HTMLDivElement) => { 62 | vertex.style.cursor = `url(${razorCursor}), pointer`; 63 | }, 64 | obstacle3: (vertex: HTMLDivElement) => { 65 | vertex.style.backgroundColor = ''; 66 | vertex.style.backgroundImage = `url(${meruem})`; 67 | vertex.style.backgroundRepeat = 'no-repeat'; 68 | vertex.style.backgroundPosition = 'center'; 69 | vertex.style.backgroundSize = '70%'; 70 | vertex.style.cursor = `url(${meruemCursor}), pointer`; 71 | }, 72 | cursorObstacle3: (vertex: HTMLDivElement) => { 73 | vertex.style.cursor = `url(${meruemCursor}), pointer`; 74 | }, 75 | unvisited: (vertex: HTMLDivElement) => { 76 | vertex.style.backgroundColor = ''; 77 | vertex.style.backgroundImage = ''; 78 | vertex.style.color = Colors.COLOR_HUNTERXHUNTER_GREEN; 79 | vertex.style.cursor = `url(${hisokaCursor}), pointer`; 80 | }, 81 | visited: (vertex: HTMLDivElement) => { 82 | vertex.style.backgroundColor = Colors.COLOR_HUNTERXHUNTER_GREEN; 83 | }, 84 | visiting: (vertex: HTMLDivElement) => { 85 | vertex.style.backgroundColor = Colors.COLOR_HUNTERXHUNTER_DARK_ORANGE; 86 | }, 87 | pathHorizontalStart: (vertex: HTMLDivElement) => { 88 | vertex.style.backgroundImage = `url(${gon})`; 89 | vertex.style.backgroundColor = Colors.COLOR_HUNTERXHUNTER_DARK_ORANGE; 90 | }, 91 | pathVerticalStart: (vertex: HTMLDivElement) => { 92 | vertex.style.backgroundImage = `url(${gon})`; 93 | vertex.style.backgroundColor = Colors.COLOR_HUNTERXHUNTER_DARK_ORANGE; 94 | }, 95 | pathHorizontalEnd: (vertex: HTMLDivElement) => { 96 | vertex.style.backgroundImage = `url(${ging})`; 97 | vertex.style.backgroundColor = Colors.COLOR_HUNTERXHUNTER_DARK_ORANGE; 98 | }, 99 | pathVerticalEnd: (vertex: HTMLDivElement) => { 100 | vertex.style.backgroundImage = `url(${ging})`; 101 | vertex.style.backgroundColor = Colors.COLOR_HUNTERXHUNTER_DARK_ORANGE; 102 | }, 103 | pathHorizontal: (vertex: HTMLDivElement) => { 104 | vertex.style.backgroundColor = Colors.COLOR_HUNTERXHUNTER_DARK_ORANGE; 105 | }, 106 | pathVertical: (vertex: HTMLDivElement) => { 107 | vertex.style.backgroundColor = Colors.COLOR_HUNTERXHUNTER_DARK_ORANGE; 108 | }, 109 | revertObstacle: (vertex: HTMLDivElement) => { 110 | vertex.style.backgroundColor = ''; 111 | vertex.style.backgroundImage = ''; 112 | }, 113 | revertPath: (vertex: HTMLDivElement) => { 114 | vertex.style.backgroundColor = ''; 115 | }, 116 | bodyBackground: (body: HTMLBodyElement) => { 117 | body.style.background = `linear-gradient(to top, ${Colors.COLOR_HUNTERXHUNTER_DARK_BROWN}, ${Colors.COLOR_HUNTERXHUNTER_BROWN})`; 118 | }, 119 | header: (div: HTMLDivElement) => { 120 | div.style.backgroundColor = 'transparent'; 121 | div.style.color = Colors.COLOR_HUNTERXHUNTER_WHITE; 122 | div.style.fontFamily = 'Arial, sans-serif'; 123 | div.style.letterSpacing = ''; 124 | div.style.fontSize = '2rem'; 125 | }, 126 | heading: (heading: HTMLHeadingElement) => { 127 | heading.style.background = `linear-gradient(to top, ${Colors.COLOR_HUNTERXHUNTER_DARK_ORANGE}, ${Colors.COLOR_HUNTERXHUNTER_LIGHT_ORANGE})`; 128 | heading.style.backgroundClip = 'text'; 129 | heading.style.webkitBackgroundClip = 'text'; 130 | heading.style.webkitTextFillColor = 'transparent'; 131 | heading.style.webkitTextStroke = `4px ${Colors.COLOR_HUNTERXHUNTER_MAROON}`; 132 | }, 133 | controller: (div: HTMLDivElement) => { 134 | div.style.color = Colors.COLOR_HUNTERXHUNTER_LIGHT_ORANGE; 135 | }, 136 | button: (button: HTMLButtonElement) => { 137 | button.style.backgroundColor = 'transparent'; 138 | button.style.color = Colors.COLOR_HUNTERXHUNTER_LIGHT_ORANGE; 139 | 140 | if (!is_touch_device()) { 141 | button.addEventListener('mouseover', () => { 142 | button.style.backgroundColor = 143 | Colors.COLOR_HUNTERXHUNTER_LIGHT_ORANGE; 144 | button.style.color = Colors.COLOR_HUNTERXHUNTER_WHITE; 145 | }); 146 | 147 | button.addEventListener('mouseout', () => { 148 | button.style.backgroundColor = 'transparent'; 149 | button.style.color = Colors.COLOR_HUNTERXHUNTER_LIGHT_ORANGE; 150 | }); 151 | } 152 | }, 153 | dropdown: (heading: HTMLHeadingElement) => { 154 | heading.style.backgroundColor = Colors.COLOR_HUNTERXHUNTER_GREEN; 155 | heading.style.color = Colors.COLOR_HUNTERXHUNTER_WHITE; 156 | }, 157 | options: (div: HTMLDivElement) => { 158 | div.style.backgroundColor = Colors.COLOR_HUNTERXHUNTER_DARK_ORANGE; 159 | }, 160 | option: (li: HTMLLIElement) => { 161 | li.style.backgroundColor = 'transparent'; 162 | li.style.color = Colors.COLOR_HUNTERXHUNTER_WHITE; 163 | 164 | if (!is_touch_device()) { 165 | li.addEventListener('mouseover', () => { 166 | li.style.backgroundColor = Colors.COLOR_HUNTERXHUNTER_GREEN; 167 | li.style.color = Colors.COLOR_HUNTERXHUNTER_WHITE; 168 | }); 169 | 170 | li.addEventListener('mouseout', () => { 171 | li.style.backgroundColor = 'transparent'; 172 | li.style.color = Colors.COLOR_HUNTERXHUNTER_WHITE; 173 | }); 174 | } 175 | }, 176 | }; 177 | 178 | export const hunterxhunterObstacleOptions: Obstacle = { 179 | wall: 'Hisoka (Barrier)', 180 | obstacle1: 'Phantom Troupe (Weight: 2)', 181 | obstacle2: 'Razor (Weight: 3)', 182 | obstacle3: 'Meruem (Weight: 4)', 183 | }; 184 | -------------------------------------------------------------------------------- /src/utils/themes/img/badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/badge.png -------------------------------------------------------------------------------- /src/utils/themes/img/bigmom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/bigmom.png -------------------------------------------------------------------------------- /src/utils/themes/img/bigmomCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/bigmomCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/blackbeard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/blackbeard.png -------------------------------------------------------------------------------- /src/utils/themes/img/blackbeardCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/blackbeardCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/building.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/building.png -------------------------------------------------------------------------------- /src/utils/themes/img/buildingCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/buildingCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/captainAmerica.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/captainAmerica.png -------------------------------------------------------------------------------- /src/utils/themes/img/captainMarvel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/captainMarvel.png -------------------------------------------------------------------------------- /src/utils/themes/img/car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/car.png -------------------------------------------------------------------------------- /src/utils/themes/img/carObstacle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/carObstacle.png -------------------------------------------------------------------------------- /src/utils/themes/img/carObstacleCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/carObstacleCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/celtics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/celtics.png -------------------------------------------------------------------------------- /src/utils/themes/img/celticsCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/celticsCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/chineseFood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/chineseFood.png -------------------------------------------------------------------------------- /src/utils/themes/img/chineseFoodCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/chineseFoodCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/clippers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/clippers.png -------------------------------------------------------------------------------- /src/utils/themes/img/clippersCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/clippersCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/cone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/cone.png -------------------------------------------------------------------------------- /src/utils/themes/img/coneCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/coneCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/gasStation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/gasStation.png -------------------------------------------------------------------------------- /src/utils/themes/img/gasStationCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/gasStationCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/gauntlet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/gauntlet.png -------------------------------------------------------------------------------- /src/utils/themes/img/ging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/ging.png -------------------------------------------------------------------------------- /src/utils/themes/img/gon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/gon.png -------------------------------------------------------------------------------- /src/utils/themes/img/groudon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/groudon.png -------------------------------------------------------------------------------- /src/utils/themes/img/groudonCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/groudonCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/hill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/hill.png -------------------------------------------------------------------------------- /src/utils/themes/img/hillCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/hillCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/hisoka.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/hisoka.png -------------------------------------------------------------------------------- /src/utils/themes/img/hisokaCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/hisokaCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/holly.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/holly.jpg -------------------------------------------------------------------------------- /src/utils/themes/img/ironMan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/ironMan.png -------------------------------------------------------------------------------- /src/utils/themes/img/kaido.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/kaido.png -------------------------------------------------------------------------------- /src/utils/themes/img/kaidoCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/kaidoCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/kyogre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/kyogre.png -------------------------------------------------------------------------------- /src/utils/themes/img/kyogreCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/kyogreCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/lakers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/lakers.png -------------------------------------------------------------------------------- /src/utils/themes/img/latias.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/latias.png -------------------------------------------------------------------------------- /src/utils/themes/img/latiasCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/latiasCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/location.png -------------------------------------------------------------------------------- /src/utils/themes/img/meruem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/meruem.png -------------------------------------------------------------------------------- /src/utils/themes/img/meruemCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/meruemCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/michael.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/michael.png -------------------------------------------------------------------------------- /src/utils/themes/img/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/phone.png -------------------------------------------------------------------------------- /src/utils/themes/img/phoneCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/phoneCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/pistons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/pistons.png -------------------------------------------------------------------------------- /src/utils/themes/img/pistonsCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/pistonsCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/pokemonPlayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/pokemonPlayer.png -------------------------------------------------------------------------------- /src/utils/themes/img/rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/rain.png -------------------------------------------------------------------------------- /src/utils/themes/img/rainCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/rainCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/rayquaza.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/rayquaza.png -------------------------------------------------------------------------------- /src/utils/themes/img/rayquazaCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/rayquazaCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/razor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/razor.png -------------------------------------------------------------------------------- /src/utils/themes/img/razorCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/razorCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/road-horizontal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/road-horizontal.jpg -------------------------------------------------------------------------------- /src/utils/themes/img/road-vertical.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/road-vertical.jpg -------------------------------------------------------------------------------- /src/utils/themes/img/shanks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/shanks.png -------------------------------------------------------------------------------- /src/utils/themes/img/shanksCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/shanksCursor.png -------------------------------------------------------------------------------- /src/utils/themes/img/spurs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/spurs.png -------------------------------------------------------------------------------- /src/utils/themes/img/strawhat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/strawhat.png -------------------------------------------------------------------------------- /src/utils/themes/img/sunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/sunny.png -------------------------------------------------------------------------------- /src/utils/themes/img/thanos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/thanos.png -------------------------------------------------------------------------------- /src/utils/themes/img/thor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/thor.png -------------------------------------------------------------------------------- /src/utils/themes/img/treasure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/treasure.png -------------------------------------------------------------------------------- /src/utils/themes/img/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/tree.png -------------------------------------------------------------------------------- /src/utils/themes/img/trophy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/trophy.png -------------------------------------------------------------------------------- /src/utils/themes/img/troupe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/troupe.png -------------------------------------------------------------------------------- /src/utils/themes/img/troupeCursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/princhcanal/pathfinding-visualizer/eaf2556eeb8fdd077ba05dca72245c4994f143fa/src/utils/themes/img/troupeCursor.png -------------------------------------------------------------------------------- /src/utils/themes/index.ts: -------------------------------------------------------------------------------- 1 | import { carTheme } from './car'; 2 | import { avengersTheme } from './avengers'; 3 | import { carObstacleOptions } from './car'; 4 | import { avengersObstacleOptions } from './avengers'; 5 | import { onePieceObstacleOptions, onePieceTheme } from './onePiece'; 6 | import { lakersObstacleOptions, lakersTheme } from './lakers'; 7 | import { pokemonTheme, pokemonObstacleOptions } from './pokemon'; 8 | import { theOfficeTheme, theOfficeObstacleOptions } from './theOffice'; 9 | import { 10 | hunterxhunterTheme, 11 | hunterxhunterObstacleOptions, 12 | } from './hunterxhunter'; 13 | 14 | type GraphManipulation = (vertex: HTMLDivElement) => void; 15 | type BodyManipulation = (body: HTMLBodyElement) => void; 16 | type DivManipulation = (div: HTMLDivElement) => void; 17 | type ButtonManipulation = (button: HTMLButtonElement) => void; 18 | type HeadingManipulation = (heading: HTMLHeadingElement) => void; 19 | type ListItemManipulation = (li: HTMLLIElement) => void; 20 | type Themes = { [key: string]: GraphTheme }; 21 | type Obstacles = { [key: string]: Obstacle }; 22 | 23 | export type GraphTheme = { 24 | start: GraphManipulation; 25 | end: GraphManipulation; 26 | wall: GraphManipulation; 27 | obstacle1: GraphManipulation; 28 | obstacle2: GraphManipulation; 29 | obstacle3: GraphManipulation; 30 | unvisited: GraphManipulation; 31 | visited: GraphManipulation; 32 | visiting: GraphManipulation; 33 | pathHorizontalStart: GraphManipulation; 34 | pathVerticalStart: GraphManipulation; 35 | pathHorizontalEnd: GraphManipulation; 36 | pathVerticalEnd: GraphManipulation; 37 | pathHorizontal: GraphManipulation; 38 | pathVertical: GraphManipulation; 39 | 40 | revertObstacle: GraphManipulation; 41 | revertPath: GraphManipulation; 42 | 43 | bodyBackground: BodyManipulation; 44 | header: DivManipulation; 45 | heading: HeadingManipulation; 46 | controller: DivManipulation; 47 | button: ButtonManipulation; 48 | dropdown: HeadingManipulation; 49 | options: DivManipulation; 50 | option: ListItemManipulation; 51 | cursorWall: GraphManipulation; 52 | cursorObstacle1: GraphManipulation; 53 | cursorObstacle2: GraphManipulation; 54 | cursorObstacle3: GraphManipulation; 55 | }; 56 | 57 | export type Obstacle = { 58 | wall: string; 59 | obstacle1: string; 60 | obstacle2: string; 61 | obstacle3: string; 62 | }; 63 | 64 | export const themes: Themes = { 65 | car: carTheme, 66 | avengers: avengersTheme, 67 | onePiece: onePieceTheme, 68 | lakers: lakersTheme, 69 | pokemon: pokemonTheme, 70 | theOffice: theOfficeTheme, 71 | hunterxhunter: hunterxhunterTheme, 72 | }; 73 | 74 | export const themeOptions = { 75 | car: 'Car', 76 | avengers: 'Avengers', 77 | onePiece: 'One Piece', 78 | lakers: 'Lakers', 79 | pokemon: 'Pokemon', 80 | theOffice: 'The Office', 81 | hunterxhunter: 'HunterxHunter', 82 | }; 83 | 84 | export const obstacleOptions: Obstacles = { 85 | car: carObstacleOptions, 86 | avengers: avengersObstacleOptions, 87 | onePiece: onePieceObstacleOptions, 88 | lakers: lakersObstacleOptions, 89 | pokemon: pokemonObstacleOptions, 90 | theOffice: theOfficeObstacleOptions, 91 | hunterxhunter: hunterxhunterObstacleOptions, 92 | }; 93 | 94 | export const is_touch_device = () => { 95 | try { 96 | document.createEvent('TouchEvent'); 97 | return true; 98 | } catch (e) { 99 | return false; 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /src/utils/themes/lakers.ts: -------------------------------------------------------------------------------- 1 | import { GraphTheme, Obstacle, is_touch_device } from './index'; 2 | import * as Colors from '../colors'; 3 | import lakers from './img/lakers.png'; 4 | import trophy from './img/trophy.png'; 5 | import celtics from './img/celtics.png'; 6 | import celticsCursor from './img/celticsCursor.png'; 7 | import pistons from './img/pistons.png'; 8 | import pistonsCursor from './img/pistonsCursor.png'; 9 | import clippers from './img/clippers.png'; 10 | import clippersCursor from './img/clippersCursor.png'; 11 | import spurs from './img/spurs.png'; 12 | 13 | export const lakersTheme: GraphTheme = { 14 | start: (vertex: HTMLDivElement) => { 15 | vertex.style.backgroundColor = ''; 16 | vertex.style.backgroundImage = `url(${lakers})`; 17 | vertex.style.backgroundRepeat = 'no-repeat'; 18 | vertex.style.backgroundPosition = 'center'; 19 | vertex.style.backgroundSize = '100%'; 20 | vertex.style.cursor = 'grab'; 21 | }, 22 | end: (vertex: HTMLDivElement) => { 23 | vertex.style.backgroundColor = ''; 24 | vertex.style.backgroundImage = `url(${trophy})`; 25 | vertex.style.backgroundRepeat = 'no-repeat'; 26 | vertex.style.backgroundPosition = 'center'; 27 | vertex.style.backgroundSize = '40%'; 28 | vertex.style.cursor = 'grab'; 29 | }, 30 | wall: (vertex: HTMLDivElement) => { 31 | vertex.style.backgroundColor = ''; 32 | vertex.style.backgroundImage = `url(${celtics})`; 33 | vertex.style.backgroundRepeat = 'no-repeat'; 34 | vertex.style.backgroundPosition = 'center'; 35 | vertex.style.backgroundSize = '90%'; 36 | vertex.style.cursor = `url(${celticsCursor}), pointer`; 37 | }, 38 | cursorWall: (vertex: HTMLDivElement) => { 39 | vertex.style.cursor = `url(${celticsCursor}), pointer`; 40 | }, 41 | obstacle1: (vertex: HTMLDivElement) => { 42 | vertex.style.backgroundColor = ''; 43 | vertex.style.backgroundImage = `url(${pistons})`; 44 | vertex.style.backgroundRepeat = 'no-repeat'; 45 | vertex.style.backgroundPosition = 'center'; 46 | vertex.style.backgroundSize = '140%'; 47 | vertex.style.cursor = `url(${pistonsCursor}), pointer`; 48 | }, 49 | cursorObstacle1: (vertex: HTMLDivElement) => { 50 | vertex.style.cursor = `url(${pistonsCursor}), pointer`; 51 | }, 52 | obstacle2: (vertex: HTMLDivElement) => { 53 | vertex.style.backgroundColor = ''; 54 | vertex.style.backgroundImage = `url(${clippers})`; 55 | vertex.style.backgroundRepeat = 'no-repeat'; 56 | vertex.style.backgroundPosition = 'center'; 57 | vertex.style.backgroundSize = '90%'; 58 | vertex.style.cursor = `url(${clippersCursor}), pointer`; 59 | }, 60 | cursorObstacle2: (vertex: HTMLDivElement) => { 61 | vertex.style.cursor = `url(${clippersCursor}), pointer`; 62 | }, 63 | obstacle3: (vertex: HTMLDivElement) => { 64 | vertex.style.backgroundColor = ''; 65 | vertex.style.backgroundImage = `url(${spurs})`; 66 | vertex.style.backgroundRepeat = 'no-repeat'; 67 | vertex.style.backgroundPosition = 'center'; 68 | vertex.style.backgroundSize = '80%'; 69 | vertex.style.cursor = `url(${spurs}), pointer`; 70 | }, 71 | cursorObstacle3: (vertex: HTMLDivElement) => { 72 | vertex.style.cursor = `url(${spurs}), pointer`; 73 | }, 74 | unvisited: (vertex: HTMLDivElement) => { 75 | vertex.style.backgroundColor = ''; 76 | vertex.style.backgroundImage = ''; 77 | vertex.style.color = Colors.COLOR_LAKERS_GOLD; 78 | vertex.style.cursor = `url(${celticsCursor}), pointer`; 79 | }, 80 | visited: (vertex: HTMLDivElement) => { 81 | vertex.style.backgroundColor = Colors.COLOR_LAKERS_GOLD; 82 | }, 83 | visiting: (vertex: HTMLDivElement) => { 84 | vertex.style.backgroundColor = Colors.COLOR_LAKERS_BLACK; 85 | }, 86 | pathHorizontalStart: (vertex: HTMLDivElement) => { 87 | vertex.style.backgroundImage = `url(${lakers})`; 88 | vertex.style.backgroundColor = Colors.COLOR_LAKERS_WHITE; 89 | vertex.style.cursor = 'grab'; 90 | }, 91 | pathVerticalStart: (vertex: HTMLDivElement) => { 92 | vertex.style.backgroundImage = `url(${lakers})`; 93 | vertex.style.backgroundColor = Colors.COLOR_LAKERS_WHITE; 94 | vertex.style.cursor = 'grab'; 95 | }, 96 | pathHorizontalEnd: (vertex: HTMLDivElement) => { 97 | vertex.style.backgroundImage = `url(${trophy})`; 98 | vertex.style.backgroundColor = Colors.COLOR_LAKERS_WHITE; 99 | vertex.style.cursor = 'grab'; 100 | }, 101 | pathVerticalEnd: (vertex: HTMLDivElement) => { 102 | vertex.style.backgroundImage = `url(${trophy})`; 103 | vertex.style.backgroundColor = Colors.COLOR_LAKERS_WHITE; 104 | vertex.style.cursor = 'grab'; 105 | }, 106 | pathHorizontal: (vertex: HTMLDivElement) => { 107 | vertex.style.backgroundColor = Colors.COLOR_LAKERS_WHITE; 108 | }, 109 | pathVertical: (vertex: HTMLDivElement) => { 110 | vertex.style.backgroundColor = Colors.COLOR_LAKERS_WHITE; 111 | }, 112 | revertObstacle: (vertex: HTMLDivElement) => { 113 | vertex.style.backgroundColor = ''; 114 | vertex.style.backgroundImage = ''; 115 | }, 116 | revertPath: (vertex: HTMLDivElement) => { 117 | vertex.style.backgroundColor = ''; 118 | }, 119 | bodyBackground: (body: HTMLBodyElement) => { 120 | body.style.background = Colors.COLOR_LAKERS_PURPLE; 121 | }, 122 | header: (div: HTMLDivElement) => { 123 | div.style.backgroundColor = Colors.COLOR_LAKERS_PURPLE; 124 | div.style.color = Colors.COLOR_LAKERS_GOLD; 125 | div.style.fontFamily = 'Lakers, sans-serif'; 126 | div.style.letterSpacing = ''; 127 | div.style.fontSize = '2rem'; 128 | }, 129 | heading: (heading: HTMLHeadingElement) => { 130 | heading.style.background = Colors.COLOR_LAKERS_GOLD; 131 | heading.style.backgroundClip = 'text'; 132 | heading.style.webkitBackgroundClip = 'text'; 133 | heading.style.webkitTextFillColor = 'transparent'; 134 | heading.style.webkitTextStroke = `3px ${Colors.COLOR_LAKERS_BLACK}`; 135 | }, 136 | controller: (div: HTMLDivElement) => { 137 | div.style.color = Colors.COLOR_LAKERS_GOLD; 138 | }, 139 | button: (button: HTMLButtonElement) => { 140 | button.style.backgroundColor = 'transparent'; 141 | button.style.color = Colors.COLOR_LAKERS_GOLD; 142 | 143 | if (!is_touch_device()) { 144 | button.addEventListener('mouseover', () => { 145 | button.style.backgroundColor = Colors.COLOR_LAKERS_GOLD; 146 | button.style.color = Colors.COLOR_LAKERS_PURPLE; 147 | }); 148 | 149 | button.addEventListener('mouseout', () => { 150 | button.style.backgroundColor = 'transparent'; 151 | button.style.color = Colors.COLOR_LAKERS_GOLD; 152 | }); 153 | } 154 | }, 155 | dropdown: (heading: HTMLHeadingElement) => { 156 | heading.style.backgroundColor = Colors.COLOR_LAKERS_GOLD; 157 | heading.style.color = Colors.COLOR_LAKERS_PURPLE; 158 | }, 159 | options: (div: HTMLDivElement) => { 160 | div.style.backgroundColor = Colors.COLOR_LAKERS_BLACK; 161 | }, 162 | option: (li: HTMLLIElement) => { 163 | li.style.backgroundColor = 'transparent'; 164 | li.style.color = Colors.COLOR_LAKERS_PURPLE; 165 | 166 | if (!is_touch_device()) { 167 | li.addEventListener('mouseover', () => { 168 | li.style.backgroundColor = Colors.COLOR_LAKERS_GOLD; 169 | li.style.color = Colors.COLOR_LAKERS_PURPLE; 170 | }); 171 | 172 | li.addEventListener('mouseout', () => { 173 | li.style.backgroundColor = 'transparent'; 174 | li.style.color = Colors.COLOR_LAKERS_PURPLE; 175 | }); 176 | } 177 | }, 178 | }; 179 | 180 | export const lakersObstacleOptions: Obstacle = { 181 | wall: 'Celtics (Barrier)', 182 | obstacle1: 'Pistons (Weight: 2)', 183 | obstacle2: 'Clippers (Weight: 3)', 184 | obstacle3: 'Spurs (Weight: 4)', 185 | }; 186 | -------------------------------------------------------------------------------- /src/utils/themes/onePiece.ts: -------------------------------------------------------------------------------- 1 | import { GraphTheme, Obstacle, is_touch_device } from './index'; 2 | import * as Colors from '../colors'; 3 | import strawhat from './img/strawhat.png'; 4 | import treasure from './img/treasure.png'; 5 | import kaido from './img/kaido.png'; 6 | import kaidoCursor from './img/kaidoCursor.png'; 7 | import bigmom from './img/bigmom.png'; 8 | import bigmomCursor from './img/bigmomCursor.png'; 9 | import shanks from './img/shanks.png'; 10 | import shanksCursor from './img/shanksCursor.png'; 11 | import blackbeard from './img/blackbeard.png'; 12 | import blackbeardCursor from './img/blackbeardCursor.png'; 13 | import sunny from './img/sunny.png'; 14 | 15 | export const onePieceTheme: GraphTheme = { 16 | start: (vertex: HTMLDivElement) => { 17 | vertex.style.backgroundColor = ''; 18 | vertex.style.backgroundImage = `url(${strawhat})`; 19 | vertex.style.backgroundRepeat = 'no-repeat'; 20 | vertex.style.backgroundPosition = 'center'; 21 | vertex.style.backgroundSize = '100%'; 22 | vertex.style.cursor = 'grab'; 23 | }, 24 | end: (vertex: HTMLDivElement) => { 25 | vertex.style.backgroundColor = ''; 26 | vertex.style.backgroundImage = `url(${treasure})`; 27 | vertex.style.backgroundRepeat = 'no-repeat'; 28 | vertex.style.backgroundPosition = 'center'; 29 | vertex.style.backgroundSize = '100%'; 30 | vertex.style.cursor = 'grab'; 31 | }, 32 | wall: (vertex: HTMLDivElement) => { 33 | vertex.style.backgroundColor = ''; 34 | vertex.style.backgroundImage = `url(${kaido})`; 35 | vertex.style.backgroundRepeat = 'no-repeat'; 36 | vertex.style.backgroundPosition = 'center'; 37 | vertex.style.backgroundSize = '100%'; 38 | vertex.style.cursor = `url(${kaidoCursor}), pointer`; 39 | }, 40 | cursorWall: (vertex: HTMLDivElement) => { 41 | vertex.style.cursor = `url(${kaidoCursor}), pointer`; 42 | }, 43 | obstacle1: (vertex: HTMLDivElement) => { 44 | vertex.style.backgroundColor = ''; 45 | vertex.style.backgroundImage = `url(${bigmom})`; 46 | vertex.style.backgroundRepeat = 'no-repeat'; 47 | vertex.style.backgroundPosition = 'center'; 48 | vertex.style.backgroundSize = '100%'; 49 | vertex.style.cursor = `url(${bigmomCursor}), pointer`; 50 | }, 51 | cursorObstacle1: (vertex: HTMLDivElement) => { 52 | vertex.style.cursor = `url(${bigmomCursor}), pointer`; 53 | }, 54 | obstacle2: (vertex: HTMLDivElement) => { 55 | vertex.style.backgroundColor = ''; 56 | vertex.style.backgroundImage = `url(${shanks})`; 57 | vertex.style.backgroundRepeat = 'no-repeat'; 58 | vertex.style.backgroundPosition = 'center'; 59 | vertex.style.backgroundSize = '100%'; 60 | vertex.style.cursor = `url(${shanksCursor}), pointer`; 61 | }, 62 | cursorObstacle2: (vertex: HTMLDivElement) => { 63 | vertex.style.cursor = `url(${shanksCursor}), pointer`; 64 | }, 65 | obstacle3: (vertex: HTMLDivElement) => { 66 | vertex.style.backgroundColor = ''; 67 | vertex.style.backgroundImage = `url(${blackbeard})`; 68 | vertex.style.backgroundRepeat = 'no-repeat'; 69 | vertex.style.backgroundPosition = 'center'; 70 | vertex.style.backgroundSize = '80%'; 71 | vertex.style.cursor = `url(${blackbeardCursor}), pointer`; 72 | }, 73 | cursorObstacle3: (vertex: HTMLDivElement) => { 74 | vertex.style.cursor = `url(${blackbeardCursor}), pointer`; 75 | }, 76 | unvisited: (vertex: HTMLDivElement) => { 77 | vertex.style.backgroundColor = ''; 78 | vertex.style.backgroundImage = ''; 79 | vertex.style.color = Colors.COLOR_ONEPIECE_RED; 80 | vertex.style.cursor = `url(${kaidoCursor}), pointer`; 81 | }, 82 | visited: (vertex: HTMLDivElement) => { 83 | vertex.style.backgroundColor = Colors.COLOR_ONEPIECE_YELLOW; 84 | }, 85 | visiting: (vertex: HTMLDivElement) => { 86 | vertex.style.backgroundColor = Colors.COLOR_ONEPIECE_RED; 87 | }, 88 | pathHorizontalStart: (vertex: HTMLDivElement) => { 89 | vertex.style.backgroundImage = `url(${strawhat})`; 90 | vertex.style.backgroundColor = Colors.COLOR_ONEPIECE_RED; 91 | }, 92 | pathVerticalStart: (vertex: HTMLDivElement) => { 93 | vertex.style.backgroundImage = `url(${strawhat})`; 94 | vertex.style.backgroundColor = Colors.COLOR_ONEPIECE_RED; 95 | }, 96 | pathHorizontalEnd: (vertex: HTMLDivElement) => { 97 | vertex.style.backgroundImage = `url(${treasure})`; 98 | vertex.style.backgroundColor = Colors.COLOR_ONEPIECE_RED; 99 | }, 100 | pathVerticalEnd: (vertex: HTMLDivElement) => { 101 | vertex.style.backgroundImage = `url(${treasure})`; 102 | vertex.style.backgroundColor = Colors.COLOR_ONEPIECE_RED; 103 | }, 104 | pathHorizontal: (vertex: HTMLDivElement) => { 105 | vertex.style.backgroundImage = `url(${sunny})`; 106 | vertex.style.backgroundColor = Colors.COLOR_ONEPIECE_RED; 107 | vertex.style.backgroundRepeat = 'no-repeat'; 108 | vertex.style.backgroundPosition = 'center'; 109 | vertex.style.backgroundSize = 'cover'; 110 | }, 111 | pathVertical: (vertex: HTMLDivElement) => { 112 | vertex.style.backgroundImage = `url(${sunny})`; 113 | vertex.style.backgroundColor = Colors.COLOR_ONEPIECE_RED; 114 | vertex.style.backgroundRepeat = 'no-repeat'; 115 | vertex.style.backgroundPosition = 'center'; 116 | vertex.style.backgroundSize = 'cover'; 117 | }, 118 | revertObstacle: (vertex: HTMLDivElement) => { 119 | vertex.style.backgroundColor = ''; 120 | vertex.style.backgroundImage = ''; 121 | }, 122 | revertPath: (vertex: HTMLDivElement) => { 123 | vertex.style.backgroundImage = ''; 124 | }, 125 | bodyBackground: (body: HTMLBodyElement) => { 126 | body.style.background = `linear-gradient(to top right, ${Colors.COLOR_ONEPIECE_DARKBLUE}, ${Colors.COLOR_ONEPIECE_LIGHTBLUE})`; 127 | }, 128 | header: (div: HTMLDivElement) => { 129 | div.style.backgroundColor = 'transparent'; 130 | div.style.color = Colors.COLOR_ONEPIECE_DARKBLUE; 131 | div.style.fontFamily = 'One Piece, sans-serif'; 132 | div.style.letterSpacing = '1rem'; 133 | div.style.fontSize = '2rem'; 134 | }, 135 | heading: (heading: HTMLHeadingElement) => { 136 | heading.style.background = `linear-gradient(to top, ${Colors.COLOR_ONEPIECE_DARKBLUE}, ${Colors.COLOR_ONEPIECE_LIGHTBLUE})`; 137 | heading.style.backgroundClip = 'text'; 138 | heading.style.webkitBackgroundClip = 'text'; 139 | heading.style.webkitTextFillColor = 'transparent'; 140 | heading.style.webkitTextStroke = `2px ${Colors.COLOR_ONEPIECE_BLACK}`; 141 | }, 142 | controller: (div: HTMLDivElement) => { 143 | div.style.color = Colors.COLOR_ONEPIECE_DARKBLUE; 144 | }, 145 | button: (button: HTMLButtonElement) => { 146 | button.style.backgroundColor = 'transparent'; 147 | button.style.color = Colors.COLOR_ONEPIECE_DARKBLUE; 148 | 149 | if (!is_touch_device()) { 150 | button.addEventListener('mouseover', () => { 151 | button.style.backgroundColor = Colors.COLOR_ONEPIECE_DARKBLUE; 152 | button.style.color = Colors.COLOR_ONEPIECE_YELLOW; 153 | }); 154 | 155 | button.addEventListener('mouseout', () => { 156 | button.style.backgroundColor = 'transparent'; 157 | button.style.color = Colors.COLOR_ONEPIECE_DARKBLUE; 158 | }); 159 | } 160 | }, 161 | dropdown: (heading: HTMLHeadingElement) => { 162 | heading.style.backgroundColor = Colors.COLOR_ONEPIECE_DARKBLUE; 163 | heading.style.color = Colors.COLOR_ONEPIECE_YELLOW; 164 | }, 165 | options: (div: HTMLDivElement) => { 166 | div.style.backgroundColor = Colors.COLOR_ONEPIECE_DARKBLUE; 167 | }, 168 | option: (li: HTMLLIElement) => { 169 | li.style.backgroundColor = 'transparent'; 170 | li.style.color = Colors.COLOR_ONEPIECE_YELLOW; 171 | 172 | if (!is_touch_device()) { 173 | li.addEventListener('mouseover', () => { 174 | li.style.backgroundColor = Colors.COLOR_ONEPIECE_YELLOW; 175 | li.style.color = Colors.COLOR_ONEPIECE_DARKBLUE; 176 | }); 177 | 178 | li.addEventListener('mouseout', () => { 179 | li.style.backgroundColor = 'transparent'; 180 | li.style.color = Colors.COLOR_ONEPIECE_YELLOW; 181 | }); 182 | } 183 | }, 184 | }; 185 | 186 | export const onePieceObstacleOptions: Obstacle = { 187 | wall: 'Kaido (Barrier)', 188 | obstacle1: 'Big Mom (Weight: 2)', 189 | obstacle2: 'Shanks (Weight: 3)', 190 | obstacle3: 'Blackbeard (Weight: 4)', 191 | }; 192 | -------------------------------------------------------------------------------- /src/utils/themes/pokemon.ts: -------------------------------------------------------------------------------- 1 | import { GraphTheme, Obstacle, is_touch_device } from './index'; 2 | import * as Colors from '../colors'; 3 | import pokemonPlayer from './img/pokemonPlayer.png'; 4 | import badge from './img/badge.png'; 5 | import rayquaza from './img/rayquaza.png'; 6 | import rayquazaCursor from './img/rayquazaCursor.png'; 7 | import groudon from './img/groudon.png'; 8 | import groudonCursor from './img/groudonCursor.png'; 9 | import kyogre from './img/kyogre.png'; 10 | import kyogreCursor from './img/kyogreCursor.png'; 11 | import latias from './img/latias.png'; 12 | import latiasCursor from './img/latiasCursor.png'; 13 | 14 | export const pokemonTheme: GraphTheme = { 15 | start: (vertex: HTMLDivElement) => { 16 | vertex.style.backgroundColor = ''; 17 | vertex.style.backgroundImage = `url(${pokemonPlayer})`; 18 | vertex.style.backgroundRepeat = 'no-repeat'; 19 | vertex.style.backgroundPosition = 'center'; 20 | vertex.style.backgroundSize = '50%'; 21 | vertex.style.cursor = 'grab'; 22 | }, 23 | end: (vertex: HTMLDivElement) => { 24 | vertex.style.backgroundColor = ''; 25 | vertex.style.backgroundImage = `url(${badge})`; 26 | vertex.style.backgroundRepeat = 'no-repeat'; 27 | vertex.style.backgroundPosition = 'center'; 28 | vertex.style.backgroundSize = '120%'; 29 | vertex.style.cursor = 'grab'; 30 | }, 31 | wall: (vertex: HTMLDivElement) => { 32 | vertex.style.backgroundColor = ''; 33 | vertex.style.backgroundImage = `url(${rayquaza})`; 34 | vertex.style.backgroundRepeat = 'no-repeat'; 35 | vertex.style.backgroundPosition = 'center'; 36 | vertex.style.backgroundSize = '80%'; 37 | vertex.style.cursor = `url(${rayquazaCursor}), pointer`; 38 | }, 39 | cursorWall: (vertex: HTMLDivElement) => { 40 | vertex.style.cursor = `url(${rayquazaCursor}), pointer`; 41 | }, 42 | obstacle1: (vertex: HTMLDivElement) => { 43 | vertex.style.backgroundColor = ''; 44 | vertex.style.backgroundImage = `url(${groudon})`; 45 | vertex.style.backgroundRepeat = 'no-repeat'; 46 | vertex.style.backgroundPosition = 'center'; 47 | vertex.style.backgroundSize = '100%'; 48 | vertex.style.cursor = `url(${groudonCursor}), pointer`; 49 | }, 50 | cursorObstacle1: (vertex: HTMLDivElement) => { 51 | vertex.style.cursor = `url(${groudonCursor}), pointer`; 52 | }, 53 | obstacle2: (vertex: HTMLDivElement) => { 54 | vertex.style.backgroundColor = ''; 55 | vertex.style.backgroundImage = `url(${kyogre})`; 56 | vertex.style.backgroundRepeat = 'no-repeat'; 57 | vertex.style.backgroundPosition = 'center'; 58 | vertex.style.backgroundSize = '100%'; 59 | vertex.style.cursor = `url(${kyogreCursor}), pointer`; 60 | }, 61 | cursorObstacle2: (vertex: HTMLDivElement) => { 62 | vertex.style.cursor = `url(${kyogreCursor}), pointer`; 63 | }, 64 | obstacle3: (vertex: HTMLDivElement) => { 65 | vertex.style.backgroundColor = ''; 66 | vertex.style.backgroundImage = `url(${latias})`; 67 | vertex.style.backgroundRepeat = 'no-repeat'; 68 | vertex.style.backgroundPosition = 'center'; 69 | vertex.style.backgroundSize = '100%'; 70 | vertex.style.cursor = `url(${latiasCursor}), pointer`; 71 | }, 72 | cursorObstacle3: (vertex: HTMLDivElement) => { 73 | vertex.style.cursor = `url(${latiasCursor}), pointer`; 74 | }, 75 | unvisited: (vertex: HTMLDivElement) => { 76 | vertex.style.backgroundColor = ''; 77 | vertex.style.backgroundImage = ''; 78 | vertex.style.color = Colors.COLOR_POKEMON_BLUE; 79 | vertex.style.cursor = `url(${rayquazaCursor}), pointer`; 80 | }, 81 | visited: (vertex: HTMLDivElement) => { 82 | vertex.style.backgroundColor = Colors.COLOR_POKEMON_BLUE; 83 | }, 84 | visiting: (vertex: HTMLDivElement) => { 85 | vertex.style.backgroundColor = Colors.COLOR_POKEMON_YELLOW; 86 | }, 87 | pathHorizontalStart: (vertex: HTMLDivElement) => { 88 | vertex.style.backgroundImage = `url(${pokemonPlayer})`; 89 | vertex.style.backgroundColor = Colors.COLOR_POKEMON_YELLOW; 90 | }, 91 | pathVerticalStart: (vertex: HTMLDivElement) => { 92 | vertex.style.backgroundImage = `url(${pokemonPlayer})`; 93 | vertex.style.backgroundColor = Colors.COLOR_POKEMON_YELLOW; 94 | }, 95 | pathHorizontalEnd: (vertex: HTMLDivElement) => { 96 | vertex.style.backgroundImage = `url(${badge})`; 97 | vertex.style.backgroundColor = Colors.COLOR_POKEMON_YELLOW; 98 | }, 99 | pathVerticalEnd: (vertex: HTMLDivElement) => { 100 | vertex.style.backgroundImage = `url(${badge})`; 101 | vertex.style.backgroundColor = Colors.COLOR_POKEMON_YELLOW; 102 | }, 103 | pathHorizontal: (vertex: HTMLDivElement) => { 104 | vertex.style.backgroundColor = Colors.COLOR_POKEMON_YELLOW; 105 | }, 106 | pathVertical: (vertex: HTMLDivElement) => { 107 | vertex.style.backgroundColor = Colors.COLOR_POKEMON_YELLOW; 108 | }, 109 | revertObstacle: (vertex: HTMLDivElement) => { 110 | vertex.style.backgroundColor = ''; 111 | vertex.style.backgroundImage = ''; 112 | }, 113 | revertPath: (vertex: HTMLDivElement) => { 114 | vertex.style.backgroundColor = ''; 115 | }, 116 | bodyBackground: (body: HTMLBodyElement) => { 117 | body.style.background = `linear-gradient(to top, ${Colors.COLOR_POKEMON_EMERALD}, ${Colors.COLOR_POKEMON_BLUE})`; 118 | // body.style.background = `linear-gradient(to top, ${Colors.COLOR_POKEMON_BLUE}, ${Colors.COLOR_POKEMON_EMERALD})`; 119 | }, 120 | header: (div: HTMLDivElement) => { 121 | div.style.backgroundColor = 'transparent'; 122 | div.style.color = Colors.COLOR_POKEMON_YELLOW; 123 | div.style.fontFamily = 'Pokemon, sans-serif'; 124 | div.style.letterSpacing = '0.5rem'; 125 | div.style.fontSize = '2rem'; 126 | }, 127 | heading: (heading: HTMLHeadingElement) => { 128 | heading.style.background = Colors.COLOR_POKEMON_YELLOW; 129 | heading.style.backgroundClip = 'text'; 130 | heading.style.webkitBackgroundClip = 'text'; 131 | heading.style.webkitTextFillColor = 'transparent'; 132 | heading.style.webkitTextStroke = `6px ${Colors.COLOR_POKEMON_BLUE}`; 133 | }, 134 | controller: (div: HTMLDivElement) => { 135 | div.style.color = Colors.COLOR_POKEMON_YELLOW; 136 | }, 137 | button: (button: HTMLButtonElement) => { 138 | button.style.backgroundColor = 'transparent'; 139 | button.style.color = Colors.COLOR_POKEMON_YELLOW; 140 | 141 | if (!is_touch_device()) { 142 | button.addEventListener('mouseover', () => { 143 | button.style.backgroundColor = Colors.COLOR_POKEMON_YELLOW; 144 | button.style.color = Colors.COLOR_POKEMON_EMERALD; 145 | }); 146 | 147 | button.addEventListener('mouseout', () => { 148 | button.style.backgroundColor = 'transparent'; 149 | button.style.color = Colors.COLOR_POKEMON_YELLOW; 150 | }); 151 | } 152 | }, 153 | dropdown: (heading: HTMLHeadingElement) => { 154 | heading.style.backgroundColor = Colors.COLOR_POKEMON_YELLOW; 155 | heading.style.color = Colors.COLOR_POKEMON_EMERALD; 156 | }, 157 | options: (div: HTMLDivElement) => { 158 | div.style.backgroundColor = Colors.COLOR_POKEMON_EMERALD; 159 | }, 160 | option: (li: HTMLLIElement) => { 161 | li.style.backgroundColor = 'transparent'; 162 | li.style.color = Colors.COLOR_POKEMON_YELLOW; 163 | 164 | if (!is_touch_device()) { 165 | li.addEventListener('mouseover', () => { 166 | li.style.backgroundColor = Colors.COLOR_POKEMON_YELLOW; 167 | li.style.color = Colors.COLOR_POKEMON_EMERALD; 168 | }); 169 | 170 | li.addEventListener('mouseout', () => { 171 | li.style.backgroundColor = 'transparent'; 172 | li.style.color = Colors.COLOR_POKEMON_YELLOW; 173 | }); 174 | } 175 | }, 176 | }; 177 | 178 | export const pokemonObstacleOptions: Obstacle = { 179 | wall: 'Rayquaza (Barrier)', 180 | obstacle1: 'Groudon (Weight: 2)', 181 | obstacle2: 'Kyogre (Weight: 3)', 182 | obstacle3: 'Latias (Weight: 4)', 183 | }; 184 | -------------------------------------------------------------------------------- /src/utils/themes/theOffice.ts: -------------------------------------------------------------------------------- 1 | import { GraphTheme, Obstacle, is_touch_device } from './index'; 2 | import * as Colors from '../colors'; 3 | import holly from './img/holly.jpg'; 4 | import michael from './img/michael.png'; 5 | import gasStation from './img/gasStation.png'; 6 | import gasStationCursor from './img/gasStationCursor.png'; 7 | import phone from './img/phone.png'; 8 | import phoneCursor from './img/phoneCursor.png'; 9 | import chineseFood from './img/chineseFood.png'; 10 | import chineseFoodCursor from './img/chineseFoodCursor.png'; 11 | import building from './img/building.png'; 12 | import buildingCursor from './img/buildingCursor.png'; 13 | 14 | export const theOfficeTheme: GraphTheme = { 15 | start: (vertex: HTMLDivElement) => { 16 | vertex.style.backgroundColor = ''; 17 | vertex.style.backgroundImage = `url(${holly})`; 18 | vertex.style.backgroundRepeat = 'no-repeat'; 19 | vertex.style.backgroundPosition = 'center'; 20 | vertex.style.backgroundSize = '80%'; 21 | vertex.style.cursor = 'grab'; 22 | }, 23 | end: (vertex: HTMLDivElement) => { 24 | vertex.style.backgroundColor = ''; 25 | vertex.style.backgroundImage = `url(${michael})`; 26 | vertex.style.backgroundRepeat = 'no-repeat'; 27 | vertex.style.backgroundPosition = 'center'; 28 | vertex.style.backgroundSize = '100%'; 29 | vertex.style.cursor = 'grab'; 30 | }, 31 | wall: (vertex: HTMLDivElement) => { 32 | vertex.style.backgroundColor = ''; 33 | vertex.style.backgroundImage = `url(${gasStation})`; 34 | vertex.style.backgroundRepeat = 'no-repeat'; 35 | vertex.style.backgroundPosition = 'center'; 36 | vertex.style.backgroundSize = 'cover'; 37 | vertex.style.cursor = `url(${gasStationCursor}), pointer`; 38 | }, 39 | cursorWall: (vertex: HTMLDivElement) => { 40 | vertex.style.cursor = `url(${gasStationCursor}), pointer`; 41 | }, 42 | obstacle1: (vertex: HTMLDivElement) => { 43 | vertex.style.backgroundColor = ''; 44 | vertex.style.backgroundImage = `url(${phone})`; 45 | vertex.style.backgroundRepeat = 'no-repeat'; 46 | vertex.style.backgroundPosition = 'center'; 47 | vertex.style.backgroundSize = '80%'; 48 | vertex.style.cursor = `url(${phoneCursor}), pointer`; 49 | }, 50 | cursorObstacle1: (vertex: HTMLDivElement) => { 51 | vertex.style.cursor = `url(${phoneCursor}), pointer`; 52 | }, 53 | obstacle2: (vertex: HTMLDivElement) => { 54 | vertex.style.backgroundColor = ''; 55 | vertex.style.backgroundImage = `url(${chineseFood})`; 56 | vertex.style.backgroundRepeat = 'no-repeat'; 57 | vertex.style.backgroundPosition = 'center'; 58 | vertex.style.backgroundSize = '80%'; 59 | vertex.style.cursor = `url(${chineseFoodCursor}), pointer`; 60 | }, 61 | cursorObstacle2: (vertex: HTMLDivElement) => { 62 | vertex.style.cursor = `url(${chineseFoodCursor}), pointer`; 63 | }, 64 | obstacle3: (vertex: HTMLDivElement) => { 65 | vertex.style.backgroundColor = ''; 66 | vertex.style.backgroundImage = `url(${building})`; 67 | vertex.style.backgroundRepeat = 'no-repeat'; 68 | vertex.style.backgroundPosition = 'center'; 69 | vertex.style.backgroundSize = '60%'; 70 | vertex.style.cursor = `url(${buildingCursor}), pointer`; 71 | }, 72 | cursorObstacle3: (vertex: HTMLDivElement) => { 73 | vertex.style.cursor = `url(${buildingCursor}), pointer`; 74 | }, 75 | unvisited: (vertex: HTMLDivElement) => { 76 | vertex.style.backgroundColor = ''; 77 | vertex.style.backgroundImage = ''; 78 | vertex.style.color = Colors.COLOR_THEOFFICE_WHITE; 79 | vertex.style.cursor = `url(${gasStationCursor}), pointer`; 80 | }, 81 | visited: (vertex: HTMLDivElement) => { 82 | vertex.style.backgroundColor = Colors.COLOR_THEOFFICE_WHITE; 83 | }, 84 | visiting: (vertex: HTMLDivElement) => { 85 | vertex.style.backgroundColor = Colors.COLOR_THEOFFICE_BLUE; 86 | }, 87 | pathHorizontalStart: (vertex: HTMLDivElement) => { 88 | vertex.style.backgroundImage = `url(${holly})`; 89 | vertex.style.backgroundColor = Colors.COLOR_THEOFFICE_BROWN; 90 | }, 91 | pathVerticalStart: (vertex: HTMLDivElement) => { 92 | vertex.style.backgroundImage = `url(${holly})`; 93 | vertex.style.backgroundColor = Colors.COLOR_THEOFFICE_BROWN; 94 | }, 95 | pathHorizontalEnd: (vertex: HTMLDivElement) => { 96 | vertex.style.backgroundImage = `url(${michael})`; 97 | vertex.style.backgroundColor = Colors.COLOR_THEOFFICE_BROWN; 98 | }, 99 | pathVerticalEnd: (vertex: HTMLDivElement) => { 100 | vertex.style.backgroundImage = `url(${michael})`; 101 | vertex.style.backgroundColor = Colors.COLOR_THEOFFICE_BROWN; 102 | }, 103 | pathHorizontal: (vertex: HTMLDivElement) => { 104 | vertex.style.backgroundColor = Colors.COLOR_THEOFFICE_BROWN; 105 | }, 106 | pathVertical: (vertex: HTMLDivElement) => { 107 | vertex.style.backgroundColor = Colors.COLOR_THEOFFICE_BROWN; 108 | }, 109 | revertObstacle: (vertex: HTMLDivElement) => { 110 | vertex.style.backgroundColor = ''; 111 | vertex.style.backgroundImage = ''; 112 | }, 113 | revertPath: (vertex: HTMLDivElement) => { 114 | vertex.style.backgroundImage = ''; 115 | }, 116 | bodyBackground: (body: HTMLBodyElement) => { 117 | body.style.background = Colors.COLOR_THEOFFICE_BLACK; 118 | }, 119 | header: (div: HTMLDivElement) => { 120 | div.style.backgroundColor = 'transparent'; 121 | div.style.color = Colors.COLOR_THEOFFICE_WHITE; 122 | div.style.fontFamily = 'The Office, sans-serif'; 123 | div.style.letterSpacing = ''; 124 | div.style.fontSize = '2rem'; 125 | }, 126 | heading: (heading: HTMLHeadingElement) => { 127 | heading.style.background = Colors.COLOR_THEOFFICE_WHITE; 128 | heading.style.backgroundClip = 'text'; 129 | heading.style.webkitBackgroundClip = 'text'; 130 | heading.style.webkitTextFillColor = 'transparent'; 131 | heading.style.webkitTextStroke = ''; 132 | }, 133 | controller: (div: HTMLDivElement) => { 134 | div.style.color = Colors.COLOR_THEOFFICE_WHITE; 135 | }, 136 | button: (button: HTMLButtonElement) => { 137 | button.style.backgroundColor = 'transparent'; 138 | button.style.color = Colors.COLOR_THEOFFICE_WHITE; 139 | 140 | if (!is_touch_device()) { 141 | button.addEventListener('mouseover', () => { 142 | button.style.backgroundColor = Colors.COLOR_THEOFFICE_BROWN; 143 | button.style.color = Colors.COLOR_THEOFFICE_WHITE; 144 | }); 145 | 146 | button.addEventListener('mouseout', () => { 147 | button.style.backgroundColor = 'transparent'; 148 | button.style.color = Colors.COLOR_THEOFFICE_WHITE; 149 | }); 150 | } 151 | }, 152 | dropdown: (heading: HTMLHeadingElement) => { 153 | heading.style.backgroundColor = Colors.COLOR_THEOFFICE_BROWN; 154 | heading.style.color = Colors.COLOR_THEOFFICE_WHITE; 155 | }, 156 | options: (div: HTMLDivElement) => { 157 | div.style.backgroundColor = Colors.COLOR_THEOFFICE_WHITE; 158 | }, 159 | option: (li: HTMLLIElement) => { 160 | li.style.backgroundColor = 'transparent'; 161 | li.style.color = Colors.COLOR_THEOFFICE_BLACK; 162 | 163 | if (!is_touch_device()) { 164 | li.addEventListener('mouseover', () => { 165 | li.style.backgroundColor = Colors.COLOR_THEOFFICE_BROWN; 166 | li.style.color = Colors.COLOR_THEOFFICE_WHITE; 167 | }); 168 | 169 | li.addEventListener('mouseout', () => { 170 | li.style.backgroundColor = 'transparent'; 171 | li.style.color = Colors.COLOR_THEOFFICE_BLACK; 172 | }); 173 | } 174 | }, 175 | }; 176 | 177 | export const theOfficeObstacleOptions: Obstacle = { 178 | wall: 'Gas Station (Barrier)', 179 | obstacle1: 'Cellphone Store (Weight: 2)', 180 | obstacle2: "Mr. Choo's Chinese Food (Weight: 3)", 181 | obstacle3: 'Top of Building (Weight: 4)', 182 | }; 183 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react" 17 | }, 18 | "include": ["src"] 19 | } 20 | --------------------------------------------------------------------------------