Nomi78600 commited on
Commit
fe446ed
·
0 Parent(s):
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +36 -0
  2. .gitignore +130 -0
  3. FYP/GameEnv.py +515 -0
  4. FYP/Goals.py +99 -0
  5. FYP/Walls.py +122 -0
  6. FYP/car.png +0 -0
  7. FYP/ddqn_keras.py +126 -0
  8. FYP/ddqn_model.keras +0 -0
  9. FYP/main.py +81 -0
  10. FYP/main_test_model.py +69 -0
  11. FYP/requirements.txt +3 -0
  12. FYP/track.png +0 -0
  13. README.md +10 -0
  14. fyp1/best_neat_genome.pkl +3 -0
  15. fyp1/car.png +0 -0
  16. fyp1/config.txt +79 -0
  17. fyp1/map.png +0 -0
  18. fyp1/map2.png +0 -0
  19. fyp1/model_testing_NEAT.py +35 -0
  20. fyp1/newcar.py +288 -0
  21. fyp1/requirements.txt +2 -0
  22. gui/.dockerignore +9 -0
  23. gui/.gitignore +9 -0
  24. gui/Dockerfile +23 -0
  25. gui/gui/__init__.py +0 -0
  26. gui/gui/asgi.py +16 -0
  27. gui/gui/settings.py +124 -0
  28. gui/gui/urls.py +28 -0
  29. gui/gui/wsgi.py +16 -0
  30. gui/interface/__init__.py +0 -0
  31. gui/interface/admin.py +3 -0
  32. gui/interface/apps.py +6 -0
  33. gui/interface/migrations/__init__.py +0 -0
  34. gui/interface/models.py +3 -0
  35. gui/interface/simulations/ddqn_simulation.py +70 -0
  36. gui/interface/simulations/fyp1_simulation/best_neat_genome.pkl +3 -0
  37. gui/interface/simulations/fyp1_simulation/car.png +0 -0
  38. gui/interface/simulations/fyp1_simulation/config.txt +79 -0
  39. gui/interface/simulations/fyp1_simulation/map2.png +0 -0
  40. gui/interface/simulations/fyp1_simulation/newcar.py +288 -0
  41. gui/interface/simulations/fyp_simulation/GameEnv.py +512 -0
  42. gui/interface/simulations/fyp_simulation/Goals.py +99 -0
  43. gui/interface/simulations/fyp_simulation/Walls.py +113 -0
  44. gui/interface/simulations/fyp_simulation/car.png +0 -0
  45. gui/interface/simulations/fyp_simulation/ddqn_keras.py +126 -0
  46. gui/interface/simulations/fyp_simulation/ddqn_model.keras +0 -0
  47. gui/interface/simulations/fyp_simulation/track.png +0 -0
  48. gui/interface/simulations/neat_simulation.py +33 -0
  49. gui/interface/static/background.jpg +0 -0
  50. 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}