-
Notifications
You must be signed in to change notification settings - Fork 0
/
car.py
159 lines (129 loc) · 6.77 KB
/
car.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# Original version from https://github.com/NeuralNine/ai-car-simulation
# The following license applies to all changes made:
# This file is part of simulation for cars learning how to steer using a Genetic Algorithm
# Everything is openly developed on GitHub: https://github.com/Tix3Dev/genetic_algorithm_self_driving_car
#
# Copyright (C) 2023 Yves Vollmeier <https://github.com/Tix3Dev>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import pygame
import math
import random
from dna import DNA
CAR_SIZE_X = 60
CAR_SIZE_Y = 60
class Car:
def __init__(self, screen, game_map, border_color, dna=None):
self.screen = screen
self.sprite = pygame.image.load('assets/car.png').convert()
self.sprite = pygame.transform.scale(self.sprite, (CAR_SIZE_X, CAR_SIZE_Y))
self.rotated_sprite = self.sprite
self.game_map = game_map
self.border_color = border_color
self.position = [830, 920]
self.angle = 0
# self.speed = 4 + random.random()
self.speed = 10
self.center = [self.position[0] + CAR_SIZE_X / 2, self.position[1] + CAR_SIZE_Y / 2]
if dna:
self.dna = dna
else:
self.dna = DNA()
self.fitness = 0
self.radars = []
self.radar_max_len = 300
# we now need to do this already, oltherwise first training data GA gets is all zero
# From -45 To 75 With Step-Size 45 Check Radar
for d in range(-45, 75, 45):
self.check_radar(d)
self.alive = True
self.dist_passed = 0 # TODO: I could add time_passed, mostly makes sense if car should learn to accelerate too
def draw(self):
self.screen.blit(self.rotated_sprite, self.position)
self.draw_radar()
def draw_radar(self):
for radar in self.radars:
position = radar[0]
pygame.draw.line(self.screen, (0, 255, 0), self.center, position, 1)
pygame.draw.circle(self.screen, (0, 255, 0), position, 5)
def check_collision(self):
self.alive = True
for point in self.corners:
# If Any Corner Touches Border Color -> Crash
# Assumes Rectangle
if self.game_map.get_at((int(point[0]), int(point[1]))) == self.border_color:
self.alive = False
break
def check_radar(self, degree):
length = 0
x = int(self.center[0] + math.cos(math.radians(360 - (self.angle + degree))) * length)
y = int(self.center[1] + math.sin(math.radians(360 - (self.angle + degree))) * length)
# TODO: this is fantastic, there is a max length to the radars, just like in the DNA
# While We Don't Hit self.border_color AND length < prec * 10 (e.g. 7 * 10) -> go further and further
while not self.game_map.get_at((x, y)) == self.border_color and length < self.radar_max_len:
length = length + 1
x = int(self.center[0] + math.cos(math.radians(360 - (self.angle + degree))) * length)
y = int(self.center[1] + math.sin(math.radians(360 - (self.angle + degree))) * length)
# Calculate Distance To Border And Append To Radars List
dist = int(math.sqrt(math.pow(x - self.center[0], 2) + math.pow(y - self.center[1], 2)))
self.radars.append([(x, y), dist])
def update(self):
# Get Rotated Sprite And Move Into The Right X-Direction
# Don't Let The Car Go Closer Than 20px To The Edge
self.rotated_sprite = self.rotate_center(self.sprite, self.angle)
self.position[0] += math.cos(math.radians(360 - self.angle)) * self.speed
self.position[0] = max(self.position[0], 20)
self.position[0] = min(self.position[0], self.screen.get_size()[0] - 120)
# Increase Distance and Time
*/
self.dist_passed += self.speed
# Same For Y-Position
self.position[1] += math.sin(math.radians(360 - self.angle)) * self.speed
self.position[1] = max(self.position[1], 20)
self.position[1] = min(self.position[1], self.screen.get_size()[0] - 120)
# Calculate New Center
self.center = [int(self.position[0]) + CAR_SIZE_X / 2, int(self.position[1]) + CAR_SIZE_Y / 2]
# Calculate Four Corners
# Length Is Half The Side
length = 0.5 * CAR_SIZE_X
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]
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]
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]
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]
self.corners = [left_top, right_top, left_bottom, right_bottom]
# Check Collisions And Clear Radars
self.check_collision()
self.radars.clear()
# From -45 To 75 With Step-Size 45 Check Radar
for d in range(-45, 75, 45):
self.check_radar(d)
def get_data(self):
# Get Distances To Border
radars = self.radars
return_values = [0, 0, 0]
for i, radar in enumerate(radars):
# radar will have actual length radar_max_len
# this will be divided into parts, so that they can be counted
# from 0 to n (inclusive)
return_values[i] = int(radar[1] / (self.radar_max_len / self.dna.precision))
return return_values
def is_alive(self):
return self.alive
def get_reward(self):
# NOTE: hard to check if goes in wrong direction...
return self.dist_passed / (CAR_SIZE_X / 2)
def rotate_center(self, image, angle):
rectangle = image.get_rect()
rotated_image = pygame.transform.rotate(image, angle)
rotated_rectangle = rectangle.copy()
rotated_rectangle.center = rotated_image.get_rect().center
rotated_image = rotated_image.subsurface(rotated_rectangle).copy()
return rotated_image