├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── public ├── 01-cars-before-01.gif ├── 01-cars-before-02.gif ├── 01-cars-before-03.gif ├── 02-car-after-01.gif ├── 02-car-after-02.gif ├── 02-car-after-03.gif ├── article │ ├── images │ │ ├── 01-cover-00.jpg │ │ ├── 01-cover-01.jpg │ │ ├── 02-cars-before-01.gif │ │ ├── 02-cars-before-02.gif │ │ ├── 02-cars-before-03.gif │ │ ├── 03-car-after-01.gif │ │ ├── 03-car-after-02.gif │ │ ├── 03-car-after-03.gif │ │ ├── 03-car-muscles-01.gif │ │ ├── 03-car-muscles-03.png │ │ ├── 04-sensors-01.jpg │ │ ├── 04-sensors-02.gif │ │ ├── 05-sigmoid-01.png │ │ ├── 05-sigmoid-01.svg │ │ ├── 05-sigmoid-02.png │ │ ├── 05-sigmoid-03.png │ │ ├── 06-floating-point-conversion-01.png │ │ ├── 06-genome-examples.png │ │ ├── 07-genetic-algorithm-flow-01.png │ │ ├── 08-distance-to-parkin-lot.png │ │ ├── 09-fitness-function.png │ │ ├── 10-loss-history-00.png │ │ └── 11-fin.png │ ├── index.md │ └── index.ru.md ├── favicon.ico ├── index.html ├── models │ ├── beetle.glb │ └── wheel.glb ├── robots.txt ├── site-meta-image-01.jpg └── site-meta-image-02.jpg ├── serve.json ├── src ├── App.tsx ├── checkpoints │ ├── README.md │ ├── ckpt--population-1000--generation-25.json │ ├── ckpt--population-1000--generation-36.json │ └── ckpt--population-1000--generation-45.json ├── components │ ├── evolution │ │ ├── AutomaticParkingAnalytics.tsx │ │ ├── BestGenomes.tsx │ │ ├── EvolutionAnalytics.tsx │ │ ├── EvolutionBoardParams.tsx │ │ ├── EvolutionCheckpointSaver.tsx │ │ ├── EvolutionTabAutomatic.tsx │ │ ├── EvolutionTabEvolution.tsx │ │ ├── EvolutionTabManual.tsx │ │ ├── EvolutionTabs.tsx │ │ ├── EvolutionTiming.tsx │ │ ├── GenomePreview.tsx │ │ ├── LossHistory.tsx │ │ ├── PopulationTable.tsx │ │ ├── constants │ │ │ ├── evolution.ts │ │ │ └── genomes.ts │ │ └── utils │ │ │ └── evolution.ts │ ├── screens │ │ └── HomeScreen.tsx │ ├── shared │ │ ├── ErrorBoundary.tsx │ │ ├── FadeIn.css │ │ ├── FadeIn.tsx │ │ ├── Footer.tsx │ │ ├── FormElementsRow.tsx │ │ ├── Header.tsx │ │ ├── Hint.tsx │ │ ├── Layout.css │ │ ├── Layout.tsx │ │ ├── MainNav.tsx │ │ ├── Row.tsx │ │ ├── Timer.css │ │ └── Timer.tsx │ └── world │ │ ├── World.tsx │ │ ├── car │ │ ├── Car.tsx │ │ ├── CarLabel.tsx │ │ ├── Chassis.tsx │ │ ├── ChassisModel.tsx │ │ ├── ChassisModelSimple.tsx │ │ ├── SensorRay.tsx │ │ ├── Sensors.tsx │ │ ├── Wheel.tsx │ │ ├── WheelModel.tsx │ │ ├── WheelModelSimple.tsx │ │ └── constants.ts │ │ ├── cars │ │ ├── DynamicCars.tsx │ │ └── StaticCars.tsx │ │ ├── constants │ │ ├── cars.ts │ │ ├── models.ts │ │ ├── parking.ts │ │ ├── performance.ts │ │ └── world.ts │ │ ├── controllers │ │ ├── CarJoystickController.tsx │ │ └── CarKeyboardController.tsx │ │ ├── parkings │ │ ├── ParkingAutomatic.tsx │ │ └── ParkingManual.tsx │ │ ├── surroundings │ │ ├── Ground.tsx │ │ └── ParkingSpot.tsx │ │ ├── types │ │ ├── car.ts │ │ └── models.ts │ │ └── utils │ │ ├── controllers.ts │ │ ├── events.ts │ │ ├── materials.ts │ │ ├── models.ts │ │ └── uuid.ts ├── constants │ ├── app.ts │ ├── links.ts │ └── routes.ts ├── hooks │ └── useKeyPress.ts ├── index.tsx ├── libs │ ├── __tests__ │ │ ├── carGenetic.test.ts │ │ └── genetic.test.ts │ ├── carGenetic.ts │ ├── genetic.ts │ └── math │ │ ├── __tests__ │ │ ├── floats.test.ts │ │ ├── geometry.test.ts │ │ ├── polynomial.test.ts │ │ ├── probability.test.ts │ │ └── sigmoid.test.ts │ │ ├── floats.ts │ │ ├── geometry.ts │ │ ├── polynomial.ts │ │ ├── probability.ts │ │ └── sigmoid.ts ├── react-app-env.d.ts ├── setupTests.ts ├── types │ └── vectors.ts └── utils │ ├── colors.ts │ ├── logger.ts │ ├── storage.ts │ └── url.ts └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # @see: https://docs.github.com/en/github/administering-a-repository/displaying-a-sponsor-button-in-your-repository 2 | github: trekhleb 3 | patreon: trekhleb 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | .idea 3 | 4 | # dependencies 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Oleksii Trekhleb 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🧬 Self-Parking Car Evolution 2 | 3 | Training the car to do self-parking using a genetic algorithm. 4 | 5 | > - 🚕 [Launch the demo](https://trekhleb.dev/self-parking-car-evolution) 6 | > - 📃 [Read about how it works](https://trekhleb.dev/blog/2021/self-parking-car-evolution/) 7 | 8 | [![Self-Parking Car Evolution](./public/site-meta-image-02.jpg)](https://trekhleb.dev/self-parking-car-evolution) 9 | 10 | This is an experimental project with the aim to learn the basics of how [genetic algorithm](https://en.wikipedia.org/wiki/Genetic_algorithm) works by teaching the cars to do the self-parking. The evolution process is happening directly in the browser. You may check the [evolution source-code](https://github.com/trekhleb/self-parking-car-evolution/tree/master/src/libs) (in TypeScript) or read the [explanation of how it works](https://trekhleb.dev/blog/2021/self-parking-car-evolution/) in my blog-post. 11 | 12 | **At the beginning of the evolution** the generation of cars has random genomes which make them behave something like this: 13 | 14 | ![Self-parking cars at the beginning of the evolution](./public/01-cars-before-01.gif) 15 | 16 | **On the 40th generation** the cars start learning what the self-parking is and start getting closer to the parking spot (although hitting the other cars along the way): 17 | 18 | ![Self-parking car in ](./public/02-car-after-01.gif) 19 | 20 | Another example with a bit more challenging starting point: 21 | 22 | ![Self-parking car in ](./public/02-car-after-03.gif) 23 | 24 | ## Genetic Source-Code 25 | 26 | The `≈92%` of the code in this repository relates to the UI logic (3D simulation of the cars world, form controls for the evolution training process, etc.). 27 | 28 | However, the actual [code that implements a genetic algorithm](https://github.com/trekhleb/self-parking-car-evolution/tree/master/src/libs) takes less than `<500` lines of code. 29 | 30 | ## Development Details 31 | 32 | The project is a [React](https://create-react-app.dev/) application written on TypeScript. Styled with [BaseWeb](https://baseweb.design/). 33 | 34 | The 3D world simulation is made with [Three.js](https://threejs.org/) library using [@react-three/fiber](https://github.com/pmndrs/react-three-fiber) wrapper. The physics is simulated with [Cannon.js](https://github.com/schteppe/cannon.js) using [cannon-es](https://github.com/pmndrs/cannon-es) wrapper. 35 | 36 | The whole evolution simulation is happening directly in the browser. 37 | 38 | To launch the project, fork/clone it and run the following commands: 39 | 40 | ```shell 41 | npm install 42 | npm run start 43 | ``` 44 | 45 | The website will be available on `http://localhost:3000/self-parking-car-evolution`. 46 | 47 | **Hints:** 48 | 49 | - You may upload one of the [pre-trained checkpoints](https://github.com/trekhleb/self-parking-car-evolution/tree/master/src/checkpoints) to avoid starting the evolution from scratch. 50 | - Use the `?debug=true` URL param to see the FPS performance monitor and debugging logs in the console (i.e. `http://localhost:3000/self-parking-car-evolution?debug=true`). 51 | - Training progress is being saved to the local storage for each generation (not for each batch/group). 52 | 53 | ## Author 54 | 55 | - [@trekhleb](https://trekhleb.dev) 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "self-parking-car-evolution", 3 | "version": "0.1.0", 4 | "private": false, 5 | "author": "Oleksii Trekhleb (https://trekhleb.dev)", 6 | "homepage": "https://trekhleb.dev/self-parking-car-evolution", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/trekhleb/self-parking-car-evolution.git" 10 | }, 11 | "dependencies": { 12 | "@nivo/core": "^0.72.0", 13 | "@nivo/line": "^0.72.0", 14 | "@react-three/cannon": "^1.1.1", 15 | "@react-three/drei": "^4.3.3", 16 | "@react-three/fiber": "^6.0.19", 17 | "@testing-library/jest-dom": "^5.12.0", 18 | "@testing-library/react": "^11.2.6", 19 | "@testing-library/user-event": "^12.8.3", 20 | "@types/file-saver": "^2.0.3", 21 | "@types/jest": "^26.0.23", 22 | "@types/lodash": "^4.14.170", 23 | "@types/node": "^12.20.10", 24 | "@types/react": "^17.0.4", 25 | "@types/react-dom": "^17.0.3", 26 | "@types/react-router-dom": "^5.1.7", 27 | "@types/styletron-engine-atomic": "^1.1.0", 28 | "@types/styletron-react": "^5.0.2", 29 | "@types/styletron-standard": "^2.0.1", 30 | "@types/three": "^0.127.1", 31 | "baseui": "^10.0.0", 32 | "cannon-es": "^0.17.0", 33 | "file-saver": "^2.0.5", 34 | "gh-pages": "^3.1.0", 35 | "lodash": "^4.17.21", 36 | "nice-color-palettes": "^3.0.0", 37 | "react": "^17.0.2", 38 | "react-dom": "^17.0.2", 39 | "react-icons": "^4.2.0", 40 | "react-nipple": "^1.0.2", 41 | "react-router-dom": "^5.2.0", 42 | "react-scripts": "4.0.3", 43 | "styletron-engine-atomic": "^1.4.8", 44 | "styletron-react": "^6.0.1", 45 | "three": "^0.128.0", 46 | "three-mesh-bvh": "^0.3.7", 47 | "typescript": "^4.2.4", 48 | "web-vitals": "^1.1.1" 49 | }, 50 | "devDependencies": { 51 | "serve": "^12.0.0" 52 | }, 53 | "scripts": { 54 | "start": "react-scripts start", 55 | "build": "react-scripts build", 56 | "test": "react-scripts test", 57 | "lint": "eslint 'src/**/*.{js,ts,tsx}'", 58 | "eject": "react-scripts eject", 59 | "predeploy": "npm run build", 60 | "deploy": "gh-pages -d ./build", 61 | "serve-build": "serve --config=serve.json --no-clipboard --no-compression --cors -p 5000" 62 | }, 63 | "eslintConfig": { 64 | "extends": [ 65 | "react-app", 66 | "react-app/jest" 67 | ] 68 | }, 69 | "browserslist": { 70 | "production": [ 71 | ">0.2%", 72 | "not dead", 73 | "not op_mini all" 74 | ], 75 | "development": [ 76 | "last 1 chrome version", 77 | "last 1 firefox version", 78 | "last 1 safari version" 79 | ] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /public/01-cars-before-01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/01-cars-before-01.gif -------------------------------------------------------------------------------- /public/01-cars-before-02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/01-cars-before-02.gif -------------------------------------------------------------------------------- /public/01-cars-before-03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/01-cars-before-03.gif -------------------------------------------------------------------------------- /public/02-car-after-01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/02-car-after-01.gif -------------------------------------------------------------------------------- /public/02-car-after-02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/02-car-after-02.gif -------------------------------------------------------------------------------- /public/02-car-after-03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/02-car-after-03.gif -------------------------------------------------------------------------------- /public/article/images/01-cover-00.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/01-cover-00.jpg -------------------------------------------------------------------------------- /public/article/images/01-cover-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/01-cover-01.jpg -------------------------------------------------------------------------------- /public/article/images/02-cars-before-01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/02-cars-before-01.gif -------------------------------------------------------------------------------- /public/article/images/02-cars-before-02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/02-cars-before-02.gif -------------------------------------------------------------------------------- /public/article/images/02-cars-before-03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/02-cars-before-03.gif -------------------------------------------------------------------------------- /public/article/images/03-car-after-01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/03-car-after-01.gif -------------------------------------------------------------------------------- /public/article/images/03-car-after-02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/03-car-after-02.gif -------------------------------------------------------------------------------- /public/article/images/03-car-after-03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/03-car-after-03.gif -------------------------------------------------------------------------------- /public/article/images/03-car-muscles-01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/03-car-muscles-01.gif -------------------------------------------------------------------------------- /public/article/images/03-car-muscles-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/03-car-muscles-03.png -------------------------------------------------------------------------------- /public/article/images/04-sensors-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/04-sensors-01.jpg -------------------------------------------------------------------------------- /public/article/images/04-sensors-02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/04-sensors-02.gif -------------------------------------------------------------------------------- /public/article/images/05-sigmoid-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/05-sigmoid-01.png -------------------------------------------------------------------------------- /public/article/images/05-sigmoid-01.svg: -------------------------------------------------------------------------------- 1 | 2 | {\displaystyle f(x)={\frac {1}{1+e^{-x}}}} 3 | 14 | 38 | -------------------------------------------------------------------------------- /public/article/images/05-sigmoid-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/05-sigmoid-02.png -------------------------------------------------------------------------------- /public/article/images/05-sigmoid-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/05-sigmoid-03.png -------------------------------------------------------------------------------- /public/article/images/06-floating-point-conversion-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/06-floating-point-conversion-01.png -------------------------------------------------------------------------------- /public/article/images/06-genome-examples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/06-genome-examples.png -------------------------------------------------------------------------------- /public/article/images/07-genetic-algorithm-flow-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/07-genetic-algorithm-flow-01.png -------------------------------------------------------------------------------- /public/article/images/08-distance-to-parkin-lot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/08-distance-to-parkin-lot.png -------------------------------------------------------------------------------- /public/article/images/09-fitness-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/09-fitness-function.png -------------------------------------------------------------------------------- /public/article/images/10-loss-history-00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/10-loss-history-00.png -------------------------------------------------------------------------------- /public/article/images/11-fin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/article/images/11-fin.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Self-Parking Car Evolution 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 34 | 35 | 36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /public/models/beetle.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/models/beetle.glb -------------------------------------------------------------------------------- /public/models/wheel.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/models/wheel.glb -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/site-meta-image-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/site-meta-image-01.jpg -------------------------------------------------------------------------------- /public/site-meta-image-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekhleb/self-parking-car-evolution/b9fb92d4d78ac935945beb9aa82ae83997c295ad/public/site-meta-image-02.jpg -------------------------------------------------------------------------------- /serve.json: -------------------------------------------------------------------------------- 1 | { 2 | "public": "build", 3 | "rewrites": [ 4 | { 5 | "source": "/self-parking-car-evolution/favicon.ico", 6 | "destination": "/favicon.ico" 7 | }, 8 | { 9 | "source": "/self-parking-car-evolution/models/:model_name", 10 | "destination": "/models/:model_name" 11 | }, 12 | { 13 | "source": "/self-parking-car-evolution/static/:resource_type/:resource", 14 | "destination": "/static/:resource_type/:resource" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { HashRouter, Switch, Route } from 'react-router-dom'; 3 | 4 | import Layout from './components/shared/Layout'; 5 | import { routes } from './constants/routes'; 6 | import HomeScreen from './components/screens/HomeScreen'; 7 | 8 | function App() { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /src/checkpoints/README.md: -------------------------------------------------------------------------------- 1 | # Self-Parking Car Evolution Checkpoints 2 | 3 | Checkpoint is a `json` file that contain the history of the evolution and the list of genomes from the latest generation 4 | 5 | To read more about self-parking car evolution go to the [main README of the project](https://github.com/trekhleb/self-parking-car-evolution). 6 | -------------------------------------------------------------------------------- /src/components/evolution/AutomaticParkingAnalytics.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Block } from 'baseui/block'; 3 | import { Button, SIZE as BUTTON_SIZE, KIND as BUTTON_KIND, SHAPE as BUTTON_SHAPE } from 'baseui/button'; 4 | import { Checkbox, LABEL_PLACEMENT } from 'baseui/checkbox'; 5 | import { ButtonGroup, MODE as BUTTON_GROUP_MODE, SIZE as BUTTON_GROUP_SIZE } from 'baseui/button-group'; 6 | 7 | import EvolutionTiming from './EvolutionTiming'; 8 | import BestGenomes from './BestGenomes'; 9 | import { Genome } from '../../libs/genetic'; 10 | import { FormControl } from 'baseui/form-control'; 11 | import Row from '../shared/Row'; 12 | import Hint from '../shared/Hint'; 13 | import { DynamicCarsPosition } from '../world/constants/cars'; 14 | 15 | type AutomaticParkingAnalyticsProps = { 16 | genomes: Genome[], 17 | generationLifetimeMs: number, 18 | batchVersion: string, 19 | bestGenome: Genome | null, 20 | minLoss: number | null, 21 | carsBatchIndex: number | null, 22 | performanceBoost: boolean, 23 | selectedGenomeIndex: number, 24 | carsPosition: DynamicCarsPosition, 25 | onCarsPositionChange: (carsPosition: DynamicCarsPosition) => void, 26 | onChangeGenomeIndex: (index: number) => void, 27 | onBestGenomeEdit?: (genome: Genome) => void, 28 | onPerformanceBoost: (state: boolean) => void, 29 | }; 30 | 31 | function AutomaticParkingAnalytics(props: AutomaticParkingAnalyticsProps) { 32 | const { 33 | genomes, 34 | bestGenome, 35 | generationLifetimeMs, 36 | batchVersion, 37 | minLoss, 38 | carsBatchIndex, 39 | selectedGenomeIndex, 40 | performanceBoost, 41 | carsPosition, 42 | onCarsPositionChange, 43 | onChangeGenomeIndex, 44 | onPerformanceBoost, 45 | onBestGenomeEdit = (genome: Genome) => {}, 46 | } = props; 47 | 48 | const timingDetails = ( 49 | 50 | 57 | 58 | ); 59 | 60 | const carLicencePlates = genomes.map((genome: Genome, genomeIndex: number) => ( 61 | 67 | 77 | 78 | )); 79 | 80 | const carsSwitcher = ( 81 | 82 | 'Select the pre-trained car genome'} 84 | > 85 | 86 | {carLicencePlates} 87 | 88 | 89 | 90 | ); 91 | 92 | const performanceBooster = ( 93 | 94 | 95 | onPerformanceBoost(e.target.checked)} 99 | labelPlacement={LABEL_PLACEMENT.right} 100 | > 101 | 102 | 103 | Performance boost 104 | 105 | 108 | 109 | 110 | 111 | 112 | ); 113 | 114 | const selectedCarsPosition: Record = { 115 | 'middle': 0, 116 | 'front': 1, 117 | 'rear': 3, 118 | }; 119 | 120 | const carsPositionFromIndex = (positionIndex: number): DynamicCarsPosition => { 121 | // @ts-ignore 122 | const positions: DynamicCarsPosition[] = Object.keys(selectedCarsPosition); 123 | return positions[positionIndex]; 124 | }; 125 | 126 | const carsStartPositionChanger = ( 127 | 128 | 129 | { 134 | onCarsPositionChange(carsPositionFromIndex(index)); 135 | }} 136 | > 137 | 138 | 139 | 140 | 141 | 142 | ); 143 | 144 | return ( 145 | <> 146 | {timingDetails} 147 | 148 | 153 | 154 | {carsStartPositionChanger} 155 | 156 | 157 | {performanceBooster} 158 | 159 | 160 | 161 | {carsSwitcher} 162 | 163 | 170 | 171 | ); 172 | } 173 | 174 | export default AutomaticParkingAnalytics; 175 | -------------------------------------------------------------------------------- /src/components/evolution/BestGenomes.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Panel, StatelessAccordion } from 'baseui/accordion'; 3 | import { Genome } from '../../libs/genetic'; 4 | import GenomePreview from './GenomePreview'; 5 | import { CarLicencePlateType } from '../world/types/car'; 6 | 7 | const GENOME_PANELS = { 8 | firstBestGenome: 'first-best-genome', 9 | secondBestGenome: 'second-best-genome', 10 | }; 11 | 12 | type BestGenomesProps = { 13 | bestGenomePanelTitle?: string, 14 | bestGenome: Genome | null, 15 | bestCarLicencePlate?: CarLicencePlateType | null, 16 | minLoss?: number | null, 17 | secondBestGenomePanelTitle?: string, 18 | secondBestGenome?: Genome | null, 19 | secondBestCarLicencePlate?: CarLicencePlateType | null, 20 | secondMinLoss?: number | null, 21 | editable?: boolean, 22 | onBestGenomeEdit?: (genome: Genome) => void, 23 | }; 24 | 25 | function BestGenomes(props: BestGenomesProps): React.ReactElement { 26 | const { 27 | bestGenomePanelTitle = '1st Best Car Genome', 28 | bestGenome, 29 | bestCarLicencePlate, 30 | minLoss, 31 | secondBestGenomePanelTitle = '2nd Best Car Genome', 32 | secondBestGenome, 33 | secondBestCarLicencePlate, 34 | secondMinLoss, 35 | editable = false, 36 | onBestGenomeEdit = (genome: Genome) => {}, 37 | } = props; 38 | 39 | const [genomeExpandedTabs, setGenomeExpandedTabs] = React.useState([ 40 | GENOME_PANELS.firstBestGenome 41 | ]); 42 | 43 | const onPanelChange = ( 44 | {key, expanded}: {key: React.Key, expanded: React.Key[]} 45 | ) => { 46 | const newGenomeExpandedTabs = [...genomeExpandedTabs]; 47 | const openedTabIndex = newGenomeExpandedTabs.indexOf(key); 48 | if (openedTabIndex === -1) { 49 | newGenomeExpandedTabs.push(key); 50 | } else { 51 | newGenomeExpandedTabs.splice(openedTabIndex); 52 | } 53 | setGenomeExpandedTabs(newGenomeExpandedTabs); 54 | }; 55 | 56 | const bestGenomePreview = ( 57 | 64 | ); 65 | 66 | const secondBestGenomePreview = secondBestGenome !== undefined ? ( 67 | 72 | ) : null; 73 | 74 | const panels = []; 75 | 76 | const firstBestGenomePanel = ( 77 | 78 | {bestGenomePreview} 79 | 80 | ); 81 | 82 | const secondBestGenomePanel = secondBestGenomePreview ? ( 83 | 84 | {secondBestGenomePreview} 85 | 86 | ) : null; 87 | 88 | panels.push(firstBestGenomePanel); 89 | if (secondBestGenomePanel) { 90 | panels.push(secondBestGenomePanel); 91 | } 92 | 93 | return ( 94 | 98 | {panels} 99 | 100 | ); 101 | } 102 | 103 | export default BestGenomes; 104 | -------------------------------------------------------------------------------- /src/components/evolution/EvolutionAnalytics.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Block } from 'baseui/block'; 3 | 4 | import PopulationTable, { CarsInProgressType, CarsLossType } from './PopulationTable'; 5 | import EvolutionBoardParams from './EvolutionBoardParams'; 6 | import EvolutionTiming from './EvolutionTiming'; 7 | import LossHistory from './LossHistory'; 8 | import BestGenomes from './BestGenomes'; 9 | import { CarLicencePlateType, CarsType } from '../world/types/car'; 10 | import { Genome, Percentage, Probability } from '../../libs/genetic'; 11 | 12 | type EvolutionAnalyticsProps = { 13 | generationIndex: number | null, 14 | carsBatchIndex: number | null, 15 | totalBatches: number | null, 16 | worldIndex: number, 17 | generationLifetimeMs: number, 18 | generationSize: number, 19 | carsBatchSize: number, 20 | mutationProbability: Probability, 21 | performanceBoost: boolean, 22 | needToRetry: boolean, 23 | longLivingChampionsPercentage: Percentage, 24 | generationLifetime: number, 25 | batchVersion: string, 26 | onGenerationSizeChange: (size: number) => void, 27 | onBatchSizeChange: (size: number) => void, 28 | onGenerationLifetimeChange: (time: number) => void, 29 | onReset: () => void, 30 | onMutationProbabilityChange: (probability: Probability) => void, 31 | onLongLivingChampionsPercentageChange: (percentage: Percentage) => void, 32 | onPerformanceBoost: (state: boolean) => void, 33 | lossHistory: number[], 34 | avgLossHistory: number[], 35 | cars: CarsType, 36 | carsInProgress: CarsInProgressType, 37 | carsLoss: CarsLossType[], 38 | bestGenome: Genome | null, 39 | bestCarLicencePlate: CarLicencePlateType | null, 40 | minLoss: number | null, 41 | secondBestGenome: Genome | null, 42 | secondBestCarLicencePlate: CarLicencePlateType | null, 43 | secondMinLoss: number | null, 44 | }; 45 | 46 | function EvolutionAnalytics(props: EvolutionAnalyticsProps) { 47 | const { 48 | generationIndex, 49 | carsBatchIndex, 50 | totalBatches, 51 | needToRetry, 52 | mutationProbability, 53 | longLivingChampionsPercentage, 54 | worldIndex, 55 | generationLifetimeMs, 56 | generationSize, 57 | performanceBoost, 58 | carsBatchSize, 59 | generationLifetime, 60 | batchVersion, 61 | onGenerationSizeChange, 62 | onBatchSizeChange, 63 | onGenerationLifetimeChange, 64 | onReset, 65 | onMutationProbabilityChange, 66 | onLongLivingChampionsPercentageChange, 67 | onPerformanceBoost, 68 | lossHistory, 69 | avgLossHistory, 70 | cars, 71 | carsInProgress, 72 | carsLoss, 73 | bestGenome, 74 | bestCarLicencePlate, 75 | minLoss, 76 | secondBestGenome, 77 | secondBestCarLicencePlate, 78 | secondMinLoss, 79 | } = props; 80 | 81 | const timingDetails = ( 82 | 83 | 92 | 93 | ); 94 | 95 | const evolutionParams = ( 96 | 97 | 112 | 113 | ); 114 | 115 | const lossHistoryChart = ( 116 | 117 | 121 | 122 | ); 123 | 124 | const populationTable = ( 125 | 126 | 135 | 136 | ); 137 | 138 | return ( 139 | <> 140 | {timingDetails} 141 | {evolutionParams} 142 | 143 | 144 | {lossHistoryChart} 145 | 146 | 147 | {populationTable} 148 | 149 | 150 | 158 | 159 | ); 160 | } 161 | 162 | export default EvolutionAnalytics; 163 | -------------------------------------------------------------------------------- /src/components/evolution/EvolutionCheckpointSaver.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Block } from 'baseui/block'; 3 | import { 4 | Button, 5 | SIZE as BUTTON_SIZE, 6 | SHAPE as BUTTON_SHAPE, 7 | KIND as BUTTON_KIND, 8 | } from 'baseui/button'; 9 | import { BiDownload, BiUpload } from 'react-icons/all'; 10 | import { saveAs } from 'file-saver'; 11 | import { 12 | Modal, 13 | ModalHeader, 14 | ModalBody, 15 | SIZE, 16 | ROLE 17 | } from 'baseui/modal'; 18 | import { FileUploader } from 'baseui/file-uploader'; 19 | import { Notification, KIND as NOTIFICATION_KIND } from 'baseui/notification'; 20 | import { Paragraph3 } from 'baseui/typography'; 21 | 22 | import Row from '../shared/Row'; 23 | import { Generation, Percentage, Probability } from '../../libs/genetic'; 24 | import { CHECKPOINTS_PATH } from '../../constants/links'; 25 | import demoCheckpoint from '../../checkpoints/ckpt--population-1000--generation-36.json'; 26 | 27 | export type EvolutionCheckpoint = { 28 | dateTime: string, 29 | generationIndex: number, 30 | lossHistory: number[], 31 | avgLossHistory: number[], 32 | performanceBoost: boolean, 33 | generationSize: number, 34 | generationLifetime: number, 35 | carsBatchSize: number, 36 | mutationProbability: Probability, 37 | longLivingChampionsPercentage: Percentage, 38 | generation: Generation, 39 | }; 40 | 41 | type EvolutionCheckpointSaverProps = { 42 | onRestoreFromCheckpoint: (checkpoint: EvolutionCheckpoint) => void, 43 | onCheckpointToFile: () => EvolutionCheckpoint, 44 | }; 45 | 46 | function EvolutionCheckpointSaver(props: EvolutionCheckpointSaverProps) { 47 | const { 48 | onRestoreFromCheckpoint, 49 | onCheckpointToFile, 50 | } = props; 51 | 52 | const [showCheckpointModal, setShowCheckpointModal] = useState(false); 53 | const [checkpointIsProcessing, setCheckpointIsProcessing] = useState(false); 54 | const [checkpointErrorMessage, setCheckpointErrorMessage] = useState(null); 55 | 56 | const onSaveEvolution = () => { 57 | const checkpoint: EvolutionCheckpoint = onCheckpointToFile(); 58 | const fileName = `ckpt--population-${checkpoint.generationSize}--generation-${checkpoint.generationIndex}.json`; 59 | const checkpointString: string = JSON.stringify(checkpoint); 60 | const checkpointBlob = new Blob( 61 | [checkpointString], 62 | { type: 'application/json' }, 63 | ); 64 | saveAs(checkpointBlob, fileName); 65 | }; 66 | 67 | const onCheckpointModalOpen = () => { 68 | setCheckpointErrorMessage(null); 69 | setCheckpointIsProcessing(false); 70 | setShowCheckpointModal(true); 71 | }; 72 | 73 | const onCheckpointModalClose = () => { 74 | setShowCheckpointModal(false); 75 | }; 76 | 77 | const onCancelCheckpointUpload = () => { 78 | setCheckpointIsProcessing(false); 79 | }; 80 | 81 | const onFileDrop = (acceptedFiles: File[]) => { 82 | try { 83 | setCheckpointIsProcessing(true); 84 | 85 | const onFileReaderLoaded = (event: Event) => { 86 | // @ts-ignore 87 | const checkpoint: EvolutionCheckpoint = JSON.parse(event.target.result); 88 | onRestoreFromCheckpoint(checkpoint); 89 | setCheckpointIsProcessing(false); 90 | onCheckpointModalClose(); 91 | }; 92 | 93 | const fileReader = new FileReader(); 94 | fileReader.onload = onFileReaderLoaded; 95 | fileReader.readAsText(acceptedFiles[0]); 96 | } catch (error: any) { 97 | setCheckpointErrorMessage(error.message); 98 | setCheckpointIsProcessing(false); 99 | } 100 | }; 101 | 102 | const onUseDemoCheckpoint = () => { 103 | try { 104 | // @ts-ignore 105 | onRestoreFromCheckpoint(demoCheckpoint); 106 | onCheckpointModalClose(); 107 | } catch (error: any) { 108 | setCheckpointErrorMessage(error.message); 109 | } 110 | }; 111 | 112 | const checkpointError = checkpointErrorMessage ? ( 113 | 119 | {checkpointErrorMessage} 120 | 121 | ) : null; 122 | 123 | const checkpointModal = ( 124 | 133 | Restore evolution from the checkpoint file 134 | 135 | {checkpointError} 136 | 137 | Checkpoint is a json file that contain the history of the evolution and the list of genomes from the latest generation. 138 | 139 | 140 | You may save your own evolution progress to the checkpoint file or use one of the pre-trained checkpoints. 141 | 142 | 143 | 144 | 152 | 153 | 154 | 161 | 162 | 163 | ); 164 | 165 | return ( 166 | <> 167 | 168 | 169 | 177 | 178 | 179 | 180 | 188 | 189 | 190 | 191 | {checkpointModal} 192 | 193 | ); 194 | } 195 | 196 | export default EvolutionCheckpointSaver; 197 | -------------------------------------------------------------------------------- /src/components/evolution/EvolutionTabAutomatic.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react'; 2 | import { Block } from 'baseui/block'; 3 | import { useSnackbar, DURATION } from 'baseui/snackbar'; 4 | import { Check } from 'baseui/icon'; 5 | import { Notification } from 'baseui/notification'; 6 | 7 | import { Generation, Genome } from '../../libs/genetic'; 8 | import { CarLicencePlateType, CarType } from '../world/types/car'; 9 | import { 10 | SECOND, 11 | TRAINED_CAR_GENERATION_LIFETIME 12 | } from './EvolutionBoardParams'; 13 | import { generationToCars } from './utils/evolution'; 14 | import { loggerBuilder } from '../../utils/logger'; 15 | import { BEST_GENOMES } from './constants/genomes'; 16 | import AutomaticParkingAnalytics from './AutomaticParkingAnalytics'; 17 | import World from '../world/World'; 18 | import ParkingAutomatic from '../world/parkings/ParkingAutomatic'; 19 | import { DynamicCarsPosition, DYNAMIC_CARS_POSITION_MIDDLE } from '../world/constants/cars'; 20 | import { DYNAMIC_CARS_POSITION_FRONT } from '../world/constants/cars'; 21 | import { getIntSearchParam, getStringSearchParam, setSearchParam } from '../../utils/url'; 22 | 23 | const defaultGenomeIndex = 0; 24 | 25 | const GENOME_IDX_URL_PARAM = 'genome_idx'; 26 | const START_POSITION_URL_PARAM = 'position'; 27 | const DEFAULT_START_POSITION = DYNAMIC_CARS_POSITION_FRONT; 28 | 29 | function EvolutionTabAutomatic() { 30 | const {enqueue} = useSnackbar(); 31 | 32 | const bestTrainedCarLossRef = useRef(null); 33 | const onTrainedCarLossUpdate = (licensePlate: CarLicencePlateType, loss: number) => { 34 | bestTrainedCarLossRef.current = loss; 35 | }; 36 | 37 | const [performanceBoost, setPerformanceBoost] = useState(false); 38 | 39 | const [selectedGenomeIndex, setSelectedGenomeIndex] = useState( 40 | getIntSearchParam(GENOME_IDX_URL_PARAM, defaultGenomeIndex) 41 | ); 42 | 43 | const [dynamicCarsPosition, setDynamicCarsPosition] = useState(getCarsPositionFromURL()); 44 | 45 | const bestDefaultTrainedGeneration: Generation = [ 46 | BEST_GENOMES[dynamicCarsPosition][defaultGenomeIndex], 47 | ]; 48 | 49 | const [bestTrainedCarLoss, setBestTrainedCarLoss] = useState(null); 50 | const [bestTrainedCarCycleIndex, setBestTrainedCarCycleIndex] = useState(0); 51 | const [bestTrainedGeneration, setBestTrainedGeneration] = useState(bestDefaultTrainedGeneration); 52 | const [bestTrainedCars, setBestTrainedCars] = useState( 53 | Object.values( 54 | generationToCars({ 55 | generation: bestDefaultTrainedGeneration, 56 | generationIndex: 0, 57 | onLossUpdate: onTrainedCarLossUpdate, 58 | }) 59 | ) 60 | ); 61 | 62 | const automaticParkingLifetimeTimer = useRef(null); 63 | 64 | const logger = loggerBuilder({ context: 'AutomaticTab' }); 65 | 66 | const automaticParkingCycleLifetimeMs = TRAINED_CAR_GENERATION_LIFETIME * SECOND; 67 | const automaticWorldVersion = `automatic-${bestTrainedCarCycleIndex}`; 68 | 69 | const onAutomaticCycleLifetimeEnd = () => { 70 | logger.info(`Automatic cycle #${bestTrainedCarCycleIndex} lifetime ended`); 71 | setBestTrainedCarLoss(bestTrainedCarLossRef.current); 72 | setBestTrainedCarCycleIndex(bestTrainedCarCycleIndex + 1); 73 | }; 74 | 75 | const cancelAutomaticCycleTimer = () => { 76 | logger.info('Trying to cancel automatic parking cycle timer'); 77 | if (automaticParkingLifetimeTimer.current === null) { 78 | return; 79 | } 80 | clearTimeout(automaticParkingLifetimeTimer.current); 81 | automaticParkingLifetimeTimer.current = null; 82 | }; 83 | 84 | const countDownAutomaticParkingCycleLifetime = (onLifetimeEnd: () => void) => { 85 | logger.info(`Automatic parking cycle started`); 86 | cancelAutomaticCycleTimer(); 87 | automaticParkingLifetimeTimer.current = setTimeout(onLifetimeEnd, automaticParkingCycleLifetimeMs); 88 | }; 89 | 90 | const onPerformanceBoost = (state: boolean) => { 91 | setPerformanceBoost(state); 92 | }; 93 | 94 | const onBestGenomeEdit = (editedGenome: Genome) => { 95 | logger.info('Updating genome', editedGenome); 96 | 97 | const updatedGeneration: Generation = [editedGenome]; 98 | 99 | setBestTrainedGeneration(updatedGeneration); 100 | 101 | setBestTrainedCars(Object.values( 102 | generationToCars({ 103 | generation: updatedGeneration, 104 | generationIndex: 0, 105 | onLossUpdate: onTrainedCarLossUpdate, 106 | }) 107 | )); 108 | 109 | bestTrainedCarLossRef.current = null; 110 | setBestTrainedCarLoss(null); 111 | setBestTrainedCarCycleIndex(bestTrainedCarCycleIndex + 1); 112 | 113 | countDownAutomaticParkingCycleLifetime(onAutomaticCycleLifetimeEnd); 114 | 115 | enqueue({ 116 | message: 'Genome has been updated and applied to the displayed car', 117 | startEnhancer: ({size}) => , 118 | }, DURATION.medium); 119 | }; 120 | 121 | const onChangeGenomeIndex = (index: number) => { 122 | setSelectedGenomeIndex(index); 123 | onBestGenomeEdit(BEST_GENOMES[dynamicCarsPosition][index]); 124 | setSearchParam(GENOME_IDX_URL_PARAM, `${index}`); 125 | }; 126 | 127 | const onCarsPositionChange = (position: DynamicCarsPosition) => { 128 | setDynamicCarsPosition(position); 129 | setSelectedGenomeIndex(defaultGenomeIndex); 130 | onBestGenomeEdit(BEST_GENOMES[position][defaultGenomeIndex]); 131 | setSearchParam(START_POSITION_URL_PARAM, position); 132 | setSearchParam(GENOME_IDX_URL_PARAM, `${defaultGenomeIndex}`); 133 | }; 134 | 135 | // Start the automatic parking cycles. 136 | useEffect(() => { 137 | countDownAutomaticParkingCycleLifetime(onAutomaticCycleLifetimeEnd); 138 | return () => { 139 | cancelAutomaticCycleTimer(); 140 | }; 141 | // eslint-disable-next-line react-hooks/exhaustive-deps 142 | }, [bestTrainedCarCycleIndex]); 143 | 144 | return ( 145 | 146 | 150 | 157 | 158 | 159 | 160 | See the trained (almost) self-parking car in action

