From 65808e4c626b679602b7305d0f78b6622520aa46 Mon Sep 17 00:00:00 2001 From: alexandre Date: Tue, 22 Oct 2019 11:13:31 +0200 Subject: [PATCH] code refactor --- car.py | 138 +++++++++++++++++++++++++++++++++++++++++++++++++ car20.png | Bin 0 -> 5378 bytes car25.png | Bin 5996 -> 0 bytes main.py | 152 ++---------------------------------------------------- params.py | 9 +++- 5 files changed, 148 insertions(+), 151 deletions(-) create mode 100644 car.py create mode 100644 car20.png delete mode 100644 car25.png diff --git a/car.py b/car.py new file mode 100644 index 0000000..3272945 --- /dev/null +++ b/car.py @@ -0,0 +1,138 @@ +import numpy as np +import pygame + +from brain import Neural_Network +from params import GY, CAR_SIZE, VISION_LENGTH, VISION_SPAN, THROTTLE_POWER, screen +from trigo import angle_to_vector, get_line_feats, segments_intersection, distance + +IMG = pygame.image.load("car20.png")#.convert() + + +class Car(pygame.sprite.Sprite): + def __init__(self): + pygame.sprite.Sprite.__init__(self) + self.top_surface = pygame.Surface((CAR_SIZE, CAR_SIZE)) + self.original_image = IMG + # self.image = pygame.Surface((CAR_SIZE, CAR_SIZE)) + # self.image.fill((0,255,0)) + # self.original_image = self.image + + self.image = self.original_image + + self.rect = self.image.get_rect() + self.rect.center = (75, GY -50) + self.speed = 1 + self.heading = 0 + self.heading_change = 0 + self.vision_length = VISION_LENGTH # line liength + self.vision_span = VISION_SPAN # degrees + self.draw_sensors = True + + # lets add 3 sensors as a start + # 1 straight ahead + # 2 left 15° + # 3 right 15 ° + # we will give each of them a max lenght to + # and we will eventually detect any line crossing the sensor and + # retain the min value as a distance to collision input + self.center_sensor = None + self.left_sensor = None + self.right_sensor = None + self.sensors = [self.left_sensor, self.center_sensor, self.right_sensor] + self.probes = [self.vision_length] *3 + + self.brain = Neural_Network() + + self.update_sensors() + self.probe_brain() + self.run = True + + + def update_sensors(self): + center = self.rect.center + vc = angle_to_vector(self.heading) + self.center_sensor = [center, (int(self.vision_length * vc[0] + center[0]), int(-self.vision_length * vc[1] + center[1]))] + + vl = angle_to_vector(self.heading+self.vision_span) + self.left_sensor = [center, (int(self.vision_length * vl[0] + center[0]), int(-self.vision_length * vl[1] + center[1]))] + + vr = angle_to_vector(self.heading-self.vision_span) + self.right_sensor = [center, (int(self.vision_length * vr[0] + center[0]), int(-self.vision_length * vr[1] + center[1]))] + + + def update_position(self): + vec = angle_to_vector(self.heading) + old_center = self.rect.center + self.rect.center = (self.speed * vec[0] / 2 + old_center[0], -self.speed * vec[1] / 2 + old_center[1]) + self.update_sensors() + + + + def probe_lines_proximity(self, lines): + # print(self.center_sensor, lines[0]) + self.probes = [self.vision_length*2] *3 + for idx,sensor in enumerate([self.left_sensor, self.center_sensor, self.right_sensor]) : + for line in lines : + ip = segments_intersection(sensor, line) + # print(ip) + if ip : + pygame.draw.circle(screen, (125,125,255), ip, 4, 2) + dist = int(distance(ip,self.rect.center)) + self.probes[idx] = min(dist, self.probes[idx]) + if dist < 1.2 * self.speed or self.speed < 0.01 : + self.run = False + print(f'Car {id(self)} crashed') + + # else : + # self.probes[idx] = self.vision_length * 2 + # print(self.probes) + + + def probe_brain(self): + res = self.brain.predict(np.array(self.probes)) + self.heading_change = res[0] * 10 + self.throttle = res[1] * 10 + + + def update(self): + # rotate + old_center = self.rect.center + self.image = pygame.transform.rotate(self.original_image, self.heading) + self.rect = self.image.get_rect() + self.rect.center = old_center + self.update_position() + if self.speed < 0.01 : + self.run = False + print(f'Car {id(self)} crashed') + print( + 'id', id(self), + 'Speed', self.speed, + 'heading', self.heading, + 'throttle', self.throttle, + 'heading change', self.heading_change, + ) + + if self.speed : + self.heading += self.heading_change * 10 / self.speed + self.heading = self.heading % 360 + + self.speed += self.throttle #THROTTLE_POWER + # if self.throttle : + # self.speed += self.throttle #THROTTLE_POWER + # else : + # self.speed -= self.throttle #THROTTLE_POWER + + self.speed = max(0, self.speed) + self.speed = min(self.speed, 7) + + super().update() + + + def show_features(self): + if self.draw_sensors: + pygame.draw.line(screen, (255,0,0), self.center_sensor[0], self.center_sensor[1]) + pygame.draw.line(screen, (0,255,0), self.left_sensor[0], self.left_sensor[1]) + pygame.draw.line(screen, (0,0,255), self.right_sensor[0], self.right_sensor[1]) + pygame.draw.circle(screen, (125,255,125), self.rect.center, 4, 2) + + diff --git a/car20.png b/car20.png new file mode 100644 index 0000000000000000000000000000000000000000..390690c859aadcaf981300aeb2fd4042b87ce4d0 GIT binary patch literal 5378 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 literal 0 HcmV?d00001 diff --git a/car25.png b/car25.png deleted file mode 100644 index cc3bae4078bff3d2b069ef9e1e69f1f1d987eee5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5996 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 arv3rKl1y(C_8Qs%0000