Skip to content

Commit c21b6f8

Browse files
committed
feat: improve fitness
1 parent bb4122d commit c21b6f8

File tree

4 files changed

+79
-57
lines changed

4 files changed

+79
-57
lines changed

ai/car_ai.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33

44
import neat
5+
import pygame
56
from render.car import Car, Action
67
from render.neural_network.nn import NN
7-
import pygame
8+
from render.track import Track
89

910

1011
# ------------------ CLASSES ------------------
@@ -14,7 +15,7 @@ class CarAI:
1415
TOTAL_GENERATIONS = 0
1516
TIME_LIMIT = 15
1617

17-
def __init__(self, genomes: neat.DefaultGenome, config: neat.Config, start_position: list):
18+
def __init__(self, genomes: neat.DefaultGenome, config: neat.Config, start_position: list, track: Track):
1819
CarAI.TOTAL_GENERATIONS += 1
1920

2021
self.genomes = genomes
@@ -31,7 +32,7 @@ def __init__(self, genomes: neat.DefaultGenome, config: neat.Config, start_posit
3132
net = neat.nn.FeedForwardNetwork.create(genome, config)
3233
self.nets.append(net)
3334
genome.fitness = 0
34-
self.cars.append(Car(start_position))
35+
self.cars.append(Car(start_position, track))
3536
self.nns.append(NN(config, genome, (60, 130)))
3637

3738
self.remaining_cars = len(self.cars)

render/car.py

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pygame
55
import math
66
from render.colors import Color
7+
from render.track import Track
78

89

910
# ------------------ GLOBAL VARIABLES ------------------
@@ -39,7 +40,7 @@ class Car:
3940
DRAW_SENSORS = True
4041
SENSORS_DRAW_DISTANCE = 1920
4142

42-
def __init__(self, start_position: list):
43+
def __init__(self, start_position: list, track: Track):
4344
# The _sprite is the untouched sprite (not rotated) while the sprite is the one which will be moved around
4445
self._sprite = pygame.image.load(CAR_SPRITE_PATH).convert_alpha()
4546

@@ -67,6 +68,15 @@ def __init__(self, start_position: list):
6768

6869
self.driven_distance = 0
6970
self.speed_penalty = 0
71+
track_width = track.width
72+
track_height = track.height
73+
self.track_diagonal = math.sqrt(track_width**2 + track_height**2)
74+
75+
self.DISTANCE_NORMALIZER = self.track_diagonal / 2
76+
self.MAX_EXPECTED_SPEED = self.track_diagonal / 100
77+
self.minimum_speed = self.CAR_SIZE_X / 6
78+
self.angle_increment = math.degrees(math.atan2(self.CAR_SIZE_Y, self.speed * 10))
79+
self.penalty_factor = self.track_diagonal / 1000
7080

7181
def draw(self, track: pygame.Surface) -> None:
7282
"""Draw the car on the track (and its sensors if enabled)
@@ -177,6 +187,13 @@ def check_sensor(self, degree: int, track: pygame.Surface) -> None:
177187
distance = int(math.hypot(x - self.center[0], y - self.center[1]))
178188
self.sensors.append([(x, y), distance])
179189

190+
def update_adaptive_parameters(self) -> None:
191+
"""Update the adaptive parameters of the car such as the angle increment, the minimum speed, etc."""
192+
self.angle_increment = math.degrees(math.atan2(self.CAR_SIZE_Y, self.speed * 10))
193+
194+
min_sensor_distance = min(sensor[1] for sensor in self.sensors) if self.sensors else self.CAR_SIZE_X
195+
self.minimum_speed = max(self.CAR_SIZE_X / 6, min_sensor_distance / 20)
196+
180197
def update_center(self) -> None:
181198
"""Update the center of the car after a rotation (when it turns left or right)"""
182199
sprite_as_rect = self._sprite.get_rect()
@@ -205,7 +222,7 @@ def update_sprite(self, track: pygame.Surface) -> None:
205222
self.position[1] += sin * self.speed
206223

207224
# Update the driven distance with the speed
208-
self.driven_distance += self.speed
225+
self.update_adaptive_parameters()
209226

210227
# Calculate Corners
211228
self.refresh_corners_positions()
@@ -242,17 +259,16 @@ def get_reward(self) -> float:
242259
float: The calculated reward
243260
"""
244261

245-
# Reward for distance driven
246-
distance_reward = self.driven_distance / 10000
247-
248-
MAX_EXPECTED_SPEED = 5000
249-
speed_reward = (self.speed / MAX_EXPECTED_SPEED) ** 0.5 # Square root to apply diminishing returns
250-
251-
malus = self.speed_penalty / 100
252-
253-
# Calculate the final reward
254-
final_reward = distance_reward + speed_reward - malus
255-
262+
distance_reward = self.driven_distance / self.DISTANCE_NORMALIZER
263+
264+
speed_reward = (self.speed / self.MAX_EXPECTED_SPEED) ** 0.5
265+
266+
malus = self.speed_penalty / self.penalty_factor
267+
268+
progress_factor = min(1.0, self.driven_distance / (self.track_diagonal * 0.75))
269+
270+
final_reward = (distance_reward + speed_reward - malus) * (1 + progress_factor)
271+
256272
return final_reward
257273

258274

@@ -266,7 +282,7 @@ def brake(self) -> None:
266282
self.speed -= Car.SPEED_INCREMENT
267283
else:
268284
self.speed = Car.MINIMUM_SPEED
269-
self.speed_penalty += 1 # speed_penalty for going too slow
285+
self.speed_penalty += 1
270286

271287
def turn_left(self) -> None:
272288
"""Turn the car to the left"""

render/engine.py

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,49 +9,12 @@
99
from ai.car_ai import CarAI
1010
from render.car import Car
1111
from render.colors import Color
12-
12+
from render.track import Track
1313

1414

1515
# ------------------ CLASSES ------------------
1616

1717

18-
class Track:
19-
20-
BRUSH_LIMIT_SIZE = 25
21-
22-
def __init__(self, width: int, height: int):
23-
self.surface = pygame.Surface((width, height))
24-
self.surface.fill(Color.WHITE)
25-
self.brush_size = 50
26-
self.last_position = None
27-
28-
def draw(self, position: Tuple[int, int], color: Tuple[int, int, int]):
29-
if self.last_position:
30-
self.draw_interpolated(self.last_position, position, color)
31-
else:
32-
pygame.draw.circle(self.surface, color, position, self.brush_size)
33-
self.last_position = position
34-
35-
def adjust_brush_size(self, amount: int):
36-
self.brush_size = max(Track.BRUSH_LIMIT_SIZE, self.brush_size + amount)
37-
38-
def draw_interpolated(self, start: Tuple[int, int], end: Tuple[int, int], color: Tuple[int, int, int]):
39-
dx = end[0] - start[0]
40-
dy = end[1] - start[1]
41-
distance = max(abs(dx), abs(dy))
42-
43-
for i in range(distance):
44-
x = int(start[0] + float(i) / distance * dx)
45-
y = int(start[1] + float(i) / distance * dy)
46-
pygame.draw.circle(self.surface, color, (x, y), self.brush_size)
47-
48-
def reset_last_position(self):
49-
self.last_position = None
50-
51-
def get_surface(self) -> pygame.Surface:
52-
return self.surface
53-
54-
5518
class Engine:
5619
WIDTH = 1900
5720
HEIGHT = 950
@@ -70,7 +33,7 @@ def __init__(self, neat_config_path: str, debug: bool, max_simulations: int):
7033
self.clock = pygame.time.Clock()
7134

7235
self.track = Track(self.WIDTH, self.HEIGHT)
73-
self.car = Car([0, 0])
36+
self.car = Car([0, 0], self.track)
7437
self.decided_car_pos = None
7538

7639
self.state = "drawing_track"
@@ -148,7 +111,7 @@ def start_ai(self):
148111
population.run(self.run_simulation, self.max_simulations)
149112

150113
def run_simulation(self, genomes: List[neat.DefaultGenome], config: neat.Config) -> None:
151-
car_ai = CarAI(genomes, config, self.decided_car_pos)
114+
car_ai = CarAI(genomes, config, self.decided_car_pos, self.track)
152115
timer = time.time()
153116

154117
while True:

render/track.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import pygame
2+
from typing import Tuple
3+
from render.colors import Color
4+
5+
6+
class Track:
7+
8+
BRUSH_LIMIT_SIZE = 25
9+
10+
def __init__(self, width: int, height: int):
11+
self.width = width
12+
self.height = height
13+
self.surface = pygame.Surface((width, height))
14+
self.surface.fill(Color.WHITE)
15+
self.brush_size = 50
16+
self.last_position = None
17+
18+
def draw(self, position: Tuple[int, int], color: Tuple[int, int, int]):
19+
if self.last_position:
20+
self.draw_interpolated(self.last_position, position, color)
21+
else:
22+
pygame.draw.circle(self.surface, color, position, self.brush_size)
23+
self.last_position = position
24+
25+
def adjust_brush_size(self, amount: int):
26+
self.brush_size = max(Track.BRUSH_LIMIT_SIZE, self.brush_size + amount)
27+
28+
def draw_interpolated(self, start: Tuple[int, int], end: Tuple[int, int], color: Tuple[int, int, int]):
29+
dx = end[0] - start[0]
30+
dy = end[1] - start[1]
31+
distance = max(abs(dx), abs(dy))
32+
33+
for i in range(distance):
34+
x = int(start[0] + float(i) / distance * dx)
35+
y = int(start[1] + float(i) / distance * dy)
36+
pygame.draw.circle(self.surface, color, (x, y), self.brush_size)
37+
38+
def reset_last_position(self):
39+
self.last_position = None
40+
41+
def get_surface(self) -> pygame.Surface:
42+
return self.surface

0 commit comments

Comments
 (0)