161 | You may also update genome values to see how it affects the car's behavior 162 |
163 |
164 | 179 |
180 | ); 181 | } 182 | 183 | function getCarsPositionFromURL(): DynamicCarsPosition { 184 | // @ts-ignore 185 | const carPositionFromUrl: DynamicCarsPosition = getStringSearchParam( 186 | START_POSITION_URL_PARAM, 187 | DEFAULT_START_POSITION 188 | ); 189 | if ([DYNAMIC_CARS_POSITION_FRONT, DYNAMIC_CARS_POSITION_MIDDLE].includes(carPositionFromUrl)) { 190 | return carPositionFromUrl; 191 | } 192 | return DEFAULT_START_POSITION; 193 | } 194 | 195 | export default EvolutionTabAutomatic; 196 | -------------------------------------------------------------------------------- /src/components/evolution/EvolutionTabManual.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Block } from 'baseui/block'; 3 | import { Notification } from 'baseui/notification'; 4 | 5 | import World from '../world/World'; 6 | import ParkingManual from '../world/parkings/ParkingManual'; 7 | 8 | function EvolutionTabManual() { 9 | return ( 10 | 11 | 16 | 21 | 22 | 23 | 24 | Try to park the car by yourself

25 | WASD keys to drive, SPACE to break, Joystick for mobile 26 |
27 |
28 |
29 | ); 30 | } 31 | 32 | export default EvolutionTabManual; 33 | -------------------------------------------------------------------------------- /src/components/evolution/EvolutionTabs.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Tab, Tabs } from 'baseui/tabs'; 3 | import { Block } from 'baseui/block'; 4 | 5 | import { StyleObject } from 'styletron-standard'; 6 | import ErrorBoundary from '../shared/ErrorBoundary'; 7 | import { getSearchParam, setSearchParam } from '../../utils/url'; 8 | import EvolutionTabManual from './EvolutionTabManual'; 9 | import EvolutionTabEvolution from './EvolutionTabEvolution'; 10 | import EvolutionTabAutomatic from './EvolutionTabAutomatic'; 11 | import { BiDna } from 'react-icons/bi'; 12 | import { FaRegHandSpock } from 'react-icons/fa'; 13 | import { RiGuideLine } from 'react-icons/ri'; 14 | 15 | const WORLD_SEARCH_PARAM = 'parking'; 16 | 17 | const TAB_KEYS: Record = { 18 | evolution: 'evolution', 19 | automatic: 'automatic', 20 | manual: 'manual', 21 | }; 22 | 23 | const tabBarStyle: StyleObject = { 24 | paddingLeft: 0, 25 | paddingRight: 0, 26 | overflow: 'hidden', 27 | }; 28 | 29 | const tabContentStyle: StyleObject = { 30 | paddingLeft: 0, 31 | paddingRight: 0, 32 | paddingTop: 0, 33 | paddingBottom: 0, 34 | }; 35 | 36 | const tabStyle: StyleObject = { 37 | marginLeft: 0, 38 | marginRight: 0, 39 | paddingLeft: '20px', 40 | paddingRight: '20px', 41 | }; 42 | 43 | function EvolutionTabs() { 44 | let worldKey: string = getSearchParam(WORLD_SEARCH_PARAM) || TAB_KEYS.evolution; 45 | if (!TAB_KEYS.hasOwnProperty(worldKey)) { 46 | worldKey = TAB_KEYS.evolution; 47 | } 48 | 49 | const [activeWorldKey, setActiveWorldKey] = useState(worldKey); 50 | 51 | const onTabSwitch = ({ activeKey }: {activeKey: React.Key}) => { 52 | setActiveWorldKey(activeKey); 53 | setSearchParam(WORLD_SEARCH_PARAM, `${activeKey}`); 54 | } 55 | 56 | return ( 57 | 66 | } 71 | title="Parking Evolution" 72 | /> 73 | )} 74 | > 75 | 76 | 77 | 78 | 79 | 80 | } 85 | title="Automatic Parking" 86 | /> 87 | )} 88 | > 89 | 90 | 91 | 92 | 93 | 94 | } 99 | title="Manual Parking" 100 | /> 101 | )} 102 | > 103 | 104 | 105 | 106 | 107 | 108 | ); 109 | } 110 | 111 | type TabTitleProps = { 112 | icon: React.ReactNode, 113 | title: string, 114 | }; 115 | 116 | const TabTitle = (props: TabTitleProps) => { 117 | const {icon, title} = props; 118 | return ( 119 | 124 | 130 | {icon} 131 | 132 | 133 | {title} 134 | 135 | 136 | ); 137 | }; 138 | 139 | export default EvolutionTabs; 140 | -------------------------------------------------------------------------------- /src/components/evolution/EvolutionTiming.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Label3 } from 'baseui/typography'; 3 | import {Tag, VARIANT as TAG_VARIANT} from 'baseui/tag'; 4 | import { Block } from 'baseui/block'; 5 | import {Notification, KIND as NOTIFICATION_KIND} from 'baseui/notification'; 6 | import { VscDebugRestart } from 'react-icons/all'; 7 | 8 | import Timer from '../shared/Timer'; 9 | 10 | type EvolutionTimingProps = { 11 | generationIndex?: number | null, 12 | totalBatches?: number | null, 13 | batchIndex?: number | null, 14 | generationLifetimeMs?: number, 15 | batchVersion?: string, 16 | worldVersion?: string, 17 | retry?: boolean, 18 | groupLabel?: string, 19 | batchLifetimeLabel?: string, 20 | }; 21 | 22 | function EvolutionTiming(props: EvolutionTimingProps) { 23 | const { 24 | generationIndex, 25 | batchIndex, 26 | totalBatches, 27 | generationLifetimeMs, 28 | batchVersion, 29 | worldVersion, 30 | retry = false, 31 | groupLabel = 'Group', 32 | batchLifetimeLabel = 'Group lifetime', 33 | } = props; 34 | 35 | const batchesCounter = retry ? ( 36 | 37 | 38 | 39 | ) : ( 40 | <> 41 | #{(batchIndex || 0) + 1} 42 | {totalBatches && ( / {totalBatches})} 43 | 44 | ); 45 | 46 | const generationInfo = generationIndex !== undefined ? ( 47 | 48 | 49 | #{(generationIndex || 0) + 1} 50 | 51 | 52 | ) : null; 53 | 54 | const groupInfo = batchIndex !== undefined ? ( 55 | 56 | 57 | {batchesCounter} 58 | 59 | 60 | ) : null; 61 | 62 | const groupLifetime = generationLifetimeMs !== undefined && batchVersion !== undefined ? ( 63 | 64 | 65 | 66 | 67 | 68 | ) : null; 69 | 70 | const worldAge = worldVersion !== undefined ? ( 71 | 72 | 73 | 74 | 75 | 76 | ) : null; 77 | 78 | return ( 79 | 87 | 95 | {generationInfo} 96 | {groupInfo} 97 | {groupLifetime} 98 | {worldAge} 99 | 100 | 101 | ); 102 | } 103 | 104 | type TimingColumnProps = { 105 | caption: string, 106 | children: React.ReactNode, 107 | }; 108 | 109 | function TimingColumn(props: TimingColumnProps) { 110 | const {caption, children} = props; 111 | return ( 112 | 119 | 125 | {caption}: 126 | 127 | 128 | {children} 129 | 130 | 131 | ); 132 | } 133 | 134 | export default EvolutionTiming; 135 | -------------------------------------------------------------------------------- /src/components/evolution/GenomePreview.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Block } from 'baseui/block'; 3 | import { CSSProperties, FormEvent, useState } from 'react'; 4 | import { Textarea } from 'baseui/textarea'; 5 | import { Button, SHAPE as BUTTON_SHAPE, KIND as BUTTON_KIND, SIZE as BUTTON_SIZE } from 'baseui/button'; 6 | import { BiEdit, FaRegSave } from 'react-icons/all'; 7 | 8 | import { Gene, Genome } from '../../libs/genetic'; 9 | import { CarLicencePlateType } from '../world/types/car'; 10 | import { FormControl } from 'baseui/form-control'; 11 | import { formatLossValue } from './utils/evolution'; 12 | import { CAR_SENSORS_NUM, carLossToFitness, decodeGenome, FormulaCoefficients } from '../../libs/carGenetic'; 13 | import { FITNESS_ALPHA } from './constants/evolution'; 14 | 15 | type GenomePreviewProps = { 16 | genome: Genome | null, 17 | title?: string, 18 | licencePlate?: CarLicencePlateType | null, 19 | loss?: number | null, 20 | editable?: boolean, 21 | onGenomeEdit?: (genome: Genome) => void, 22 | }; 23 | 24 | const genomeSeparator = ' '; 25 | 26 | const commonGenomeStyles: CSSProperties = { 27 | paddingTop: '15px', 28 | paddingRight: '15px', 29 | paddingBottom: '15px', 30 | paddingLeft: '15px', 31 | fontSize: '12px', 32 | backgroundColor: '#FFFFFF', 33 | lineHeight: '20px', 34 | fontFamily: 'monospace', 35 | }; 36 | 37 | function GenomePreview(props: GenomePreviewProps) { 38 | const { 39 | title, 40 | genome, 41 | licencePlate, 42 | loss, 43 | editable = false, 44 | onGenomeEdit = (genome: Genome) => {}, 45 | } = props; 46 | 47 | const [shortEngineFormula] = useState(true); 48 | const [shortWheelsFormula] = useState(true); 49 | 50 | const [isEditableGenome, setIsEditableGenome] = useState(false); 51 | const [editedGenome, setEditedGenome] = useState(genome); 52 | const [genomeError, setGenomeError] = useState(null); 53 | 54 | const onGenomeUpdate = (genomeString: string) => { 55 | if (!genome) { 56 | return; 57 | } 58 | 59 | const genomeFromString: Genome = genomeString 60 | .trim() 61 | .split('') 62 | .filter((geneString: string) => ['0', '1'].includes(geneString)) 63 | .map((geneString: string) => { 64 | const gene: Gene = geneString === '0' ? 0 : 1; 65 | return gene; 66 | }); 67 | setEditedGenome(genomeFromString); 68 | 69 | if (genomeFromString.length !== genome.length) { 70 | setGenomeError(`Genome must have ${genome.length} genes. Currently it has ${genomeFromString.length} genes.`); 71 | } else { 72 | setGenomeError(null); 73 | } 74 | }; 75 | 76 | const onEditToggle = () => { 77 | if (editedGenome && isEditableGenome && !genomeError) { 78 | onGenomeEdit(editedGenome) 79 | } 80 | setIsEditableGenome(!isEditableGenome); 81 | }; 82 | 83 | const genomeCaption = ( 84 | 85 | {genome && ( 86 | 87 | Genes: {genome.length} 88 | 89 | )} 90 | {licencePlate && ( 91 | 92 | Licence plate: {licencePlate} 93 | 94 | )} 95 | {loss && ( 96 | 97 | Loss: {formatLossValue(loss)} 98 | 99 | )} 100 | {loss && ( 101 | 102 | Fitness: {formatLossValue(carLossToFitness(loss, FITNESS_ALPHA))} 103 | 104 | )} 105 | 106 | ); 107 | 108 | const label = title || 'Car genome'; 109 | 110 | const genomeEditButtonIcon = !isEditableGenome ? ( 111 | 112 | ) : null; 113 | 114 | const genomeSaveButtonIcon = isEditableGenome ? ( 115 | 116 | ) : null; 117 | 118 | const genomeEditButtons = editable ? ( 119 | 126 | 153 | 154 | ) : null; 155 | 156 | const genomePreviewLabel = ( 157 | 158 | {label} 159 | 160 | ); 161 | 162 | const genomeString = (genome || []).join(genomeSeparator); 163 | const genomePreviewOutput = ( 164 | 168 | 169 | {genomeString} 170 | 171 | 172 | ); 173 | 174 | const editedGenomeString = (editedGenome || []).join(genomeSeparator); 175 | const genomeEditableOutput = ( 176 | genomePreviewLabel} 178 | caption={( 179 | 180 | Copy/paste whole genome or double-click the specific gene and change it 181 | 182 | )} 183 | error={genomeError} 184 | > 185 |