Commit ·
fe446ed
0
Parent(s):
main
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +36 -0
- .gitignore +130 -0
- FYP/GameEnv.py +515 -0
- FYP/Goals.py +99 -0
- FYP/Walls.py +122 -0
- FYP/car.png +0 -0
- FYP/ddqn_keras.py +126 -0
- FYP/ddqn_model.keras +0 -0
- FYP/main.py +81 -0
- FYP/main_test_model.py +69 -0
- FYP/requirements.txt +3 -0
- FYP/track.png +0 -0
- README.md +10 -0
- fyp1/best_neat_genome.pkl +3 -0
- fyp1/car.png +0 -0
- fyp1/config.txt +79 -0
- fyp1/map.png +0 -0
- fyp1/map2.png +0 -0
- fyp1/model_testing_NEAT.py +35 -0
- fyp1/newcar.py +288 -0
- fyp1/requirements.txt +2 -0
- gui/.dockerignore +9 -0
- gui/.gitignore +9 -0
- gui/Dockerfile +23 -0
- gui/gui/__init__.py +0 -0
- gui/gui/asgi.py +16 -0
- gui/gui/settings.py +124 -0
- gui/gui/urls.py +28 -0
- gui/gui/wsgi.py +16 -0
- gui/interface/__init__.py +0 -0
- gui/interface/admin.py +3 -0
- gui/interface/apps.py +6 -0
- gui/interface/migrations/__init__.py +0 -0
- gui/interface/models.py +3 -0
- gui/interface/simulations/ddqn_simulation.py +70 -0
- gui/interface/simulations/fyp1_simulation/best_neat_genome.pkl +3 -0
- gui/interface/simulations/fyp1_simulation/car.png +0 -0
- gui/interface/simulations/fyp1_simulation/config.txt +79 -0
- gui/interface/simulations/fyp1_simulation/map2.png +0 -0
- gui/interface/simulations/fyp1_simulation/newcar.py +288 -0
- gui/interface/simulations/fyp_simulation/GameEnv.py +512 -0
- gui/interface/simulations/fyp_simulation/Goals.py +99 -0
- gui/interface/simulations/fyp_simulation/Walls.py +113 -0
- gui/interface/simulations/fyp_simulation/car.png +0 -0
- gui/interface/simulations/fyp_simulation/ddqn_keras.py +126 -0
- gui/interface/simulations/fyp_simulation/ddqn_model.keras +0 -0
- gui/interface/simulations/fyp_simulation/track.png +0 -0
- gui/interface/simulations/neat_simulation.py +33 -0
- gui/interface/static/background.jpg +0 -0
- gui/interface/static/interface/css/all.min.css +9 -0
.gitattributes
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# Byte-compiled / optimized / DLL files
|
| 3 |
+
__pycache__/
|
| 4 |
+
*.pyc
|
| 5 |
+
*.pyo
|
| 6 |
+
*.pyd
|
| 7 |
+
|
| 8 |
+
# Distribution / packaging
|
| 9 |
+
.Python
|
| 10 |
+
build/
|
| 11 |
+
develop-eggs/
|
| 12 |
+
dist/
|
| 13 |
+
downloads/
|
| 14 |
+
eggs/
|
| 15 |
+
.eggs/
|
| 16 |
+
lib/
|
| 17 |
+
lib64/
|
| 18 |
+
parts/
|
| 19 |
+
sdist/
|
| 20 |
+
var/
|
| 21 |
+
wheels/
|
| 22 |
+
pip-wheel-metadata/
|
| 23 |
+
share/python-wheels/
|
| 24 |
+
*.egg-info/
|
| 25 |
+
.installed.cfg
|
| 26 |
+
*.egg
|
| 27 |
+
MANIFEST
|
| 28 |
+
|
| 29 |
+
# PyInstaller
|
| 30 |
+
# Usually these files are written by a python script from a template
|
| 31 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 32 |
+
*.manifest
|
| 33 |
+
*.spec
|
| 34 |
+
|
| 35 |
+
# Installer logs
|
| 36 |
+
pip-log.txt
|
| 37 |
+
pip-delete-this-directory.txt
|
| 38 |
+
|
| 39 |
+
# Unit test / coverage reports
|
| 40 |
+
htmlcov/
|
| 41 |
+
.tox/
|
| 42 |
+
.nox/
|
| 43 |
+
.coverage
|
| 44 |
+
.coverage.*
|
| 45 |
+
.cache
|
| 46 |
+
nosetests.xml
|
| 47 |
+
coverage.xml
|
| 48 |
+
*.cover
|
| 49 |
+
*.py,cover
|
| 50 |
+
.hypothesis/
|
| 51 |
+
.pytest_cache/
|
| 52 |
+
|
| 53 |
+
# Translations
|
| 54 |
+
*.mo
|
| 55 |
+
*.pot
|
| 56 |
+
|
| 57 |
+
# Django stuff:
|
| 58 |
+
*.log
|
| 59 |
+
local_settings.py
|
| 60 |
+
db.sqlite3
|
| 61 |
+
db.sqlite3-journal
|
| 62 |
+
|
| 63 |
+
# Scrapy stuff:
|
| 64 |
+
.scrapy
|
| 65 |
+
|
| 66 |
+
# Sphinx documentation
|
| 67 |
+
docs/_build/
|
| 68 |
+
|
| 69 |
+
# PyBuilder
|
| 70 |
+
target/
|
| 71 |
+
|
| 72 |
+
# Jupyter Notebook
|
| 73 |
+
.ipynb_checkpoints
|
| 74 |
+
|
| 75 |
+
# IPython
|
| 76 |
+
profile_default/
|
| 77 |
+
ipython_config.py
|
| 78 |
+
|
| 79 |
+
# pyenv
|
| 80 |
+
.python-version
|
| 81 |
+
|
| 82 |
+
# celery beat schedule file
|
| 83 |
+
celerybeat-schedule
|
| 84 |
+
|
| 85 |
+
# SageMath parsed files
|
| 86 |
+
*.sage.py
|
| 87 |
+
|
| 88 |
+
# Environments
|
| 89 |
+
.env
|
| 90 |
+
.venv
|
| 91 |
+
env/
|
| 92 |
+
venv/
|
| 93 |
+
ENV/
|
| 94 |
+
env.bak
|
| 95 |
+
venv.bak
|
| 96 |
+
|
| 97 |
+
# Spyder project settings
|
| 98 |
+
.spyderproject
|
| 99 |
+
.spyproject
|
| 100 |
+
|
| 101 |
+
# Rope project settings
|
| 102 |
+
.ropeproject
|
| 103 |
+
|
| 104 |
+
# mkdocs documentation
|
| 105 |
+
/site
|
| 106 |
+
|
| 107 |
+
# mypy
|
| 108 |
+
.mypy_cache/
|
| 109 |
+
.dmypy.json
|
| 110 |
+
dmypy.json
|
| 111 |
+
|
| 112 |
+
# Pyre type checker
|
| 113 |
+
.pyre/
|
| 114 |
+
|
| 115 |
+
# pytype static analyzer
|
| 116 |
+
.pytype/
|
| 117 |
+
|
| 118 |
+
# Cython debug symbols
|
| 119 |
+
cython_debug/
|
| 120 |
+
|
| 121 |
+
# VS Code
|
| 122 |
+
.vscode/
|
| 123 |
+
|
| 124 |
+
# FYP files
|
| 125 |
+
<<<<<<< HEAD
|
| 126 |
+
=======
|
| 127 |
+
fyp report .docx
|
| 128 |
+
fyp video.mp4
|
| 129 |
+
>>>>>>> ff272df21f75042ffc26863b5f3a0d584f9977cb
|
| 130 |
+
|
FYP/GameEnv.py
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pygame
|
| 2 |
+
import math
|
| 3 |
+
from Walls import Wall
|
| 4 |
+
from Walls import getWalls
|
| 5 |
+
from Goals import Goal
|
| 6 |
+
from Goals import getGoals
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
GOALREWARD = 1
|
| 11 |
+
LIFE_REWARD = 0
|
| 12 |
+
PENALTY = -1
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def distance(pt1, pt2):
|
| 16 |
+
return(((pt1.x - pt2.x)**2 + (pt1.y - pt2.y)**2)**0.5)
|
| 17 |
+
|
| 18 |
+
def rotate(origin,point,angle):
|
| 19 |
+
qx = origin.x + math.cos(angle) * (point.x - origin.x) - math.sin(angle) * (point.y - origin.y)
|
| 20 |
+
qy = origin.y + math.sin(angle) * (point.x - origin.x) + math.cos(angle) * (point.y - origin.y)
|
| 21 |
+
q = myPoint(qx, qy)
|
| 22 |
+
return q
|
| 23 |
+
|
| 24 |
+
def rotateRect(pt1, pt2, pt3, pt4, angle):
|
| 25 |
+
|
| 26 |
+
pt_center = myPoint((pt1.x + pt3.x)/2, (pt1.y + pt3.y)/2)
|
| 27 |
+
|
| 28 |
+
pt1 = rotate(pt_center,pt1,angle)
|
| 29 |
+
pt2 = rotate(pt_center,pt2,angle)
|
| 30 |
+
pt3 = rotate(pt_center,pt3,angle)
|
| 31 |
+
pt4 = rotate(pt_center,pt4,angle)
|
| 32 |
+
|
| 33 |
+
return pt1, pt2, pt3, pt4
|
| 34 |
+
|
| 35 |
+
class myPoint:
|
| 36 |
+
def __init__(self, x, y):
|
| 37 |
+
self.x = x
|
| 38 |
+
self.y = y
|
| 39 |
+
|
| 40 |
+
class myLine:
|
| 41 |
+
def __init__(self, pt1, pt2):
|
| 42 |
+
self.pt1 = myPoint(pt1.x, pt1.y)
|
| 43 |
+
self.pt2 = myPoint(pt2.x, pt2.y)
|
| 44 |
+
|
| 45 |
+
class Ray:
|
| 46 |
+
def __init__(self,x,y,angle):
|
| 47 |
+
self.x = x
|
| 48 |
+
self.y = y
|
| 49 |
+
self.angle = angle
|
| 50 |
+
|
| 51 |
+
def cast(self, wall):
|
| 52 |
+
x1 = wall.x1
|
| 53 |
+
y1 = wall.y1
|
| 54 |
+
x2 = wall.x2
|
| 55 |
+
y2 = wall.y2
|
| 56 |
+
|
| 57 |
+
vec = rotate(myPoint(0,0), myPoint(0,-1000), self.angle)
|
| 58 |
+
|
| 59 |
+
x3 = self.x
|
| 60 |
+
y3 = self.y
|
| 61 |
+
x4 = self.x + vec.x
|
| 62 |
+
y4 = self.y + vec.y
|
| 63 |
+
|
| 64 |
+
den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
|
| 65 |
+
|
| 66 |
+
if(den == 0):
|
| 67 |
+
den = 0
|
| 68 |
+
else:
|
| 69 |
+
t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den
|
| 70 |
+
u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den
|
| 71 |
+
|
| 72 |
+
if t > 0 and t < 1 and u < 1 and u > 0:
|
| 73 |
+
pt = myPoint(math.floor(x1 + t * (x2 - x1)), math.floor(y1 + t * (y2 - y1)))
|
| 74 |
+
return(pt)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class Car:
|
| 79 |
+
def __init__(self, x, y):
|
| 80 |
+
self.pt = myPoint(x, y)
|
| 81 |
+
self.x = x
|
| 82 |
+
self.y = y
|
| 83 |
+
self.width = 14
|
| 84 |
+
self.height = 30
|
| 85 |
+
|
| 86 |
+
self.points = 0
|
| 87 |
+
|
| 88 |
+
self.original_image = pygame.image.load("car.png").convert()
|
| 89 |
+
self.image = self.original_image # This will reference the rotated image.
|
| 90 |
+
self.image.set_colorkey((0,0,0))
|
| 91 |
+
self.rect = self.image.get_rect().move(self.x, self.y)
|
| 92 |
+
|
| 93 |
+
self.angle = math.radians(180)
|
| 94 |
+
self.soll_angle = self.angle
|
| 95 |
+
|
| 96 |
+
self.dvel = 1
|
| 97 |
+
self.vel = 0
|
| 98 |
+
self.velX = 0
|
| 99 |
+
self.velY = 0
|
| 100 |
+
self.maxvel = 15 # before 15
|
| 101 |
+
|
| 102 |
+
self.angle = math.radians(180)
|
| 103 |
+
self.soll_angle = self.angle
|
| 104 |
+
|
| 105 |
+
self.pt1 = myPoint(self.pt.x - self.width / 2, self.pt.y - self.height / 2)
|
| 106 |
+
self.pt2 = myPoint(self.pt.x + self.width / 2, self.pt.y - self.height / 2)
|
| 107 |
+
self.pt3 = myPoint(self.pt.x + self.width / 2, self.pt.y + self.height / 2)
|
| 108 |
+
self.pt4 = myPoint(self.pt.x - self.width / 2, self.pt.y + self.height / 2)
|
| 109 |
+
|
| 110 |
+
self.p1 = self.pt1
|
| 111 |
+
self.p2 = self.pt2
|
| 112 |
+
self.p3 = self.pt3
|
| 113 |
+
self.p4 = self.pt4
|
| 114 |
+
|
| 115 |
+
self.distances = []
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def action(self, choice):
|
| 119 |
+
if choice == 0:
|
| 120 |
+
pass
|
| 121 |
+
elif choice == 1:
|
| 122 |
+
self.accelerate(self.dvel)
|
| 123 |
+
elif choice == 8:
|
| 124 |
+
self.accelerate(self.dvel)
|
| 125 |
+
self.turn(1)
|
| 126 |
+
elif choice == 7:
|
| 127 |
+
self.accelerate(self.dvel)
|
| 128 |
+
self.turn(-1)
|
| 129 |
+
elif choice == 4:
|
| 130 |
+
self.accelerate(-self.dvel)
|
| 131 |
+
elif choice == 5:
|
| 132 |
+
self.accelerate(-self.dvel)
|
| 133 |
+
self.turn(1)
|
| 134 |
+
elif choice == 6:
|
| 135 |
+
self.accelerate(-self.dvel)
|
| 136 |
+
self.turn(-1)
|
| 137 |
+
elif choice == 3:
|
| 138 |
+
self.turn(1)
|
| 139 |
+
elif choice == 2:
|
| 140 |
+
self.turn(-1)
|
| 141 |
+
pass
|
| 142 |
+
|
| 143 |
+
def accelerate(self,dvel):
|
| 144 |
+
dvel = dvel * 2
|
| 145 |
+
|
| 146 |
+
self.vel = self.vel + dvel
|
| 147 |
+
|
| 148 |
+
if self.vel > self.maxvel:
|
| 149 |
+
self.vel = self.maxvel
|
| 150 |
+
|
| 151 |
+
if self.vel < -self.maxvel:
|
| 152 |
+
self.vel = -self.maxvel
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def turn(self, dir):
|
| 156 |
+
self.soll_angle = self.soll_angle + dir * math.radians(15)
|
| 157 |
+
|
| 158 |
+
def update(self):
|
| 159 |
+
|
| 160 |
+
#drifting code
|
| 161 |
+
self.angle = self.soll_angle
|
| 162 |
+
|
| 163 |
+
vec_temp = rotate(myPoint(0,0), myPoint(0,self.vel), self.angle)
|
| 164 |
+
self.velX, self.velY = vec_temp.x, vec_temp.y
|
| 165 |
+
|
| 166 |
+
self.x = self.x + self.velX
|
| 167 |
+
self.y = self.y + self.velY
|
| 168 |
+
|
| 169 |
+
self.rect.center = self.x, self.y
|
| 170 |
+
|
| 171 |
+
self.pt1 = myPoint(self.pt1.x + self.velX, self.pt1.y + self.velY)
|
| 172 |
+
self.pt2 = myPoint(self.pt2.x + self.velX, self.pt2.y + self.velY)
|
| 173 |
+
self.pt3 = myPoint(self.pt3.x + self.velX, self.pt3.y + self.velY)
|
| 174 |
+
self.pt4 = myPoint(self.pt4.x + self.velX, self.pt4.y + self.velY)
|
| 175 |
+
|
| 176 |
+
self.p1 ,self.p2 ,self.p3 ,self.p4 = rotateRect(self.pt1, self.pt2, self.pt3, self.pt4, self.soll_angle)
|
| 177 |
+
|
| 178 |
+
self.image = pygame.transform.rotate(self.original_image, 90 - self.soll_angle * 180 / math.pi)
|
| 179 |
+
x, y = self.rect.center # Save its current center.
|
| 180 |
+
self.rect = self.image.get_rect() # Replace old rect with new rect.
|
| 181 |
+
self.rect.center = (x, y)
|
| 182 |
+
|
| 183 |
+
def cast(self, walls):
|
| 184 |
+
|
| 185 |
+
ray1 = Ray(self.x, self.y, self.soll_angle)
|
| 186 |
+
ray2 = Ray(self.x, self.y, self.soll_angle - math.radians(30))
|
| 187 |
+
ray3 = Ray(self.x, self.y, self.soll_angle + math.radians(30))
|
| 188 |
+
ray4 = Ray(self.x, self.y, self.soll_angle + math.radians(45))
|
| 189 |
+
ray5 = Ray(self.x, self.y, self.soll_angle - math.radians(45))
|
| 190 |
+
ray6 = Ray(self.x, self.y, self.soll_angle + math.radians(90))
|
| 191 |
+
ray7 = Ray(self.x, self.y, self.soll_angle - math.radians(90))
|
| 192 |
+
ray8 = Ray(self.x, self.y, self.soll_angle + math.radians(180))
|
| 193 |
+
|
| 194 |
+
ray9 = Ray(self.x, self.y, self.soll_angle + math.radians(10))
|
| 195 |
+
ray10 = Ray(self.x, self.y, self.soll_angle - math.radians(10))
|
| 196 |
+
ray11 = Ray(self.x, self.y, self.soll_angle + math.radians(135))
|
| 197 |
+
ray12 = Ray(self.x, self.y, self.soll_angle - math.radians(135))
|
| 198 |
+
ray13 = Ray(self.x, self.y, self.soll_angle + math.radians(20))
|
| 199 |
+
ray14 = Ray(self.x, self.y, self.soll_angle - math.radians(20))
|
| 200 |
+
|
| 201 |
+
ray15 = Ray(self.p1.x,self.p1.y, self.soll_angle + math.radians(90))
|
| 202 |
+
ray16 = Ray(self.p2.x,self.p2.y, self.soll_angle - math.radians(90))
|
| 203 |
+
|
| 204 |
+
ray17 = Ray(self.p1.x,self.p1.y, self.soll_angle + math.radians(0))
|
| 205 |
+
ray18 = Ray(self.p2.x,self.p2.y, self.soll_angle - math.radians(0))
|
| 206 |
+
|
| 207 |
+
self.rays = []
|
| 208 |
+
self.rays.append(ray1)
|
| 209 |
+
self.rays.append(ray2)
|
| 210 |
+
self.rays.append(ray3)
|
| 211 |
+
self.rays.append(ray4)
|
| 212 |
+
self.rays.append(ray5)
|
| 213 |
+
self.rays.append(ray6)
|
| 214 |
+
self.rays.append(ray7)
|
| 215 |
+
self.rays.append(ray8)
|
| 216 |
+
|
| 217 |
+
self.rays.append(ray9)
|
| 218 |
+
self.rays.append(ray10)
|
| 219 |
+
self.rays.append(ray11)
|
| 220 |
+
self.rays.append(ray12)
|
| 221 |
+
self.rays.append(ray13)
|
| 222 |
+
self.rays.append(ray14)
|
| 223 |
+
|
| 224 |
+
self.rays.append(ray15)
|
| 225 |
+
self.rays.append(ray16)
|
| 226 |
+
|
| 227 |
+
self.rays.append(ray17)
|
| 228 |
+
self.rays.append(ray18)
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
observations = []
|
| 232 |
+
self.closestRays = []
|
| 233 |
+
|
| 234 |
+
for ray in self.rays:
|
| 235 |
+
closest = None #myPoint(0,0)
|
| 236 |
+
record = math.inf
|
| 237 |
+
for wall in walls:
|
| 238 |
+
pt = ray.cast(wall)
|
| 239 |
+
if pt:
|
| 240 |
+
dist = distance(myPoint(self.x, self.y),pt)
|
| 241 |
+
if dist < record:
|
| 242 |
+
record = dist
|
| 243 |
+
closest = pt
|
| 244 |
+
|
| 245 |
+
if closest:
|
| 246 |
+
#append distance for current ray
|
| 247 |
+
self.closestRays.append(closest)
|
| 248 |
+
observations.append(record)
|
| 249 |
+
|
| 250 |
+
else:
|
| 251 |
+
observations.append(1000)
|
| 252 |
+
|
| 253 |
+
for i in range(len(observations)):
|
| 254 |
+
#invert observation values 0 is far away 1 is close
|
| 255 |
+
observations[i] = ((1000 - observations[i]) / 1000)
|
| 256 |
+
|
| 257 |
+
observations.append(self.vel / self.maxvel)
|
| 258 |
+
return observations
|
| 259 |
+
|
| 260 |
+
def collision(self, wall):
|
| 261 |
+
|
| 262 |
+
line1 = myLine(self.p1, self.p2)
|
| 263 |
+
line2 = myLine(self.p2, self.p3)
|
| 264 |
+
line3 = myLine(self.p3, self.p4)
|
| 265 |
+
line4 = myLine(self.p4, self.p1)
|
| 266 |
+
|
| 267 |
+
x1 = wall.x1
|
| 268 |
+
y1 = wall.y1
|
| 269 |
+
x2 = wall.x2
|
| 270 |
+
y2 = wall.y2
|
| 271 |
+
|
| 272 |
+
lines = []
|
| 273 |
+
lines.append(line1)
|
| 274 |
+
lines.append(line2)
|
| 275 |
+
lines.append(line3)
|
| 276 |
+
lines.append(line4)
|
| 277 |
+
|
| 278 |
+
for li in lines:
|
| 279 |
+
|
| 280 |
+
x3 = li.pt1.x
|
| 281 |
+
y3 = li.pt1.y
|
| 282 |
+
x4 = li.pt2.x
|
| 283 |
+
y4 = li.pt2.y
|
| 284 |
+
|
| 285 |
+
den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
|
| 286 |
+
|
| 287 |
+
if(den == 0):
|
| 288 |
+
den = 0
|
| 289 |
+
else:
|
| 290 |
+
t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den
|
| 291 |
+
u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den
|
| 292 |
+
|
| 293 |
+
if t > 0 and t < 1 and u < 1 and u > 0:
|
| 294 |
+
return(True)
|
| 295 |
+
|
| 296 |
+
return(False)
|
| 297 |
+
|
| 298 |
+
def score(self, goal):
|
| 299 |
+
|
| 300 |
+
line1 = myLine(self.p1, self.p3)
|
| 301 |
+
|
| 302 |
+
vec = rotate(myPoint(0,0), myPoint(0,-50), self.angle)
|
| 303 |
+
line1 = myLine(myPoint(self.x,self.y),myPoint(self.x + vec.x, self.y + vec.y))
|
| 304 |
+
|
| 305 |
+
x1 = goal.x1
|
| 306 |
+
y1 = goal.y1
|
| 307 |
+
x2 = goal.x2
|
| 308 |
+
y2 = goal.y2
|
| 309 |
+
|
| 310 |
+
x3 = line1.pt1.x
|
| 311 |
+
y3 = line1.pt1.y
|
| 312 |
+
x4 = line1.pt2.x
|
| 313 |
+
y4 = line1.pt2.y
|
| 314 |
+
|
| 315 |
+
den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
|
| 316 |
+
|
| 317 |
+
if(den == 0):
|
| 318 |
+
den = 0
|
| 319 |
+
else:
|
| 320 |
+
t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den
|
| 321 |
+
u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den
|
| 322 |
+
|
| 323 |
+
if t > 0 and t < 1 and u < 1 and u > 0:
|
| 324 |
+
pt = math.floor(x1 + t * (x2 - x1)), math.floor(y1 + t * (y2 - y1))
|
| 325 |
+
|
| 326 |
+
d = distance(myPoint(self.x, self.y), myPoint(pt[0], pt[1]))
|
| 327 |
+
if d < 20:
|
| 328 |
+
#pygame.draw.circle(win, (0,255,0), pt, 5)
|
| 329 |
+
self.points += GOALREWARD
|
| 330 |
+
return(True)
|
| 331 |
+
|
| 332 |
+
return(False)
|
| 333 |
+
|
| 334 |
+
def reset(self):
|
| 335 |
+
|
| 336 |
+
self.x = 50
|
| 337 |
+
self.y = 300
|
| 338 |
+
self.velX = 0
|
| 339 |
+
self.velY = 0
|
| 340 |
+
self.vel = 0
|
| 341 |
+
self.angle = math.radians(180)
|
| 342 |
+
self.soll_angle = self.angle
|
| 343 |
+
self.points = 0
|
| 344 |
+
|
| 345 |
+
self.pt1 = myPoint(self.pt.x - self.width / 2, self.pt.y - self.height / 2)
|
| 346 |
+
self.pt2 = myPoint(self.pt.x + self.width / 2, self.pt.y - self.height / 2)
|
| 347 |
+
self.pt3 = myPoint(self.pt.x + self.width / 2, self.pt.y + self.height / 2)
|
| 348 |
+
self.pt4 = myPoint(self.pt.x - self.width / 2, self.pt.y + self.height / 2)
|
| 349 |
+
|
| 350 |
+
self.p1 = self.pt1
|
| 351 |
+
self.p2 = self.pt2
|
| 352 |
+
self.p3 = self.pt3
|
| 353 |
+
self.p4 = self.pt4
|
| 354 |
+
|
| 355 |
+
def draw(self, win):
|
| 356 |
+
win.blit(self.image, self.rect)
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
class RacingEnv:
|
| 360 |
+
|
| 361 |
+
def __init__(self):
|
| 362 |
+
pygame.init()
|
| 363 |
+
self.font = pygame.font.Font(pygame.font.get_default_font(), 36)
|
| 364 |
+
|
| 365 |
+
self.fps = 120
|
| 366 |
+
self.width = 1000
|
| 367 |
+
self.height = 600
|
| 368 |
+
self.history = []
|
| 369 |
+
|
| 370 |
+
self.screen = pygame.display.set_mode((self.width, self.height))
|
| 371 |
+
pygame.display.set_caption("IMS using DDQN")
|
| 372 |
+
self.screen.fill((0,0,0))
|
| 373 |
+
self.back_image = pygame.image.load("track.png").convert()
|
| 374 |
+
self.back_rect = self.back_image.get_rect().move(0, 0)
|
| 375 |
+
self.action_space = None
|
| 376 |
+
self.observation_space = None
|
| 377 |
+
self.game_reward = 0
|
| 378 |
+
self.score = 0
|
| 379 |
+
|
| 380 |
+
self.reset()
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
def reset(self):
|
| 384 |
+
self.screen.fill((0, 0, 0))
|
| 385 |
+
|
| 386 |
+
self.car = Car(50, 300)
|
| 387 |
+
self.walls = getWalls()
|
| 388 |
+
self.goals = getGoals()
|
| 389 |
+
self.game_reward = 0
|
| 390 |
+
|
| 391 |
+
def step(self, action):
|
| 392 |
+
|
| 393 |
+
done = False
|
| 394 |
+
self.car.action(action)
|
| 395 |
+
self.car.update()
|
| 396 |
+
reward = LIFE_REWARD
|
| 397 |
+
|
| 398 |
+
# Check if car passes Goal and scores
|
| 399 |
+
index = 1
|
| 400 |
+
for goal in self.goals:
|
| 401 |
+
|
| 402 |
+
if index > len(self.goals):
|
| 403 |
+
index = 1
|
| 404 |
+
if goal.isactiv:
|
| 405 |
+
if self.car.score(goal):
|
| 406 |
+
goal.isactiv = False
|
| 407 |
+
self.goals[index-2].isactiv = True
|
| 408 |
+
reward += GOALREWARD
|
| 409 |
+
|
| 410 |
+
index = index + 1
|
| 411 |
+
|
| 412 |
+
#check if car crashed in the wall
|
| 413 |
+
for wall in self.walls:
|
| 414 |
+
if self.car.collision(wall):
|
| 415 |
+
reward += PENALTY
|
| 416 |
+
done = True
|
| 417 |
+
|
| 418 |
+
new_state = self.car.cast(self.walls)
|
| 419 |
+
#normalize states
|
| 420 |
+
if done:
|
| 421 |
+
new_state = None
|
| 422 |
+
|
| 423 |
+
return new_state, reward, done
|
| 424 |
+
|
| 425 |
+
def render(self, action):
|
| 426 |
+
|
| 427 |
+
DRAW_WALLS = True
|
| 428 |
+
DRAW_GOALS = True
|
| 429 |
+
DRAW_RAYS = True
|
| 430 |
+
|
| 431 |
+
pygame.time.delay(10)
|
| 432 |
+
|
| 433 |
+
self.clock = pygame.time.Clock()
|
| 434 |
+
self.screen.fill((0, 0, 0))
|
| 435 |
+
|
| 436 |
+
self.screen.blit(self.back_image, self.back_rect)
|
| 437 |
+
# Draw checkered start line (black and white boxes)
|
| 438 |
+
start_x = 50 # same as Car's starting x
|
| 439 |
+
start_y = 300 # same as Car's starting y
|
| 440 |
+
line_width = 80
|
| 441 |
+
line_height = 20
|
| 442 |
+
num_squares = 16 # Number of squares in the checkered line
|
| 443 |
+
square_width = line_width // num_squares
|
| 444 |
+
for i in range(num_squares):
|
| 445 |
+
color = (255, 255, 255) if i % 2 == 0 else (0, 0, 0)
|
| 446 |
+
rect_x = start_x - line_width // 2 + i * square_width
|
| 447 |
+
pygame.draw.rect(self.screen, color, (rect_x, start_y - line_height // 2, square_width, line_height))
|
| 448 |
+
if DRAW_WALLS:
|
| 449 |
+
for wall in self.walls:
|
| 450 |
+
wall.draw(self.screen)
|
| 451 |
+
|
| 452 |
+
if DRAW_GOALS:
|
| 453 |
+
for goal in self.goals:
|
| 454 |
+
goal.draw(self.screen)
|
| 455 |
+
if goal.isactiv:
|
| 456 |
+
goal.draw(self.screen)
|
| 457 |
+
|
| 458 |
+
self.car.draw(self.screen)
|
| 459 |
+
|
| 460 |
+
if DRAW_RAYS:
|
| 461 |
+
i = 0
|
| 462 |
+
for pt in self.car.closestRays:
|
| 463 |
+
pygame.draw.circle(self.screen, (0,0,255), (pt.x, pt.y), 5)
|
| 464 |
+
i += 1
|
| 465 |
+
if i < 15:
|
| 466 |
+
pygame.draw.line(self.screen, (255,255,255), (self.car.x, self.car.y), (pt.x, pt.y), 1)
|
| 467 |
+
elif i >=15 and i < 17:
|
| 468 |
+
pygame.draw.line(self.screen, (255,255,255), ((self.car.p1.x + self.car.p2.x)/2, (self.car.p1.y + self.car.p2.y)/2), (pt.x, pt.y), 1)
|
| 469 |
+
elif i == 17:
|
| 470 |
+
pygame.draw.line(self.screen, (255,255,255), (self.car.p1.x , self.car.p1.y ), (pt.x, pt.y), 1)
|
| 471 |
+
else:
|
| 472 |
+
pygame.draw.line(self.screen, (255,255,255), (self.car.p2.x, self.car.p2.y), (pt.x, pt.y), 1)
|
| 473 |
+
|
| 474 |
+
#render controll
|
| 475 |
+
pygame.draw.rect(self.screen,(255,255,255),(800, 100, 40, 40),2)
|
| 476 |
+
pygame.draw.rect(self.screen,(255,255,255),(850, 100, 40, 40),2)
|
| 477 |
+
pygame.draw.rect(self.screen,(255,255,255),(900, 100, 40, 40),2)
|
| 478 |
+
pygame.draw.rect(self.screen,(255,255,255),(850, 50, 40, 40),2)
|
| 479 |
+
|
| 480 |
+
if action == 4:
|
| 481 |
+
pygame.draw.rect(self.screen,(0,255,0),(850, 50, 40, 40))
|
| 482 |
+
elif action == 6:
|
| 483 |
+
pygame.draw.rect(self.screen,(0,255,0),(850, 50, 40, 40))
|
| 484 |
+
pygame.draw.rect(self.screen,(0,255,0),(800, 100, 40, 40))
|
| 485 |
+
elif action == 5:
|
| 486 |
+
pygame.draw.rect(self.screen,(0,255,0),(850, 50, 40, 40))
|
| 487 |
+
pygame.draw.rect(self.screen,(0,255,0),(900, 100, 40, 40))
|
| 488 |
+
elif action == 1:
|
| 489 |
+
pygame.draw.rect(self.screen,(0,255,0),(850, 100, 40, 40))
|
| 490 |
+
elif action == 8:
|
| 491 |
+
pygame.draw.rect(self.screen,(0,255,0),(850, 100, 40, 40))
|
| 492 |
+
pygame.draw.rect(self.screen,(0,255,0),(800, 100, 40, 40))
|
| 493 |
+
elif action == 7:
|
| 494 |
+
pygame.draw.rect(self.screen,(0,255,0),(850, 100, 40, 40))
|
| 495 |
+
pygame.draw.rect(self.screen,(0,255,0),(900, 100, 40, 40))
|
| 496 |
+
elif action == 2:
|
| 497 |
+
pygame.draw.rect(self.screen,(0,255,0),(800, 100, 40, 40))
|
| 498 |
+
elif action == 3:
|
| 499 |
+
pygame.draw.rect(self.screen,(0,255,0),(900, 100, 40, 40))
|
| 500 |
+
|
| 501 |
+
# score
|
| 502 |
+
text_surface = self.font.render(f'Points {self.car.points}', True, pygame.Color('green'))
|
| 503 |
+
self.screen.blit(text_surface, dest=(0, 0))
|
| 504 |
+
# speed
|
| 505 |
+
text_surface = self.font.render(f'Speed {self.car.vel*-1}', True, pygame.Color('green'))
|
| 506 |
+
self.screen.blit(text_surface, dest=(800, 0))
|
| 507 |
+
|
| 508 |
+
self.clock.tick(self.fps)
|
| 509 |
+
pygame.display.update()
|
| 510 |
+
|
| 511 |
+
def close(self):
|
| 512 |
+
pygame.quit()
|
| 513 |
+
|
| 514 |
+
|
| 515 |
+
|
FYP/Goals.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pygame
|
| 2 |
+
|
| 3 |
+
class Goal:
|
| 4 |
+
def __init__(self, x1, y1, x2, y2):
|
| 5 |
+
self.x1 = x1
|
| 6 |
+
self.y1 = y1
|
| 7 |
+
self.x2 = x2
|
| 8 |
+
self.y2 = y2
|
| 9 |
+
|
| 10 |
+
self.isactiv = False
|
| 11 |
+
|
| 12 |
+
def draw(self, win):
|
| 13 |
+
pygame.draw.line(win, (0,255,0), (self.x1, self.y1), (self.x2, self.y2), 2)
|
| 14 |
+
if self.isactiv:
|
| 15 |
+
pygame.draw.line(win, (255,0,0), (self.x1, self.y1), (self.x2, self.y2), 2)
|
| 16 |
+
|
| 17 |
+
# the file of shame
|
| 18 |
+
def getGoals():
|
| 19 |
+
goals = []
|
| 20 |
+
|
| 21 |
+
goal1 = Goal(0,200,120,200)
|
| 22 |
+
goal2 = Goal(0,100,120,150)
|
| 23 |
+
goal2_5 = Goal(0,0,150,130)
|
| 24 |
+
goal3 = Goal(120,0,170,120)
|
| 25 |
+
goal3_5 = Goal(200,0,200,120)
|
| 26 |
+
goal4 = Goal(270,0,270,110)
|
| 27 |
+
goal4_5 = Goal(350,0,350,110)
|
| 28 |
+
goal5 = Goal(450,0,450,110)
|
| 29 |
+
goal5_5 = Goal(525,0,525,110)
|
| 30 |
+
goal6 = Goal(600,0,550,130)
|
| 31 |
+
goal6_5 = Goal(550,130,700,60)
|
| 32 |
+
goal7 = Goal(550,130,700,130)
|
| 33 |
+
goal7_5 = Goal(550,130,650,200)
|
| 34 |
+
goal8 = Goal(550,130,570,240)
|
| 35 |
+
goal9 = Goal(410,130,430,260)
|
| 36 |
+
goal9_5 = Goal(430,260,300,350)
|
| 37 |
+
goal10 = Goal(430,260,260,260)
|
| 38 |
+
goal10_5 = Goal(430,260,280,180)
|
| 39 |
+
goal11 = Goal(430,260,400,400)
|
| 40 |
+
goal12 = Goal(550,260,570,400)
|
| 41 |
+
goal13 = Goal(750,400,650,200)
|
| 42 |
+
goal14 = Goal(750,400,800,160)
|
| 43 |
+
goal15 = Goal(750,400,950,240)
|
| 44 |
+
goal16 = Goal(750,400,980,440)
|
| 45 |
+
goal17 = Goal(750,400,900,600)
|
| 46 |
+
goal18 = Goal(750,460,750,600)
|
| 47 |
+
goal19 = Goal(670,460,670,600)
|
| 48 |
+
goal19_5 = Goal(590,460,590,600)
|
| 49 |
+
goal20 = Goal(510,460,510,600)
|
| 50 |
+
goal20_5 = Goal(430,460,430,600)
|
| 51 |
+
goal21 = Goal(350,460,350,600)
|
| 52 |
+
goal21_5 = Goal(280,460,278,600)
|
| 53 |
+
goal22 = Goal(210,460,190,600)
|
| 54 |
+
goal22_5 = Goal(80,600,175,440)
|
| 55 |
+
goal23 = Goal(150,420,0,570)
|
| 56 |
+
goal23_5 = Goal(0,450,130,400)
|
| 57 |
+
goal24 = Goal(0,380,130,380)
|
| 58 |
+
|
| 59 |
+
goals.append(goal1)
|
| 60 |
+
goals.append(goal2)
|
| 61 |
+
goals.append(goal2_5)
|
| 62 |
+
goals.append(goal3)
|
| 63 |
+
goals.append(goal3_5)
|
| 64 |
+
goals.append(goal4)
|
| 65 |
+
goals.append(goal4_5)
|
| 66 |
+
goals.append(goal5)
|
| 67 |
+
goals.append(goal5_5)
|
| 68 |
+
goals.append(goal6)
|
| 69 |
+
goals.append(goal6_5)
|
| 70 |
+
goals.append(goal7)
|
| 71 |
+
goals.append(goal7_5)
|
| 72 |
+
goals.append(goal8)
|
| 73 |
+
goals.append(goal9)
|
| 74 |
+
goals.append(goal10_5)
|
| 75 |
+
goals.append(goal10)
|
| 76 |
+
goals.append(goal9_5)
|
| 77 |
+
goals.append(goal11)
|
| 78 |
+
goals.append(goal12)
|
| 79 |
+
goals.append(goal13)
|
| 80 |
+
goals.append(goal14)
|
| 81 |
+
goals.append(goal15)
|
| 82 |
+
goals.append(goal16)
|
| 83 |
+
goals.append(goal17)
|
| 84 |
+
goals.append(goal18)
|
| 85 |
+
goals.append(goal19)
|
| 86 |
+
goals.append(goal19_5)
|
| 87 |
+
goals.append(goal20)
|
| 88 |
+
goals.append(goal20_5)
|
| 89 |
+
goals.append(goal21)
|
| 90 |
+
goals.append(goal21_5)
|
| 91 |
+
goals.append(goal22)
|
| 92 |
+
goals.append(goal22_5)
|
| 93 |
+
goals.append(goal23)
|
| 94 |
+
goals.append(goal23_5)
|
| 95 |
+
goals.append(goal24)
|
| 96 |
+
|
| 97 |
+
goals[len(goals)-1].isactiv = True
|
| 98 |
+
|
| 99 |
+
return(goals)
|
FYP/Walls.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pygame
|
| 2 |
+
|
| 3 |
+
class Wall:
|
| 4 |
+
def __init__(self, x1, y1, x2, y2):
|
| 5 |
+
self.x1 = x1
|
| 6 |
+
self.y1 = y1
|
| 7 |
+
self.x2 = x2
|
| 8 |
+
self.y2 = y2
|
| 9 |
+
|
| 10 |
+
def draw(self, win):
|
| 11 |
+
pygame.draw.line(win, (255,255,255), (self.x1, self.y1), (self.x2, self.y2), 5)
|
| 12 |
+
|
| 13 |
+
def getWalls():
|
| 14 |
+
walls = []
|
| 15 |
+
|
| 16 |
+
wall1 = Wall(12, 451, 15, 130)
|
| 17 |
+
wall2 = Wall(15, 130, 61, 58)
|
| 18 |
+
wall3 = Wall(61, 58, 149, 14)
|
| 19 |
+
wall4 = Wall(149, 14, 382, 20)
|
| 20 |
+
wall5 = Wall(382, 20, 549, 31)
|
| 21 |
+
wall6 = Wall(549, 31, 636, 58)
|
| 22 |
+
wall7 = Wall(636, 58, 678, 102)
|
| 23 |
+
wall8 = Wall(678, 102, 669, 167)
|
| 24 |
+
wall9 = Wall(669, 167, 600, 206)
|
| 25 |
+
wall10 = Wall(600, 206, 507, 214)
|
| 26 |
+
wall11 = Wall(507, 214, 422, 232)
|
| 27 |
+
wall12 = Wall(422, 232, 375, 263)
|
| 28 |
+
wall13 = Wall(375, 263, 379, 283)
|
| 29 |
+
wall14 = Wall(379, 283, 454, 299)
|
| 30 |
+
wall15 = Wall(454, 299, 613, 286)
|
| 31 |
+
wall16 = Wall(613, 286, 684, 238)
|
| 32 |
+
wall17 = Wall(684, 238, 752, 180)
|
| 33 |
+
wall18 = Wall(752, 180, 862, 185)
|
| 34 |
+
wall19 = Wall(862, 185, 958, 279)
|
| 35 |
+
wall20 = Wall(958, 279, 953, 410)
|
| 36 |
+
wall21 = Wall(953, 410, 925, 505)
|
| 37 |
+
wall22 = Wall(925, 505, 804, 566)
|
| 38 |
+
wall23 = Wall(804, 566, 150, 570)
|
| 39 |
+
wall24 = Wall(150, 570, 46, 529)
|
| 40 |
+
wall25 = Wall(46, 529, 12, 451)
|
| 41 |
+
wall27 = Wall(104, 436, 96, 161)
|
| 42 |
+
wall28 = Wall(96, 161, 122, 122)
|
| 43 |
+
wall29 = Wall(122, 122, 199, 91)
|
| 44 |
+
wall30 = Wall(199, 91, 376, 94)
|
| 45 |
+
wall31 = Wall(376, 94, 469, 100)
|
| 46 |
+
wall32 = Wall(469, 100, 539, 102)
|
| 47 |
+
wall33 = Wall(539, 102, 585, 121)
|
| 48 |
+
wall34 = Wall(585, 121, 585, 139)
|
| 49 |
+
wall35 = Wall(585, 139, 454, 158)
|
| 50 |
+
wall36 = Wall(454, 158, 352, 183)
|
| 51 |
+
wall37 = Wall(352, 183, 293, 239)
|
| 52 |
+
wall38 = Wall(293, 239, 294, 318)
|
| 53 |
+
wall39 = Wall(294, 318, 361, 357)
|
| 54 |
+
wall40 = Wall(361, 357, 490, 373)
|
| 55 |
+
wall41 = Wall(490, 373, 671, 359)
|
| 56 |
+
wall42 = Wall(671, 359, 752, 300) #
|
| 57 |
+
wall43 = Wall(752, 300, 812, 310)#
|
| 58 |
+
wall44 = Wall(812, 310, 854, 369)
|
| 59 |
+
wall45 = Wall(854, 369, 854, 429)
|
| 60 |
+
wall46 = Wall(854, 429, 754, 483)
|
| 61 |
+
wall47 = Wall(754, 483, 192, 489)
|
| 62 |
+
wall48 = Wall(192, 489, 104, 436)
|
| 63 |
+
|
| 64 |
+
walls.append(wall1)
|
| 65 |
+
walls.append(wall2)
|
| 66 |
+
walls.append(wall3)
|
| 67 |
+
walls.append(wall4)
|
| 68 |
+
walls.append(wall5)
|
| 69 |
+
walls.append(wall6)
|
| 70 |
+
walls.append(wall7)
|
| 71 |
+
walls.append(wall8)
|
| 72 |
+
walls.append(wall9)
|
| 73 |
+
walls.append(wall10)
|
| 74 |
+
walls.append(wall11)
|
| 75 |
+
walls.append(wall12)
|
| 76 |
+
walls.append(wall13)
|
| 77 |
+
walls.append(wall14)
|
| 78 |
+
walls.append(wall15)
|
| 79 |
+
walls.append(wall16)
|
| 80 |
+
walls.append(wall17)
|
| 81 |
+
walls.append(wall18)
|
| 82 |
+
walls.append(wall19)
|
| 83 |
+
walls.append(wall20)
|
| 84 |
+
walls.append(wall21)
|
| 85 |
+
walls.append(wall22)
|
| 86 |
+
walls.append(wall23)
|
| 87 |
+
walls.append(wall24)
|
| 88 |
+
walls.append(wall25)
|
| 89 |
+
|
| 90 |
+
walls.append(wall27)
|
| 91 |
+
walls.append(wall28)
|
| 92 |
+
walls.append(wall29)
|
| 93 |
+
walls.append(wall30)
|
| 94 |
+
walls.append(wall31)
|
| 95 |
+
walls.append(wall32)
|
| 96 |
+
walls.append(wall33)
|
| 97 |
+
walls.append(wall34)
|
| 98 |
+
walls.append(wall35)
|
| 99 |
+
walls.append(wall36)
|
| 100 |
+
walls.append(wall37)
|
| 101 |
+
walls.append(wall38)
|
| 102 |
+
walls.append(wall39)
|
| 103 |
+
walls.append(wall40)
|
| 104 |
+
walls.append(wall41)
|
| 105 |
+
walls.append(wall42)
|
| 106 |
+
walls.append(wall43)
|
| 107 |
+
walls.append(wall44)
|
| 108 |
+
walls.append(wall45)
|
| 109 |
+
walls.append(wall46)
|
| 110 |
+
walls.append(wall47)
|
| 111 |
+
walls.append(wall48)
|
| 112 |
+
|
| 113 |
+
return(walls)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
|
FYP/car.png
ADDED
|
FYP/ddqn_keras.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# File: ddqn_keras.py
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import tensorflow as tf
|
| 5 |
+
from keras.models import Sequential, load_model
|
| 6 |
+
from keras.layers import Dense
|
| 7 |
+
from keras.optimizers import Adam
|
| 8 |
+
|
| 9 |
+
class ReplayBuffer:
|
| 10 |
+
def __init__(self, max_size, input_shape, n_actions, discrete=False):
|
| 11 |
+
self.mem_size = max_size
|
| 12 |
+
self.mem_cntr = 0
|
| 13 |
+
self.discrete = discrete
|
| 14 |
+
self.state_memory = np.zeros((self.mem_size, input_shape))
|
| 15 |
+
self.new_state_memory = np.zeros((self.mem_size, input_shape))
|
| 16 |
+
dtype = np.int8 if self.discrete else np.float32
|
| 17 |
+
self.action_memory = np.zeros((self.mem_size, n_actions), dtype=dtype)
|
| 18 |
+
self.reward_memory = np.zeros(self.mem_size)
|
| 19 |
+
self.terminal_memory = np.zeros(self.mem_size, dtype=np.float32)
|
| 20 |
+
|
| 21 |
+
def store_transition(self, state, action, reward, state_, done):
|
| 22 |
+
index = self.mem_cntr % self.mem_size
|
| 23 |
+
self.state_memory[index] = state
|
| 24 |
+
self.new_state_memory[index] = state_
|
| 25 |
+
if self.discrete:
|
| 26 |
+
actions = np.zeros(self.action_memory.shape[1])
|
| 27 |
+
actions[action] = 1.0
|
| 28 |
+
self.action_memory[index] = actions
|
| 29 |
+
else:
|
| 30 |
+
self.action_memory[index] = action
|
| 31 |
+
self.reward_memory[index] = reward
|
| 32 |
+
self.terminal_memory[index] = 1 - done
|
| 33 |
+
self.mem_cntr += 1
|
| 34 |
+
|
| 35 |
+
def sample_buffer(self, batch_size):
|
| 36 |
+
max_mem = min(self.mem_cntr, self.mem_size)
|
| 37 |
+
batch = np.random.choice(max_mem, batch_size)
|
| 38 |
+
|
| 39 |
+
states = self.state_memory[batch]
|
| 40 |
+
actions = self.action_memory[batch]
|
| 41 |
+
rewards = self.reward_memory[batch]
|
| 42 |
+
states_ = self.new_state_memory[batch]
|
| 43 |
+
terminal = self.terminal_memory[batch]
|
| 44 |
+
|
| 45 |
+
return states, actions, rewards, states_, terminal
|
| 46 |
+
|
| 47 |
+
class DDQNAgent:
|
| 48 |
+
def __init__(self, alpha, gamma, n_actions, epsilon, batch_size,
|
| 49 |
+
input_dims, epsilon_dec=0.999995, epsilon_end=0.10,
|
| 50 |
+
mem_size=25000, fname='ddqn_model.keras', replace_target=25):
|
| 51 |
+
self.action_space = [i for i in range(n_actions)]
|
| 52 |
+
self.n_actions = n_actions
|
| 53 |
+
self.gamma = gamma
|
| 54 |
+
self.epsilon = epsilon
|
| 55 |
+
self.epsilon_dec = epsilon_dec
|
| 56 |
+
self.epsilon_min = epsilon_end
|
| 57 |
+
self.batch_size = batch_size
|
| 58 |
+
self.model_file = fname
|
| 59 |
+
self.replace_target = replace_target
|
| 60 |
+
self.memory = ReplayBuffer(mem_size, input_dims, n_actions, discrete=True)
|
| 61 |
+
|
| 62 |
+
self.brain_eval = Brain(input_dims, n_actions, batch_size)
|
| 63 |
+
self.brain_target = Brain(input_dims, n_actions, batch_size)
|
| 64 |
+
|
| 65 |
+
def remember(self, state, action, reward, new_state, done):
|
| 66 |
+
self.memory.store_transition(state, action, reward, new_state, done)
|
| 67 |
+
|
| 68 |
+
def choose_action(self, state):
|
| 69 |
+
state = np.array(state)[np.newaxis, :]
|
| 70 |
+
rand = np.random.random()
|
| 71 |
+
if rand < self.epsilon:
|
| 72 |
+
action = np.random.choice(self.action_space)
|
| 73 |
+
else:
|
| 74 |
+
actions = self.brain_eval.model.predict(state, verbose=0)
|
| 75 |
+
action = np.argmax(actions)
|
| 76 |
+
return action
|
| 77 |
+
|
| 78 |
+
def learn(self):
|
| 79 |
+
if self.memory.mem_cntr > self.batch_size:
|
| 80 |
+
state, action, reward, new_state, done = self.memory.sample_buffer(self.batch_size)
|
| 81 |
+
action_indices = np.dot(action, np.array(self.action_space, dtype=np.int8))
|
| 82 |
+
|
| 83 |
+
q_next = self.brain_target.model.predict(new_state, verbose=0)
|
| 84 |
+
q_eval = self.brain_eval.model.predict(new_state, verbose=0)
|
| 85 |
+
q_pred = self.brain_eval.model.predict(state, verbose=0)
|
| 86 |
+
|
| 87 |
+
max_actions = np.argmax(q_eval, axis=1)
|
| 88 |
+
|
| 89 |
+
q_target = np.copy(q_pred)
|
| 90 |
+
batch_index = np.arange(self.batch_size, dtype=np.int32)
|
| 91 |
+
q_target[batch_index, action_indices] = reward + self.gamma * q_next[batch_index, max_actions.astype(int)] * done
|
| 92 |
+
|
| 93 |
+
self.brain_eval.model.fit(state, q_target, verbose=0)
|
| 94 |
+
self.epsilon = max(self.epsilon * self.epsilon_dec, self.epsilon_min)
|
| 95 |
+
|
| 96 |
+
def update_network_parameters(self):
|
| 97 |
+
self.brain_target.copy_weights(self.brain_eval)
|
| 98 |
+
|
| 99 |
+
def save_model(self):
|
| 100 |
+
self.brain_eval.model.save(self.model_file) # Save using .keras format
|
| 101 |
+
|
| 102 |
+
def load_model(self):
|
| 103 |
+
self.brain_eval.model = tf.keras.models.load_model(self.model_file)
|
| 104 |
+
self.brain_target.model = tf.keras.models.load_model(self.model_file)
|
| 105 |
+
if self.epsilon == 0.0:
|
| 106 |
+
self.update_network_parameters()
|
| 107 |
+
|
| 108 |
+
class Brain:
|
| 109 |
+
def __init__(self, NbrStates, NbrActions, batch_size=256):
|
| 110 |
+
self.NbrStates = NbrStates
|
| 111 |
+
self.NbrActions = NbrActions
|
| 112 |
+
self.batch_size = batch_size
|
| 113 |
+
self.model = self.createModel()
|
| 114 |
+
|
| 115 |
+
def createModel(self):
|
| 116 |
+
model = Sequential()
|
| 117 |
+
model.add(Dense(256, activation='relu', input_shape=(self.NbrStates,)))
|
| 118 |
+
model.add(Dense(self.NbrActions, activation=tf.keras.activations.softmax))
|
| 119 |
+
model.compile(loss='mse', optimizer=Adam(learning_rate=0.001))
|
| 120 |
+
return model
|
| 121 |
+
|
| 122 |
+
def copy_weights(self, TrainNet):
|
| 123 |
+
variables1 = self.model.trainable_variables
|
| 124 |
+
variables2 = TrainNet.model.trainable_variables
|
| 125 |
+
for v1, v2 in zip(variables1, variables2):
|
| 126 |
+
v1.assign(v2.numpy())
|
FYP/ddqn_model.keras
ADDED
|
Binary file (92.9 kB). View file
|
|
|
FYP/main.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import GameEnv
|
| 2 |
+
import numpy as np
|
| 3 |
+
from ddqn_keras import DDQNAgent
|
| 4 |
+
import pygame
|
| 5 |
+
import os
|
| 6 |
+
import tensorflow as tf
|
| 7 |
+
|
| 8 |
+
os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'
|
| 9 |
+
|
| 10 |
+
model_path = "ddqn_model.keras"
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
TOTAL_GAMETIME = 1000
|
| 15 |
+
N_EPISODES = 10000
|
| 16 |
+
REPLACE_TARGET = 50
|
| 17 |
+
|
| 18 |
+
game = GameEnv.RacingEnv()
|
| 19 |
+
game.fps = 60
|
| 20 |
+
|
| 21 |
+
ddqn_agent = DDQNAgent(alpha=0.0005, gamma=0.99, n_actions=5, epsilon=1.00,
|
| 22 |
+
epsilon_end=0.10, epsilon_dec=0.9995, replace_target=REPLACE_TARGET,
|
| 23 |
+
batch_size=512, input_dims=19, fname=model_path)
|
| 24 |
+
|
| 25 |
+
ddqn_scores = []
|
| 26 |
+
eps_history = []
|
| 27 |
+
best_score = float('-inf') # Store highest score only
|
| 28 |
+
|
| 29 |
+
def run():
|
| 30 |
+
global best_score
|
| 31 |
+
for e in range(N_EPISODES):
|
| 32 |
+
game.reset()
|
| 33 |
+
done = False
|
| 34 |
+
score = 0
|
| 35 |
+
counter = 0
|
| 36 |
+
observation_, reward, done = game.step(0)
|
| 37 |
+
observation = np.array(observation_)
|
| 38 |
+
gtime = 0
|
| 39 |
+
renderFlag = e % 10 == 0 and e > 0 # Render every 10 episodes
|
| 40 |
+
|
| 41 |
+
while not done:
|
| 42 |
+
for event in pygame.event.get():
|
| 43 |
+
if event.type == pygame.QUIT:
|
| 44 |
+
return
|
| 45 |
+
|
| 46 |
+
action = ddqn_agent.choose_action(observation)
|
| 47 |
+
observation_, reward, done = game.step(action)
|
| 48 |
+
observation_ = np.array(observation_)
|
| 49 |
+
|
| 50 |
+
if reward == 0:
|
| 51 |
+
counter += 1
|
| 52 |
+
if counter > 100:
|
| 53 |
+
done = True
|
| 54 |
+
else:
|
| 55 |
+
counter = 0
|
| 56 |
+
|
| 57 |
+
score += reward
|
| 58 |
+
ddqn_agent.remember(observation, action, reward, observation_, int(done))
|
| 59 |
+
observation = observation_
|
| 60 |
+
ddqn_agent.learn()
|
| 61 |
+
gtime += 1
|
| 62 |
+
if gtime >= TOTAL_GAMETIME:
|
| 63 |
+
done = True
|
| 64 |
+
if renderFlag:
|
| 65 |
+
game.render(action)
|
| 66 |
+
|
| 67 |
+
eps_history.append(ddqn_agent.epsilon)
|
| 68 |
+
ddqn_scores.append(score)
|
| 69 |
+
avg_score = np.mean(ddqn_scores[max(0, e-100):(e+1)])
|
| 70 |
+
|
| 71 |
+
# Save model only if it achieves a new highest score
|
| 72 |
+
if score > best_score:
|
| 73 |
+
best_score = score
|
| 74 |
+
ddqn_agent.save_model()
|
| 75 |
+
print(f"🎯 New best score {best_score}! Model saved.")
|
| 76 |
+
|
| 77 |
+
if e % REPLACE_TARGET == 0 and e > REPLACE_TARGET:
|
| 78 |
+
ddqn_agent.update_network_parameters()
|
| 79 |
+
|
| 80 |
+
print(f'Episode: {e}, Reward (Total): {int(score)}, Avg Reward: {int(avg_score)}, Best Reward: {int(best_score)}, Epsilon: {ddqn_agent.epsilon}')
|
| 81 |
+
run()
|
FYP/main_test_model.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import GameEnv
|
| 2 |
+
import numpy as np
|
| 3 |
+
from ddqn_keras import DDQNAgent
|
| 4 |
+
import pygame
|
| 5 |
+
import tensorflow as tf
|
| 6 |
+
|
| 7 |
+
# model_path = "ddqn_model.keras"
|
| 8 |
+
model_path="ddqn_model.keras"
|
| 9 |
+
|
| 10 |
+
try:
|
| 11 |
+
model = tf.keras.models.load_model(model_path)
|
| 12 |
+
print("Model loaded successfully!")
|
| 13 |
+
except Exception as e:
|
| 14 |
+
print(f" Failed to load model: {e}")
|
| 15 |
+
|
| 16 |
+
TOTAL_GAMETIME = 10000
|
| 17 |
+
N_EPISODES = 10000
|
| 18 |
+
REPLACE_TARGET = 10
|
| 19 |
+
|
| 20 |
+
game = GameEnv.RacingEnv()
|
| 21 |
+
game.fps = 60
|
| 22 |
+
|
| 23 |
+
ddqn_agent = DDQNAgent(alpha=0.0005, gamma=0.99, n_actions=5, epsilon=0.02,
|
| 24 |
+
epsilon_end=0.01, epsilon_dec=0.999, replace_target=REPLACE_TARGET,
|
| 25 |
+
batch_size=64, input_dims=19, fname=model_path)
|
| 26 |
+
|
| 27 |
+
ddqn_agent.load_model()
|
| 28 |
+
ddqn_agent.update_network_parameters()
|
| 29 |
+
|
| 30 |
+
def run():
|
| 31 |
+
for e in range(N_EPISODES):
|
| 32 |
+
game.reset()
|
| 33 |
+
done = False
|
| 34 |
+
score = 0
|
| 35 |
+
counter = 0
|
| 36 |
+
gtime = 0
|
| 37 |
+
observation_, reward, done = game.step(0)
|
| 38 |
+
observation = np.array(observation_)
|
| 39 |
+
|
| 40 |
+
while not done:
|
| 41 |
+
for event in pygame.event.get():
|
| 42 |
+
if event.type == pygame.QUIT:
|
| 43 |
+
return
|
| 44 |
+
|
| 45 |
+
action = ddqn_agent.choose_action(observation)
|
| 46 |
+
observation_, reward, done = game.step(action)
|
| 47 |
+
observation_ = np.array(observation_)
|
| 48 |
+
|
| 49 |
+
if reward == 0:
|
| 50 |
+
counter += 1
|
| 51 |
+
if counter > 100:
|
| 52 |
+
done = True
|
| 53 |
+
else:
|
| 54 |
+
counter = 0
|
| 55 |
+
|
| 56 |
+
score += reward
|
| 57 |
+
observation = observation_
|
| 58 |
+
gtime += 1
|
| 59 |
+
if gtime >= TOTAL_GAMETIME:
|
| 60 |
+
done = True
|
| 61 |
+
game.render(action)
|
| 62 |
+
# if score > best_score:
|
| 63 |
+
# best_score = score
|
| 64 |
+
# ddqn_agent.save_model()
|
| 65 |
+
# print(f" New best score {best_score}! Model saved.")
|
| 66 |
+
|
| 67 |
+
print(f"Episode: {e}, Reward: {score}")
|
| 68 |
+
|
| 69 |
+
run()
|
FYP/requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pygame
|
| 2 |
+
numpy
|
| 3 |
+
tensorflow
|
FYP/track.png
ADDED
|
README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: IMS
|
| 3 |
+
emoji: 🔥
|
| 4 |
+
colorFrom: pink
|
| 5 |
+
colorTo: gray
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
fyp1/best_neat_genome.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:166b4658e68cab4f72d9ea7c248081aa13ac34d447a2df4bfe26fbed975c5274
|
| 3 |
+
size 1199
|
fyp1/car.png
ADDED
|
fyp1/config.txt
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[NEAT]
|
| 2 |
+
fitness_criterion = max
|
| 3 |
+
fitness_threshold = 100000000
|
| 4 |
+
pop_size = 30
|
| 5 |
+
reset_on_extinction = True
|
| 6 |
+
|
| 7 |
+
[DefaultGenome]
|
| 8 |
+
# node activation options
|
| 9 |
+
activation_default = tanh
|
| 10 |
+
activation_mutate_rate = 0.01
|
| 11 |
+
activation_options = tanh
|
| 12 |
+
|
| 13 |
+
# node aggregation options
|
| 14 |
+
aggregation_default = sum
|
| 15 |
+
aggregation_mutate_rate = 0.01
|
| 16 |
+
aggregation_options = sum
|
| 17 |
+
|
| 18 |
+
# node bias options
|
| 19 |
+
bias_init_mean = 0.0
|
| 20 |
+
bias_init_stdev = 1.0
|
| 21 |
+
bias_max_value = 30.0
|
| 22 |
+
bias_min_value = -30.0
|
| 23 |
+
bias_mutate_power = 0.5
|
| 24 |
+
bias_mutate_rate = 0.7
|
| 25 |
+
bias_replace_rate = 0.1
|
| 26 |
+
|
| 27 |
+
# genome compatibility options
|
| 28 |
+
compatibility_disjoint_coefficient = 1.0
|
| 29 |
+
compatibility_weight_coefficient = 0.5
|
| 30 |
+
|
| 31 |
+
# connection add/remove rates
|
| 32 |
+
conn_add_prob = 0.5
|
| 33 |
+
conn_delete_prob = 0.5
|
| 34 |
+
|
| 35 |
+
# connection enable options
|
| 36 |
+
enabled_default = True
|
| 37 |
+
enabled_mutate_rate = 0.01
|
| 38 |
+
|
| 39 |
+
feed_forward = True
|
| 40 |
+
initial_connection = full
|
| 41 |
+
|
| 42 |
+
# node add/remove rates
|
| 43 |
+
node_add_prob = 0.2
|
| 44 |
+
node_delete_prob = 0.2
|
| 45 |
+
|
| 46 |
+
# network parameters
|
| 47 |
+
num_hidden = 0
|
| 48 |
+
num_inputs = 5
|
| 49 |
+
num_outputs = 4
|
| 50 |
+
|
| 51 |
+
# node response options
|
| 52 |
+
response_init_mean = 1.0
|
| 53 |
+
response_init_stdev = 0.0
|
| 54 |
+
response_max_value = 30.0
|
| 55 |
+
response_min_value = -30.0
|
| 56 |
+
response_mutate_power = 0.0
|
| 57 |
+
response_mutate_rate = 0.0
|
| 58 |
+
response_replace_rate = 0.0
|
| 59 |
+
|
| 60 |
+
# connection weight options
|
| 61 |
+
weight_init_mean = 0.0
|
| 62 |
+
weight_init_stdev = 1.0
|
| 63 |
+
weight_max_value = 30
|
| 64 |
+
weight_min_value = -30
|
| 65 |
+
weight_mutate_power = 0.5
|
| 66 |
+
weight_mutate_rate = 0.8
|
| 67 |
+
weight_replace_rate = 0.1
|
| 68 |
+
|
| 69 |
+
[DefaultSpeciesSet]
|
| 70 |
+
compatibility_threshold = 2.0
|
| 71 |
+
|
| 72 |
+
[DefaultStagnation]
|
| 73 |
+
species_fitness_func = max
|
| 74 |
+
max_stagnation = 20
|
| 75 |
+
species_elitism = 2
|
| 76 |
+
|
| 77 |
+
[DefaultReproduction]
|
| 78 |
+
elitism = 3
|
| 79 |
+
survival_threshold = 0.2
|
fyp1/map.png
ADDED
|
fyp1/map2.png
ADDED
|
fyp1/model_testing_NEAT.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import neat
|
| 2 |
+
import pickle
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
# Import your simulation function
|
| 6 |
+
from newcar import run_simulation # Make sure run_simulation accepts ([(genome_id, genome)], config)
|
| 7 |
+
|
| 8 |
+
# Paths
|
| 9 |
+
config_path = "config.txt"
|
| 10 |
+
genome_path = "best_neat_genome.pkl"
|
| 11 |
+
|
| 12 |
+
# Load NEAT config
|
| 13 |
+
config = neat.config.Config(
|
| 14 |
+
neat.DefaultGenome,
|
| 15 |
+
neat.DefaultReproduction,
|
| 16 |
+
neat.DefaultSpeciesSet,
|
| 17 |
+
neat.DefaultStagnation,
|
| 18 |
+
config_path
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
# Load the best genome
|
| 22 |
+
if not os.path.exists(genome_path):
|
| 23 |
+
raise FileNotFoundError(f"Best genome file not found at {genome_path}")
|
| 24 |
+
|
| 25 |
+
with open(genome_path, "rb") as f:
|
| 26 |
+
best_genome = pickle.load(f)
|
| 27 |
+
|
| 28 |
+
# print("\n✅ Best genome loaded successfully!")
|
| 29 |
+
# print(f"Fitness: {best_genome.fitness}")
|
| 30 |
+
|
| 31 |
+
# Wrap the genome in the expected format: list of tuples (genome_id, genome)
|
| 32 |
+
test_genomes = [(0, best_genome)]
|
| 33 |
+
|
| 34 |
+
# Run the simulation for testing
|
| 35 |
+
run_simulation(test_genomes, config)
|
fyp1/newcar.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import random
|
| 3 |
+
import sys
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
import neat
|
| 7 |
+
import pygame
|
| 8 |
+
|
| 9 |
+
# Constants
|
| 10 |
+
#WIDTH = 1500
|
| 11 |
+
#HEIGHT = 800
|
| 12 |
+
|
| 13 |
+
WIDTH = 1920
|
| 14 |
+
HEIGHT = 1080
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
CAR_SIZE_X = 60
|
| 19 |
+
CAR_SIZE_Y = 60
|
| 20 |
+
|
| 21 |
+
BORDER_COLOR = (255, 255, 255, 255) # Color To Crash on Hit
|
| 22 |
+
|
| 23 |
+
current_generation = 0 # Generation counter
|
| 24 |
+
|
| 25 |
+
class Car:
|
| 26 |
+
|
| 27 |
+
def __init__(self):
|
| 28 |
+
# Load Car Sprite and Rotate
|
| 29 |
+
self.sprite = pygame.image.load('car.png').convert() # Convert Speeds Up A Lot
|
| 30 |
+
self.sprite = pygame.transform.scale(self.sprite, (CAR_SIZE_X, CAR_SIZE_Y))
|
| 31 |
+
self.rotated_sprite = self.sprite
|
| 32 |
+
|
| 33 |
+
# self.position = [690, 740] # Starting Position
|
| 34 |
+
self.position = [830, 920] # Starting Position
|
| 35 |
+
self.angle = 0
|
| 36 |
+
self.speed = 0
|
| 37 |
+
|
| 38 |
+
self.speed_set = False # Flag For Default Speed Later on
|
| 39 |
+
|
| 40 |
+
self.center = [self.position[0] + CAR_SIZE_X / 2, self.position[1] + CAR_SIZE_Y / 2] # Calculate Center
|
| 41 |
+
|
| 42 |
+
self.radars = [] # List For Sensors / Radars
|
| 43 |
+
self.drawing_radars = [] # Radars To Be Drawn
|
| 44 |
+
|
| 45 |
+
self.alive = True # Boolean To Check If Car is Crashed
|
| 46 |
+
|
| 47 |
+
self.distance = 0 # Distance Driven
|
| 48 |
+
self.time = 0 # Time Passed
|
| 49 |
+
|
| 50 |
+
def draw(self, screen):
|
| 51 |
+
screen.blit(self.rotated_sprite, self.position) # Draw Sprite
|
| 52 |
+
self.draw_radar(screen) #OPTIONAL FOR SENSORS
|
| 53 |
+
|
| 54 |
+
def draw_radar(self, screen):
|
| 55 |
+
# Optionally Draw All Sensors / Radars
|
| 56 |
+
for radar in self.radars:
|
| 57 |
+
position = radar[0]
|
| 58 |
+
pygame.draw.line(screen, (0, 255, 0), self.center, position, 1)
|
| 59 |
+
pygame.draw.circle(screen, (0, 255, 0), position, 5)
|
| 60 |
+
|
| 61 |
+
def check_collision(self, game_map):
|
| 62 |
+
self.alive = True
|
| 63 |
+
for point in self.corners:
|
| 64 |
+
# If Any Corner Touches Border Color -> Crash
|
| 65 |
+
# Assumes Rectangle
|
| 66 |
+
if game_map.get_at((int(point[0]), int(point[1]))) == BORDER_COLOR:
|
| 67 |
+
self.alive = False
|
| 68 |
+
break
|
| 69 |
+
|
| 70 |
+
def check_radar(self, degree, game_map):
|
| 71 |
+
length = 0
|
| 72 |
+
x = int(self.center[0] + math.cos(math.radians(360 - (self.angle + degree))) * length)
|
| 73 |
+
y = int(self.center[1] + math.sin(math.radians(360 - (self.angle + degree))) * length)
|
| 74 |
+
|
| 75 |
+
# While We Don't Hit BORDER_COLOR AND length < 300 (just a max) -> go further and further
|
| 76 |
+
while not game_map.get_at((x, y)) == BORDER_COLOR and length < 300:
|
| 77 |
+
length = length + 1
|
| 78 |
+
x = int(self.center[0] + math.cos(math.radians(360 - (self.angle + degree))) * length)
|
| 79 |
+
y = int(self.center[1] + math.sin(math.radians(360 - (self.angle + degree))) * length)
|
| 80 |
+
|
| 81 |
+
# Calculate Distance To Border And Append To Radars List
|
| 82 |
+
dist = int(math.sqrt(math.pow(x - self.center[0], 2) + math.pow(y - self.center[1], 2)))
|
| 83 |
+
self.radars.append([(x, y), dist])
|
| 84 |
+
|
| 85 |
+
def update(self, game_map):
|
| 86 |
+
# Set The Speed To 20 For The First Time
|
| 87 |
+
# Only When Having 4 Output Nodes With Speed Up and Down
|
| 88 |
+
if not self.speed_set:
|
| 89 |
+
self.speed = 20
|
| 90 |
+
self.speed_set = True
|
| 91 |
+
|
| 92 |
+
# Get Rotated Sprite And Move Into The Right X-Direction
|
| 93 |
+
# Don't Let The Car Go Closer Than 20px To The Edge
|
| 94 |
+
self.rotated_sprite = self.rotate_center(self.sprite, self.angle)
|
| 95 |
+
self.position[0] += math.cos(math.radians(360 - self.angle)) * self.speed
|
| 96 |
+
self.position[0] = max(self.position[0], 20)
|
| 97 |
+
self.position[0] = min(self.position[0], WIDTH - 120)
|
| 98 |
+
|
| 99 |
+
# Increase Distance and Time
|
| 100 |
+
self.distance += self.speed
|
| 101 |
+
self.time += 1
|
| 102 |
+
|
| 103 |
+
# Same For Y-Position
|
| 104 |
+
self.position[1] += math.sin(math.radians(360 - self.angle)) * self.speed
|
| 105 |
+
self.position[1] = max(self.position[1], 20)
|
| 106 |
+
self.position[1] = min(self.position[1], WIDTH - 120)
|
| 107 |
+
|
| 108 |
+
# Calculate New Center
|
| 109 |
+
self.center = [int(self.position[0]) + CAR_SIZE_X / 2, int(self.position[1]) + CAR_SIZE_Y / 2]
|
| 110 |
+
|
| 111 |
+
# Calculate Four Corners
|
| 112 |
+
# Length Is Half The Side
|
| 113 |
+
length = 0.5 * CAR_SIZE_X
|
| 114 |
+
left_top = [self.center[0] + math.cos(math.radians(360 - (self.angle + 30))) * length, self.center[1] + math.sin(math.radians(360 - (self.angle + 30))) * length]
|
| 115 |
+
right_top = [self.center[0] + math.cos(math.radians(360 - (self.angle + 150))) * length, self.center[1] + math.sin(math.radians(360 - (self.angle + 150))) * length]
|
| 116 |
+
left_bottom = [self.center[0] + math.cos(math.radians(360 - (self.angle + 210))) * length, self.center[1] + math.sin(math.radians(360 - (self.angle + 210))) * length]
|
| 117 |
+
right_bottom = [self.center[0] + math.cos(math.radians(360 - (self.angle + 330))) * length, self.center[1] + math.sin(math.radians(360 - (self.angle + 330))) * length]
|
| 118 |
+
self.corners = [left_top, right_top, left_bottom, right_bottom]
|
| 119 |
+
|
| 120 |
+
# Check Collisions And Clear Radars
|
| 121 |
+
self.check_collision(game_map)
|
| 122 |
+
self.radars.clear()
|
| 123 |
+
|
| 124 |
+
# From -90 To 120 With Step-Size 45 Check Radar
|
| 125 |
+
for d in range(-90, 120, 45):
|
| 126 |
+
self.check_radar(d, game_map)
|
| 127 |
+
|
| 128 |
+
def get_data(self):
|
| 129 |
+
# Get Distances To Border
|
| 130 |
+
radars = self.radars
|
| 131 |
+
return_values = [0, 0, 0, 0, 0]
|
| 132 |
+
for i, radar in enumerate(radars):
|
| 133 |
+
return_values[i] = int(radar[1] / 30)
|
| 134 |
+
|
| 135 |
+
return return_values
|
| 136 |
+
|
| 137 |
+
def is_alive(self):
|
| 138 |
+
# Basic Alive Function
|
| 139 |
+
return self.alive
|
| 140 |
+
|
| 141 |
+
def get_reward(self):
|
| 142 |
+
# Calculate Reward (Maybe Change?)
|
| 143 |
+
return self.distance / 50.0
|
| 144 |
+
# return self.distance / (CAR_SIZE_X / 2)
|
| 145 |
+
|
| 146 |
+
def rotate_center(self, image, angle):
|
| 147 |
+
# Rotate The Rectangle
|
| 148 |
+
rectangle = image.get_rect()
|
| 149 |
+
rotated_image = pygame.transform.rotate(image, angle)
|
| 150 |
+
rotated_rectangle = rectangle.copy()
|
| 151 |
+
rotated_rectangle.center = rotated_image.get_rect().center
|
| 152 |
+
rotated_image = rotated_image.subsurface(rotated_rectangle).copy()
|
| 153 |
+
return rotated_image
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def run_simulation(genomes, config):
|
| 157 |
+
|
| 158 |
+
# Empty Collections For Nets and Cars
|
| 159 |
+
nets = []
|
| 160 |
+
cars = []
|
| 161 |
+
|
| 162 |
+
# Initialize PyGame And The Display
|
| 163 |
+
pygame.init()
|
| 164 |
+
screen = pygame.display.set_mode((WIDTH, HEIGHT))
|
| 165 |
+
|
| 166 |
+
# For All Genomes Passed Create A New Neural Network
|
| 167 |
+
for i, g in genomes:
|
| 168 |
+
net = neat.nn.FeedForwardNetwork.create(g, config)
|
| 169 |
+
nets.append(net)
|
| 170 |
+
g.fitness = 0
|
| 171 |
+
|
| 172 |
+
cars.append(Car())
|
| 173 |
+
|
| 174 |
+
# Clock Settings
|
| 175 |
+
# Font Settings & Loading Map
|
| 176 |
+
clock = pygame.time.Clock()
|
| 177 |
+
generation_font = pygame.font.SysFont("Arial", 30)
|
| 178 |
+
alive_font = pygame.font.SysFont("Arial", 20)
|
| 179 |
+
game_map = pygame.image.load('map2.png').convert() # Convert Speeds Up A Lot
|
| 180 |
+
|
| 181 |
+
global current_generation
|
| 182 |
+
current_generation += 1
|
| 183 |
+
|
| 184 |
+
# Simple Counter To Roughly Limit Time (Not Good Practice)
|
| 185 |
+
counter = 0
|
| 186 |
+
|
| 187 |
+
while True:
|
| 188 |
+
# Exit On Quit Event
|
| 189 |
+
for event in pygame.event.get():
|
| 190 |
+
if event.type == pygame.QUIT:
|
| 191 |
+
sys.exit(0)
|
| 192 |
+
|
| 193 |
+
# For Each Car Get The Acton It Takes
|
| 194 |
+
for i, car in enumerate(cars):
|
| 195 |
+
output = nets[i].activate(car.get_data())
|
| 196 |
+
choice = output.index(max(output))
|
| 197 |
+
if choice == 0:
|
| 198 |
+
car.angle += 10 # Left
|
| 199 |
+
elif choice == 1:
|
| 200 |
+
car.angle -= 10 # Right
|
| 201 |
+
elif choice == 2:
|
| 202 |
+
if(car.speed - 2 >= 12):
|
| 203 |
+
car.speed -= 2 # Slow Down
|
| 204 |
+
else:
|
| 205 |
+
car.speed += 2 # Speed Up
|
| 206 |
+
|
| 207 |
+
# Check If Car Is Still Alive
|
| 208 |
+
# Increase Fitness If Yes And Break Loop If Not
|
| 209 |
+
still_alive = 0
|
| 210 |
+
for i, car in enumerate(cars):
|
| 211 |
+
if car.is_alive():
|
| 212 |
+
still_alive += 1
|
| 213 |
+
car.update(_map)
|
| 214 |
+
genomes[i][1].fitness += car.get_reward()
|
| 215 |
+
|
| 216 |
+
if still_alive == 0:
|
| 217 |
+
break
|
| 218 |
+
|
| 219 |
+
counter += 1
|
| 220 |
+
if counter == 30 * 40: # Stop After About 20 Seconds
|
| 221 |
+
break
|
| 222 |
+
|
| 223 |
+
# Draw Map And All Cars That Are Alive
|
| 224 |
+
screen.blit(game_map, (0, 0))
|
| 225 |
+
for car in cars:
|
| 226 |
+
if car.is_alive():
|
| 227 |
+
car.draw(screen)
|
| 228 |
+
|
| 229 |
+
# Display Info
|
| 230 |
+
text = generation_font.render("Generation: " + str(current_generation), True, (0,0,0))
|
| 231 |
+
text_rect = text.get_rect()
|
| 232 |
+
text_rect.center = (900, 450)
|
| 233 |
+
screen.blit(text, text_rect)
|
| 234 |
+
|
| 235 |
+
text = alive_font.render("Still Alive: " + str(still_alive), True, (0, 0, 0))
|
| 236 |
+
text_rect = text.get_rect()
|
| 237 |
+
text_rect.center = (900, 490)
|
| 238 |
+
screen.blit(text, text_rect)
|
| 239 |
+
|
| 240 |
+
pygame.display.flip()
|
| 241 |
+
clock.tick(30) # 60 FPS
|
| 242 |
+
|
| 243 |
+
if __name__ == "__main__":
|
| 244 |
+
import pickle
|
| 245 |
+
import os
|
| 246 |
+
|
| 247 |
+
# Load Config
|
| 248 |
+
config_path = "config.txt"
|
| 249 |
+
config = neat.config.Config(neat.DefaultGenome,
|
| 250 |
+
neat.DefaultReproduction,
|
| 251 |
+
neat.DefaultSpeciesSet,
|
| 252 |
+
neat.DefaultStagnation,
|
| 253 |
+
config_path)
|
| 254 |
+
|
| 255 |
+
# Create Population And Add Reporters
|
| 256 |
+
population = neat.Population(config)
|
| 257 |
+
population.add_reporter(neat.StdOutReporter(True))
|
| 258 |
+
stats = neat.StatisticsReporter()
|
| 259 |
+
population.add_reporter(stats)
|
| 260 |
+
|
| 261 |
+
# Path to store best genome
|
| 262 |
+
best_genome_path = "best_neat_genome.pkl"
|
| 263 |
+
|
| 264 |
+
# Function to save genome
|
| 265 |
+
def save_best_genome(genome):
|
| 266 |
+
with open(best_genome_path, "wb") as f:
|
| 267 |
+
pickle.dump(genome, f)
|
| 268 |
+
print("\n✅ Best genome saved!\n")
|
| 269 |
+
|
| 270 |
+
# Load previous best genome if exists
|
| 271 |
+
best_fitness_so_far = -1
|
| 272 |
+
if os.path.exists(best_genome_path):
|
| 273 |
+
with open(best_genome_path, "rb") as f:
|
| 274 |
+
old_best = pickle.load(f)
|
| 275 |
+
best_fitness_so_far = old_best.fitness if hasattr(old_best, "fitness") else -1
|
| 276 |
+
print(f"\n📂 Loaded previous best fitness: {best_fitness_so_far}\n")
|
| 277 |
+
|
| 278 |
+
# Run NEAT algorithm
|
| 279 |
+
def eval_genomes(genomes, config):
|
| 280 |
+
global best_fitness_so_far
|
| 281 |
+
run_simulation(genomes, config)
|
| 282 |
+
for genome_id, genome in genomes:
|
| 283 |
+
if genome.fitness > best_fitness_so_far:
|
| 284 |
+
best_fitness_so_far = genome.fitness
|
| 285 |
+
save_best_genome(genome)
|
| 286 |
+
|
| 287 |
+
# Run NEAT with our eval function
|
| 288 |
+
population.run(eval_genomes, 1000)
|
fyp1/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
neat-python
|
| 2 |
+
pygame
|
gui/.dockerignore
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.git
|
| 2 |
+
.gitignore
|
| 3 |
+
.dockerignore
|
| 4 |
+
__pycache__
|
| 5 |
+
*.pyc
|
| 6 |
+
*.pyo
|
| 7 |
+
*.pyd
|
| 8 |
+
.env
|
| 9 |
+
db.sqlite3
|
gui/.gitignore
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Environment variables
|
| 2 |
+
.env
|
| 3 |
+
|
| 4 |
+
# Django
|
| 5 |
+
db.sqlite3
|
| 6 |
+
|
| 7 |
+
# Python
|
| 8 |
+
__pycache__/
|
| 9 |
+
*.pyc
|
gui/Dockerfile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use an official Python runtime as a parent image
|
| 2 |
+
FROM python:3.12
|
| 3 |
+
|
| 4 |
+
# Set the working directory in the container
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Copy the requirements file into the container
|
| 8 |
+
COPY requirements.txt .
|
| 9 |
+
|
| 10 |
+
# Install any needed packages specified in requirements.txt
|
| 11 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 12 |
+
|
| 13 |
+
# Copy the rest of the application's code into the container
|
| 14 |
+
COPY . .
|
| 15 |
+
|
| 16 |
+
# Collect static files
|
| 17 |
+
RUN python manage.py collectstatic --no-input
|
| 18 |
+
|
| 19 |
+
# Expose the port the app runs on
|
| 20 |
+
EXPOSE 8000
|
| 21 |
+
|
| 22 |
+
# Run the application
|
| 23 |
+
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "gui.wsgi"]
|
gui/gui/__init__.py
ADDED
|
File without changes
|
gui/gui/asgi.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
ASGI config for gui project.
|
| 3 |
+
|
| 4 |
+
It exposes the ASGI callable as a module-level variable named ``application``.
|
| 5 |
+
|
| 6 |
+
For more information on this file, see
|
| 7 |
+
https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import os
|
| 11 |
+
|
| 12 |
+
from django.core.asgi import get_asgi_application
|
| 13 |
+
|
| 14 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gui.settings')
|
| 15 |
+
|
| 16 |
+
application = get_asgi_application()
|
gui/gui/settings.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Django settings for gui project.
|
| 3 |
+
|
| 4 |
+
Generated by 'django-admin startproject' using Django 5.1.
|
| 5 |
+
|
| 6 |
+
For more information on this file, see
|
| 7 |
+
https://docs.djangoproject.com/en/5.1/topics/settings/
|
| 8 |
+
|
| 9 |
+
For the full list of settings and their values, see
|
| 10 |
+
https://docs.djangoproject.com/en/5.1/ref/settings/
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
from decouple import config
|
| 15 |
+
|
| 16 |
+
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
| 17 |
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# Quick-start development settings - unsuitable for production
|
| 21 |
+
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
|
| 22 |
+
|
| 23 |
+
# SECURITY WARNING: keep the secret key used in production secret!
|
| 24 |
+
SECRET_KEY = config('SECRET_KEY')
|
| 25 |
+
|
| 26 |
+
# SECURITY WARNING: don't run with debug turned on in production!
|
| 27 |
+
DEBUG = config('DEBUG', default=False, cast=bool)
|
| 28 |
+
|
| 29 |
+
ALLOWED_HOSTS = ['*']
|
| 30 |
+
|
| 31 |
+
# Application definition
|
| 32 |
+
|
| 33 |
+
INSTALLED_APPS = [
|
| 34 |
+
'django.contrib.admin',
|
| 35 |
+
'django.contrib.auth',
|
| 36 |
+
'django.contrib.contenttypes',
|
| 37 |
+
'django.contrib.sessions',
|
| 38 |
+
'django.contrib.messages',
|
| 39 |
+
'django.contrib.staticfiles',
|
| 40 |
+
'interface', # Your app
|
| 41 |
+
]
|
| 42 |
+
|
| 43 |
+
MIDDLEWARE = [
|
| 44 |
+
'django.middleware.security.SecurityMiddleware',
|
| 45 |
+
'whitenoise.middleware.WhiteNoiseMiddleware',
|
| 46 |
+
'django.contrib.sessions.middleware.SessionMiddleware',
|
| 47 |
+
'django.middleware.common.CommonMiddleware',
|
| 48 |
+
'django.middleware.csrf.CsrfViewMiddleware',
|
| 49 |
+
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
| 50 |
+
'django.contrib.messages.middleware.MessageMiddleware',
|
| 51 |
+
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
| 52 |
+
]
|
| 53 |
+
|
| 54 |
+
ROOT_URLCONF = 'gui.urls'
|
| 55 |
+
|
| 56 |
+
TEMPLATES = [
|
| 57 |
+
{
|
| 58 |
+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
| 59 |
+
'DIRS': [BASE_DIR / 'templates'], # Add your templates directory here
|
| 60 |
+
'APP_DIRS': True,
|
| 61 |
+
'OPTIONS': {
|
| 62 |
+
'context_processors': [
|
| 63 |
+
'django.template.context_processors.debug',
|
| 64 |
+
'django.template.context_processors.request',
|
| 65 |
+
'django.contrib.auth.context_processors.auth',
|
| 66 |
+
'django.contrib.messages.context_processors.messages',
|
| 67 |
+
],
|
| 68 |
+
},
|
| 69 |
+
},
|
| 70 |
+
]
|
| 71 |
+
|
| 72 |
+
WSGI_APPLICATION = 'gui.wsgi.application'
|
| 73 |
+
|
| 74 |
+
# Database
|
| 75 |
+
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
|
| 76 |
+
|
| 77 |
+
DATABASES = {
|
| 78 |
+
'default': {
|
| 79 |
+
'ENGINE': 'django.db.backends.sqlite3',
|
| 80 |
+
'NAME': BASE_DIR / 'db.sqlite3',
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
# Password validation
|
| 85 |
+
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
|
| 86 |
+
|
| 87 |
+
AUTH_PASSWORD_VALIDATORS = [
|
| 88 |
+
{
|
| 89 |
+
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
| 90 |
+
},
|
| 91 |
+
{
|
| 92 |
+
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
| 93 |
+
},
|
| 94 |
+
{
|
| 95 |
+
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
| 99 |
+
},
|
| 100 |
+
]
|
| 101 |
+
|
| 102 |
+
# Internationalization
|
| 103 |
+
# https://docs.djangoproject.com/en/5.1/topics/i18n/
|
| 104 |
+
|
| 105 |
+
LANGUAGE_CODE = 'en-us'
|
| 106 |
+
|
| 107 |
+
TIME_ZONE = 'UTC'
|
| 108 |
+
|
| 109 |
+
USE_I18N = True
|
| 110 |
+
|
| 111 |
+
USE_TZ = True
|
| 112 |
+
|
| 113 |
+
# Static files (CSS, JavaScript, Images)
|
| 114 |
+
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
| 115 |
+
|
| 116 |
+
STATIC_URL = '/static/' # Change to include a leading slash
|
| 117 |
+
STATICFILES_DIRS = [BASE_DIR / 'static'] # Ensure Django knows where to find static files
|
| 118 |
+
|
| 119 |
+
# Default primary key field type
|
| 120 |
+
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
| 121 |
+
|
| 122 |
+
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
| 123 |
+
|
| 124 |
+
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
gui/gui/urls.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
URL configuration for gui project.
|
| 3 |
+
|
| 4 |
+
The `urlpatterns` list routes URLs to views. For more information please see:
|
| 5 |
+
https://docs.djangoproject.com/en/5.1/topics/http/urls/
|
| 6 |
+
Examples:
|
| 7 |
+
Function views
|
| 8 |
+
1. Add an import: from my_app import views
|
| 9 |
+
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
| 10 |
+
Class-based views
|
| 11 |
+
1. Add an import: from other_app.views import Home
|
| 12 |
+
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
| 13 |
+
Including another URLconf
|
| 14 |
+
1. Import the include() function: from django.urls import include, path
|
| 15 |
+
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
| 16 |
+
"""
|
| 17 |
+
from django.contrib import admin
|
| 18 |
+
from django.urls import path
|
| 19 |
+
from interface.views import index, about, contact,run_neat_simulation,run_ddqn_simulation
|
| 20 |
+
|
| 21 |
+
urlpatterns = [
|
| 22 |
+
path('admin/', admin.site.urls),
|
| 23 |
+
path('', index, name='index'),
|
| 24 |
+
path('about/', about, name='about'),
|
| 25 |
+
path('contact/', contact, name='contact'),
|
| 26 |
+
path('run-neat/', run_neat_simulation, name='run_neat'),
|
| 27 |
+
path('run-ddqn/', run_ddqn_simulation, name='run_ddqn'),
|
| 28 |
+
]
|
gui/gui/wsgi.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
WSGI config for gui project.
|
| 3 |
+
|
| 4 |
+
It exposes the WSGI callable as a module-level variable named ``application``.
|
| 5 |
+
|
| 6 |
+
For more information on this file, see
|
| 7 |
+
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import os
|
| 11 |
+
|
| 12 |
+
from django.core.wsgi import get_wsgi_application
|
| 13 |
+
|
| 14 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gui.settings')
|
| 15 |
+
|
| 16 |
+
application = get_wsgi_application()
|
gui/interface/__init__.py
ADDED
|
File without changes
|
gui/interface/admin.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.contrib import admin
|
| 2 |
+
|
| 3 |
+
# Register your models here.
|
gui/interface/apps.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.apps import AppConfig
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class InterfaceConfig(AppConfig):
|
| 5 |
+
default_auto_field = 'django.db.models.BigAutoField'
|
| 6 |
+
name = 'interface'
|
gui/interface/migrations/__init__.py
ADDED
|
File without changes
|
gui/interface/models.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.db import models
|
| 2 |
+
|
| 3 |
+
# Create your models here.
|
gui/interface/simulations/ddqn_simulation.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import numpy as np
|
| 3 |
+
import pygame
|
| 4 |
+
import tensorflow as tf
|
| 5 |
+
from .fyp_simulation import GameEnv
|
| 6 |
+
from .fyp_simulation.ddqn_keras import DDQNAgent
|
| 7 |
+
|
| 8 |
+
# model_path = "ddqn_model.keras"
|
| 9 |
+
model_path = os.path.join(os.path.dirname(__file__), 'fyp_simulation', 'ddqn_model.keras')
|
| 10 |
+
|
| 11 |
+
try:
|
| 12 |
+
model = tf.keras.models.load_model(model_path)
|
| 13 |
+
print("Model loaded successfully!")
|
| 14 |
+
except Exception as e:
|
| 15 |
+
print(f" Failed to load model: {e}")
|
| 16 |
+
|
| 17 |
+
TOTAL_GAMETIME = 10000
|
| 18 |
+
N_EPISODES = 10000
|
| 19 |
+
REPLACE_TARGET = 10
|
| 20 |
+
|
| 21 |
+
game = GameEnv.RacingEnv()
|
| 22 |
+
game.fps = 60
|
| 23 |
+
|
| 24 |
+
ddqn_agent = DDQNAgent(alpha=0.0005, gamma=0.99, n_actions=5, epsilon=0.02,
|
| 25 |
+
epsilon_end=0.01, epsilon_dec=0.999, replace_target=REPLACE_TARGET,
|
| 26 |
+
batch_size=64, input_dims=19, fname=model_path)
|
| 27 |
+
|
| 28 |
+
ddqn_agent.load_model()
|
| 29 |
+
ddqn_agent.update_network_parameters()
|
| 30 |
+
|
| 31 |
+
def run():
|
| 32 |
+
for e in range(N_EPISODES):
|
| 33 |
+
game.reset()
|
| 34 |
+
done = False
|
| 35 |
+
score = 0
|
| 36 |
+
counter = 0
|
| 37 |
+
gtime = 0
|
| 38 |
+
observation_, reward, done = game.step(0)
|
| 39 |
+
observation = np.array(observation_)
|
| 40 |
+
|
| 41 |
+
while not done:
|
| 42 |
+
for event in pygame.event.get():
|
| 43 |
+
if event.type == pygame.QUIT:
|
| 44 |
+
return
|
| 45 |
+
|
| 46 |
+
action = ddqn_agent.choose_action(observation)
|
| 47 |
+
observation_, reward, done = game.step(action)
|
| 48 |
+
observation_ = np.array(observation_)
|
| 49 |
+
|
| 50 |
+
if reward == 0:
|
| 51 |
+
counter += 1
|
| 52 |
+
if counter > 100:
|
| 53 |
+
done = True
|
| 54 |
+
else:
|
| 55 |
+
counter = 0
|
| 56 |
+
|
| 57 |
+
score += reward
|
| 58 |
+
observation = observation_
|
| 59 |
+
gtime += 1
|
| 60 |
+
if gtime >= TOTAL_GAMETIME:
|
| 61 |
+
done = True
|
| 62 |
+
game.render(action)
|
| 63 |
+
# if score > best_score:
|
| 64 |
+
# best_score = score
|
| 65 |
+
# ddqn_agent.save_model()
|
| 66 |
+
# print(f" New best score {best_score}! Model saved.")
|
| 67 |
+
|
| 68 |
+
print(f"Episode: {e}, Reward: {score}")
|
| 69 |
+
|
| 70 |
+
run()
|
gui/interface/simulations/fyp1_simulation/best_neat_genome.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:166b4658e68cab4f72d9ea7c248081aa13ac34d447a2df4bfe26fbed975c5274
|
| 3 |
+
size 1199
|
gui/interface/simulations/fyp1_simulation/car.png
ADDED
|
gui/interface/simulations/fyp1_simulation/config.txt
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[NEAT]
|
| 2 |
+
fitness_criterion = max
|
| 3 |
+
fitness_threshold = 100000000
|
| 4 |
+
pop_size = 30
|
| 5 |
+
reset_on_extinction = True
|
| 6 |
+
|
| 7 |
+
[DefaultGenome]
|
| 8 |
+
# node activation options
|
| 9 |
+
activation_default = tanh
|
| 10 |
+
activation_mutate_rate = 0.01
|
| 11 |
+
activation_options = tanh
|
| 12 |
+
|
| 13 |
+
# node aggregation options
|
| 14 |
+
aggregation_default = sum
|
| 15 |
+
aggregation_mutate_rate = 0.01
|
| 16 |
+
aggregation_options = sum
|
| 17 |
+
|
| 18 |
+
# node bias options
|
| 19 |
+
bias_init_mean = 0.0
|
| 20 |
+
bias_init_stdev = 1.0
|
| 21 |
+
bias_max_value = 30.0
|
| 22 |
+
bias_min_value = -30.0
|
| 23 |
+
bias_mutate_power = 0.5
|
| 24 |
+
bias_mutate_rate = 0.7
|
| 25 |
+
bias_replace_rate = 0.1
|
| 26 |
+
|
| 27 |
+
# genome compatibility options
|
| 28 |
+
compatibility_disjoint_coefficient = 1.0
|
| 29 |
+
compatibility_weight_coefficient = 0.5
|
| 30 |
+
|
| 31 |
+
# connection add/remove rates
|
| 32 |
+
conn_add_prob = 0.5
|
| 33 |
+
conn_delete_prob = 0.5
|
| 34 |
+
|
| 35 |
+
# connection enable options
|
| 36 |
+
enabled_default = True
|
| 37 |
+
enabled_mutate_rate = 0.01
|
| 38 |
+
|
| 39 |
+
feed_forward = True
|
| 40 |
+
initial_connection = full
|
| 41 |
+
|
| 42 |
+
# node add/remove rates
|
| 43 |
+
node_add_prob = 0.2
|
| 44 |
+
node_delete_prob = 0.2
|
| 45 |
+
|
| 46 |
+
# network parameters
|
| 47 |
+
num_hidden = 0
|
| 48 |
+
num_inputs = 5
|
| 49 |
+
num_outputs = 4
|
| 50 |
+
|
| 51 |
+
# node response options
|
| 52 |
+
response_init_mean = 1.0
|
| 53 |
+
response_init_stdev = 0.0
|
| 54 |
+
response_max_value = 30.0
|
| 55 |
+
response_min_value = -30.0
|
| 56 |
+
response_mutate_power = 0.0
|
| 57 |
+
response_mutate_rate = 0.0
|
| 58 |
+
response_replace_rate = 0.0
|
| 59 |
+
|
| 60 |
+
# connection weight options
|
| 61 |
+
weight_init_mean = 0.0
|
| 62 |
+
weight_init_stdev = 1.0
|
| 63 |
+
weight_max_value = 30
|
| 64 |
+
weight_min_value = -30
|
| 65 |
+
weight_mutate_power = 0.5
|
| 66 |
+
weight_mutate_rate = 0.8
|
| 67 |
+
weight_replace_rate = 0.1
|
| 68 |
+
|
| 69 |
+
[DefaultSpeciesSet]
|
| 70 |
+
compatibility_threshold = 2.0
|
| 71 |
+
|
| 72 |
+
[DefaultStagnation]
|
| 73 |
+
species_fitness_func = max
|
| 74 |
+
max_stagnation = 20
|
| 75 |
+
species_elitism = 2
|
| 76 |
+
|
| 77 |
+
[DefaultReproduction]
|
| 78 |
+
elitism = 3
|
| 79 |
+
survival_threshold = 0.2
|
gui/interface/simulations/fyp1_simulation/map2.png
ADDED
|
gui/interface/simulations/fyp1_simulation/newcar.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import random
|
| 3 |
+
import sys
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
import neat
|
| 7 |
+
import pygame
|
| 8 |
+
|
| 9 |
+
# Constants
|
| 10 |
+
#WIDTH = 1500
|
| 11 |
+
#HEIGHT = 800
|
| 12 |
+
|
| 13 |
+
WIDTH = 1920
|
| 14 |
+
HEIGHT = 1080
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
CAR_SIZE_X = 60
|
| 19 |
+
CAR_SIZE_Y = 60
|
| 20 |
+
|
| 21 |
+
BORDER_COLOR = (255, 255, 255, 255) # Color To Crash on Hit
|
| 22 |
+
|
| 23 |
+
current_generation = 0 # Generation counter
|
| 24 |
+
|
| 25 |
+
class Car:
|
| 26 |
+
|
| 27 |
+
def __init__(self):
|
| 28 |
+
# Load Car Sprite and Rotate
|
| 29 |
+
self.sprite = pygame.image.load('car.png').convert() # Convert Speeds Up A Lot
|
| 30 |
+
self.sprite = pygame.transform.scale(self.sprite, (CAR_SIZE_X, CAR_SIZE_Y))
|
| 31 |
+
self.rotated_sprite = self.sprite
|
| 32 |
+
|
| 33 |
+
# self.position = [690, 740] # Starting Position
|
| 34 |
+
self.position = [830, 920] # Starting Position
|
| 35 |
+
self.angle = 0
|
| 36 |
+
self.speed = 0
|
| 37 |
+
|
| 38 |
+
self.speed_set = False # Flag For Default Speed Later on
|
| 39 |
+
|
| 40 |
+
self.center = [self.position[0] + CAR_SIZE_X / 2, self.position[1] + CAR_SIZE_Y / 2] # Calculate Center
|
| 41 |
+
|
| 42 |
+
self.radars = [] # List For Sensors / Radars
|
| 43 |
+
self.drawing_radars = [] # Radars To Be Drawn
|
| 44 |
+
|
| 45 |
+
self.alive = True # Boolean To Check If Car is Crashed
|
| 46 |
+
|
| 47 |
+
self.distance = 0 # Distance Driven
|
| 48 |
+
self.time = 0 # Time Passed
|
| 49 |
+
|
| 50 |
+
def draw(self, screen):
|
| 51 |
+
screen.blit(self.rotated_sprite, self.position) # Draw Sprite
|
| 52 |
+
self.draw_radar(screen) #OPTIONAL FOR SENSORS
|
| 53 |
+
|
| 54 |
+
def draw_radar(self, screen):
|
| 55 |
+
# Optionally Draw All Sensors / Radars
|
| 56 |
+
for radar in self.radars:
|
| 57 |
+
position = radar[0]
|
| 58 |
+
pygame.draw.line(screen, (0, 255, 0), self.center, position, 1)
|
| 59 |
+
pygame.draw.circle(screen, (0, 255, 0), position, 5)
|
| 60 |
+
|
| 61 |
+
def check_collision(self, game_map):
|
| 62 |
+
self.alive = True
|
| 63 |
+
for point in self.corners:
|
| 64 |
+
# If Any Corner Touches Border Color -> Crash
|
| 65 |
+
# Assumes Rectangle
|
| 66 |
+
if game_map.get_at((int(point[0]), int(point[1]))) == BORDER_COLOR:
|
| 67 |
+
self.alive = False
|
| 68 |
+
break
|
| 69 |
+
|
| 70 |
+
def check_radar(self, degree, game_map):
|
| 71 |
+
length = 0
|
| 72 |
+
x = int(self.center[0] + math.cos(math.radians(360 - (self.angle + degree))) * length)
|
| 73 |
+
y = int(self.center[1] + math.sin(math.radians(360 - (self.angle + degree))) * length)
|
| 74 |
+
|
| 75 |
+
# While We Don't Hit BORDER_COLOR AND length < 300 (just a max) -> go further and further
|
| 76 |
+
while not game_map.get_at((x, y)) == BORDER_COLOR and length < 300:
|
| 77 |
+
length = length + 1
|
| 78 |
+
x = int(self.center[0] + math.cos(math.radians(360 - (self.angle + degree))) * length)
|
| 79 |
+
y = int(self.center[1] + math.sin(math.radians(360 - (self.angle + degree))) * length)
|
| 80 |
+
|
| 81 |
+
# Calculate Distance To Border And Append To Radars List
|
| 82 |
+
dist = int(math.sqrt(math.pow(x - self.center[0], 2) + math.pow(y - self.center[1], 2)))
|
| 83 |
+
self.radars.append([(x, y), dist])
|
| 84 |
+
|
| 85 |
+
def update(self, game_map):
|
| 86 |
+
# Set The Speed To 20 For The First Time
|
| 87 |
+
# Only When Having 4 Output Nodes With Speed Up and Down
|
| 88 |
+
if not self.speed_set:
|
| 89 |
+
self.speed = 20
|
| 90 |
+
self.speed_set = True
|
| 91 |
+
|
| 92 |
+
# Get Rotated Sprite And Move Into The Right X-Direction
|
| 93 |
+
# Don't Let The Car Go Closer Than 20px To The Edge
|
| 94 |
+
self.rotated_sprite = self.rotate_center(self.sprite, self.angle)
|
| 95 |
+
self.position[0] += math.cos(math.radians(360 - self.angle)) * self.speed
|
| 96 |
+
self.position[0] = max(self.position[0], 20)
|
| 97 |
+
self.position[0] = min(self.position[0], WIDTH - 120)
|
| 98 |
+
|
| 99 |
+
# Increase Distance and Time
|
| 100 |
+
self.distance += self.speed
|
| 101 |
+
self.time += 1
|
| 102 |
+
|
| 103 |
+
# Same For Y-Position
|
| 104 |
+
self.position[1] += math.sin(math.radians(360 - self.angle)) * self.speed
|
| 105 |
+
self.position[1] = max(self.position[1], 20)
|
| 106 |
+
self.position[1] = min(self.position[1], WIDTH - 120)
|
| 107 |
+
|
| 108 |
+
# Calculate New Center
|
| 109 |
+
self.center = [int(self.position[0]) + CAR_SIZE_X / 2, int(self.position[1]) + CAR_SIZE_Y / 2]
|
| 110 |
+
|
| 111 |
+
# Calculate Four Corners
|
| 112 |
+
# Length Is Half The Side
|
| 113 |
+
length = 0.5 * CAR_SIZE_X
|
| 114 |
+
left_top = [self.center[0] + math.cos(math.radians(360 - (self.angle + 30))) * length, self.center[1] + math.sin(math.radians(360 - (self.angle + 30))) * length]
|
| 115 |
+
right_top = [self.center[0] + math.cos(math.radians(360 - (self.angle + 150))) * length, self.center[1] + math.sin(math.radians(360 - (self.angle + 150))) * length]
|
| 116 |
+
left_bottom = [self.center[0] + math.cos(math.radians(360 - (self.angle + 210))) * length, self.center[1] + math.sin(math.radians(360 - (self.angle + 210))) * length]
|
| 117 |
+
right_bottom = [self.center[0] + math.cos(math.radians(360 - (self.angle + 330))) * length, self.center[1] + math.sin(math.radians(360 - (self.angle + 330))) * length]
|
| 118 |
+
self.corners = [left_top, right_top, left_bottom, right_bottom]
|
| 119 |
+
|
| 120 |
+
# Check Collisions And Clear Radars
|
| 121 |
+
self.check_collision(game_map)
|
| 122 |
+
self.radars.clear()
|
| 123 |
+
|
| 124 |
+
# From -90 To 120 With Step-Size 45 Check Radar
|
| 125 |
+
for d in range(-90, 120, 45):
|
| 126 |
+
self.check_radar(d, game_map)
|
| 127 |
+
|
| 128 |
+
def get_data(self):
|
| 129 |
+
# Get Distances To Border
|
| 130 |
+
radars = self.radars
|
| 131 |
+
return_values = [0, 0, 0, 0, 0]
|
| 132 |
+
for i, radar in enumerate(radars):
|
| 133 |
+
return_values[i] = int(radar[1] / 30)
|
| 134 |
+
|
| 135 |
+
return return_values
|
| 136 |
+
|
| 137 |
+
def is_alive(self):
|
| 138 |
+
# Basic Alive Function
|
| 139 |
+
return self.alive
|
| 140 |
+
|
| 141 |
+
def get_reward(self):
|
| 142 |
+
# Calculate Reward (Maybe Change?)
|
| 143 |
+
return self.distance / 50.0
|
| 144 |
+
# return self.distance / (CAR_SIZE_X / 2)
|
| 145 |
+
|
| 146 |
+
def rotate_center(self, image, angle):
|
| 147 |
+
# Rotate The Rectangle
|
| 148 |
+
rectangle = image.get_rect()
|
| 149 |
+
rotated_image = pygame.transform.rotate(image, angle)
|
| 150 |
+
rotated_rectangle = rectangle.copy()
|
| 151 |
+
rotated_rectangle.center = rotated_image.get_rect().center
|
| 152 |
+
rotated_image = rotated_image.subsurface(rotated_rectangle).copy()
|
| 153 |
+
return rotated_image
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def run_simulation(genomes, config):
|
| 157 |
+
|
| 158 |
+
# Empty Collections For Nets and Cars
|
| 159 |
+
nets = []
|
| 160 |
+
cars = []
|
| 161 |
+
|
| 162 |
+
# Initialize PyGame And The Display
|
| 163 |
+
pygame.init()
|
| 164 |
+
screen = pygame.display.set_mode((WIDTH, HEIGHT))
|
| 165 |
+
|
| 166 |
+
# For All Genomes Passed Create A New Neural Network
|
| 167 |
+
for i, g in genomes:
|
| 168 |
+
net = neat.nn.FeedForwardNetwork.create(g, config)
|
| 169 |
+
nets.append(net)
|
| 170 |
+
g.fitness = 0
|
| 171 |
+
|
| 172 |
+
cars.append(Car())
|
| 173 |
+
|
| 174 |
+
# Clock Settings
|
| 175 |
+
# Font Settings & Loading Map
|
| 176 |
+
clock = pygame.time.Clock()
|
| 177 |
+
generation_font = pygame.font.SysFont("Arial", 30)
|
| 178 |
+
alive_font = pygame.font.SysFont("Arial", 20)
|
| 179 |
+
game_map = pygame.image.load('map2.png').convert() # Convert Speeds Up A Lot
|
| 180 |
+
|
| 181 |
+
global current_generation
|
| 182 |
+
current_generation += 1
|
| 183 |
+
|
| 184 |
+
# Simple Counter To Roughly Limit Time (Not Good Practice)
|
| 185 |
+
counter = 0
|
| 186 |
+
|
| 187 |
+
while True:
|
| 188 |
+
# Exit On Quit Event
|
| 189 |
+
for event in pygame.event.get():
|
| 190 |
+
if event.type == pygame.QUIT:
|
| 191 |
+
sys.exit(0)
|
| 192 |
+
|
| 193 |
+
# For Each Car Get The Acton It Takes
|
| 194 |
+
for i, car in enumerate(cars):
|
| 195 |
+
output = nets[i].activate(car.get_data())
|
| 196 |
+
choice = output.index(max(output))
|
| 197 |
+
if choice == 0:
|
| 198 |
+
car.angle += 10 # Left
|
| 199 |
+
elif choice == 1:
|
| 200 |
+
car.angle -= 10 # Right
|
| 201 |
+
elif choice == 2:
|
| 202 |
+
if(car.speed - 2 >= 12):
|
| 203 |
+
car.speed -= 2 # Slow Down
|
| 204 |
+
else:
|
| 205 |
+
car.speed += 2 # Speed Up
|
| 206 |
+
|
| 207 |
+
# Check If Car Is Still Alive
|
| 208 |
+
# Increase Fitness If Yes And Break Loop If Not
|
| 209 |
+
still_alive = 0
|
| 210 |
+
for i, car in enumerate(cars):
|
| 211 |
+
if car.is_alive():
|
| 212 |
+
still_alive += 1
|
| 213 |
+
car.update(game_map)
|
| 214 |
+
genomes[i][1].fitness += car.get_reward()
|
| 215 |
+
|
| 216 |
+
if still_alive == 0:
|
| 217 |
+
break
|
| 218 |
+
|
| 219 |
+
counter += 1
|
| 220 |
+
if counter == 30 * 40: # Stop After About 20 Seconds
|
| 221 |
+
break
|
| 222 |
+
|
| 223 |
+
# Draw Map And All Cars That Are Alive
|
| 224 |
+
screen.blit(game_map, (0, 0))
|
| 225 |
+
for car in cars:
|
| 226 |
+
if car.is_alive():
|
| 227 |
+
car.draw(screen)
|
| 228 |
+
|
| 229 |
+
# Display Info
|
| 230 |
+
text = generation_font.render("Generation: " + str(current_generation), True, (0,0,0))
|
| 231 |
+
text_rect = text.get_rect()
|
| 232 |
+
text_rect.center = (900, 450)
|
| 233 |
+
screen.blit(text, text_rect)
|
| 234 |
+
|
| 235 |
+
text = alive_font.render("Still Alive: " + str(still_alive), True, (0, 0, 0))
|
| 236 |
+
text_rect = text.get_rect()
|
| 237 |
+
text_rect.center = (900, 490)
|
| 238 |
+
screen.blit(text, text_rect)
|
| 239 |
+
|
| 240 |
+
pygame.display.flip()
|
| 241 |
+
clock.tick(30) # 60 FPS
|
| 242 |
+
|
| 243 |
+
if __name__ == "__main__":
|
| 244 |
+
import pickle
|
| 245 |
+
import os
|
| 246 |
+
|
| 247 |
+
# Load Config
|
| 248 |
+
config_path = "config.txt"
|
| 249 |
+
config = neat.config.Config(neat.DefaultGenome,
|
| 250 |
+
neat.DefaultReproduction,
|
| 251 |
+
neat.DefaultSpeciesSet,
|
| 252 |
+
neat.DefaultStagnation,
|
| 253 |
+
config_path)
|
| 254 |
+
|
| 255 |
+
# Create Population And Add Reporters
|
| 256 |
+
population = neat.Population(config)
|
| 257 |
+
population.add_reporter(neat.StdOutReporter(True))
|
| 258 |
+
stats = neat.StatisticsReporter()
|
| 259 |
+
population.add_reporter(stats)
|
| 260 |
+
|
| 261 |
+
# Path to store best genome
|
| 262 |
+
best_genome_path = "best_neat_genome.pkl"
|
| 263 |
+
|
| 264 |
+
# Function to save genome
|
| 265 |
+
def save_best_genome(genome):
|
| 266 |
+
with open(best_genome_path, "wb") as f:
|
| 267 |
+
pickle.dump(genome, f)
|
| 268 |
+
print("\n✅ Best genome saved!\n")
|
| 269 |
+
|
| 270 |
+
# Load previous best genome if exists
|
| 271 |
+
best_fitness_so_far = -1
|
| 272 |
+
if os.path.exists(best_genome_path):
|
| 273 |
+
with open(best_genome_path, "rb") as f:
|
| 274 |
+
old_best = pickle.load(f)
|
| 275 |
+
best_fitness_so_far = old_best.fitness if hasattr(old_best, "fitness") else -1
|
| 276 |
+
print(f"\n📂 Loaded previous best fitness: {best_fitness_so_far}\n")
|
| 277 |
+
|
| 278 |
+
# Run NEAT algorithm
|
| 279 |
+
def eval_genomes(genomes, config):
|
| 280 |
+
global best_fitness_so_far
|
| 281 |
+
run_simulation(genomes, config)
|
| 282 |
+
for genome_id, genome in genomes:
|
| 283 |
+
if genome.fitness > best_fitness_so_far:
|
| 284 |
+
best_fitness_so_far = genome.fitness
|
| 285 |
+
save_best_genome(genome)
|
| 286 |
+
|
| 287 |
+
# Run NEAT with our eval function
|
| 288 |
+
population.run(eval_genomes, 1000)
|
gui/interface/simulations/fyp_simulation/GameEnv.py
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pygame
|
| 2 |
+
import math
|
| 3 |
+
from .Walls import Wall
|
| 4 |
+
from .Walls import getWalls
|
| 5 |
+
from .Goals import Goal
|
| 6 |
+
from .Goals import getGoals
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
GOALREWARD = 1
|
| 11 |
+
LIFE_REWARD = 0
|
| 12 |
+
PENALTY = -1
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def distance(pt1, pt2):
|
| 16 |
+
return(((pt1.x - pt2.x)**2 + (pt1.y - pt2.y)**2)**0.5)
|
| 17 |
+
|
| 18 |
+
def rotate(origin,point,angle):
|
| 19 |
+
qx = origin.x + math.cos(angle) * (point.x - origin.x) - math.sin(angle) * (point.y - origin.y)
|
| 20 |
+
qy = origin.y + math.sin(angle) * (point.x - origin.x) + math.cos(angle) * (point.y - origin.y)
|
| 21 |
+
q = myPoint(qx, qy)
|
| 22 |
+
return q
|
| 23 |
+
|
| 24 |
+
def rotateRect(pt1, pt2, pt3, pt4, angle):
|
| 25 |
+
|
| 26 |
+
pt_center = myPoint((pt1.x + pt3.x)/2, (pt1.y + pt3.y)/2)
|
| 27 |
+
|
| 28 |
+
pt1 = rotate(pt_center,pt1,angle)
|
| 29 |
+
pt2 = rotate(pt_center,pt2,angle)
|
| 30 |
+
pt3 = rotate(pt_center,pt3,angle)
|
| 31 |
+
pt4 = rotate(pt_center,pt4,angle)
|
| 32 |
+
|
| 33 |
+
return pt1, pt2, pt3, pt4
|
| 34 |
+
|
| 35 |
+
class myPoint:
|
| 36 |
+
def __init__(self, x, y):
|
| 37 |
+
self.x = x
|
| 38 |
+
self.y = y
|
| 39 |
+
|
| 40 |
+
class myLine:
|
| 41 |
+
def __init__(self, pt1, pt2):
|
| 42 |
+
self.pt1 = myPoint(pt1.x, pt1.y)
|
| 43 |
+
self.pt2 = myPoint(pt2.x, pt2.y)
|
| 44 |
+
|
| 45 |
+
class Ray:
|
| 46 |
+
def __init__(self,x,y,angle):
|
| 47 |
+
self.x = x
|
| 48 |
+
self.y = y
|
| 49 |
+
self.angle = angle
|
| 50 |
+
|
| 51 |
+
def cast(self, wall):
|
| 52 |
+
x1 = wall.x1
|
| 53 |
+
y1 = wall.y1
|
| 54 |
+
x2 = wall.x2
|
| 55 |
+
y2 = wall.y2
|
| 56 |
+
|
| 57 |
+
vec = rotate(myPoint(0,0), myPoint(0,-1000), self.angle)
|
| 58 |
+
|
| 59 |
+
x3 = self.x
|
| 60 |
+
y3 = self.y
|
| 61 |
+
x4 = self.x + vec.x
|
| 62 |
+
y4 = self.y + vec.y
|
| 63 |
+
|
| 64 |
+
den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
|
| 65 |
+
|
| 66 |
+
if(den == 0):
|
| 67 |
+
den = 0
|
| 68 |
+
else:
|
| 69 |
+
t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den
|
| 70 |
+
u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den
|
| 71 |
+
|
| 72 |
+
if t > 0 and t < 1 and u < 1 and u > 0:
|
| 73 |
+
pt = myPoint(math.floor(x1 + t * (x2 - x1)), math.floor(y1 + t * (y2 - y1)))
|
| 74 |
+
return(pt)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class Car:
|
| 79 |
+
def __init__(self, x, y):
|
| 80 |
+
self.pt = myPoint(x, y)
|
| 81 |
+
self.x = x
|
| 82 |
+
self.y = y
|
| 83 |
+
self.width = 14
|
| 84 |
+
self.height = 30
|
| 85 |
+
|
| 86 |
+
self.points = 0
|
| 87 |
+
|
| 88 |
+
self.original_image = pygame.image.load("car.png").convert()
|
| 89 |
+
self.image = self.original_image # This will reference the rotated image.
|
| 90 |
+
self.image.set_colorkey((0,0,0))
|
| 91 |
+
self.rect = self.image.get_rect().move(self.x, self.y)
|
| 92 |
+
|
| 93 |
+
self.angle = math.radians(180)
|
| 94 |
+
self.soll_angle = self.angle
|
| 95 |
+
|
| 96 |
+
self.dvel = 1
|
| 97 |
+
self.vel = 0
|
| 98 |
+
self.velX = 0
|
| 99 |
+
self.velY = 0
|
| 100 |
+
self.maxvel = 15 # before 15
|
| 101 |
+
|
| 102 |
+
self.angle = math.radians(180)
|
| 103 |
+
self.soll_angle = self.angle
|
| 104 |
+
|
| 105 |
+
self.pt1 = myPoint(self.pt.x - self.width / 2, self.pt.y - self.height / 2)
|
| 106 |
+
self.pt2 = myPoint(self.pt.x + self.width / 2, self.pt.y - self.height / 2)
|
| 107 |
+
self.pt3 = myPoint(self.pt.x + self.width / 2, self.pt.y + self.height / 2)
|
| 108 |
+
self.pt4 = myPoint(self.pt.x - self.width / 2, self.pt.y + self.height / 2)
|
| 109 |
+
|
| 110 |
+
self.p1 = self.pt1
|
| 111 |
+
self.p2 = self.pt2
|
| 112 |
+
self.p3 = self.pt3
|
| 113 |
+
self.p4 = self.pt4
|
| 114 |
+
|
| 115 |
+
self.distances = []
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def action(self, choice):
|
| 119 |
+
if choice == 0:
|
| 120 |
+
pass
|
| 121 |
+
elif choice == 1:
|
| 122 |
+
self.accelerate(self.dvel)
|
| 123 |
+
elif choice == 8:
|
| 124 |
+
self.accelerate(self.dvel)
|
| 125 |
+
self.turn(1)
|
| 126 |
+
elif choice == 7:
|
| 127 |
+
self.accelerate(self.dvel)
|
| 128 |
+
self.turn(-1)
|
| 129 |
+
elif choice == 4:
|
| 130 |
+
self.accelerate(-self.dvel)
|
| 131 |
+
elif choice == 5:
|
| 132 |
+
self.accelerate(-self.dvel)
|
| 133 |
+
self.turn(1)
|
| 134 |
+
elif choice == 6:
|
| 135 |
+
self.accelerate(-self.dvel)
|
| 136 |
+
self.turn(-1)
|
| 137 |
+
elif choice == 3:
|
| 138 |
+
self.turn(1)
|
| 139 |
+
elif choice == 2:
|
| 140 |
+
self.turn(-1)
|
| 141 |
+
pass
|
| 142 |
+
|
| 143 |
+
def accelerate(self,dvel):
|
| 144 |
+
dvel = dvel * 2
|
| 145 |
+
|
| 146 |
+
self.vel = self.vel + dvel
|
| 147 |
+
|
| 148 |
+
if self.vel > self.maxvel:
|
| 149 |
+
self.vel = self.maxvel
|
| 150 |
+
|
| 151 |
+
if self.vel < -self.maxvel:
|
| 152 |
+
self.vel = -self.maxvel
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def turn(self, dir):
|
| 156 |
+
self.soll_angle = self.soll_angle + dir * math.radians(15)
|
| 157 |
+
|
| 158 |
+
def update(self):
|
| 159 |
+
|
| 160 |
+
#drifting code
|
| 161 |
+
self.angle = self.soll_angle
|
| 162 |
+
|
| 163 |
+
vec_temp = rotate(myPoint(0,0), myPoint(0,self.vel), self.angle)
|
| 164 |
+
self.velX, self.velY = vec_temp.x, vec_temp.y
|
| 165 |
+
|
| 166 |
+
self.x = self.x + self.velX
|
| 167 |
+
self.y = self.y + self.velY
|
| 168 |
+
|
| 169 |
+
self.rect.center = self.x, self.y
|
| 170 |
+
|
| 171 |
+
self.pt1 = myPoint(self.pt1.x + self.velX, self.pt1.y + self.velY)
|
| 172 |
+
self.pt2 = myPoint(self.pt2.x + self.velX, self.pt2.y + self.velY)
|
| 173 |
+
self.pt3 = myPoint(self.pt3.x + self.velX, self.pt3.y + self.velY)
|
| 174 |
+
self.pt4 = myPoint(self.pt4.x + self.velX, self.pt4.y + self.velY)
|
| 175 |
+
|
| 176 |
+
self.p1 ,self.p2 ,self.p3 ,self.p4 = rotateRect(self.pt1, self.pt2, self.pt3, self.pt4, self.soll_angle)
|
| 177 |
+
|
| 178 |
+
self.image = pygame.transform.rotate(self.original_image, 90 - self.soll_angle * 180 / math.pi)
|
| 179 |
+
x, y = self.rect.center # Save its current center.
|
| 180 |
+
self.rect = self.image.get_rect() # Replace old rect with new rect.
|
| 181 |
+
self.rect.center = (x, y)
|
| 182 |
+
|
| 183 |
+
def cast(self, walls):
|
| 184 |
+
|
| 185 |
+
ray1 = Ray(self.x, self.y, self.soll_angle)
|
| 186 |
+
ray2 = Ray(self.x, self.y, self.soll_angle - math.radians(30))
|
| 187 |
+
ray3 = Ray(self.x, self.y, self.soll_angle + math.radians(30))
|
| 188 |
+
ray4 = Ray(self.x, self.y, self.soll_angle + math.radians(45))
|
| 189 |
+
ray5 = Ray(self.x, self.y, self.soll_angle - math.radians(45))
|
| 190 |
+
ray6 = Ray(self.x, self.y, self.soll_angle + math.radians(90))
|
| 191 |
+
ray7 = Ray(self.x, self.y, self.soll_angle - math.radians(90))
|
| 192 |
+
ray8 = Ray(self.x, self.y, self.soll_angle + math.radians(180))
|
| 193 |
+
|
| 194 |
+
ray9 = Ray(self.x, self.y, self.soll_angle + math.radians(10))
|
| 195 |
+
ray10 = Ray(self.x, self.y, self.soll_angle - math.radians(10))
|
| 196 |
+
ray11 = Ray(self.x, self.y, self.soll_angle + math.radians(135))
|
| 197 |
+
ray12 = Ray(self.x, self.y, self.soll_angle - math.radians(135))
|
| 198 |
+
ray13 = Ray(self.x, self.y, self.soll_angle + math.radians(20))
|
| 199 |
+
ray14 = Ray(self.x, self.y, self.soll_angle - math.radians(20))
|
| 200 |
+
|
| 201 |
+
ray15 = Ray(self.p1.x,self.p1.y, self.soll_angle + math.radians(90))
|
| 202 |
+
ray16 = Ray(self.p2.x,self.p2.y, self.soll_angle - math.radians(90))
|
| 203 |
+
|
| 204 |
+
ray17 = Ray(self.p1.x,self.p1.y, self.soll_angle + math.radians(0))
|
| 205 |
+
ray18 = Ray(self.p2.x,self.p2.y, self.soll_angle - math.radians(0))
|
| 206 |
+
|
| 207 |
+
self.rays = []
|
| 208 |
+
self.rays.append(ray1)
|
| 209 |
+
self.rays.append(ray2)
|
| 210 |
+
self.rays.append(ray3)
|
| 211 |
+
self.rays.append(ray4)
|
| 212 |
+
self.rays.append(ray5)
|
| 213 |
+
self.rays.append(ray6)
|
| 214 |
+
self.rays.append(ray7)
|
| 215 |
+
self.rays.append(ray8)
|
| 216 |
+
|
| 217 |
+
self.rays.append(ray9)
|
| 218 |
+
self.rays.append(ray10)
|
| 219 |
+
self.rays.append(ray11)
|
| 220 |
+
self.rays.append(ray12)
|
| 221 |
+
self.rays.append(ray13)
|
| 222 |
+
self.rays.append(ray14)
|
| 223 |
+
|
| 224 |
+
self.rays.append(ray15)
|
| 225 |
+
self.rays.append(ray16)
|
| 226 |
+
|
| 227 |
+
self.rays.append(ray17)
|
| 228 |
+
self.rays.append(ray18)
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
observations = []
|
| 232 |
+
self.closestRays = []
|
| 233 |
+
|
| 234 |
+
for ray in self.rays:
|
| 235 |
+
closest = None #myPoint(0,0)
|
| 236 |
+
record = math.inf
|
| 237 |
+
for wall in walls:
|
| 238 |
+
pt = ray.cast(wall)
|
| 239 |
+
if pt:
|
| 240 |
+
dist = distance(myPoint(self.x, self.y),pt)
|
| 241 |
+
if dist < record:
|
| 242 |
+
record = dist
|
| 243 |
+
closest = pt
|
| 244 |
+
|
| 245 |
+
if closest:
|
| 246 |
+
#append distance for current ray
|
| 247 |
+
self.closestRays.append(closest)
|
| 248 |
+
observations.append(record)
|
| 249 |
+
|
| 250 |
+
else:
|
| 251 |
+
observations.append(1000)
|
| 252 |
+
|
| 253 |
+
for i in range(len(observations)):
|
| 254 |
+
#invert observation values 0 is far away 1 is close
|
| 255 |
+
observations[i] = ((1000 - observations[i]) / 1000)
|
| 256 |
+
|
| 257 |
+
observations.append(self.vel / self.maxvel)
|
| 258 |
+
return observations
|
| 259 |
+
|
| 260 |
+
def collision(self, wall):
|
| 261 |
+
|
| 262 |
+
line1 = myLine(self.p1, self.p2)
|
| 263 |
+
line2 = myLine(self.p2, self.p3)
|
| 264 |
+
line3 = myLine(self.p3, self.p4)
|
| 265 |
+
line4 = myLine(self.p4, self.p1)
|
| 266 |
+
|
| 267 |
+
x1 = wall.x1
|
| 268 |
+
y1 = wall.y1
|
| 269 |
+
x2 = wall.x2
|
| 270 |
+
y2 = wall.y2
|
| 271 |
+
|
| 272 |
+
lines = []
|
| 273 |
+
lines.append(line1)
|
| 274 |
+
lines.append(line2)
|
| 275 |
+
lines.append(line3)
|
| 276 |
+
lines.append(line4)
|
| 277 |
+
|
| 278 |
+
for li in lines:
|
| 279 |
+
|
| 280 |
+
x3 = li.pt1.x
|
| 281 |
+
y3 = li.pt1.y
|
| 282 |
+
x4 = li.pt2.x
|
| 283 |
+
y4 = li.pt2.y
|
| 284 |
+
|
| 285 |
+
den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
|
| 286 |
+
|
| 287 |
+
if(den == 0):
|
| 288 |
+
den = 0
|
| 289 |
+
else:
|
| 290 |
+
t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den
|
| 291 |
+
u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den
|
| 292 |
+
|
| 293 |
+
if t > 0 and t < 1 and u < 1 and u > 0:
|
| 294 |
+
return(True)
|
| 295 |
+
|
| 296 |
+
return(False)
|
| 297 |
+
|
| 298 |
+
def score(self, goal):
|
| 299 |
+
|
| 300 |
+
line1 = myLine(self.p1, self.p3)
|
| 301 |
+
|
| 302 |
+
vec = rotate(myPoint(0,0), myPoint(0,-50), self.angle)
|
| 303 |
+
line1 = myLine(myPoint(self.x,self.y),myPoint(self.x + vec.x, self.y + vec.y))
|
| 304 |
+
|
| 305 |
+
x1 = goal.x1
|
| 306 |
+
y1 = goal.y1
|
| 307 |
+
x2 = goal.x2
|
| 308 |
+
y2 = goal.y2
|
| 309 |
+
|
| 310 |
+
x3 = line1.pt1.x
|
| 311 |
+
y3 = line1.pt1.y
|
| 312 |
+
x4 = line1.pt2.x
|
| 313 |
+
y4 = line1.pt2.y
|
| 314 |
+
|
| 315 |
+
den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
|
| 316 |
+
|
| 317 |
+
if(den == 0):
|
| 318 |
+
den = 0
|
| 319 |
+
else:
|
| 320 |
+
t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den
|
| 321 |
+
u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den
|
| 322 |
+
|
| 323 |
+
if t > 0 and t < 1 and u < 1 and u > 0:
|
| 324 |
+
pt = math.floor(x1 + t * (x2 - x1)), math.floor(y1 + t * (y2 - y1))
|
| 325 |
+
|
| 326 |
+
d = distance(myPoint(self.x, self.y), myPoint(pt[0], pt[1]))
|
| 327 |
+
if d < 20:
|
| 328 |
+
#pygame.draw.circle(win, (0,255,0), pt, 5)
|
| 329 |
+
self.points += GOALREWARD
|
| 330 |
+
return(True)
|
| 331 |
+
|
| 332 |
+
return(False)
|
| 333 |
+
|
| 334 |
+
def reset(self):
|
| 335 |
+
|
| 336 |
+
self.x = 50
|
| 337 |
+
self.y = 300
|
| 338 |
+
self.velX = 0
|
| 339 |
+
self.velY = 0
|
| 340 |
+
self.vel = 0
|
| 341 |
+
self.angle = math.radians(180)
|
| 342 |
+
self.soll_angle = self.angle
|
| 343 |
+
self.points = 0
|
| 344 |
+
|
| 345 |
+
self.pt1 = myPoint(self.pt.x - self.width / 2, self.pt.y - self.height / 2)
|
| 346 |
+
self.pt2 = myPoint(self.pt.x + self.width / 2, self.pt.y - self.height / 2)
|
| 347 |
+
self.pt3 = myPoint(self.pt.x + self.width / 2, self.pt.y + self.height / 2)
|
| 348 |
+
self.pt4 = myPoint(self.pt.x - self.width / 2, self.pt.y + self.height / 2)
|
| 349 |
+
|
| 350 |
+
self.p1 = self.pt1
|
| 351 |
+
self.p2 = self.pt2
|
| 352 |
+
self.p3 = self.pt3
|
| 353 |
+
self.p4 = self.pt4
|
| 354 |
+
|
| 355 |
+
def draw(self, win):
|
| 356 |
+
win.blit(self.image, self.rect)
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
class RacingEnv:
|
| 360 |
+
|
| 361 |
+
def __init__(self):
|
| 362 |
+
pygame.init()
|
| 363 |
+
self.font = pygame.font.Font(pygame.font.get_default_font(), 36)
|
| 364 |
+
|
| 365 |
+
self.fps = 120
|
| 366 |
+
self.width = 1000
|
| 367 |
+
self.height = 600
|
| 368 |
+
self.history = []
|
| 369 |
+
|
| 370 |
+
self.screen = pygame.display.set_mode((self.width, self.height))
|
| 371 |
+
pygame.display.set_caption("IMS using DDQN")
|
| 372 |
+
self.screen.fill((0,0,0))
|
| 373 |
+
self.back_image = pygame.image.load("track.png").convert()
|
| 374 |
+
self.back_rect = self.back_image.get_rect().move(0, 0)
|
| 375 |
+
self.action_space = None
|
| 376 |
+
self.observation_space = None
|
| 377 |
+
self.game_reward = 0
|
| 378 |
+
self.score = 0
|
| 379 |
+
|
| 380 |
+
self.reset()
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
def reset(self):
|
| 384 |
+
self.screen.fill((0, 0, 0))
|
| 385 |
+
|
| 386 |
+
self.car = Car(50, 300)
|
| 387 |
+
self.walls = getWalls()
|
| 388 |
+
self.goals = getGoals()
|
| 389 |
+
self.game_reward = 0
|
| 390 |
+
|
| 391 |
+
def step(self, action):
|
| 392 |
+
|
| 393 |
+
done = False
|
| 394 |
+
self.car.action(action)
|
| 395 |
+
self.car.update()
|
| 396 |
+
reward = LIFE_REWARD
|
| 397 |
+
|
| 398 |
+
# Check if car passes Goal and scores
|
| 399 |
+
index = 1
|
| 400 |
+
for goal in self.goals:
|
| 401 |
+
|
| 402 |
+
if index > len(self.goals):
|
| 403 |
+
index = 1
|
| 404 |
+
if goal.isactiv:
|
| 405 |
+
if self.car.score(goal):
|
| 406 |
+
goal.isactiv = False
|
| 407 |
+
self.goals[index-2].isactiv = True
|
| 408 |
+
reward += GOALREWARD
|
| 409 |
+
|
| 410 |
+
index = index + 1
|
| 411 |
+
|
| 412 |
+
#check if car crashed in the wall
|
| 413 |
+
for wall in self.walls:
|
| 414 |
+
if self.car.collision(wall):
|
| 415 |
+
reward += PENALTY
|
| 416 |
+
done = True
|
| 417 |
+
|
| 418 |
+
new_state = self.car.cast(self.walls)
|
| 419 |
+
#normalize states
|
| 420 |
+
if done:
|
| 421 |
+
new_state = None
|
| 422 |
+
|
| 423 |
+
return new_state, reward, done
|
| 424 |
+
|
| 425 |
+
def render(self, action):
|
| 426 |
+
|
| 427 |
+
DRAW_WALLS = True
|
| 428 |
+
DRAW_GOALS = True
|
| 429 |
+
DRAW_RAYS = True
|
| 430 |
+
|
| 431 |
+
pygame.time.delay(10)
|
| 432 |
+
|
| 433 |
+
self.clock = pygame.time.Clock()
|
| 434 |
+
self.screen.fill((0, 0, 0))
|
| 435 |
+
|
| 436 |
+
self.screen.blit(self.back_image, self.back_rect)
|
| 437 |
+
# Draw checkered start line (black and white boxes)
|
| 438 |
+
start_x = 50 # same as Car's starting x
|
| 439 |
+
start_y = 300 # same as Car's starting y
|
| 440 |
+
line_width = 80
|
| 441 |
+
line_height = 20
|
| 442 |
+
num_squares = 16 # Number of squares in the checkered line
|
| 443 |
+
square_width = line_width // num_squares
|
| 444 |
+
for i in range(num_squares):
|
| 445 |
+
color = (255, 255, 255) if i % 2 == 0 else (0, 0, 0)
|
| 446 |
+
rect_x = start_x - line_width // 2 + i * square_width
|
| 447 |
+
pygame.draw.rect(self.screen, color, (rect_x, start_y - line_height // 2, square_width, line_height))
|
| 448 |
+
if DRAW_WALLS:
|
| 449 |
+
for wall in self.walls:
|
| 450 |
+
wall.draw(self.screen)
|
| 451 |
+
|
| 452 |
+
if DRAW_GOALS:
|
| 453 |
+
for goal in self.goals:
|
| 454 |
+
goal.draw(self.screen)
|
| 455 |
+
if goal.isactiv:
|
| 456 |
+
goal.draw(self.screen)
|
| 457 |
+
|
| 458 |
+
self.car.draw(self.screen)
|
| 459 |
+
|
| 460 |
+
if DRAW_RAYS:
|
| 461 |
+
i = 0
|
| 462 |
+
for pt in self.car.closestRays:
|
| 463 |
+
pygame.draw.circle(self.screen, (0,0,255), (pt.x, pt.y), 5)
|
| 464 |
+
i += 1
|
| 465 |
+
if i < 15:
|
| 466 |
+
pygame.draw.line(self.screen, (255,255,255), (self.car.x, self.car.y), (pt.x, pt.y), 1)
|
| 467 |
+
elif i >=15 and i < 17:
|
| 468 |
+
pygame.draw.line(self.screen, (255,255,255), ((self.car.p1.x + self.car.p2.x)/2, (self.car.p1.y + self.car.p2.y)/2), (pt.x, pt.y), 1)
|
| 469 |
+
elif i == 17:
|
| 470 |
+
pygame.draw.line(self.screen, (255,255,255), (self.car.p1.x , self.car.p1.y ), (pt.x, pt.y), 1)
|
| 471 |
+
else:
|
| 472 |
+
pygame.draw.line(self.screen, (255,255,255), (self.car.p2.x, self.car.p2.y), (pt.x, pt.y), 1)
|
| 473 |
+
|
| 474 |
+
#render controll
|
| 475 |
+
pygame.draw.rect(self.screen,(255,255,255),(800, 100, 40, 40),2)
|
| 476 |
+
pygame.draw.rect(self.screen,(255,255,255),(850, 100, 40, 40),2)
|
| 477 |
+
pygame.draw.rect(self.screen,(255,255,255),(900, 100, 40, 40),2)
|
| 478 |
+
pygame.draw.rect(self.screen,(255,255,255),(850, 50, 40, 40),2)
|
| 479 |
+
|
| 480 |
+
if action == 4:
|
| 481 |
+
pygame.draw.rect(self.screen,(0,255,0),(850, 50, 40, 40))
|
| 482 |
+
elif action == 6:
|
| 483 |
+
pygame.draw.rect(self.screen,(0,255,0),(850, 50, 40, 40))
|
| 484 |
+
pygame.draw.rect(self.screen,(0,255,0),(800, 100, 40, 40))
|
| 485 |
+
elif action == 5:
|
| 486 |
+
pygame.draw.rect(self.screen,(0,255,0),(850, 50, 40, 40))
|
| 487 |
+
pygame.draw.rect(self.screen,(0,255,0),(900, 100, 40, 40))
|
| 488 |
+
elif action == 1:
|
| 489 |
+
pygame.draw.rect(self.screen,(0,255,0),(850, 100, 40, 40))
|
| 490 |
+
elif action == 8:
|
| 491 |
+
pygame.draw.rect(self.screen,(0,255,0),(850, 100, 40, 40))
|
| 492 |
+
pygame.draw.rect(self.screen,(0,255,0),(800, 100, 40, 40))
|
| 493 |
+
elif action == 7:
|
| 494 |
+
pygame.draw.rect(self.screen,(0,255,0),(850, 100, 40, 40))
|
| 495 |
+
pygame.draw.rect(self.screen,(0,255,0),(900, 100, 40, 40))
|
| 496 |
+
elif action == 2:
|
| 497 |
+
pygame.draw.rect(self.screen,(0,255,0),(800, 100, 40, 40))
|
| 498 |
+
elif action == 3:
|
| 499 |
+
pygame.draw.rect(self.screen,(0,255,0),(900, 100, 40, 40))
|
| 500 |
+
|
| 501 |
+
# score
|
| 502 |
+
text_surface = self.font.render(f'Points {self.car.points}', True, pygame.Color('green'))
|
| 503 |
+
self.screen.blit(text_surface, dest=(0, 0))
|
| 504 |
+
# speed
|
| 505 |
+
text_surface = self.font.render(f'Speed {self.car.vel*-1}', True, pygame.Color('green'))
|
| 506 |
+
self.screen.blit(text_surface, dest=(800, 0))
|
| 507 |
+
|
| 508 |
+
self.clock.tick(self.fps)
|
| 509 |
+
pygame.display.update()
|
| 510 |
+
|
| 511 |
+
def close(self):
|
| 512 |
+
pygame.quit()
|
gui/interface/simulations/fyp_simulation/Goals.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pygame
|
| 2 |
+
|
| 3 |
+
class Goal:
|
| 4 |
+
def __init__(self, x1, y1, x2, y2):
|
| 5 |
+
self.x1 = x1
|
| 6 |
+
self.y1 = y1
|
| 7 |
+
self.x2 = x2
|
| 8 |
+
self.y2 = y2
|
| 9 |
+
|
| 10 |
+
self.isactiv = False
|
| 11 |
+
|
| 12 |
+
def draw(self, win):
|
| 13 |
+
pygame.draw.line(win, (0,255,0), (self.x1, self.y1), (self.x2, self.y2), 2)
|
| 14 |
+
if self.isactiv:
|
| 15 |
+
pygame.draw.line(win, (255,0,0), (self.x1, self.y1), (self.x2, self.y2), 2)
|
| 16 |
+
|
| 17 |
+
# the file of shame
|
| 18 |
+
def getGoals():
|
| 19 |
+
goals = []
|
| 20 |
+
|
| 21 |
+
goal1 = Goal(0,200,120,200)
|
| 22 |
+
goal2 = Goal(0,100,120,150)
|
| 23 |
+
goal2_5 = Goal(0,0,150,130)
|
| 24 |
+
goal3 = Goal(120,0,170,120)
|
| 25 |
+
goal3_5 = Goal(200,0,200,120)
|
| 26 |
+
goal4 = Goal(270,0,270,110)
|
| 27 |
+
goal4_5 = Goal(350,0,350,110)
|
| 28 |
+
goal5 = Goal(450,0,450,110)
|
| 29 |
+
goal5_5 = Goal(525,0,525,110)
|
| 30 |
+
goal6 = Goal(600,0,550,130)
|
| 31 |
+
goal6_5 = Goal(550,130,700,60)
|
| 32 |
+
goal7 = Goal(550,130,700,130)
|
| 33 |
+
goal7_5 = Goal(550,130,650,200)
|
| 34 |
+
goal8 = Goal(550,130,570,240)
|
| 35 |
+
goal9 = Goal(410,130,430,260)
|
| 36 |
+
goal9_5 = Goal(430,260,300,350)
|
| 37 |
+
goal10 = Goal(430,260,260,260)
|
| 38 |
+
goal10_5 = Goal(430,260,280,180)
|
| 39 |
+
goal11 = Goal(430,260,400,400)
|
| 40 |
+
goal12 = Goal(550,260,570,400)
|
| 41 |
+
goal13 = Goal(750,400,650,200)
|
| 42 |
+
goal14 = Goal(750,400,800,160)
|
| 43 |
+
goal15 = Goal(750,400,950,240)
|
| 44 |
+
goal16 = Goal(750,400,980,440)
|
| 45 |
+
goal17 = Goal(750,400,900,600)
|
| 46 |
+
goal18 = Goal(750,460,750,600)
|
| 47 |
+
goal19 = Goal(670,460,670,600)
|
| 48 |
+
goal19_5 = Goal(590,460,590,600)
|
| 49 |
+
goal20 = Goal(510,460,510,600)
|
| 50 |
+
goal20_5 = Goal(430,460,430,600)
|
| 51 |
+
goal21 = Goal(350,460,350,600)
|
| 52 |
+
goal21_5 = Goal(280,460,278,600)
|
| 53 |
+
goal22 = Goal(210,460,190,600)
|
| 54 |
+
goal22_5 = Goal(80,600,175,440)
|
| 55 |
+
goal23 = Goal(150,420,0,570)
|
| 56 |
+
goal23_5 = Goal(0,450,130,400)
|
| 57 |
+
goal24 = Goal(0,380,130,380)
|
| 58 |
+
|
| 59 |
+
goals.append(goal1)
|
| 60 |
+
goals.append(goal2)
|
| 61 |
+
goals.append(goal2_5)
|
| 62 |
+
goals.append(goal3)
|
| 63 |
+
goals.append(goal3_5)
|
| 64 |
+
goals.append(goal4)
|
| 65 |
+
goals.append(goal4_5)
|
| 66 |
+
goals.append(goal5)
|
| 67 |
+
goals.append(goal5_5)
|
| 68 |
+
goals.append(goal6)
|
| 69 |
+
goals.append(goal6_5)
|
| 70 |
+
goals.append(goal7)
|
| 71 |
+
goals.append(goal7_5)
|
| 72 |
+
goals.append(goal8)
|
| 73 |
+
goals.append(goal9)
|
| 74 |
+
goals.append(goal10_5)
|
| 75 |
+
goals.append(goal10)
|
| 76 |
+
goals.append(goal9_5)
|
| 77 |
+
goals.append(goal11)
|
| 78 |
+
goals.append(goal12)
|
| 79 |
+
goals.append(goal13)
|
| 80 |
+
goals.append(goal14)
|
| 81 |
+
goals.append(goal15)
|
| 82 |
+
goals.append(goal16)
|
| 83 |
+
goals.append(goal17)
|
| 84 |
+
goals.append(goal18)
|
| 85 |
+
goals.append(goal19)
|
| 86 |
+
goals.append(goal19_5)
|
| 87 |
+
goals.append(goal20)
|
| 88 |
+
goals.append(goal20_5)
|
| 89 |
+
goals.append(goal21)
|
| 90 |
+
goals.append(goal21_5)
|
| 91 |
+
goals.append(goal22)
|
| 92 |
+
goals.append(goal22_5)
|
| 93 |
+
goals.append(goal23)
|
| 94 |
+
goals.append(goal23_5)
|
| 95 |
+
goals.append(goal24)
|
| 96 |
+
|
| 97 |
+
goals[len(goals)-1].isactiv = True
|
| 98 |
+
|
| 99 |
+
return(goals)
|
gui/interface/simulations/fyp_simulation/Walls.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pygame
|
| 2 |
+
|
| 3 |
+
class Wall:
|
| 4 |
+
def __init__(self, x1, y1, x2, y2):
|
| 5 |
+
self.x1 = x1
|
| 6 |
+
self.y1 = y1
|
| 7 |
+
self.x2 = x2
|
| 8 |
+
self.y2 = y2
|
| 9 |
+
|
| 10 |
+
def draw(self, win):
|
| 11 |
+
pygame.draw.line(win, (255,255,255), (self.x1, self.y1), (self.x2, self.y2), 5)
|
| 12 |
+
|
| 13 |
+
def getWalls():
|
| 14 |
+
walls = []
|
| 15 |
+
|
| 16 |
+
wall1 = Wall(12, 451, 15, 130)
|
| 17 |
+
wall2 = Wall(15, 130, 61, 58)
|
| 18 |
+
wall3 = Wall(61, 58, 149, 14)
|
| 19 |
+
wall4 = Wall(149, 14, 382, 20)
|
| 20 |
+
wall5 = Wall(382, 20, 549, 31)
|
| 21 |
+
wall6 = Wall(549, 31, 636, 58)
|
| 22 |
+
wall7 = Wall(636, 58, 678, 102)
|
| 23 |
+
wall8 = Wall(678, 102, 669, 167)
|
| 24 |
+
wall9 = Wall(669, 167, 600, 206)
|
| 25 |
+
wall10 = Wall(600, 206, 507, 214)
|
| 26 |
+
wall11 = Wall(507, 214, 422, 232)
|
| 27 |
+
wall12 = Wall(422, 232, 375, 263)
|
| 28 |
+
wall13 = Wall(375, 263, 379, 283)
|
| 29 |
+
wall14 = Wall(379, 283, 454, 299)
|
| 30 |
+
wall15 = Wall(454, 299, 613, 286)
|
| 31 |
+
wall16 = Wall(613, 286, 684, 238)
|
| 32 |
+
wall17 = Wall(684, 238, 752, 180)
|
| 33 |
+
wall18 = Wall(752, 180, 862, 185)
|
| 34 |
+
wall19 = Wall(862, 185, 958, 279)
|
| 35 |
+
wall20 = Wall(958, 279, 953, 410)
|
| 36 |
+
wall21 = Wall(953, 410, 925, 505)
|
| 37 |
+
wall22 = Wall(925, 505, 804, 566)
|
| 38 |
+
wall23 = Wall(804, 566, 150, 570)
|
| 39 |
+
wall24 = Wall(150, 570, 46, 529)
|
| 40 |
+
wall25 = Wall(46, 529, 12, 451)
|
| 41 |
+
wall27 = Wall(104, 436, 96, 161)
|
| 42 |
+
wall28 = Wall(96, 161, 122, 122)
|
| 43 |
+
wall29 = Wall(122, 122, 199, 91)
|
| 44 |
+
wall30 = Wall(199, 91, 376, 94)
|
| 45 |
+
wall31 = Wall(376, 94, 469, 100)
|
| 46 |
+
wall32 = Wall(469, 100, 539, 102)
|
| 47 |
+
wall33 = Wall(539, 102, 585, 121)
|
| 48 |
+
wall34 = Wall(585, 121, 585, 139)
|
| 49 |
+
wall35 = Wall(585, 139, 454, 158)
|
| 50 |
+
wall36 = Wall(454, 158, 352, 183)
|
| 51 |
+
wall37 = Wall(352, 183, 293, 239)
|
| 52 |
+
wall38 = Wall(293, 239, 294, 318)
|
| 53 |
+
wall39 = Wall(294, 318, 361, 357)
|
| 54 |
+
wall40 = Wall(361, 357, 490, 373)
|
| 55 |
+
wall41 = Wall(490, 373, 671, 359)
|
| 56 |
+
wall42 = Wall(671, 359, 752, 300) #
|
| 57 |
+
wall43 = Wall(752, 300, 812, 310)#
|
| 58 |
+
wall44 = Wall(812, 310, 854, 369)
|
| 59 |
+
wall45 = Wall(854, 369, 854, 429)
|
| 60 |
+
wall46 = Wall(854, 429, 754, 483)
|
| 61 |
+
wall47 = Wall(754, 483, 192, 489)
|
| 62 |
+
wall48 = Wall(192, 489, 104, 436)
|
| 63 |
+
|
| 64 |
+
walls.append(wall1)
|
| 65 |
+
walls.append(wall2)
|
| 66 |
+
walls.append(wall3)
|
| 67 |
+
walls.append(wall4)
|
| 68 |
+
walls.append(wall5)
|
| 69 |
+
walls.append(wall6)
|
| 70 |
+
walls.append(wall7)
|
| 71 |
+
walls.append(wall8)
|
| 72 |
+
walls.append(wall9)
|
| 73 |
+
walls.append(wall10)
|
| 74 |
+
walls.append(wall11)
|
| 75 |
+
walls.append(wall12)
|
| 76 |
+
walls.append(wall13)
|
| 77 |
+
walls.append(wall14)
|
| 78 |
+
walls.append(wall15)
|
| 79 |
+
walls.append(wall16)
|
| 80 |
+
walls.append(wall17)
|
| 81 |
+
walls.append(wall18)
|
| 82 |
+
walls.append(wall19)
|
| 83 |
+
walls.append(wall20)
|
| 84 |
+
walls.append(wall21)
|
| 85 |
+
walls.append(wall22)
|
| 86 |
+
walls.append(wall23)
|
| 87 |
+
walls.append(wall24)
|
| 88 |
+
walls.append(wall25)
|
| 89 |
+
|
| 90 |
+
walls.append(wall27)
|
| 91 |
+
walls.append(wall28)
|
| 92 |
+
walls.append(wall29)
|
| 93 |
+
walls.append(wall30)
|
| 94 |
+
walls.append(wall31)
|
| 95 |
+
walls.append(wall32)
|
| 96 |
+
walls.append(wall33)
|
| 97 |
+
walls.append(wall34)
|
| 98 |
+
walls.append(wall35)
|
| 99 |
+
walls.append(wall36)
|
| 100 |
+
walls.append(wall37)
|
| 101 |
+
walls.append(wall38)
|
| 102 |
+
walls.append(wall39)
|
| 103 |
+
walls.append(wall40)
|
| 104 |
+
walls.append(wall41)
|
| 105 |
+
walls.append(wall42)
|
| 106 |
+
walls.append(wall43)
|
| 107 |
+
walls.append(wall44)
|
| 108 |
+
walls.append(wall45)
|
| 109 |
+
walls.append(wall46)
|
| 110 |
+
walls.append(wall47)
|
| 111 |
+
walls.append(wall48)
|
| 112 |
+
|
| 113 |
+
return(walls)
|
gui/interface/simulations/fyp_simulation/car.png
ADDED
|
gui/interface/simulations/fyp_simulation/ddqn_keras.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# File: ddqn_keras.py
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import tensorflow as tf
|
| 5 |
+
from keras.models import Sequential, load_model
|
| 6 |
+
from keras.layers import Dense
|
| 7 |
+
from keras.optimizers import Adam
|
| 8 |
+
|
| 9 |
+
class ReplayBuffer:
|
| 10 |
+
def __init__(self, max_size, input_shape, n_actions, discrete=False):
|
| 11 |
+
self.mem_size = max_size
|
| 12 |
+
self.mem_cntr = 0
|
| 13 |
+
self.discrete = discrete
|
| 14 |
+
self.state_memory = np.zeros((self.mem_size, input_shape))
|
| 15 |
+
self.new_state_memory = np.zeros((self.mem_size, input_shape))
|
| 16 |
+
dtype = np.int8 if self.discrete else np.float32
|
| 17 |
+
self.action_memory = np.zeros((self.mem_size, n_actions), dtype=dtype)
|
| 18 |
+
self.reward_memory = np.zeros(self.mem_size)
|
| 19 |
+
self.terminal_memory = np.zeros(self.mem_size, dtype=np.float32)
|
| 20 |
+
|
| 21 |
+
def store_transition(self, state, action, reward, state_, done):
|
| 22 |
+
index = self.mem_cntr % self.mem_size
|
| 23 |
+
self.state_memory[index] = state
|
| 24 |
+
self.new_state_memory[index] = state_
|
| 25 |
+
if self.discrete:
|
| 26 |
+
actions = np.zeros(self.action_memory.shape[1])
|
| 27 |
+
actions[action] = 1.0
|
| 28 |
+
self.action_memory[index] = actions
|
| 29 |
+
else:
|
| 30 |
+
self.action_memory[index] = action
|
| 31 |
+
self.reward_memory[index] = reward
|
| 32 |
+
self.terminal_memory[index] = 1 - done
|
| 33 |
+
self.mem_cntr += 1
|
| 34 |
+
|
| 35 |
+
def sample_buffer(self, batch_size):
|
| 36 |
+
max_mem = min(self.mem_cntr, self.mem_size)
|
| 37 |
+
batch = np.random.choice(max_mem, batch_size)
|
| 38 |
+
|
| 39 |
+
states = self.state_memory[batch]
|
| 40 |
+
actions = self.action_memory[batch]
|
| 41 |
+
rewards = self.reward_memory[batch]
|
| 42 |
+
states_ = self.new_state_memory[batch]
|
| 43 |
+
terminal = self.terminal_memory[batch]
|
| 44 |
+
|
| 45 |
+
return states, actions, rewards, states_, terminal
|
| 46 |
+
|
| 47 |
+
class DDQNAgent:
|
| 48 |
+
def __init__(self, alpha, gamma, n_actions, epsilon, batch_size,
|
| 49 |
+
input_dims, epsilon_dec=0.999995, epsilon_end=0.10,
|
| 50 |
+
mem_size=25000, fname='ddqn_model.keras', replace_target=25):
|
| 51 |
+
self.action_space = [i for i in range(n_actions)]
|
| 52 |
+
self.n_actions = n_actions
|
| 53 |
+
self.gamma = gamma
|
| 54 |
+
self.epsilon = epsilon
|
| 55 |
+
self.epsilon_dec = epsilon_dec
|
| 56 |
+
self.epsilon_min = epsilon_end
|
| 57 |
+
self.batch_size = batch_size
|
| 58 |
+
self.model_file = fname
|
| 59 |
+
self.replace_target = replace_target
|
| 60 |
+
self.memory = ReplayBuffer(mem_size, input_dims, n_actions, discrete=True)
|
| 61 |
+
|
| 62 |
+
self.brain_eval = Brain(input_dims, n_actions, batch_size)
|
| 63 |
+
self.brain_target = Brain(input_dims, n_actions, batch_size)
|
| 64 |
+
|
| 65 |
+
def remember(self, state, action, reward, new_state, done):
|
| 66 |
+
self.memory.store_transition(state, action, reward, new_state, done)
|
| 67 |
+
|
| 68 |
+
def choose_action(self, state):
|
| 69 |
+
state = np.array(state)[np.newaxis, :]
|
| 70 |
+
rand = np.random.random()
|
| 71 |
+
if rand < self.epsilon:
|
| 72 |
+
action = np.random.choice(self.action_space)
|
| 73 |
+
else:
|
| 74 |
+
actions = self.brain_eval.model.predict(state, verbose=0)
|
| 75 |
+
action = np.argmax(actions)
|
| 76 |
+
return action
|
| 77 |
+
|
| 78 |
+
def learn(self):
|
| 79 |
+
if self.memory.mem_cntr > self.batch_size:
|
| 80 |
+
state, action, reward, new_state, done = self.memory.sample_buffer(self.batch_size)
|
| 81 |
+
action_indices = np.dot(action, np.array(self.action_space, dtype=np.int8))
|
| 82 |
+
|
| 83 |
+
q_next = self.brain_target.model.predict(new_state, verbose=0)
|
| 84 |
+
q_eval = self.brain_eval.model.predict(new_state, verbose=0)
|
| 85 |
+
q_pred = self.brain_eval.model.predict(state, verbose=0)
|
| 86 |
+
|
| 87 |
+
max_actions = np.argmax(q_eval, axis=1)
|
| 88 |
+
|
| 89 |
+
q_target = np.copy(q_pred)
|
| 90 |
+
batch_index = np.arange(self.batch_size, dtype=np.int32)
|
| 91 |
+
q_target[batch_index, action_indices] = reward + self.gamma * q_next[batch_index, max_actions.astype(int)] * done
|
| 92 |
+
|
| 93 |
+
self.brain_eval.model.fit(state, q_target, verbose=0)
|
| 94 |
+
self.epsilon = max(self.epsilon * self.epsilon_dec, self.epsilon_min)
|
| 95 |
+
|
| 96 |
+
def update_network_parameters(self):
|
| 97 |
+
self.brain_target.copy_weights(self.brain_eval)
|
| 98 |
+
|
| 99 |
+
def save_model(self):
|
| 100 |
+
self.brain_eval.model.save(self.model_file) # Save using .keras format
|
| 101 |
+
|
| 102 |
+
def load_model(self):
|
| 103 |
+
self.brain_eval.model = tf.keras.models.load_model(self.model_file)
|
| 104 |
+
self.brain_target.model = tf.keras.models.load_model(self.model_file)
|
| 105 |
+
if self.epsilon == 0.0:
|
| 106 |
+
self.update_network_parameters()
|
| 107 |
+
|
| 108 |
+
class Brain:
|
| 109 |
+
def __init__(self, NbrStates, NbrActions, batch_size=256):
|
| 110 |
+
self.NbrStates = NbrStates
|
| 111 |
+
self.NbrActions = NbrActions
|
| 112 |
+
self.batch_size = batch_size
|
| 113 |
+
self.model = self.createModel()
|
| 114 |
+
|
| 115 |
+
def createModel(self):
|
| 116 |
+
model = Sequential()
|
| 117 |
+
model.add(Dense(256, activation='relu', input_shape=(self.NbrStates,)))
|
| 118 |
+
model.add(Dense(self.NbrActions, activation=tf.keras.activations.softmax))
|
| 119 |
+
model.compile(loss='mse', optimizer=Adam(learning_rate=0.001))
|
| 120 |
+
return model
|
| 121 |
+
|
| 122 |
+
def copy_weights(self, TrainNet):
|
| 123 |
+
variables1 = self.model.trainable_variables
|
| 124 |
+
variables2 = TrainNet.model.trainable_variables
|
| 125 |
+
for v1, v2 in zip(variables1, variables2):
|
| 126 |
+
v1.assign(v2.numpy())
|
gui/interface/simulations/fyp_simulation/ddqn_model.keras
ADDED
|
Binary file (92.9 kB). View file
|
|
|
gui/interface/simulations/fyp_simulation/track.png
ADDED
|
gui/interface/simulations/neat_simulation.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import neat
|
| 3 |
+
import pickle
|
| 4 |
+
from .fyp1_simulation.newcar import run_simulation
|
| 5 |
+
|
| 6 |
+
# Paths
|
| 7 |
+
config_path = os.path.join(os.path.dirname(__file__), 'fyp1_simulation', 'config.txt')
|
| 8 |
+
genome_path = os.path.join(os.path.dirname(__file__), 'fyp1_simulation', 'best_neat_genome.pkl')
|
| 9 |
+
|
| 10 |
+
# Load NEAT config
|
| 11 |
+
config = neat.config.Config(
|
| 12 |
+
neat.DefaultGenome,
|
| 13 |
+
neat.DefaultReproduction,
|
| 14 |
+
neat.DefaultSpeciesSet,
|
| 15 |
+
neat.DefaultStagnation,
|
| 16 |
+
config_path
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
# Load the best genome
|
| 20 |
+
if not os.path.exists(genome_path):
|
| 21 |
+
raise FileNotFoundError(f"Best genome file not found at {genome_path}")
|
| 22 |
+
|
| 23 |
+
with open(genome_path, "rb") as f:
|
| 24 |
+
best_genome = pickle.load(f)
|
| 25 |
+
|
| 26 |
+
# print("\n✅ Best genome loaded successfully!")
|
| 27 |
+
# print(f"Fitness: {best_genome.fitness}")
|
| 28 |
+
|
| 29 |
+
# Wrap the genome in the expected format: list of tuples (genome_id, genome)
|
| 30 |
+
test_genomes = [(0, best_genome)]
|
| 31 |
+
|
| 32 |
+
# Run the simulation for testing
|
| 33 |
+
run_simulation(test_genomes, config)
|
gui/interface/static/background.jpg
ADDED
|
gui/interface/static/interface/css/all.min.css
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*!
|
| 2 |
+
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
|
| 3 |
+
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
| 4 |
+
* Copyright 2024 Fonticons, Inc.
|
| 5 |
+
*/
|
| 6 |
+
.fa{font-family:var(--fa-style-family,"Font Awesome 6 Free");font-weight:var(--fa-style,900)}.fa,.fa-brands,.fa-classic,.fa-regular,.fa-sharp-solid,.fa-solid,.fab,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:var(--fa-display,inline-block);font-style:normal;font-variant:normal;line-height:1;text-rendering:auto}.fa-classic,.fa-regular,.fa-solid,.far,.fas{font-family:"Font Awesome 6 Free"}.fa-brands,.fab{font-family:"Font Awesome 6 Brands"}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{font-size:.625em;line-height:.1em;vertical-align:.225em}.fa-xs{font-size:.75em;line-height:.08333em;vertical-align:.125em}.fa-sm{font-size:.875em;line-height:.07143em;vertical-align:.05357em}.fa-lg{font-size:1.25em;line-height:.05em;vertical-align:-.075em}.fa-xl{font-size:1.5em;line-height:.04167em;vertical-align:-.125em}.fa-2xl{font-size:2em;line-height:.03125em;vertical-align:-.1875em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:var(--fa-li-margin,2.5em);padding-left:0}.fa-ul>li{position:relative}.fa-li{left:calc(var(--fa-li-width, 2em)*-1);position:absolute;text-align:center;width:var(--fa-li-width,2em);line-height:inherit}.fa-border{border-radius:var(--fa-border-radius,.1em);border:var(--fa-border-width,.08em) var(--fa-border-style,solid) var(--fa-border-color,#eee);padding:var(--fa-border-padding,.2em .25em .15em)}.fa-pull-left{float:left;margin-right:var(--fa-pull-margin,.3em)}.fa-pull-right{float:right;margin-left:var(--fa-pull-margin,.3em)}.fa-beat{animation-name:fa-beat;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-bounce{animation-name:fa-bounce;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1))}.fa-fade{animation-name:fa-fade;animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-beat-fade,.fa-fade{animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s)}.fa-beat-fade{animation-name:fa-beat-fade;animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-flip{animation-name:fa-flip;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-shake{animation-name:fa-shake;animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-shake,.fa-spin{animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal)}.fa-spin{animation-name:fa-spin;animation-duration:var(--fa-animation-duration,2s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{animation-name:fa-spin;animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,steps(8))}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-beat-fade,.fa-bounce,.fa-fade,.fa-flip,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse{animation-delay:-1ms;animation-duration:1ms;animation-iteration-count:1;transition-delay:0s;transition-duration:0s}}@keyframes fa-beat{0%,90%{transform:scale(1)}45%{transform:scale(var(--fa-beat-scale,1.25))}}@keyframes fa-bounce{0%{transform:scale(1) translateY(0)}10%{transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{transform:scale(1) translateY(0)}to{transform:scale(1) translateY(0)}}@keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);transform:scale(1)}50%{opacity:1;transform:scale(var(--fa-beat-fade-scale,1.125))}}@keyframes fa-flip{50%{transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@keyframes fa-shake{0%{transform:rotate(-15deg)}4%{transform:rotate(15deg)}8%,24%{transform:rotate(-18deg)}12%,28%{transform:rotate(18deg)}16%{transform:rotate(-22deg)}20%{transform:rotate(22deg)}32%{transform:rotate(-12deg)}36%{transform:rotate(12deg)}40%,to{transform:rotate(0deg)}}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{transform:rotate(90deg)}.fa-rotate-180{transform:rotate(180deg)}.fa-rotate-270{transform:rotate(270deg)}.fa-flip-horizontal{transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}.fa-rotate-by{transform:rotate(var(--fa-rotate-angle,0))}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%;z-index:var(--fa-stack-z-index,auto)}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:var(--fa-inverse,#fff)}
|
| 7 |
+
|
| 8 |
+
.fa-0:before{content:"\30"}.fa-1:before{content:"\31"}.fa-2:before{content:"\32"}.fa-3:before{content:"\33"}.fa-4:before{content:"\34"}.fa-5:before{content:"\35"}.fa-6:before{content:"\36"}.fa-7:before{content:"\37"}.fa-8:before{content:"\38"}.fa-9:before{content:"\39"}.fa-fill-drip:before{content:"\f576"}.fa-arrows-to-circle:before{content:"\e4bd"}.fa-chevron-circle-right:before,.fa-circle-chevron-right:before{content:"\f138"}.fa-at:before{content:"\40"}.fa-trash-alt:before,.fa-trash-can:before{content:"\f2ed"}.fa-text-height:before{content:"\f034"}.fa-user-times:before,.fa-user-xmark:before{content:"\f235"}.fa-stethoscope:before{content:"\f0f1"}.fa-comment-alt:before,.fa-message:before{content:"\f27a"}.fa-info:before{content:"\f129"}.fa-compress-alt:before,.fa-down-left-and-up-right-to-center:before{content:"\f422"}.fa-explosion:before{content:"\e4e9"}.fa-file-alt:before,.fa-file-lines:before,.fa-file-text:before{content:"\f15c"}.fa-wave-square:before{content:"\f83e"}.fa-ring:before{content:"\f70b"}.fa-building-un:before{content:"\e4d9"}.fa-dice-three:before{content:"\f527"}.fa-calendar-alt:before,.fa-calendar-days:before{content:"\f073"}.fa-anchor-circle-check:before{content:"\e4aa"}.fa-building-circle-arrow-right:before{content:"\e4d1"}.fa-volleyball-ball:before,.fa-volleyball:before{content:"\f45f"}.fa-arrows-up-to-line:before{content:"\e4c2"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-circle-minus:before,.fa-minus-circle:before{content:"\f056"}.fa-door-open:before{content:"\f52b"}.fa-right-from-bracket:before,.fa-sign-out-alt:before{content:"\f2f5"}.fa-atom:before{content:"\f5d2"}.fa-soap:before{content:"\e06e"}.fa-heart-music-camera-bolt:before,.fa-icons:before{content:"\f86d"}.fa-microphone-alt-slash:before,.fa-microphone-lines-slash:before{content:"\f539"}.fa-bridge-circle-check:before{content:"\e4c9"}.fa-pump-medical:before{content:"\e06a"}.fa-fingerprint:before{content:"\f577"}.fa-hand-point-right:before{content:"\f0a4"}.fa-magnifying-glass-location:before,.fa-search-location:before{content:"\f689"}.fa-forward-step:before,.fa-step-forward:before{content:"\f051"}.fa-face-smile-beam:before,.fa-smile-beam:before{content:"\f5b8"}.fa-flag-checkered:before{content:"\f11e"}.fa-football-ball:before,.fa-football:before{content:"\f44e"}.fa-school-circle-exclamation:before{content:"\e56c"}.fa-crop:before{content:"\f125"}.fa-angle-double-down:before,.fa-angles-down:before{content:"\f103"}.fa-users-rectangle:before{content:"\e594"}.fa-people-roof:before{content:"\e537"}.fa-people-line:before{content:"\e534"}.fa-beer-mug-empty:before,.fa-beer:before{content:"\f0fc"}.fa-diagram-predecessor:before{content:"\e477"}.fa-arrow-up-long:before,.fa-long-arrow-up:before{content:"\f176"}.fa-burn:before,.fa-fire-flame-simple:before{content:"\f46a"}.fa-male:before,.fa-person:before{content:"\f183"}.fa-laptop:before{content:"\f109"}.fa-file-csv:before{content:"\f6dd"}.fa-menorah:before{content:"\f676"}.fa-truck-plane:before{content:"\e58f"}.fa-record-vinyl:before{content:"\f8d9"}.fa-face-grin-stars:before,.fa-grin-stars:before{content:"\f587"}.fa-bong:before{content:"\f55c"}.fa-pastafarianism:before,.fa-spaghetti-monster-flying:before{content:"\f67b"}.fa-arrow-down-up-across-line:before{content:"\e4af"}.fa-spoon:before,.fa-utensil-spoon:before{content:"\f2e5"}.fa-jar-wheat:before{content:"\e517"}.fa-envelopes-bulk:before,.fa-mail-bulk:before{content:"\f674"}.fa-file-circle-exclamation:before{content:"\e4eb"}.fa-circle-h:before,.fa-hospital-symbol:before{content:"\f47e"}.fa-pager:before{content:"\f815"}.fa-address-book:before,.fa-contact-book:before{content:"\f2b9"}.fa-strikethrough:before{content:"\f0cc"}.fa-k:before{content:"\4b"}.fa-landmark-flag:before{content:"\e51c"}.fa-pencil-alt:before,.fa-pencil:before{content:"\f303"}.fa-backward:before{content:"\f04a"}.fa-caret-right:before{content:"\f0da"}.fa-comments:before{content:"\f086"}.fa-file-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-code-pull-request:before{content:"\e13c"}.fa-clipboard-list:before{content:"\f46d"}.fa-truck-loading:before,.fa-truck-ramp-box:before{content:"\f4de"}.fa-user-check:before{content:"\f4fc"}.fa-vial-virus:before{content:"\e597"}.fa-sheet-plastic:before{content:"\e571"}.fa-blog:before{content:"\f781"}.fa-user-ninja:before{content:"\f504"}.fa-person-arrow-up-from-line:before{content:"\e539"}.fa-scroll-torah:before,.fa-torah:before{content:"\f6a0"}.fa-broom-ball:before,.fa-quidditch-broom-ball:before,.fa-quidditch:before{content:"\f458"}.fa-toggle-off:before{content:"\f204"}.fa-archive:before,.fa-box-archive:before{content:"\f187"}.fa-person-drowning:before{content:"\e545"}.fa-arrow-down-9-1:before,.fa-sort-numeric-desc:before,.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-face-grin-tongue-squint:before,.fa-grin-tongue-squint:before{content:"\f58a"}.fa-spray-can:before{content:"\f5bd"}.fa-truck-monster:before{content:"\f63b"}.fa-w:before{content:"\57"}.fa-earth-africa:before,.fa-globe-africa:before{content:"\f57c"}.fa-rainbow:before{content:"\f75b"}.fa-circle-notch:before{content:"\f1ce"}.fa-tablet-alt:before,.fa-tablet-screen-button:before{content:"\f3fa"}.fa-paw:before{content:"\f1b0"}.fa-cloud:before{content:"\f0c2"}.fa-trowel-bricks:before{content:"\e58a"}.fa-face-flushed:before,.fa-flushed:before{content:"\f579"}.fa-hospital-user:before{content:"\f80d"}.fa-tent-arrow-left-right:before{content:"\e57f"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-binoculars:before{content:"\f1e5"}.fa-microphone-slash:before{content:"\f131"}.fa-box-tissue:before{content:"\e05b"}.fa-motorcycle:before{content:"\f21c"}.fa-bell-concierge:before,.fa-concierge-bell:before{content:"\f562"}.fa-pen-ruler:before,.fa-pencil-ruler:before{content:"\f5ae"}.fa-people-arrows-left-right:before,.fa-people-arrows:before{content:"\e068"}.fa-mars-and-venus-burst:before{content:"\e523"}.fa-caret-square-right:before,.fa-square-caret-right:before{content:"\f152"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-sun-plant-wilt:before{content:"\e57a"}.fa-toilets-portable:before{content:"\e584"}.fa-hockey-puck:before{content:"\f453"}.fa-table:before{content:"\f0ce"}.fa-magnifying-glass-arrow-right:before{content:"\e521"}.fa-digital-tachograph:before,.fa-tachograph-digital:before{content:"\f566"}.fa-users-slash:before{content:"\e073"}.fa-clover:before{content:"\e139"}.fa-mail-reply:before,.fa-reply:before{content:"\f3e5"}.fa-star-and-crescent:before{content:"\f699"}.fa-house-fire:before{content:"\e50c"}.fa-minus-square:before,.fa-square-minus:before{content:"\f146"}.fa-helicopter:before{content:"\f533"}.fa-compass:before{content:"\f14e"}.fa-caret-square-down:before,.fa-square-caret-down:before{content:"\f150"}.fa-file-circle-question:before{content:"\e4ef"}.fa-laptop-code:before{content:"\f5fc"}.fa-swatchbook:before{content:"\f5c3"}.fa-prescription-bottle:before{content:"\f485"}.fa-bars:before,.fa-navicon:before{content:"\f0c9"}.fa-people-group:before{content:"\e533"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-heart-broken:before,.fa-heart-crack:before{content:"\f7a9"}.fa-external-link-square-alt:before,.fa-square-up-right:before{content:"\f360"}.fa-face-kiss-beam:before,.fa-kiss-beam:before{content:"\f597"}.fa-film:before{content:"\f008"}.fa-ruler-horizontal:before{content:"\f547"}.fa-people-robbery:before{content:"\e536"}.fa-lightbulb:before{content:"\f0eb"}.fa-caret-left:before{content:"\f0d9"}.fa-circle-exclamation:before,.fa-exclamation-circle:before{content:"\f06a"}.fa-school-circle-xmark:before{content:"\e56d"}.fa-arrow-right-from-bracket:before,.fa-sign-out:before{content:"\f08b"}.fa-chevron-circle-down:before,.fa-circle-chevron-down:before{content:"\f13a"}.fa-unlock-alt:before,.fa-unlock-keyhole:before{content:"\f13e"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-headphones-alt:before,.fa-headphones-simple:before{content:"\f58f"}.fa-sitemap:before{content:"\f0e8"}.fa-circle-dollar-to-slot:before,.fa-donate:before{content:"\f4b9"}.fa-memory:before{content:"\f538"}.fa-road-spikes:before{content:"\e568"}.fa-fire-burner:before{content:"\e4f1"}.fa-flag:before{content:"\f024"}.fa-hanukiah:before{content:"\f6e6"}.fa-feather:before{content:"\f52d"}.fa-volume-down:before,.fa-volume-low:before{content:"\f027"}.fa-comment-slash:before{content:"\f4b3"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-compress:before{content:"\f066"}.fa-wheat-alt:before,.fa-wheat-awn:before{content:"\e2cd"}.fa-ankh:before{content:"\f644"}.fa-hands-holding-child:before{content:"\e4fa"}.fa-asterisk:before{content:"\2a"}.fa-check-square:before,.fa-square-check:before{content:"\f14a"}.fa-peseta-sign:before{content:"\e221"}.fa-header:before,.fa-heading:before{content:"\f1dc"}.fa-ghost:before{content:"\f6e2"}.fa-list-squares:before,.fa-list:before{content:"\f03a"}.fa-phone-square-alt:before,.fa-square-phone-flip:before{content:"\f87b"}.fa-cart-plus:before{content:"\f217"}.fa-gamepad:before{content:"\f11b"}.fa-circle-dot:before,.fa-dot-circle:before{content:"\f192"}.fa-dizzy:before,.fa-face-dizzy:before{content:"\f567"}.fa-egg:before{content:"\f7fb"}.fa-house-medical-circle-xmark:before{content:"\e513"}.fa-campground:before{content:"\f6bb"}.fa-folder-plus:before{content:"\f65e"}.fa-futbol-ball:before,.fa-futbol:before,.fa-soccer-ball:before{content:"\f1e3"}.fa-paint-brush:before,.fa-paintbrush:before{content:"\f1fc"}.fa-lock:before{content:"\f023"}.fa-gas-pump:before{content:"\f52f"}.fa-hot-tub-person:before,.fa-hot-tub:before{content:"\f593"}.fa-map-location:before,.fa-map-marked:before{content:"\f59f"}.fa-house-flood-water:before{content:"\e50e"}.fa-tree:before{content:"\f1bb"}.fa-bridge-lock:before{content:"\e4cc"}.fa-sack-dollar:before{content:"\f81d"}.fa-edit:before,.fa-pen-to-square:before{content:"\f044"}.fa-car-side:before{content:"\f5e4"}.fa-share-alt:before,.fa-share-nodes:before{content:"\f1e0"}.fa-heart-circle-minus:before{content:"\e4ff"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-microscope:before{content:"\f610"}.fa-sink:before{content:"\e06d"}.fa-bag-shopping:before,.fa-shopping-bag:before{content:"\f290"}.fa-arrow-down-z-a:before,.fa-sort-alpha-desc:before,.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-mitten:before{content:"\f7b5"}.fa-person-rays:before{content:"\e54d"}.fa-users:before{content:"\f0c0"}.fa-eye-slash:before{content:"\f070"}.fa-flask-vial:before{content:"\e4f3"}.fa-hand-paper:before,.fa-hand:before{content:"\f256"}.fa-om:before{content:"\f679"}.fa-worm:before{content:"\e599"}.fa-house-circle-xmark:before{content:"\e50b"}.fa-plug:before{content:"\f1e6"}.fa-chevron-up:before{content:"\f077"}.fa-hand-spock:before{content:"\f259"}.fa-stopwatch:before{content:"\f2f2"}.fa-face-kiss:before,.fa-kiss:before{content:"\f596"}.fa-bridge-circle-xmark:before{content:"\e4cb"}.fa-face-grin-tongue:before,.fa-grin-tongue:before{content:"\f589"}.fa-chess-bishop:before{content:"\f43a"}.fa-face-grin-wink:before,.fa-grin-wink:before{content:"\f58c"}.fa-deaf:before,.fa-deafness:before,.fa-ear-deaf:before,.fa-hard-of-hearing:before{content:"\f2a4"}.fa-road-circle-check:before{content:"\e564"}.fa-dice-five:before{content:"\f523"}.fa-rss-square:before,.fa-square-rss:before{content:"\f143"}.fa-land-mine-on:before{content:"\e51b"}.fa-i-cursor:before{content:"\f246"}.fa-stamp:before{content:"\f5bf"}.fa-stairs:before{content:"\e289"}.fa-i:before{content:"\49"}.fa-hryvnia-sign:before,.fa-hryvnia:before{content:"\f6f2"}.fa-pills:before{content:"\f484"}.fa-face-grin-wide:before,.fa-grin-alt:before{content:"\f581"}.fa-tooth:before{content:"\f5c9"}.fa-v:before{content:"\56"}.fa-bangladeshi-taka-sign:before{content:"\e2e6"}.fa-bicycle:before{content:"\f206"}.fa-rod-asclepius:before,.fa-rod-snake:before,.fa-staff-aesculapius:before,.fa-staff-snake:before{content:"\e579"}.fa-head-side-cough-slash:before{content:"\e062"}.fa-ambulance:before,.fa-truck-medical:before{content:"\f0f9"}.fa-wheat-awn-circle-exclamation:before{content:"\e598"}.fa-snowman:before{content:"\f7d0"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-road-barrier:before{content:"\e562"}.fa-school:before{content:"\f549"}.fa-igloo:before{content:"\f7ae"}.fa-joint:before{content:"\f595"}.fa-angle-right:before{content:"\f105"}.fa-horse:before{content:"\f6f0"}.fa-q:before{content:"\51"}.fa-g:before{content:"\47"}.fa-notes-medical:before{content:"\f481"}.fa-temperature-2:before,.fa-temperature-half:before,.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-dong-sign:before{content:"\e169"}.fa-capsules:before{content:"\f46b"}.fa-poo-bolt:before,.fa-poo-storm:before{content:"\f75a"}.fa-face-frown-open:before,.fa-frown-open:before{content:"\f57a"}.fa-hand-point-up:before{content:"\f0a6"}.fa-money-bill:before{content:"\f0d6"}.fa-bookmark:before{content:"\f02e"}.fa-align-justify:before{content:"\f039"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-helmet-un:before{content:"\e503"}.fa-bullseye:before{content:"\f140"}.fa-bacon:before{content:"\f7e5"}.fa-hand-point-down:before{content:"\f0a7"}.fa-arrow-up-from-bracket:before{content:"\e09a"}.fa-folder-blank:before,.fa-folder:before{content:"\f07b"}.fa-file-medical-alt:before,.fa-file-waveform:before{content:"\f478"}.fa-radiation:before{content:"\f7b9"}.fa-chart-simple:before{content:"\e473"}.fa-mars-stroke:before{content:"\f229"}.fa-vial:before{content:"\f492"}.fa-dashboard:before,.fa-gauge-med:before,.fa-gauge:before,.fa-tachometer-alt-average:before{content:"\f624"}.fa-magic-wand-sparkles:before,.fa-wand-magic-sparkles:before{content:"\e2ca"}.fa-e:before{content:"\45"}.fa-pen-alt:before,.fa-pen-clip:before{content:"\f305"}.fa-bridge-circle-exclamation:before{content:"\e4ca"}.fa-user:before{content:"\f007"}.fa-school-circle-check:before{content:"\e56b"}.fa-dumpster:before{content:"\f793"}.fa-shuttle-van:before,.fa-van-shuttle:before{content:"\f5b6"}.fa-building-user:before{content:"\e4da"}.fa-caret-square-left:before,.fa-square-caret-left:before{content:"\f191"}.fa-highlighter:before{content:"\f591"}.fa-key:before{content:"\f084"}.fa-bullhorn:before{content:"\f0a1"}.fa-globe:before{content:"\f0ac"}.fa-synagogue:before{content:"\f69b"}.fa-person-half-dress:before{content:"\e548"}.fa-road-bridge:before{content:"\e563"}.fa-location-arrow:before{content:"\f124"}.fa-c:before{content:"\43"}.fa-tablet-button:before{content:"\f10a"}.fa-building-lock:before{content:"\e4d6"}.fa-pizza-slice:before{content:"\f818"}.fa-money-bill-wave:before{content:"\f53a"}.fa-area-chart:before,.fa-chart-area:before{content:"\f1fe"}.fa-house-flag:before{content:"\e50d"}.fa-person-circle-minus:before{content:"\e540"}.fa-ban:before,.fa-cancel:before{content:"\f05e"}.fa-camera-rotate:before{content:"\e0d8"}.fa-air-freshener:before,.fa-spray-can-sparkles:before{content:"\f5d0"}.fa-star:before{content:"\f005"}.fa-repeat:before{content:"\f363"}.fa-cross:before{content:"\f654"}.fa-box:before{content:"\f466"}.fa-venus-mars:before{content:"\f228"}.fa-arrow-pointer:before,.fa-mouse-pointer:before{content:"\f245"}.fa-expand-arrows-alt:before,.fa-maximize:before{content:"\f31e"}.fa-charging-station:before{content:"\f5e7"}.fa-shapes:before,.fa-triangle-circle-square:before{content:"\f61f"}.fa-random:before,.fa-shuffle:before{content:"\f074"}.fa-person-running:before,.fa-running:before{content:"\f70c"}.fa-mobile-retro:before{content:"\e527"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-spider:before{content:"\f717"}.fa-hands-bound:before{content:"\e4f9"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-plane-circle-exclamation:before{content:"\e556"}.fa-x-ray:before{content:"\f497"}.fa-spell-check:before{content:"\f891"}.fa-slash:before{content:"\f715"}.fa-computer-mouse:before,.fa-mouse:before{content:"\f8cc"}.fa-arrow-right-to-bracket:before,.fa-sign-in:before{content:"\f090"}.fa-shop-slash:before,.fa-store-alt-slash:before{content:"\e070"}.fa-server:before{content:"\f233"}.fa-virus-covid-slash:before{content:"\e4a9"}.fa-shop-lock:before{content:"\e4a5"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-blender-phone:before{content:"\f6b6"}.fa-building-wheat:before{content:"\e4db"}.fa-person-breastfeeding:before{content:"\e53a"}.fa-right-to-bracket:before,.fa-sign-in-alt:before{content:"\f2f6"}.fa-venus:before{content:"\f221"}.fa-passport:before{content:"\f5ab"}.fa-thumb-tack-slash:before,.fa-thumbtack-slash:before{content:"\e68f"}.fa-heart-pulse:before,.fa-heartbeat:before{content:"\f21e"}.fa-people-carry-box:before,.fa-people-carry:before{content:"\f4ce"}.fa-temperature-high:before{content:"\f769"}.fa-microchip:before{content:"\f2db"}.fa-crown:before{content:"\f521"}.fa-weight-hanging:before{content:"\f5cd"}.fa-xmarks-lines:before{content:"\e59a"}.fa-file-prescription:before{content:"\f572"}.fa-weight-scale:before,.fa-weight:before{content:"\f496"}.fa-user-friends:before,.fa-user-group:before{content:"\f500"}.fa-arrow-up-a-z:before,.fa-sort-alpha-up:before{content:"\f15e"}.fa-chess-knight:before{content:"\f441"}.fa-face-laugh-squint:before,.fa-laugh-squint:before{content:"\f59b"}.fa-wheelchair:before{content:"\f193"}.fa-arrow-circle-up:before,.fa-circle-arrow-up:before{content:"\f0aa"}.fa-toggle-on:before{content:"\f205"}.fa-person-walking:before,.fa-walking:before{content:"\f554"}.fa-l:before{content:"\4c"}.fa-fire:before{content:"\f06d"}.fa-bed-pulse:before,.fa-procedures:before{content:"\f487"}.fa-shuttle-space:before,.fa-space-shuttle:before{content:"\f197"}.fa-face-laugh:before,.fa-laugh:before{content:"\f599"}.fa-folder-open:before{content:"\f07c"}.fa-heart-circle-plus:before{content:"\e500"}.fa-code-fork:before{content:"\e13b"}.fa-city:before{content:"\f64f"}.fa-microphone-alt:before,.fa-microphone-lines:before{content:"\f3c9"}.fa-pepper-hot:before{content:"\f816"}.fa-unlock:before{content:"\f09c"}.fa-colon-sign:before{content:"\e140"}.fa-headset:before{content:"\f590"}.fa-store-slash:before{content:"\e071"}.fa-road-circle-xmark:before{content:"\e566"}.fa-user-minus:before{content:"\f503"}.fa-mars-stroke-up:before,.fa-mars-stroke-v:before{content:"\f22a"}.fa-champagne-glasses:before,.fa-glass-cheers:before{content:"\f79f"}.fa-clipboard:before{content:"\f328"}.fa-house-circle-exclamation:before{content:"\e50a"}.fa-file-arrow-up:before,.fa-file-upload:before{content:"\f574"}.fa-wifi-3:before,.fa-wifi-strong:before,.fa-wifi:before{content:"\f1eb"}.fa-bath:before,.fa-bathtub:before{content:"\f2cd"}.fa-underline:before{content:"\f0cd"}.fa-user-edit:before,.fa-user-pen:before{content:"\f4ff"}.fa-signature:before{content:"\f5b7"}.fa-stroopwafel:before{content:"\f551"}.fa-bold:before{content:"\f032"}.fa-anchor-lock:before{content:"\e4ad"}.fa-building-ngo:before{content:"\e4d7"}.fa-manat-sign:before{content:"\e1d5"}.fa-not-equal:before{content:"\f53e"}.fa-border-style:before,.fa-border-top-left:before{content:"\f853"}.fa-map-location-dot:before,.fa-map-marked-alt:before{content:"\f5a0"}.fa-jedi:before{content:"\f669"}.fa-poll:before,.fa-square-poll-vertical:before{content:"\f681"}.fa-mug-hot:before{content:"\f7b6"}.fa-battery-car:before,.fa-car-battery:before{content:"\f5df"}.fa-gift:before{content:"\f06b"}.fa-dice-two:before{content:"\f528"}.fa-chess-queen:before{content:"\f445"}.fa-glasses:before{content:"\f530"}.fa-chess-board:before{content:"\f43c"}.fa-building-circle-check:before{content:"\e4d2"}.fa-person-chalkboard:before{content:"\e53d"}.fa-mars-stroke-h:before,.fa-mars-stroke-right:before{content:"\f22b"}.fa-hand-back-fist:before,.fa-hand-rock:before{content:"\f255"}.fa-caret-square-up:before,.fa-square-caret-up:before{content:"\f151"}.fa-cloud-showers-water:before{content:"\e4e4"}.fa-bar-chart:before,.fa-chart-bar:before{content:"\f080"}.fa-hands-bubbles:before,.fa-hands-wash:before{content:"\e05e"}.fa-less-than-equal:before{content:"\f537"}.fa-train:before{content:"\f238"}.fa-eye-low-vision:before,.fa-low-vision:before{content:"\f2a8"}.fa-crow:before{content:"\f520"}.fa-sailboat:before{content:"\e445"}.fa-window-restore:before{content:"\f2d2"}.fa-plus-square:before,.fa-square-plus:before{content:"\f0fe"}.fa-torii-gate:before{content:"\f6a1"}.fa-frog:before{content:"\f52e"}.fa-bucket:before{content:"\e4cf"}.fa-image:before{content:"\f03e"}.fa-microphone:before{content:"\f130"}.fa-cow:before{content:"\f6c8"}.fa-caret-up:before{content:"\f0d8"}.fa-screwdriver:before{content:"\f54a"}.fa-folder-closed:before{content:"\e185"}.fa-house-tsunami:before{content:"\e515"}.fa-square-nfi:before{content:"\e576"}.fa-arrow-up-from-ground-water:before{content:"\e4b5"}.fa-glass-martini-alt:before,.fa-martini-glass:before{content:"\f57b"}.fa-rotate-back:before,.fa-rotate-backward:before,.fa-rotate-left:before,.fa-undo-alt:before{content:"\f2ea"}.fa-columns:before,.fa-table-columns:before{content:"\f0db"}.fa-lemon:before{content:"\f094"}.fa-head-side-mask:before{content:"\e063"}.fa-handshake:before{content:"\f2b5"}.fa-gem:before{content:"\f3a5"}.fa-dolly-box:before,.fa-dolly:before{content:"\f472"}.fa-smoking:before{content:"\f48d"}.fa-compress-arrows-alt:before,.fa-minimize:before{content:"\f78c"}.fa-monument:before{content:"\f5a6"}.fa-snowplow:before{content:"\f7d2"}.fa-angle-double-right:before,.fa-angles-right:before{content:"\f101"}.fa-cannabis:before{content:"\f55f"}.fa-circle-play:before,.fa-play-circle:before{content:"\f144"}.fa-tablets:before{content:"\f490"}.fa-ethernet:before{content:"\f796"}.fa-eur:before,.fa-euro-sign:before,.fa-euro:before{content:"\f153"}.fa-chair:before{content:"\f6c0"}.fa-check-circle:before,.fa-circle-check:before{content:"\f058"}.fa-circle-stop:before,.fa-stop-circle:before{content:"\f28d"}.fa-compass-drafting:before,.fa-drafting-compass:before{content:"\f568"}.fa-plate-wheat:before{content:"\e55a"}.fa-icicles:before{content:"\f7ad"}.fa-person-shelter:before{content:"\e54f"}.fa-neuter:before{content:"\f22c"}.fa-id-badge:before{content:"\f2c1"}.fa-marker:before{content:"\f5a1"}.fa-face-laugh-beam:before,.fa-laugh-beam:before{content:"\f59a"}.fa-helicopter-symbol:before{content:"\e502"}.fa-universal-access:before{content:"\f29a"}.fa-chevron-circle-up:before,.fa-circle-chevron-up:before{content:"\f139"}.fa-lari-sign:before{content:"\e1c8"}.fa-volcano:before{content:"\f770"}.fa-person-walking-dashed-line-arrow-right:before{content:"\e553"}.fa-gbp:before,.fa-pound-sign:before,.fa-sterling-sign:before{content:"\f154"}.fa-viruses:before{content:"\e076"}.fa-square-person-confined:before{content:"\e577"}.fa-user-tie:before{content:"\f508"}.fa-arrow-down-long:before,.fa-long-arrow-down:before{content:"\f175"}.fa-tent-arrow-down-to-line:before{content:"\e57e"}.fa-certificate:before{content:"\f0a3"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-suitcase:before{content:"\f0f2"}.fa-person-skating:before,.fa-skating:before{content:"\f7c5"}.fa-filter-circle-dollar:before,.fa-funnel-dollar:before{content:"\f662"}.fa-camera-retro:before{content:"\f083"}.fa-arrow-circle-down:before,.fa-circle-arrow-down:before{content:"\f0ab"}.fa-arrow-right-to-file:before,.fa-file-import:before{content:"\f56f"}.fa-external-link-square:before,.fa-square-arrow-up-right:before{content:"\f14c"}.fa-box-open:before{content:"\f49e"}.fa-scroll:before{content:"\f70e"}.fa-spa:before{content:"\f5bb"}.fa-location-pin-lock:before{content:"\e51f"}.fa-pause:before{content:"\f04c"}.fa-hill-avalanche:before{content:"\e507"}.fa-temperature-0:before,.fa-temperature-empty:before,.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-bomb:before{content:"\f1e2"}.fa-registered:before{content:"\f25d"}.fa-address-card:before,.fa-contact-card:before,.fa-vcard:before{content:"\f2bb"}.fa-balance-scale-right:before,.fa-scale-unbalanced-flip:before{content:"\f516"}.fa-subscript:before{content:"\f12c"}.fa-diamond-turn-right:before,.fa-directions:before{content:"\f5eb"}.fa-burst:before{content:"\e4dc"}.fa-house-laptop:before,.fa-laptop-house:before{content:"\e066"}.fa-face-tired:before,.fa-tired:before{content:"\f5c8"}.fa-money-bills:before{content:"\e1f3"}.fa-smog:before{content:"\f75f"}.fa-crutch:before{content:"\f7f7"}.fa-cloud-arrow-up:before,.fa-cloud-upload-alt:before,.fa-cloud-upload:before{content:"\f0ee"}.fa-palette:before{content:"\f53f"}.fa-arrows-turn-right:before{content:"\e4c0"}.fa-vest:before{content:"\e085"}.fa-ferry:before{content:"\e4ea"}.fa-arrows-down-to-people:before{content:"\e4b9"}.fa-seedling:before,.fa-sprout:before{content:"\f4d8"}.fa-arrows-alt-h:before,.fa-left-right:before{content:"\f337"}.fa-boxes-packing:before{content:"\e4c7"}.fa-arrow-circle-left:before,.fa-circle-arrow-left:before{content:"\f0a8"}.fa-group-arrows-rotate:before{content:"\e4f6"}.fa-bowl-food:before{content:"\e4c6"}.fa-candy-cane:before{content:"\f786"}.fa-arrow-down-wide-short:before,.fa-sort-amount-asc:before,.fa-sort-amount-down:before{content:"\f160"}.fa-cloud-bolt:before,.fa-thunderstorm:before{content:"\f76c"}.fa-remove-format:before,.fa-text-slash:before{content:"\f87d"}.fa-face-smile-wink:before,.fa-smile-wink:before{content:"\f4da"}.fa-file-word:before{content:"\f1c2"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-arrows-h:before,.fa-arrows-left-right:before{content:"\f07e"}.fa-house-lock:before{content:"\e510"}.fa-cloud-arrow-down:before,.fa-cloud-download-alt:before,.fa-cloud-download:before{content:"\f0ed"}.fa-children:before{content:"\e4e1"}.fa-blackboard:before,.fa-chalkboard:before{content:"\f51b"}.fa-user-alt-slash:before,.fa-user-large-slash:before{content:"\f4fa"}.fa-envelope-open:before{content:"\f2b6"}.fa-handshake-alt-slash:before,.fa-handshake-simple-slash:before{content:"\e05f"}.fa-mattress-pillow:before{content:"\e525"}.fa-guarani-sign:before{content:"\e19a"}.fa-arrows-rotate:before,.fa-refresh:before,.fa-sync:before{content:"\f021"}.fa-fire-extinguisher:before{content:"\f134"}.fa-cruzeiro-sign:before{content:"\e152"}.fa-greater-than-equal:before{content:"\f532"}.fa-shield-alt:before,.fa-shield-halved:before{content:"\f3ed"}.fa-atlas:before,.fa-book-atlas:before{content:"\f558"}.fa-virus:before{content:"\e074"}.fa-envelope-circle-check:before{content:"\e4e8"}.fa-layer-group:before{content:"\f5fd"}.fa-arrows-to-dot:before{content:"\e4be"}.fa-archway:before{content:"\f557"}.fa-heart-circle-check:before{content:"\e4fd"}.fa-house-chimney-crack:before,.fa-house-damage:before{content:"\f6f1"}.fa-file-archive:before,.fa-file-zipper:before{content:"\f1c6"}.fa-square:before{content:"\f0c8"}.fa-glass-martini:before,.fa-martini-glass-empty:before{content:"\f000"}.fa-couch:before{content:"\f4b8"}.fa-cedi-sign:before{content:"\e0df"}.fa-italic:before{content:"\f033"}.fa-table-cells-column-lock:before{content:"\e678"}.fa-church:before{content:"\f51d"}.fa-comments-dollar:before{content:"\f653"}.fa-democrat:before{content:"\f747"}.fa-z:before{content:"\5a"}.fa-person-skiing:before,.fa-skiing:before{content:"\f7c9"}.fa-road-lock:before{content:"\e567"}.fa-a:before{content:"\41"}.fa-temperature-arrow-down:before,.fa-temperature-down:before{content:"\e03f"}.fa-feather-alt:before,.fa-feather-pointed:before{content:"\f56b"}.fa-p:before{content:"\50"}.fa-snowflake:before{content:"\f2dc"}.fa-newspaper:before{content:"\f1ea"}.fa-ad:before,.fa-rectangle-ad:before{content:"\f641"}.fa-arrow-circle-right:before,.fa-circle-arrow-right:before{content:"\f0a9"}.fa-filter-circle-xmark:before{content:"\e17b"}.fa-locust:before{content:"\e520"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-list-1-2:before,.fa-list-numeric:before,.fa-list-ol:before{content:"\f0cb"}.fa-person-dress-burst:before{content:"\e544"}.fa-money-check-alt:before,.fa-money-check-dollar:before{content:"\f53d"}.fa-vector-square:before{content:"\f5cb"}.fa-bread-slice:before{content:"\f7ec"}.fa-language:before{content:"\f1ab"}.fa-face-kiss-wink-heart:before,.fa-kiss-wink-heart:before{content:"\f598"}.fa-filter:before{content:"\f0b0"}.fa-question:before{content:"\3f"}.fa-file-signature:before{content:"\f573"}.fa-arrows-alt:before,.fa-up-down-left-right:before{content:"\f0b2"}.fa-house-chimney-user:before{content:"\e065"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-puzzle-piece:before{content:"\f12e"}.fa-money-check:before{content:"\f53c"}.fa-star-half-alt:before,.fa-star-half-stroke:before{content:"\f5c0"}.fa-code:before{content:"\f121"}.fa-glass-whiskey:before,.fa-whiskey-glass:before{content:"\f7a0"}.fa-building-circle-exclamation:before{content:"\e4d3"}.fa-magnifying-glass-chart:before{content:"\e522"}.fa-arrow-up-right-from-square:before,.fa-external-link:before{content:"\f08e"}.fa-cubes-stacked:before{content:"\e4e6"}.fa-krw:before,.fa-won-sign:before,.fa-won:before{content:"\f159"}.fa-virus-covid:before{content:"\e4a8"}.fa-austral-sign:before{content:"\e0a9"}.fa-f:before{content:"\46"}.fa-leaf:before{content:"\f06c"}.fa-road:before{content:"\f018"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-person-circle-plus:before{content:"\e541"}.fa-chart-pie:before,.fa-pie-chart:before{content:"\f200"}.fa-bolt-lightning:before{content:"\e0b7"}.fa-sack-xmark:before{content:"\e56a"}.fa-file-excel:before{content:"\f1c3"}.fa-file-contract:before{content:"\f56c"}.fa-fish-fins:before{content:"\e4f2"}.fa-building-flag:before{content:"\e4d5"}.fa-face-grin-beam:before,.fa-grin-beam:before{content:"\f582"}.fa-object-ungroup:before{content:"\f248"}.fa-poop:before{content:"\f619"}.fa-location-pin:before,.fa-map-marker:before{content:"\f041"}.fa-kaaba:before{content:"\f66b"}.fa-toilet-paper:before{content:"\f71e"}.fa-hard-hat:before,.fa-hat-hard:before,.fa-helmet-safety:before{content:"\f807"}.fa-eject:before{content:"\f052"}.fa-arrow-alt-circle-right:before,.fa-circle-right:before{content:"\f35a"}.fa-plane-circle-check:before{content:"\e555"}.fa-face-rolling-eyes:before,.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-object-group:before{content:"\f247"}.fa-chart-line:before,.fa-line-chart:before{content:"\f201"}.fa-mask-ventilator:before{content:"\e524"}.fa-arrow-right:before{content:"\f061"}.fa-map-signs:before,.fa-signs-post:before{content:"\f277"}.fa-cash-register:before{content:"\f788"}.fa-person-circle-question:before{content:"\e542"}.fa-h:before{content:"\48"}.fa-tarp:before{content:"\e57b"}.fa-screwdriver-wrench:before,.fa-tools:before{content:"\f7d9"}.fa-arrows-to-eye:before{content:"\e4bf"}.fa-plug-circle-bolt:before{content:"\e55b"}.fa-heart:before{content:"\f004"}.fa-mars-and-venus:before{content:"\f224"}.fa-home-user:before,.fa-house-user:before{content:"\e1b0"}.fa-dumpster-fire:before{content:"\f794"}.fa-house-crack:before{content:"\e3b1"}.fa-cocktail:before,.fa-martini-glass-citrus:before{content:"\f561"}.fa-face-surprise:before,.fa-surprise:before{content:"\f5c2"}.fa-bottle-water:before{content:"\e4c5"}.fa-circle-pause:before,.fa-pause-circle:before{content:"\f28b"}.fa-toilet-paper-slash:before{content:"\e072"}.fa-apple-alt:before,.fa-apple-whole:before{content:"\f5d1"}.fa-kitchen-set:before{content:"\e51a"}.fa-r:before{content:"\52"}.fa-temperature-1:before,.fa-temperature-quarter:before,.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-cube:before{content:"\f1b2"}.fa-bitcoin-sign:before{content:"\e0b4"}.fa-shield-dog:before{content:"\e573"}.fa-solar-panel:before{content:"\f5ba"}.fa-lock-open:before{content:"\f3c1"}.fa-elevator:before{content:"\e16d"}.fa-money-bill-transfer:before{content:"\e528"}.fa-money-bill-trend-up:before{content:"\e529"}.fa-house-flood-water-circle-arrow-right:before{content:"\e50f"}.fa-poll-h:before,.fa-square-poll-horizontal:before{content:"\f682"}.fa-circle:before{content:"\f111"}.fa-backward-fast:before,.fa-fast-backward:before{content:"\f049"}.fa-recycle:before{content:"\f1b8"}.fa-user-astronaut:before{content:"\f4fb"}.fa-plane-slash:before{content:"\e069"}.fa-trademark:before{content:"\f25c"}.fa-basketball-ball:before,.fa-basketball:before{content:"\f434"}.fa-satellite-dish:before{content:"\f7c0"}.fa-arrow-alt-circle-up:before,.fa-circle-up:before{content:"\f35b"}.fa-mobile-alt:before,.fa-mobile-screen-button:before{content:"\f3cd"}.fa-volume-high:before,.fa-volume-up:before{content:"\f028"}.fa-users-rays:before{content:"\e593"}.fa-wallet:before{content:"\f555"}.fa-clipboard-check:before{content:"\f46c"}.fa-file-audio:before{content:"\f1c7"}.fa-burger:before,.fa-hamburger:before{content:"\f805"}.fa-wrench:before{content:"\f0ad"}.fa-bugs:before{content:"\e4d0"}.fa-rupee-sign:before,.fa-rupee:before{content:"\f156"}.fa-file-image:before{content:"\f1c5"}.fa-circle-question:before,.fa-question-circle:before{content:"\f059"}.fa-plane-departure:before{content:"\f5b0"}.fa-handshake-slash:before{content:"\e060"}.fa-book-bookmark:before{content:"\e0bb"}.fa-code-branch:before{content:"\f126"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-bridge:before{content:"\e4c8"}.fa-phone-alt:before,.fa-phone-flip:before{content:"\f879"}.fa-truck-front:before{content:"\e2b7"}.fa-cat:before{content:"\f6be"}.fa-anchor-circle-exclamation:before{content:"\e4ab"}.fa-truck-field:before{content:"\e58d"}.fa-route:before{content:"\f4d7"}.fa-clipboard-question:before{content:"\e4e3"}.fa-panorama:before{content:"\e209"}.fa-comment-medical:before{content:"\f7f5"}.fa-teeth-open:before{content:"\f62f"}.fa-file-circle-minus:before{content:"\e4ed"}.fa-tags:before{content:"\f02c"}.fa-wine-glass:before{content:"\f4e3"}.fa-fast-forward:before,.fa-forward-fast:before{content:"\f050"}.fa-face-meh-blank:before,.fa-meh-blank:before{content:"\f5a4"}.fa-parking:before,.fa-square-parking:before{content:"\f540"}.fa-house-signal:before{content:"\e012"}.fa-bars-progress:before,.fa-tasks-alt:before{content:"\f828"}.fa-faucet-drip:before{content:"\e006"}.fa-cart-flatbed:before,.fa-dolly-flatbed:before{content:"\f474"}.fa-ban-smoking:before,.fa-smoking-ban:before{content:"\f54d"}.fa-terminal:before{content:"\f120"}.fa-mobile-button:before{content:"\f10b"}.fa-house-medical-flag:before{content:"\e514"}.fa-basket-shopping:before,.fa-shopping-basket:before{content:"\f291"}.fa-tape:before{content:"\f4db"}.fa-bus-alt:before,.fa-bus-simple:before{content:"\f55e"}.fa-eye:before{content:"\f06e"}.fa-face-sad-cry:before,.fa-sad-cry:before{content:"\f5b3"}.fa-audio-description:before{content:"\f29e"}.fa-person-military-to-person:before{content:"\e54c"}.fa-file-shield:before{content:"\e4f0"}.fa-user-slash:before{content:"\f506"}.fa-pen:before{content:"\f304"}.fa-tower-observation:before{content:"\e586"}.fa-file-code:before{content:"\f1c9"}.fa-signal-5:before,.fa-signal-perfect:before,.fa-signal:before{content:"\f012"}.fa-bus:before{content:"\f207"}.fa-heart-circle-xmark:before{content:"\e501"}.fa-home-lg:before,.fa-house-chimney:before{content:"\e3af"}.fa-window-maximize:before{content:"\f2d0"}.fa-face-frown:before,.fa-frown:before{content:"\f119"}.fa-prescription:before{content:"\f5b1"}.fa-shop:before,.fa-store-alt:before{content:"\f54f"}.fa-floppy-disk:before,.fa-save:before{content:"\f0c7"}.fa-vihara:before{content:"\f6a7"}.fa-balance-scale-left:before,.fa-scale-unbalanced:before{content:"\f515"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-comment-dots:before,.fa-commenting:before{content:"\f4ad"}.fa-plant-wilt:before{content:"\e5aa"}.fa-diamond:before{content:"\f219"}.fa-face-grin-squint:before,.fa-grin-squint:before{content:"\f585"}.fa-hand-holding-dollar:before,.fa-hand-holding-usd:before{content:"\f4c0"}.fa-bacterium:before{content:"\e05a"}.fa-hand-pointer:before{content:"\f25a"}.fa-drum-steelpan:before{content:"\f56a"}.fa-hand-scissors:before{content:"\f257"}.fa-hands-praying:before,.fa-praying-hands:before{content:"\f684"}.fa-arrow-right-rotate:before,.fa-arrow-rotate-forward:before,.fa-arrow-rotate-right:before,.fa-redo:before{content:"\f01e"}.fa-biohazard:before{content:"\f780"}.fa-location-crosshairs:before,.fa-location:before{content:"\f601"}.fa-mars-double:before{content:"\f227"}.fa-child-dress:before{content:"\e59c"}.fa-users-between-lines:before{content:"\e591"}.fa-lungs-virus:before{content:"\e067"}.fa-face-grin-tears:before,.fa-grin-tears:before{content:"\f588"}.fa-phone:before{content:"\f095"}.fa-calendar-times:before,.fa-calendar-xmark:before{content:"\f273"}.fa-child-reaching:before{content:"\e59d"}.fa-head-side-virus:before{content:"\e064"}.fa-user-cog:before,.fa-user-gear:before{content:"\f4fe"}.fa-arrow-up-1-9:before,.fa-sort-numeric-up:before{content:"\f163"}.fa-door-closed:before{content:"\f52a"}.fa-shield-virus:before{content:"\e06c"}.fa-dice-six:before{content:"\f526"}.fa-mosquito-net:before{content:"\e52c"}.fa-bridge-water:before{content:"\e4ce"}.fa-person-booth:before{content:"\f756"}.fa-text-width:before{content:"\f035"}.fa-hat-wizard:before{content:"\f6e8"}.fa-pen-fancy:before{content:"\f5ac"}.fa-digging:before,.fa-person-digging:before{content:"\f85e"}.fa-trash:before{content:"\f1f8"}.fa-gauge-simple-med:before,.fa-gauge-simple:before,.fa-tachometer-average:before{content:"\f629"}.fa-book-medical:before{content:"\f7e6"}.fa-poo:before{content:"\f2fe"}.fa-quote-right-alt:before,.fa-quote-right:before{content:"\f10e"}.fa-shirt:before,.fa-t-shirt:before,.fa-tshirt:before{content:"\f553"}.fa-cubes:before{content:"\f1b3"}.fa-divide:before{content:"\f529"}.fa-tenge-sign:before,.fa-tenge:before{content:"\f7d7"}.fa-headphones:before{content:"\f025"}.fa-hands-holding:before{content:"\f4c2"}.fa-hands-clapping:before{content:"\e1a8"}.fa-republican:before{content:"\f75e"}.fa-arrow-left:before{content:"\f060"}.fa-person-circle-xmark:before{content:"\e543"}.fa-ruler:before{content:"\f545"}.fa-align-left:before{content:"\f036"}.fa-dice-d6:before{content:"\f6d1"}.fa-restroom:before{content:"\f7bd"}.fa-j:before{content:"\4a"}.fa-users-viewfinder:before{content:"\e595"}.fa-file-video:before{content:"\f1c8"}.fa-external-link-alt:before,.fa-up-right-from-square:before{content:"\f35d"}.fa-table-cells:before,.fa-th:before{content:"\f00a"}.fa-file-pdf:before{content:"\f1c1"}.fa-bible:before,.fa-book-bible:before{content:"\f647"}.fa-o:before{content:"\4f"}.fa-medkit:before,.fa-suitcase-medical:before{content:"\f0fa"}.fa-user-secret:before{content:"\f21b"}.fa-otter:before{content:"\f700"}.fa-female:before,.fa-person-dress:before{content:"\f182"}.fa-comment-dollar:before{content:"\f651"}.fa-briefcase-clock:before,.fa-business-time:before{content:"\f64a"}.fa-table-cells-large:before,.fa-th-large:before{content:"\f009"}.fa-book-tanakh:before,.fa-tanakh:before{content:"\f827"}.fa-phone-volume:before,.fa-volume-control-phone:before{content:"\f2a0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-clipboard-user:before{content:"\f7f3"}.fa-child:before{content:"\f1ae"}.fa-lira-sign:before{content:"\f195"}.fa-satellite:before{content:"\f7bf"}.fa-plane-lock:before{content:"\e558"}.fa-tag:before{content:"\f02b"}.fa-comment:before{content:"\f075"}.fa-birthday-cake:before,.fa-cake-candles:before,.fa-cake:before{content:"\f1fd"}.fa-envelope:before{content:"\f0e0"}.fa-angle-double-up:before,.fa-angles-up:before{content:"\f102"}.fa-paperclip:before{content:"\f0c6"}.fa-arrow-right-to-city:before{content:"\e4b3"}.fa-ribbon:before{content:"\f4d6"}.fa-lungs:before{content:"\f604"}.fa-arrow-up-9-1:before,.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-litecoin-sign:before{content:"\e1d3"}.fa-border-none:before{content:"\f850"}.fa-circle-nodes:before{content:"\e4e2"}.fa-parachute-box:before{content:"\f4cd"}.fa-indent:before{content:"\f03c"}.fa-truck-field-un:before{content:"\e58e"}.fa-hourglass-empty:before,.fa-hourglass:before{content:"\f254"}.fa-mountain:before{content:"\f6fc"}.fa-user-doctor:before,.fa-user-md:before{content:"\f0f0"}.fa-circle-info:before,.fa-info-circle:before{content:"\f05a"}.fa-cloud-meatball:before{content:"\f73b"}.fa-camera-alt:before,.fa-camera:before{content:"\f030"}.fa-square-virus:before{content:"\e578"}.fa-meteor:before{content:"\f753"}.fa-car-on:before{content:"\e4dd"}.fa-sleigh:before{content:"\f7cc"}.fa-arrow-down-1-9:before,.fa-sort-numeric-asc:before,.fa-sort-numeric-down:before{content:"\f162"}.fa-hand-holding-droplet:before,.fa-hand-holding-water:before{content:"\f4c1"}.fa-water:before{content:"\f773"}.fa-calendar-check:before{content:"\f274"}.fa-braille:before{content:"\f2a1"}.fa-prescription-bottle-alt:before,.fa-prescription-bottle-medical:before{content:"\f486"}.fa-landmark:before{content:"\f66f"}.fa-truck:before{content:"\f0d1"}.fa-crosshairs:before{content:"\f05b"}.fa-person-cane:before{content:"\e53c"}.fa-tent:before{content:"\e57d"}.fa-vest-patches:before{content:"\e086"}.fa-check-double:before{content:"\f560"}.fa-arrow-down-a-z:before,.fa-sort-alpha-asc:before,.fa-sort-alpha-down:before{content:"\f15d"}.fa-money-bill-wheat:before{content:"\e52a"}.fa-cookie:before{content:"\f563"}.fa-arrow-left-rotate:before,.fa-arrow-rotate-back:before,.fa-arrow-rotate-backward:before,.fa-arrow-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-hard-drive:before,.fa-hdd:before{content:"\f0a0"}.fa-face-grin-squint-tears:before,.fa-grin-squint-tears:before{content:"\f586"}.fa-dumbbell:before{content:"\f44b"}.fa-list-alt:before,.fa-rectangle-list:before{content:"\f022"}.fa-tarp-droplet:before{content:"\e57c"}.fa-house-medical-circle-check:before{content:"\e511"}.fa-person-skiing-nordic:before,.fa-skiing-nordic:before{content:"\f7ca"}.fa-calendar-plus:before{content:"\f271"}.fa-plane-arrival:before{content:"\f5af"}.fa-arrow-alt-circle-left:before,.fa-circle-left:before{content:"\f359"}.fa-subway:before,.fa-train-subway:before{content:"\f239"}.fa-chart-gantt:before{content:"\e0e4"}.fa-indian-rupee-sign:before,.fa-indian-rupee:before,.fa-inr:before{content:"\e1bc"}.fa-crop-alt:before,.fa-crop-simple:before{content:"\f565"}.fa-money-bill-1:before,.fa-money-bill-alt:before{content:"\f3d1"}.fa-left-long:before,.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-dna:before{content:"\f471"}.fa-virus-slash:before{content:"\e075"}.fa-minus:before,.fa-subtract:before{content:"\f068"}.fa-chess:before{content:"\f439"}.fa-arrow-left-long:before,.fa-long-arrow-left:before{content:"\f177"}.fa-plug-circle-check:before{content:"\e55c"}.fa-street-view:before{content:"\f21d"}.fa-franc-sign:before{content:"\e18f"}.fa-volume-off:before{content:"\f026"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before,.fa-hands-american-sign-language-interpreting:before,.fa-hands-asl-interpreting:before{content:"\f2a3"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-droplet-slash:before,.fa-tint-slash:before{content:"\f5c7"}.fa-mosque:before{content:"\f678"}.fa-mosquito:before{content:"\e52b"}.fa-star-of-david:before{content:"\f69a"}.fa-person-military-rifle:before{content:"\e54b"}.fa-cart-shopping:before,.fa-shopping-cart:before{content:"\f07a"}.fa-vials:before{content:"\f493"}.fa-plug-circle-plus:before{content:"\e55f"}.fa-place-of-worship:before{content:"\f67f"}.fa-grip-vertical:before{content:"\f58e"}.fa-arrow-turn-up:before,.fa-level-up:before{content:"\f148"}.fa-u:before{content:"\55"}.fa-square-root-alt:before,.fa-square-root-variable:before{content:"\f698"}.fa-clock-four:before,.fa-clock:before{content:"\f017"}.fa-backward-step:before,.fa-step-backward:before{content:"\f048"}.fa-pallet:before{content:"\f482"}.fa-faucet:before{content:"\e005"}.fa-baseball-bat-ball:before{content:"\f432"}.fa-s:before{content:"\53"}.fa-timeline:before{content:"\e29c"}.fa-keyboard:before{content:"\f11c"}.fa-caret-down:before{content:"\f0d7"}.fa-clinic-medical:before,.fa-house-chimney-medical:before{content:"\f7f2"}.fa-temperature-3:before,.fa-temperature-three-quarters:before,.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-mobile-android-alt:before,.fa-mobile-screen:before{content:"\f3cf"}.fa-plane-up:before{content:"\e22d"}.fa-piggy-bank:before{content:"\f4d3"}.fa-battery-3:before,.fa-battery-half:before{content:"\f242"}.fa-mountain-city:before{content:"\e52e"}.fa-coins:before{content:"\f51e"}.fa-khanda:before{content:"\f66d"}.fa-sliders-h:before,.fa-sliders:before{content:"\f1de"}.fa-folder-tree:before{content:"\f802"}.fa-network-wired:before{content:"\f6ff"}.fa-map-pin:before{content:"\f276"}.fa-hamsa:before{content:"\f665"}.fa-cent-sign:before{content:"\e3f5"}.fa-flask:before{content:"\f0c3"}.fa-person-pregnant:before{content:"\e31e"}.fa-wand-sparkles:before{content:"\f72b"}.fa-ellipsis-v:before,.fa-ellipsis-vertical:before{content:"\f142"}.fa-ticket:before{content:"\f145"}.fa-power-off:before{content:"\f011"}.fa-long-arrow-alt-right:before,.fa-right-long:before{content:"\f30b"}.fa-flag-usa:before{content:"\f74d"}.fa-laptop-file:before{content:"\e51d"}.fa-teletype:before,.fa-tty:before{content:"\f1e4"}.fa-diagram-next:before{content:"\e476"}.fa-person-rifle:before{content:"\e54e"}.fa-house-medical-circle-exclamation:before{content:"\e512"}.fa-closed-captioning:before{content:"\f20a"}.fa-hiking:before,.fa-person-hiking:before{content:"\f6ec"}.fa-venus-double:before{content:"\f226"}.fa-images:before{content:"\f302"}.fa-calculator:before{content:"\f1ec"}.fa-people-pulling:before{content:"\e535"}.fa-n:before{content:"\4e"}.fa-cable-car:before,.fa-tram:before{content:"\f7da"}.fa-cloud-rain:before{content:"\f73d"}.fa-building-circle-xmark:before{content:"\e4d4"}.fa-ship:before{content:"\f21a"}.fa-arrows-down-to-line:before{content:"\e4b8"}.fa-download:before{content:"\f019"}.fa-face-grin:before,.fa-grin:before{content:"\f580"}.fa-backspace:before,.fa-delete-left:before{content:"\f55a"}.fa-eye-dropper-empty:before,.fa-eye-dropper:before,.fa-eyedropper:before{content:"\f1fb"}.fa-file-circle-check:before{content:"\e5a0"}.fa-forward:before{content:"\f04e"}.fa-mobile-android:before,.fa-mobile-phone:before,.fa-mobile:before{content:"\f3ce"}.fa-face-meh:before,.fa-meh:before{content:"\f11a"}.fa-align-center:before{content:"\f037"}.fa-book-dead:before,.fa-book-skull:before{content:"\f6b7"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-heart-circle-exclamation:before{content:"\e4fe"}.fa-home-alt:before,.fa-home-lg-alt:before,.fa-home:before,.fa-house:before{content:"\f015"}.fa-calendar-week:before{content:"\f784"}.fa-laptop-medical:before{content:"\f812"}.fa-b:before{content:"\42"}.fa-file-medical:before{content:"\f477"}.fa-dice-one:before{content:"\f525"}.fa-kiwi-bird:before{content:"\f535"}.fa-arrow-right-arrow-left:before,.fa-exchange:before{content:"\f0ec"}.fa-redo-alt:before,.fa-rotate-forward:before,.fa-rotate-right:before{content:"\f2f9"}.fa-cutlery:before,.fa-utensils:before{content:"\f2e7"}.fa-arrow-up-wide-short:before,.fa-sort-amount-up:before{content:"\f161"}.fa-mill-sign:before{content:"\e1ed"}.fa-bowl-rice:before{content:"\e2eb"}.fa-skull:before{content:"\f54c"}.fa-broadcast-tower:before,.fa-tower-broadcast:before{content:"\f519"}.fa-truck-pickup:before{content:"\f63c"}.fa-long-arrow-alt-up:before,.fa-up-long:before{content:"\f30c"}.fa-stop:before{content:"\f04d"}.fa-code-merge:before{content:"\f387"}.fa-upload:before{content:"\f093"}.fa-hurricane:before{content:"\f751"}.fa-mound:before{content:"\e52d"}.fa-toilet-portable:before{content:"\e583"}.fa-compact-disc:before{content:"\f51f"}.fa-file-arrow-down:before,.fa-file-download:before{content:"\f56d"}.fa-caravan:before{content:"\f8ff"}.fa-shield-cat:before{content:"\e572"}.fa-bolt:before,.fa-zap:before{content:"\f0e7"}.fa-glass-water:before{content:"\e4f4"}.fa-oil-well:before{content:"\e532"}.fa-vault:before{content:"\e2c5"}.fa-mars:before{content:"\f222"}.fa-toilet:before{content:"\f7d8"}.fa-plane-circle-xmark:before{content:"\e557"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen-sign:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble-sign:before,.fa-ruble:before{content:"\f158"}.fa-sun:before{content:"\f185"}.fa-guitar:before{content:"\f7a6"}.fa-face-laugh-wink:before,.fa-laugh-wink:before{content:"\f59c"}.fa-horse-head:before{content:"\f7ab"}.fa-bore-hole:before{content:"\e4c3"}.fa-industry:before{content:"\f275"}.fa-arrow-alt-circle-down:before,.fa-circle-down:before{content:"\f358"}.fa-arrows-turn-to-dots:before{content:"\e4c1"}.fa-florin-sign:before{content:"\e184"}.fa-arrow-down-short-wide:before,.fa-sort-amount-desc:before,.fa-sort-amount-down-alt:before{content:"\f884"}.fa-less-than:before{content:"\3c"}.fa-angle-down:before{content:"\f107"}.fa-car-tunnel:before{content:"\e4de"}.fa-head-side-cough:before{content:"\e061"}.fa-grip-lines:before{content:"\f7a4"}.fa-thumbs-down:before{content:"\f165"}.fa-user-lock:before{content:"\f502"}.fa-arrow-right-long:before,.fa-long-arrow-right:before{content:"\f178"}.fa-anchor-circle-xmark:before{content:"\e4ac"}.fa-ellipsis-h:before,.fa-ellipsis:before{content:"\f141"}.fa-chess-pawn:before{content:"\f443"}.fa-first-aid:before,.fa-kit-medical:before{content:"\f479"}.fa-person-through-window:before{content:"\e5a9"}.fa-toolbox:before{content:"\f552"}.fa-hands-holding-circle:before{content:"\e4fb"}.fa-bug:before{content:"\f188"}.fa-credit-card-alt:before,.fa-credit-card:before{content:"\f09d"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-hand-holding-hand:before{content:"\e4f7"}.fa-book-open-reader:before,.fa-book-reader:before{content:"\f5da"}.fa-mountain-sun:before{content:"\e52f"}.fa-arrows-left-right-to-line:before{content:"\e4ba"}.fa-dice-d20:before{content:"\f6cf"}.fa-truck-droplet:before{content:"\e58c"}.fa-file-circle-xmark:before{content:"\e5a1"}.fa-temperature-arrow-up:before,.fa-temperature-up:before{content:"\e040"}.fa-medal:before{content:"\f5a2"}.fa-bed:before{content:"\f236"}.fa-h-square:before,.fa-square-h:before{content:"\f0fd"}.fa-podcast:before{content:"\f2ce"}.fa-temperature-4:before,.fa-temperature-full:before,.fa-thermometer-4:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-bell:before{content:"\f0f3"}.fa-superscript:before{content:"\f12b"}.fa-plug-circle-xmark:before{content:"\e560"}.fa-star-of-life:before{content:"\f621"}.fa-phone-slash:before{content:"\f3dd"}.fa-paint-roller:before{content:"\f5aa"}.fa-hands-helping:before,.fa-handshake-angle:before{content:"\f4c4"}.fa-location-dot:before,.fa-map-marker-alt:before{content:"\f3c5"}.fa-file:before{content:"\f15b"}.fa-greater-than:before{content:"\3e"}.fa-person-swimming:before,.fa-swimmer:before{content:"\f5c4"}.fa-arrow-down:before{content:"\f063"}.fa-droplet:before,.fa-tint:before{content:"\f043"}.fa-eraser:before{content:"\f12d"}.fa-earth-america:before,.fa-earth-americas:before,.fa-earth:before,.fa-globe-americas:before{content:"\f57d"}.fa-person-burst:before{content:"\e53b"}.fa-dove:before{content:"\f4ba"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-socks:before{content:"\f696"}.fa-inbox:before{content:"\f01c"}.fa-section:before{content:"\e447"}.fa-gauge-high:before,.fa-tachometer-alt-fast:before,.fa-tachometer-alt:before{content:"\f625"}.fa-envelope-open-text:before{content:"\f658"}.fa-hospital-alt:before,.fa-hospital-wide:before,.fa-hospital:before{content:"\f0f8"}.fa-wine-bottle:before{content:"\f72f"}.fa-chess-rook:before{content:"\f447"}.fa-bars-staggered:before,.fa-reorder:before,.fa-stream:before{content:"\f550"}.fa-dharmachakra:before{content:"\f655"}.fa-hotdog:before{content:"\f80f"}.fa-blind:before,.fa-person-walking-with-cane:before{content:"\f29d"}.fa-drum:before{content:"\f569"}.fa-ice-cream:before{content:"\f810"}.fa-heart-circle-bolt:before{content:"\e4fc"}.fa-fax:before{content:"\f1ac"}.fa-paragraph:before{content:"\f1dd"}.fa-check-to-slot:before,.fa-vote-yea:before{content:"\f772"}.fa-star-half:before{content:"\f089"}.fa-boxes-alt:before,.fa-boxes-stacked:before,.fa-boxes:before{content:"\f468"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-assistive-listening-systems:before,.fa-ear-listen:before{content:"\f2a2"}.fa-tree-city:before{content:"\e587"}.fa-play:before{content:"\f04b"}.fa-font:before{content:"\f031"}.fa-table-cells-row-lock:before{content:"\e67a"}.fa-rupiah-sign:before{content:"\e23d"}.fa-magnifying-glass:before,.fa-search:before{content:"\f002"}.fa-ping-pong-paddle-ball:before,.fa-table-tennis-paddle-ball:before,.fa-table-tennis:before{content:"\f45d"}.fa-diagnoses:before,.fa-person-dots-from-line:before{content:"\f470"}.fa-trash-can-arrow-up:before,.fa-trash-restore-alt:before{content:"\f82a"}.fa-naira-sign:before{content:"\e1f6"}.fa-cart-arrow-down:before{content:"\f218"}.fa-walkie-talkie:before{content:"\f8ef"}.fa-file-edit:before,.fa-file-pen:before{content:"\f31c"}.fa-receipt:before{content:"\f543"}.fa-pen-square:before,.fa-pencil-square:before,.fa-square-pen:before{content:"\f14b"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-person-circle-exclamation:before{content:"\e53f"}.fa-chevron-down:before{content:"\f078"}.fa-battery-5:before,.fa-battery-full:before,.fa-battery:before{content:"\f240"}.fa-skull-crossbones:before{content:"\f714"}.fa-code-compare:before{content:"\e13a"}.fa-list-dots:before,.fa-list-ul:before{content:"\f0ca"}.fa-school-lock:before{content:"\e56f"}.fa-tower-cell:before{content:"\e585"}.fa-down-long:before,.fa-long-arrow-alt-down:before{content:"\f309"}.fa-ranking-star:before{content:"\e561"}.fa-chess-king:before{content:"\f43f"}.fa-person-harassing:before{content:"\e549"}.fa-brazilian-real-sign:before{content:"\e46c"}.fa-landmark-alt:before,.fa-landmark-dome:before{content:"\f752"}.fa-arrow-up:before{content:"\f062"}.fa-television:before,.fa-tv-alt:before,.fa-tv:before{content:"\f26c"}.fa-shrimp:before{content:"\e448"}.fa-list-check:before,.fa-tasks:before{content:"\f0ae"}.fa-jug-detergent:before{content:"\e519"}.fa-circle-user:before,.fa-user-circle:before{content:"\f2bd"}.fa-user-shield:before{content:"\f505"}.fa-wind:before{content:"\f72e"}.fa-car-burst:before,.fa-car-crash:before{content:"\f5e1"}.fa-y:before{content:"\59"}.fa-person-snowboarding:before,.fa-snowboarding:before{content:"\f7ce"}.fa-shipping-fast:before,.fa-truck-fast:before{content:"\f48b"}.fa-fish:before{content:"\f578"}.fa-user-graduate:before{content:"\f501"}.fa-adjust:before,.fa-circle-half-stroke:before{content:"\f042"}.fa-clapperboard:before{content:"\e131"}.fa-circle-radiation:before,.fa-radiation-alt:before{content:"\f7ba"}.fa-baseball-ball:before,.fa-baseball:before{content:"\f433"}.fa-jet-fighter-up:before{content:"\e518"}.fa-diagram-project:before,.fa-project-diagram:before{content:"\f542"}.fa-copy:before{content:"\f0c5"}.fa-volume-mute:before,.fa-volume-times:before,.fa-volume-xmark:before{content:"\f6a9"}.fa-hand-sparkles:before{content:"\e05d"}.fa-grip-horizontal:before,.fa-grip:before{content:"\f58d"}.fa-share-from-square:before,.fa-share-square:before{content:"\f14d"}.fa-child-combatant:before,.fa-child-rifle:before{content:"\e4e0"}.fa-gun:before{content:"\e19b"}.fa-phone-square:before,.fa-square-phone:before{content:"\f098"}.fa-add:before,.fa-plus:before{content:"\2b"}.fa-expand:before{content:"\f065"}.fa-computer:before{content:"\e4e5"}.fa-close:before,.fa-multiply:before,.fa-remove:before,.fa-times:before,.fa-xmark:before{content:"\f00d"}.fa-arrows-up-down-left-right:before,.fa-arrows:before{content:"\f047"}.fa-chalkboard-teacher:before,.fa-chalkboard-user:before{content:"\f51c"}.fa-peso-sign:before{content:"\e222"}.fa-building-shield:before{content:"\e4d8"}.fa-baby:before{content:"\f77c"}.fa-users-line:before{content:"\e592"}.fa-quote-left-alt:before,.fa-quote-left:before{content:"\f10d"}.fa-tractor:before{content:"\f722"}.fa-trash-arrow-up:before,.fa-trash-restore:before{content:"\f829"}.fa-arrow-down-up-lock:before{content:"\e4b0"}.fa-lines-leaning:before{content:"\e51e"}.fa-ruler-combined:before{content:"\f546"}.fa-copyright:before{content:"\f1f9"}.fa-equals:before{content:"\3d"}.fa-blender:before{content:"\f517"}.fa-teeth:before{content:"\f62e"}.fa-ils:before,.fa-shekel-sign:before,.fa-shekel:before,.fa-sheqel-sign:before,.fa-sheqel:before{content:"\f20b"}.fa-map:before{content:"\f279"}.fa-rocket:before{content:"\f135"}.fa-photo-film:before,.fa-photo-video:before{content:"\f87c"}.fa-folder-minus:before{content:"\f65d"}.fa-store:before{content:"\f54e"}.fa-arrow-trend-up:before{content:"\e098"}.fa-plug-circle-minus:before{content:"\e55e"}.fa-sign-hanging:before,.fa-sign:before{content:"\f4d9"}.fa-bezier-curve:before{content:"\f55b"}.fa-bell-slash:before{content:"\f1f6"}.fa-tablet-android:before,.fa-tablet:before{content:"\f3fb"}.fa-school-flag:before{content:"\e56e"}.fa-fill:before{content:"\f575"}.fa-angle-up:before{content:"\f106"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-holly-berry:before{content:"\f7aa"}.fa-chevron-left:before{content:"\f053"}.fa-bacteria:before{content:"\e059"}.fa-hand-lizard:before{content:"\f258"}.fa-notdef:before{content:"\e1fe"}.fa-disease:before{content:"\f7fa"}.fa-briefcase-medical:before{content:"\f469"}.fa-genderless:before{content:"\f22d"}.fa-chevron-right:before{content:"\f054"}.fa-retweet:before{content:"\f079"}.fa-car-alt:before,.fa-car-rear:before{content:"\f5de"}.fa-pump-soap:before{content:"\e06b"}.fa-video-slash:before{content:"\f4e2"}.fa-battery-2:before,.fa-battery-quarter:before{content:"\f243"}.fa-radio:before{content:"\f8d7"}.fa-baby-carriage:before,.fa-carriage-baby:before{content:"\f77d"}.fa-traffic-light:before{content:"\f637"}.fa-thermometer:before{content:"\f491"}.fa-vr-cardboard:before{content:"\f729"}.fa-hand-middle-finger:before{content:"\f806"}.fa-percent:before,.fa-percentage:before{content:"\25"}.fa-truck-moving:before{content:"\f4df"}.fa-glass-water-droplet:before{content:"\e4f5"}.fa-display:before{content:"\e163"}.fa-face-smile:before,.fa-smile:before{content:"\f118"}.fa-thumb-tack:before,.fa-thumbtack:before{content:"\f08d"}.fa-trophy:before{content:"\f091"}.fa-person-praying:before,.fa-pray:before{content:"\f683"}.fa-hammer:before{content:"\f6e3"}.fa-hand-peace:before{content:"\f25b"}.fa-rotate:before,.fa-sync-alt:before{content:"\f2f1"}.fa-spinner:before{content:"\f110"}.fa-robot:before{content:"\f544"}.fa-peace:before{content:"\f67c"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-warehouse:before{content:"\f494"}.fa-arrow-up-right-dots:before{content:"\e4b7"}.fa-splotch:before{content:"\f5bc"}.fa-face-grin-hearts:before,.fa-grin-hearts:before{content:"\f584"}.fa-dice-four:before{content:"\f524"}.fa-sim-card:before{content:"\f7c4"}.fa-transgender-alt:before,.fa-transgender:before{content:"\f225"}.fa-mercury:before{content:"\f223"}.fa-arrow-turn-down:before,.fa-level-down:before{content:"\f149"}.fa-person-falling-burst:before{content:"\e547"}.fa-award:before{content:"\f559"}.fa-ticket-alt:before,.fa-ticket-simple:before{content:"\f3ff"}.fa-building:before{content:"\f1ad"}.fa-angle-double-left:before,.fa-angles-left:before{content:"\f100"}.fa-qrcode:before{content:"\f029"}.fa-clock-rotate-left:before,.fa-history:before{content:"\f1da"}.fa-face-grin-beam-sweat:before,.fa-grin-beam-sweat:before{content:"\f583"}.fa-arrow-right-from-file:before,.fa-file-export:before{content:"\f56e"}.fa-shield-blank:before,.fa-shield:before{content:"\f132"}.fa-arrow-up-short-wide:before,.fa-sort-amount-up-alt:before{content:"\f885"}.fa-house-medical:before{content:"\e3b2"}.fa-golf-ball-tee:before,.fa-golf-ball:before{content:"\f450"}.fa-chevron-circle-left:before,.fa-circle-chevron-left:before{content:"\f137"}.fa-house-chimney-window:before{content:"\e00d"}.fa-pen-nib:before{content:"\f5ad"}.fa-tent-arrow-turn-left:before{content:"\e580"}.fa-tents:before{content:"\e582"}.fa-magic:before,.fa-wand-magic:before{content:"\f0d0"}.fa-dog:before{content:"\f6d3"}.fa-carrot:before{content:"\f787"}.fa-moon:before{content:"\f186"}.fa-wine-glass-alt:before,.fa-wine-glass-empty:before{content:"\f5ce"}.fa-cheese:before{content:"\f7ef"}.fa-yin-yang:before{content:"\f6ad"}.fa-music:before{content:"\f001"}.fa-code-commit:before{content:"\f386"}.fa-temperature-low:before{content:"\f76b"}.fa-biking:before,.fa-person-biking:before{content:"\f84a"}.fa-broom:before{content:"\f51a"}.fa-shield-heart:before{content:"\e574"}.fa-gopuram:before{content:"\f664"}.fa-earth-oceania:before,.fa-globe-oceania:before{content:"\e47b"}.fa-square-xmark:before,.fa-times-square:before,.fa-xmark-square:before{content:"\f2d3"}.fa-hashtag:before{content:"\23"}.fa-expand-alt:before,.fa-up-right-and-down-left-from-center:before{content:"\f424"}.fa-oil-can:before{content:"\f613"}.fa-t:before{content:"\54"}.fa-hippo:before{content:"\f6ed"}.fa-chart-column:before{content:"\e0e3"}.fa-infinity:before{content:"\f534"}.fa-vial-circle-check:before{content:"\e596"}.fa-person-arrow-down-to-line:before{content:"\e538"}.fa-voicemail:before{content:"\f897"}.fa-fan:before{content:"\f863"}.fa-person-walking-luggage:before{content:"\e554"}.fa-arrows-alt-v:before,.fa-up-down:before{content:"\f338"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-calendar:before{content:"\f133"}.fa-trailer:before{content:"\e041"}.fa-bahai:before,.fa-haykal:before{content:"\f666"}.fa-sd-card:before{content:"\f7c2"}.fa-dragon:before{content:"\f6d5"}.fa-shoe-prints:before{content:"\f54b"}.fa-circle-plus:before,.fa-plus-circle:before{content:"\f055"}.fa-face-grin-tongue-wink:before,.fa-grin-tongue-wink:before{content:"\f58b"}.fa-hand-holding:before{content:"\f4bd"}.fa-plug-circle-exclamation:before{content:"\e55d"}.fa-chain-broken:before,.fa-chain-slash:before,.fa-link-slash:before,.fa-unlink:before{content:"\f127"}.fa-clone:before{content:"\f24d"}.fa-person-walking-arrow-loop-left:before{content:"\e551"}.fa-arrow-up-z-a:before,.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-fire-alt:before,.fa-fire-flame-curved:before{content:"\f7e4"}.fa-tornado:before{content:"\f76f"}.fa-file-circle-plus:before{content:"\e494"}.fa-book-quran:before,.fa-quran:before{content:"\f687"}.fa-anchor:before{content:"\f13d"}.fa-border-all:before{content:"\f84c"}.fa-angry:before,.fa-face-angry:before{content:"\f556"}.fa-cookie-bite:before{content:"\f564"}.fa-arrow-trend-down:before{content:"\e097"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-draw-polygon:before{content:"\f5ee"}.fa-balance-scale:before,.fa-scale-balanced:before{content:"\f24e"}.fa-gauge-simple-high:before,.fa-tachometer-fast:before,.fa-tachometer:before{content:"\f62a"}.fa-shower:before{content:"\f2cc"}.fa-desktop-alt:before,.fa-desktop:before{content:"\f390"}.fa-m:before{content:"\4d"}.fa-table-list:before,.fa-th-list:before{content:"\f00b"}.fa-comment-sms:before,.fa-sms:before{content:"\f7cd"}.fa-book:before{content:"\f02d"}.fa-user-plus:before{content:"\f234"}.fa-check:before{content:"\f00c"}.fa-battery-4:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-house-circle-check:before{content:"\e509"}.fa-angle-left:before{content:"\f104"}.fa-diagram-successor:before{content:"\e47a"}.fa-truck-arrow-right:before{content:"\e58b"}.fa-arrows-split-up-and-left:before{content:"\e4bc"}.fa-fist-raised:before,.fa-hand-fist:before{content:"\f6de"}.fa-cloud-moon:before{content:"\f6c3"}.fa-briefcase:before{content:"\f0b1"}.fa-person-falling:before{content:"\e546"}.fa-image-portrait:before,.fa-portrait:before{content:"\f3e0"}.fa-user-tag:before{content:"\f507"}.fa-rug:before{content:"\e569"}.fa-earth-europe:before,.fa-globe-europe:before{content:"\f7a2"}.fa-cart-flatbed-suitcase:before,.fa-luggage-cart:before{content:"\f59d"}.fa-rectangle-times:before,.fa-rectangle-xmark:before,.fa-times-rectangle:before,.fa-window-close:before{content:"\f410"}.fa-baht-sign:before{content:"\e0ac"}.fa-book-open:before{content:"\f518"}.fa-book-journal-whills:before,.fa-journal-whills:before{content:"\f66a"}.fa-handcuffs:before{content:"\e4f8"}.fa-exclamation-triangle:before,.fa-triangle-exclamation:before,.fa-warning:before{content:"\f071"}.fa-database:before{content:"\f1c0"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-bottle-droplet:before{content:"\e4c4"}.fa-mask-face:before{content:"\e1d7"}.fa-hill-rockslide:before{content:"\e508"}.fa-exchange-alt:before,.fa-right-left:before{content:"\f362"}.fa-paper-plane:before{content:"\f1d8"}.fa-road-circle-exclamation:before{content:"\e565"}.fa-dungeon:before{content:"\f6d9"}.fa-align-right:before{content:"\f038"}.fa-money-bill-1-wave:before,.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-life-ring:before{content:"\f1cd"}.fa-hands:before,.fa-sign-language:before,.fa-signing:before{content:"\f2a7"}.fa-calendar-day:before{content:"\f783"}.fa-ladder-water:before,.fa-swimming-pool:before,.fa-water-ladder:before{content:"\f5c5"}.fa-arrows-up-down:before,.fa-arrows-v:before{content:"\f07d"}.fa-face-grimace:before,.fa-grimace:before{content:"\f57f"}.fa-wheelchair-alt:before,.fa-wheelchair-move:before{content:"\e2ce"}.fa-level-down-alt:before,.fa-turn-down:before{content:"\f3be"}.fa-person-walking-arrow-right:before{content:"\e552"}.fa-envelope-square:before,.fa-square-envelope:before{content:"\f199"}.fa-dice:before{content:"\f522"}.fa-bowling-ball:before{content:"\f436"}.fa-brain:before{content:"\f5dc"}.fa-band-aid:before,.fa-bandage:before{content:"\f462"}.fa-calendar-minus:before{content:"\f272"}.fa-circle-xmark:before,.fa-times-circle:before,.fa-xmark-circle:before{content:"\f057"}.fa-gifts:before{content:"\f79c"}.fa-hotel:before{content:"\f594"}.fa-earth-asia:before,.fa-globe-asia:before{content:"\f57e"}.fa-id-card-alt:before,.fa-id-card-clip:before{content:"\f47f"}.fa-magnifying-glass-plus:before,.fa-search-plus:before{content:"\f00e"}.fa-thumbs-up:before{content:"\f164"}.fa-user-clock:before{content:"\f4fd"}.fa-allergies:before,.fa-hand-dots:before{content:"\f461"}.fa-file-invoice:before{content:"\f570"}.fa-window-minimize:before{content:"\f2d1"}.fa-coffee:before,.fa-mug-saucer:before{content:"\f0f4"}.fa-brush:before{content:"\f55d"}.fa-mask:before{content:"\f6fa"}.fa-magnifying-glass-minus:before,.fa-search-minus:before{content:"\f010"}.fa-ruler-vertical:before{content:"\f548"}.fa-user-alt:before,.fa-user-large:before{content:"\f406"}.fa-train-tram:before{content:"\e5b4"}.fa-user-nurse:before{content:"\f82f"}.fa-syringe:before{content:"\f48e"}.fa-cloud-sun:before{content:"\f6c4"}.fa-stopwatch-20:before{content:"\e06f"}.fa-square-full:before{content:"\f45c"}.fa-magnet:before{content:"\f076"}.fa-jar:before{content:"\e516"}.fa-note-sticky:before,.fa-sticky-note:before{content:"\f249"}.fa-bug-slash:before{content:"\e490"}.fa-arrow-up-from-water-pump:before{content:"\e4b6"}.fa-bone:before{content:"\f5d7"}.fa-table-cells-row-unlock:before{content:"\e691"}.fa-user-injured:before{content:"\f728"}.fa-face-sad-tear:before,.fa-sad-tear:before{content:"\f5b4"}.fa-plane:before{content:"\f072"}.fa-tent-arrows-down:before{content:"\e581"}.fa-exclamation:before{content:"\21"}.fa-arrows-spin:before{content:"\e4bb"}.fa-print:before{content:"\f02f"}.fa-try:before,.fa-turkish-lira-sign:before,.fa-turkish-lira:before{content:"\e2bb"}.fa-dollar-sign:before,.fa-dollar:before,.fa-usd:before{content:"\24"}.fa-x:before{content:"\58"}.fa-magnifying-glass-dollar:before,.fa-search-dollar:before{content:"\f688"}.fa-users-cog:before,.fa-users-gear:before{content:"\f509"}.fa-person-military-pointing:before{content:"\e54a"}.fa-bank:before,.fa-building-columns:before,.fa-institution:before,.fa-museum:before,.fa-university:before{content:"\f19c"}.fa-umbrella:before{content:"\f0e9"}.fa-trowel:before{content:"\e589"}.fa-d:before{content:"\44"}.fa-stapler:before{content:"\e5af"}.fa-masks-theater:before,.fa-theater-masks:before{content:"\f630"}.fa-kip-sign:before{content:"\e1c4"}.fa-hand-point-left:before{content:"\f0a5"}.fa-handshake-alt:before,.fa-handshake-simple:before{content:"\f4c6"}.fa-fighter-jet:before,.fa-jet-fighter:before{content:"\f0fb"}.fa-share-alt-square:before,.fa-square-share-nodes:before{content:"\f1e1"}.fa-barcode:before{content:"\f02a"}.fa-plus-minus:before{content:"\e43c"}.fa-video-camera:before,.fa-video:before{content:"\f03d"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-hand-holding-medical:before{content:"\e05c"}.fa-person-circle-check:before{content:"\e53e"}.fa-level-up-alt:before,.fa-turn-up:before{content:"\f3bf"}
|
| 9 |
+
.fa-sr-only,.fa-sr-only-focusable:not(:focus),.sr-only,.sr-only-focusable:not(:focus){position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}:host,:root{--fa-style-family-brands:"Font Awesome 6 Brands";--fa-font-brands:normal 400 1em/1 "Font Awesome 6 Brands"}@font-face{font-family:"Font Awesome 6 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}.fa-brands,.fab{font-weight:400}.fa-monero:before{content:"\f3d0"}.fa-hooli:before{content:"\f427"}.fa-yelp:before{content:"\f1e9"}.fa-cc-visa:before{content:"\f1f0"}.fa-lastfm:before{content:"\f202"}.fa-shopware:before{content:"\f5b5"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-aws:before{content:"\f375"}.fa-redhat:before{content:"\f7bc"}.fa-yoast:before{content:"\f2b1"}.fa-cloudflare:before{content:"\e07d"}.fa-ups:before{content:"\f7e0"}.fa-pixiv:before{content:"\e640"}.fa-wpexplorer:before{content:"\f2de"}.fa-dyalog:before{content:"\f399"}.fa-bity:before{content:"\f37a"}.fa-stackpath:before{content:"\f842"}.fa-buysellads:before{content:"\f20d"}.fa-first-order:before{content:"\f2b0"}.fa-modx:before{content:"\f285"}.fa-guilded:before{content:"\e07e"}.fa-vnv:before{content:"\f40b"}.fa-js-square:before,.fa-square-js:before{content:"\f3b9"}.fa-microsoft:before{content:"\f3ca"}.fa-qq:before{content:"\f1d6"}.fa-orcid:before{content:"\f8d2"}.fa-java:before{content:"\f4e4"}.fa-invision:before{content:"\f7b0"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-centercode:before{content:"\f380"}.fa-glide-g:before{content:"\f2a6"}.fa-drupal:before{content:"\f1a9"}.fa-jxl:before{content:"\e67b"}.fa-dart-lang:before{content:"\e693"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-unity:before{content:"\e049"}.fa-whmcs:before{content:"\f40d"}.fa-rocketchat:before{content:"\f3e8"}.fa-vk:before{content:"\f189"}.fa-untappd:before{content:"\f405"}.fa-mailchimp:before{content:"\f59e"}.fa-css3-alt:before{content:"\f38b"}.fa-reddit-square:before,.fa-square-reddit:before{content:"\f1a2"}.fa-vimeo-v:before{content:"\f27d"}.fa-contao:before{content:"\f26d"}.fa-square-font-awesome:before{content:"\e5ad"}.fa-deskpro:before{content:"\f38f"}.fa-brave:before{content:"\e63c"}.fa-sistrix:before{content:"\f3ee"}.fa-instagram-square:before,.fa-square-instagram:before{content:"\e055"}.fa-battle-net:before{content:"\f835"}.fa-the-red-yeti:before{content:"\f69d"}.fa-hacker-news-square:before,.fa-square-hacker-news:before{content:"\f3af"}.fa-edge:before{content:"\f282"}.fa-threads:before{content:"\e618"}.fa-napster:before{content:"\f3d2"}.fa-snapchat-square:before,.fa-square-snapchat:before{content:"\f2ad"}.fa-google-plus-g:before{content:"\f0d5"}.fa-artstation:before{content:"\f77a"}.fa-markdown:before{content:"\f60f"}.fa-sourcetree:before{content:"\f7d3"}.fa-google-plus:before{content:"\f2b3"}.fa-diaspora:before{content:"\f791"}.fa-foursquare:before{content:"\f180"}.fa-stack-overflow:before{content:"\f16c"}.fa-github-alt:before{content:"\f113"}.fa-phoenix-squadron:before{content:"\f511"}.fa-pagelines:before{content:"\f18c"}.fa-algolia:before{content:"\f36c"}.fa-red-river:before{content:"\f3e3"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-safari:before{content:"\f267"}.fa-google:before{content:"\f1a0"}.fa-font-awesome-alt:before,.fa-square-font-awesome-stroke:before{content:"\f35c"}.fa-atlassian:before{content:"\f77b"}.fa-linkedin-in:before{content:"\f0e1"}.fa-digital-ocean:before{content:"\f391"}.fa-nimblr:before{content:"\f5a8"}.fa-chromecast:before{content:"\f838"}.fa-evernote:before{content:"\f839"}.fa-hacker-news:before{content:"\f1d4"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-adversal:before{content:"\f36a"}.fa-creative-commons:before{content:"\f25e"}.fa-watchman-monitoring:before{content:"\e087"}.fa-fonticons:before{content:"\f280"}.fa-weixin:before{content:"\f1d7"}.fa-shirtsinbulk:before{content:"\f214"}.fa-codepen:before{content:"\f1cb"}.fa-git-alt:before{content:"\f841"}.fa-lyft:before{content:"\f3c3"}.fa-rev:before{content:"\f5b2"}.fa-windows:before{content:"\f17a"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-square-viadeo:before,.fa-viadeo-square:before{content:"\f2aa"}.fa-meetup:before{content:"\f2e0"}.fa-centos:before{content:"\f789"}.fa-adn:before{content:"\f170"}.fa-cloudsmith:before{content:"\f384"}.fa-opensuse:before{content:"\e62b"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-dribbble-square:before,.fa-square-dribbble:before{content:"\f397"}.fa-codiepie:before{content:"\f284"}.fa-node:before{content:"\f419"}.fa-mix:before{content:"\f3cb"}.fa-steam:before{content:"\f1b6"}.fa-cc-apple-pay:before{content:"\f416"}.fa-scribd:before{content:"\f28a"}.fa-debian:before{content:"\e60b"}.fa-openid:before{content:"\f19b"}.fa-instalod:before{content:"\e081"}.fa-expeditedssl:before{content:"\f23e"}.fa-sellcast:before{content:"\f2da"}.fa-square-twitter:before,.fa-twitter-square:before{content:"\f081"}.fa-r-project:before{content:"\f4f7"}.fa-delicious:before{content:"\f1a5"}.fa-freebsd:before{content:"\f3a4"}.fa-vuejs:before{content:"\f41f"}.fa-accusoft:before{content:"\f369"}.fa-ioxhost:before{content:"\f208"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-app-store:before{content:"\f36f"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-itunes-note:before{content:"\f3b5"}.fa-golang:before{content:"\e40f"}.fa-kickstarter:before,.fa-square-kickstarter:before{content:"\f3bb"}.fa-grav:before{content:"\f2d6"}.fa-weibo:before{content:"\f18a"}.fa-uncharted:before{content:"\e084"}.fa-firstdraft:before{content:"\f3a1"}.fa-square-youtube:before,.fa-youtube-square:before{content:"\f431"}.fa-wikipedia-w:before{content:"\f266"}.fa-rendact:before,.fa-wpressr:before{content:"\f3e4"}.fa-angellist:before{content:"\f209"}.fa-galactic-republic:before{content:"\f50c"}.fa-nfc-directional:before{content:"\e530"}.fa-skype:before{content:"\f17e"}.fa-joget:before{content:"\f3b7"}.fa-fedora:before{content:"\f798"}.fa-stripe-s:before{content:"\f42a"}.fa-meta:before{content:"\e49b"}.fa-laravel:before{content:"\f3bd"}.fa-hotjar:before{content:"\f3b1"}.fa-bluetooth-b:before{content:"\f294"}.fa-square-letterboxd:before{content:"\e62e"}.fa-sticker-mule:before{content:"\f3f7"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-hips:before{content:"\f452"}.fa-behance:before{content:"\f1b4"}.fa-reddit:before{content:"\f1a1"}.fa-discord:before{content:"\f392"}.fa-chrome:before{content:"\f268"}.fa-app-store-ios:before{content:"\f370"}.fa-cc-discover:before{content:"\f1f2"}.fa-wpbeginner:before{content:"\f297"}.fa-confluence:before{content:"\f78d"}.fa-shoelace:before{content:"\e60c"}.fa-mdb:before{content:"\f8ca"}.fa-dochub:before{content:"\f394"}.fa-accessible-icon:before{content:"\f368"}.fa-ebay:before{content:"\f4f4"}.fa-amazon:before{content:"\f270"}.fa-unsplash:before{content:"\e07c"}.fa-yarn:before{content:"\f7e3"}.fa-square-steam:before,.fa-steam-square:before{content:"\f1b7"}.fa-500px:before{content:"\f26e"}.fa-square-vimeo:before,.fa-vimeo-square:before{content:"\f194"}.fa-asymmetrik:before{content:"\f372"}.fa-font-awesome-flag:before,.fa-font-awesome-logo-full:before,.fa-font-awesome:before{content:"\f2b4"}.fa-gratipay:before{content:"\f184"}.fa-apple:before{content:"\f179"}.fa-hive:before{content:"\e07f"}.fa-gitkraken:before{content:"\f3a6"}.fa-keybase:before{content:"\f4f5"}.fa-apple-pay:before{content:"\f415"}.fa-padlet:before{content:"\e4a0"}.fa-amazon-pay:before{content:"\f42c"}.fa-github-square:before,.fa-square-github:before{content:"\f092"}.fa-stumbleupon:before{content:"\f1a4"}.fa-fedex:before{content:"\f797"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-shopify:before{content:"\e057"}.fa-neos:before{content:"\f612"}.fa-square-threads:before{content:"\e619"}.fa-hackerrank:before{content:"\f5f7"}.fa-researchgate:before{content:"\f4f8"}.fa-swift:before{content:"\f8e1"}.fa-angular:before{content:"\f420"}.fa-speakap:before{content:"\f3f3"}.fa-angrycreative:before{content:"\f36e"}.fa-y-combinator:before{content:"\f23b"}.fa-empire:before{content:"\f1d1"}.fa-envira:before{content:"\f299"}.fa-google-scholar:before{content:"\e63b"}.fa-gitlab-square:before,.fa-square-gitlab:before{content:"\e5ae"}.fa-studiovinari:before{content:"\f3f8"}.fa-pied-piper:before{content:"\f2ae"}.fa-wordpress:before{content:"\f19a"}.fa-product-hunt:before{content:"\f288"}.fa-firefox:before{content:"\f269"}.fa-linode:before{content:"\f2b8"}.fa-goodreads:before{content:"\f3a8"}.fa-odnoklassniki-square:before,.fa-square-odnoklassniki:before{content:"\f264"}.fa-jsfiddle:before{content:"\f1cc"}.fa-sith:before{content:"\f512"}.fa-themeisle:before{content:"\f2b2"}.fa-page4:before{content:"\f3d7"}.fa-hashnode:before{content:"\e499"}.fa-react:before{content:"\f41b"}.fa-cc-paypal:before{content:"\f1f4"}.fa-squarespace:before{content:"\f5be"}.fa-cc-stripe:before{content:"\f1f5"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-bitcoin:before{content:"\f379"}.fa-keycdn:before{content:"\f3ba"}.fa-opera:before{content:"\f26a"}.fa-itch-io:before{content:"\f83a"}.fa-umbraco:before{content:"\f8e8"}.fa-galactic-senate:before{content:"\f50d"}.fa-ubuntu:before{content:"\f7df"}.fa-draft2digital:before{content:"\f396"}.fa-stripe:before{content:"\f429"}.fa-houzz:before{content:"\f27c"}.fa-gg:before{content:"\f260"}.fa-dhl:before{content:"\f790"}.fa-pinterest-square:before,.fa-square-pinterest:before{content:"\f0d3"}.fa-xing:before{content:"\f168"}.fa-blackberry:before{content:"\f37b"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-playstation:before{content:"\f3df"}.fa-quinscape:before{content:"\f459"}.fa-less:before{content:"\f41d"}.fa-blogger-b:before{content:"\f37d"}.fa-opencart:before{content:"\f23d"}.fa-vine:before{content:"\f1ca"}.fa-signal-messenger:before{content:"\e663"}.fa-paypal:before{content:"\f1ed"}.fa-gitlab:before{content:"\f296"}.fa-typo3:before{content:"\f42b"}.fa-reddit-alien:before{content:"\f281"}.fa-yahoo:before{content:"\f19e"}.fa-dailymotion:before{content:"\e052"}.fa-affiliatetheme:before{content:"\f36b"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-bootstrap:before{content:"\f836"}.fa-odnoklassniki:before{content:"\f263"}.fa-nfc-symbol:before{content:"\e531"}.fa-mintbit:before{content:"\e62f"}.fa-ethereum:before{content:"\f42e"}.fa-speaker-deck:before{content:"\f83c"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-patreon:before{content:"\f3d9"}.fa-avianex:before{content:"\f374"}.fa-ello:before{content:"\f5f1"}.fa-gofore:before{content:"\f3a7"}.fa-bimobject:before{content:"\f378"}.fa-brave-reverse:before{content:"\e63d"}.fa-facebook-f:before{content:"\f39e"}.fa-google-plus-square:before,.fa-square-google-plus:before{content:"\f0d4"}.fa-web-awesome:before{content:"\e682"}.fa-mandalorian:before{content:"\f50f"}.fa-first-order-alt:before{content:"\f50a"}.fa-osi:before{content:"\f41a"}.fa-google-wallet:before{content:"\f1ee"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-periscope:before{content:"\f3da"}.fa-fulcrum:before{content:"\f50b"}.fa-cloudscale:before{content:"\f383"}.fa-forumbee:before{content:"\f211"}.fa-mizuni:before{content:"\f3cc"}.fa-schlix:before{content:"\f3ea"}.fa-square-xing:before,.fa-xing-square:before{content:"\f169"}.fa-bandcamp:before{content:"\f2d5"}.fa-wpforms:before{content:"\f298"}.fa-cloudversify:before{content:"\f385"}.fa-usps:before{content:"\f7e1"}.fa-megaport:before{content:"\f5a3"}.fa-magento:before{content:"\f3c4"}.fa-spotify:before{content:"\f1bc"}.fa-optin-monster:before{content:"\f23c"}.fa-fly:before{content:"\f417"}.fa-aviato:before{content:"\f421"}.fa-itunes:before{content:"\f3b4"}.fa-cuttlefish:before{content:"\f38c"}.fa-blogger:before{content:"\f37c"}.fa-flickr:before{content:"\f16e"}.fa-viber:before{content:"\f409"}.fa-soundcloud:before{content:"\f1be"}.fa-digg:before{content:"\f1a6"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-letterboxd:before{content:"\e62d"}.fa-symfony:before{content:"\f83d"}.fa-maxcdn:before{content:"\f136"}.fa-etsy:before{content:"\f2d7"}.fa-facebook-messenger:before{content:"\f39f"}.fa-audible:before{content:"\f373"}.fa-think-peaks:before{content:"\f731"}.fa-bilibili:before{content:"\e3d9"}.fa-erlang:before{content:"\f39d"}.fa-x-twitter:before{content:"\e61b"}.fa-cotton-bureau:before{content:"\f89e"}.fa-dashcube:before{content:"\f210"}.fa-42-group:before,.fa-innosoft:before{content:"\e080"}.fa-stack-exchange:before{content:"\f18d"}.fa-elementor:before{content:"\f430"}.fa-pied-piper-square:before,.fa-square-pied-piper:before{content:"\e01e"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-palfed:before{content:"\f3d8"}.fa-superpowers:before{content:"\f2dd"}.fa-resolving:before{content:"\f3e7"}.fa-xbox:before{content:"\f412"}.fa-square-web-awesome-stroke:before{content:"\e684"}.fa-searchengin:before{content:"\f3eb"}.fa-tiktok:before{content:"\e07b"}.fa-facebook-square:before,.fa-square-facebook:before{content:"\f082"}.fa-renren:before{content:"\f18b"}.fa-linux:before{content:"\f17c"}.fa-glide:before{content:"\f2a5"}.fa-linkedin:before{content:"\f08c"}.fa-hubspot:before{content:"\f3b2"}.fa-deploydog:before{content:"\f38e"}.fa-twitch:before{content:"\f1e8"}.fa-flutter:before{content:"\e694"}.fa-ravelry:before{content:"\f2d9"}.fa-mixer:before{content:"\e056"}.fa-lastfm-square:before,.fa-square-lastfm:before{content:"\f203"}.fa-vimeo:before{content:"\f40a"}.fa-mendeley:before{content:"\f7b3"}.fa-uniregistry:before{content:"\f404"}.fa-figma:before{content:"\f799"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-dropbox:before{content:"\f16b"}.fa-instagram:before{content:"\f16d"}.fa-cmplid:before{content:"\e360"}.fa-upwork:before{content:"\e641"}.fa-facebook:before{content:"\f09a"}.fa-gripfire:before{content:"\f3ac"}.fa-jedi-order:before{content:"\f50e"}.fa-uikit:before{content:"\f403"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-phabricator:before{content:"\f3db"}.fa-ussunnah:before{content:"\f407"}.fa-earlybirds:before{content:"\f39a"}.fa-trade-federation:before{content:"\f513"}.fa-autoprefixer:before{content:"\f41c"}.fa-whatsapp:before{content:"\f232"}.fa-square-upwork:before{content:"\e67c"}.fa-slideshare:before{content:"\f1e7"}.fa-google-play:before{content:"\f3ab"}.fa-viadeo:before{content:"\f2a9"}.fa-line:before{content:"\f3c0"}.fa-google-drive:before{content:"\f3aa"}.fa-servicestack:before{content:"\f3ec"}.fa-simplybuilt:before{content:"\f215"}.fa-bitbucket:before{content:"\f171"}.fa-imdb:before{content:"\f2d8"}.fa-deezer:before{content:"\e077"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-jira:before{content:"\f7b1"}.fa-docker:before{content:"\f395"}.fa-screenpal:before{content:"\e570"}.fa-bluetooth:before{content:"\f293"}.fa-gitter:before{content:"\f426"}.fa-d-and-d:before{content:"\f38d"}.fa-microblog:before{content:"\e01a"}.fa-cc-diners-club:before{content:"\f24c"}.fa-gg-circle:before{content:"\f261"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-yandex:before{content:"\f413"}.fa-readme:before{content:"\f4d5"}.fa-html5:before{content:"\f13b"}.fa-sellsy:before{content:"\f213"}.fa-square-web-awesome:before{content:"\e683"}.fa-sass:before{content:"\f41e"}.fa-wirsindhandwerk:before,.fa-wsh:before{content:"\e2d0"}.fa-buromobelexperte:before{content:"\f37f"}.fa-salesforce:before{content:"\f83b"}.fa-octopus-deploy:before{content:"\e082"}.fa-medapps:before{content:"\f3c6"}.fa-ns8:before{content:"\f3d5"}.fa-pinterest-p:before{content:"\f231"}.fa-apper:before{content:"\f371"}.fa-fort-awesome:before{content:"\f286"}.fa-waze:before{content:"\f83f"}.fa-bluesky:before{content:"\e671"}.fa-cc-jcb:before{content:"\f24b"}.fa-snapchat-ghost:before,.fa-snapchat:before{content:"\f2ab"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-rust:before{content:"\e07a"}.fa-wix:before{content:"\f5cf"}.fa-behance-square:before,.fa-square-behance:before{content:"\f1b5"}.fa-supple:before{content:"\f3f9"}.fa-webflow:before{content:"\e65c"}.fa-rebel:before{content:"\f1d0"}.fa-css3:before{content:"\f13c"}.fa-staylinked:before{content:"\f3f5"}.fa-kaggle:before{content:"\f5fa"}.fa-space-awesome:before{content:"\e5ac"}.fa-deviantart:before{content:"\f1bd"}.fa-cpanel:before{content:"\f388"}.fa-goodreads-g:before{content:"\f3a9"}.fa-git-square:before,.fa-square-git:before{content:"\f1d2"}.fa-square-tumblr:before,.fa-tumblr-square:before{content:"\f174"}.fa-trello:before{content:"\f181"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-get-pocket:before{content:"\f265"}.fa-perbyte:before{content:"\e083"}.fa-grunt:before{content:"\f3ad"}.fa-weebly:before{content:"\f5cc"}.fa-connectdevelop:before{content:"\f20e"}.fa-leanpub:before{content:"\f212"}.fa-black-tie:before{content:"\f27e"}.fa-themeco:before{content:"\f5c6"}.fa-python:before{content:"\f3e2"}.fa-android:before{content:"\f17b"}.fa-bots:before{content:"\e340"}.fa-free-code-camp:before{content:"\f2c5"}.fa-hornbill:before{content:"\f592"}.fa-js:before{content:"\f3b8"}.fa-ideal:before{content:"\e013"}.fa-git:before{content:"\f1d3"}.fa-dev:before{content:"\f6cc"}.fa-sketch:before{content:"\f7c6"}.fa-yandex-international:before{content:"\f414"}.fa-cc-amex:before{content:"\f1f3"}.fa-uber:before{content:"\f402"}.fa-github:before{content:"\f09b"}.fa-php:before{content:"\f457"}.fa-alipay:before{content:"\f642"}.fa-youtube:before{content:"\f167"}.fa-skyatlas:before{content:"\f216"}.fa-firefox-browser:before{content:"\e007"}.fa-replyd:before{content:"\f3e6"}.fa-suse:before{content:"\f7d6"}.fa-jenkins:before{content:"\f3b6"}.fa-twitter:before{content:"\f099"}.fa-rockrms:before{content:"\f3e9"}.fa-pinterest:before{content:"\f0d2"}.fa-buffer:before{content:"\f837"}.fa-npm:before{content:"\f3d4"}.fa-yammer:before{content:"\f840"}.fa-btc:before{content:"\f15a"}.fa-dribbble:before{content:"\f17d"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-internet-explorer:before{content:"\f26b"}.fa-stubber:before{content:"\e5c7"}.fa-telegram-plane:before,.fa-telegram:before{content:"\f2c6"}.fa-old-republic:before{content:"\f510"}.fa-odysee:before{content:"\e5c6"}.fa-square-whatsapp:before,.fa-whatsapp-square:before{content:"\f40c"}.fa-node-js:before{content:"\f3d3"}.fa-edge-legacy:before{content:"\e078"}.fa-slack-hash:before,.fa-slack:before{content:"\f198"}.fa-medrt:before{content:"\f3c8"}.fa-usb:before{content:"\f287"}.fa-tumblr:before{content:"\f173"}.fa-vaadin:before{content:"\f408"}.fa-quora:before{content:"\f2c4"}.fa-square-x-twitter:before{content:"\e61a"}.fa-reacteurope:before{content:"\f75d"}.fa-medium-m:before,.fa-medium:before{content:"\f23a"}.fa-amilia:before{content:"\f36d"}.fa-mixcloud:before{content:"\f289"}.fa-flipboard:before{content:"\f44d"}.fa-viacoin:before{content:"\f237"}.fa-critical-role:before{content:"\f6c9"}.fa-sitrox:before{content:"\e44a"}.fa-discourse:before{content:"\f393"}.fa-joomla:before{content:"\f1aa"}.fa-mastodon:before{content:"\f4f6"}.fa-airbnb:before{content:"\f834"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-buy-n-large:before{content:"\f8a6"}.fa-gulp:before{content:"\f3ae"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-strava:before{content:"\f428"}.fa-ember:before{content:"\f423"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-teamspeak:before{content:"\f4f9"}.fa-pushed:before{content:"\f3e1"}.fa-wordpress-simple:before{content:"\f411"}.fa-nutritionix:before{content:"\f3d6"}.fa-wodu:before{content:"\e088"}.fa-google-pay:before{content:"\e079"}.fa-intercom:before{content:"\f7af"}.fa-zhihu:before{content:"\f63f"}.fa-korvue:before{content:"\f42f"}.fa-pix:before{content:"\e43a"}.fa-steam-symbol:before{content:"\f3f6"}:host,:root{--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-weight:400}:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}@font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:900;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:400;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-v4compatibility.woff2) format("woff2"),url(../webfonts/fa-v4compatibility.ttf) format("truetype");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a}
|