-
Notifications
You must be signed in to change notification settings - Fork 0
/
game.py
310 lines (207 loc) · 8.64 KB
/
game.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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
import pygame
import neat
import os
import time
import sys
from math import dist
pygame.init()
pygame.display.set_caption("Car-Obstacle Game")
generation = 0
class Car:
def __init__(self): # Initializes car coordinates, y-movement, and image
self.x = 285
self.y = 675
self.X_CHANGE = 15
self.IMAGE = pygame.image.load("Orange_Car.png")
def display(self,action,screen): # controls display and x-movement of car
if action == 'right':
self.x -= self.X_CHANGE
elif action == 'left':
self.x += self.X_CHANGE
if self.x <= 0:
self.x = 0
if self.x >= 555:
self.x = 555
screen.blit(self.IMAGE, (self.x,self.y))
class Obstacle:
def __init__(self, x, y): # Initializes obstacle coordinates, y-movement, and image
self.x = x
self.y = y
self.Y_CHANGE = 5
self.IMAGE = pygame.image.load("Barrier.png")
def display(self,screen): #controls display and vertical movement of each obstacle
self.y += self.Y_CHANGE
screen.blit(self.IMAGE, (self.x,self.y))
class Pattern:
def __init__(self, x, y, style): #Pattern objects contain a list of obstacles
self.obstacles = []
self.OP_1 = self.OP_2 = 0 #Coordinates of openings in pattern for car to go through
self.STYLE = style
self.configure_patterns(x,y) #Sets up each pattern with coordinates
def configure_patterns(self,x,y):
if self.STYLE == 'horizontal':
self.obstacles = [Obstacle(x,y), Obstacle(x+165,y), Obstacle(x+330,y)]
self.OP_1 = self.OP_2 = (x / 2 if x == 105 else 547.5)
elif self.STYLE == 'right diagonal':
self.obstacles = [Obstacle(x,y), Obstacle(x+160,y-135), Obstacle(x+330,y-270)]
self.OP_1 = self.OP_2 = 540
elif self.STYLE == 'left diagonal':
self.obstacles = [Obstacle(x,y), Obstacle(x-165,y-135), Obstacle(x-330,y-270)]
self.OP_1 = self.OP_2 = 60
elif self.STYLE == 'triangle':
self.obstacles = [Obstacle(x,y), Obstacle(x+160,y-160), Obstacle(x+360,y+100)]
self.OP_1 = self.OP_2 = self.obstacles[2].x - 15
elif self.STYLE == 'offset_square':
self.obstacles = [Obstacle(x,y), Obstacle(x+465,y-240), Obstacle(x+345,y), Obstacle(x+100,y-240)]
self.OP_1 = self.obstacles[2].x - 30
self.OP_2 = self.obstacles[2].x + 15
def get_peak_y(self): #Returns the y coordinate of the top of the pattern
if self.STYLE == 'horizontal':
return self.obstacles[0].y
elif self.STYLE in ['right diagonal','left diagonal']:
return self.obstacles[2].y
elif self.STYLE in ['triangle','offset_square']:
return self.obstacles[1].y
def display(self,screen): # Controls display of patterns
for obstacle in self.obstacles:
obstacle.display(screen)
def dist_closest_obstacle(pattern, car_x, car_y): #Distance from car to closest obstacle
distances = []
for i in range(len(pattern.obstacles)):
distances.append( dist([car_x,car_y], [pattern.obstacles[i].x+67.5, pattern.obstacles[i].y+58.5]) )
min_dist = min(distances)
i = distances.index(min_dist)
return (-1 * min_dist if car_x < pattern.obstacles[i].x else min_dist)
def collision(car, pattern):
collision_status = False
for obstacle in pattern.obstacles: # Iterates over list of obstacles in a pattern to check collision
# Bottom of Orange Posts
if (obstacle.x - 25) <= car.x <= (obstacle.x + 25) or (obstacle.x + 65) <= car.x <= (obstacle.x + 115):
if car.y == (obstacle.y + 110):
collision_status = True
# Side of Orange Posts
if car.y <= (obstacle.y + 110) and car.y >= (obstacle.y + 75):
if (car.x + 45) >= (obstacle.x + 30) and car.x <= (obstacle.x + 15):
collision_status = True
elif (car.x + 45) >= (obstacle.x + 120) and car.x <= (obstacle.x + 105):
collision_status = True
# Inner bottom part of Obstacle
if car.y <= (obstacle.y + 75) and car.y >= (obstacle.y + 40):
if (car.x + 45) >= (obstacle.x + 5) and car.x <= (obstacle.x + 130):
collision_status = True
# Outer Sides of Obstacle
if (car.y + 100) >= (obstacle.y) and car.y <= (obstacle.y + 81):
if (car.x + 45) >= (obstacle.x + 15) and (car.x + 45) <= (obstacle.x + 40):
collision_status = True
if car.x >= (obstacle.x + 95) and car.x <= (obstacle.x + 120):
collision_status = True
return collision_status
def show_text(string, screen): # Function to display text for statistics and winning
if string == "stats":
stat_font = pygame.font.Font("freesansbold.ttf",20)
stat_text = stat_font.render("Pattern " + str(index + 1) +
", Gen " + str(generation), True, (255,255,255))
screen.blit(stat_text, (10,10))
elif string == "win":
win_font = pygame.font.Font("freesansbold.ttf",35)
win_text = win_font.render("You Won!", True, (124,252,0))
screen.blit(win_text, (221,368))
pygame.display.update()
def eval_genomes(genomes, config): #main function, ran for each generation
SCREEN = pygame.display.set_mode([600,800])
cars = [] #contains lists, each of which contains car object, genome, and neural net corresponding to that car
#initialization of patterns
patterns = [Pattern(105,-135,"horizontal"), Pattern(30,-580,"horizontal"),
Pattern(450,-855,"left diagonal"), Pattern(15,-1400,"triangle"),
Pattern(30,-1900,"horizontal"), Pattern(450,-2310,"left diagonal"),
Pattern(105,-2765,"horizontal"),Pattern(0,-3060,"offset_square"),
Pattern(450,-3450,"left diagonal"), Pattern(30, -4200, "horizontal"),
Pattern(0,-4600,"offset_square"),Pattern(15,-5100,"right diagonal"),
Pattern(100,-5800,"triangle"),Pattern(0,-6300,"offset_square"),
Pattern(15, -6800, "right diagonal")]
#Index that signifies upcoming / current pattern, which will serve as input for the Neural Networks
global index
index = 0
#Appends lists of a car object and its corresponding genome and neural network to the cars list
for genome_id, genome in genomes:
genome.fitness = 0
net = neat.nn.FeedForwardNetwork.create(genome, config)
cars.append([Car(), genome, net])
running = True
while running and len(cars) >= 1:
#Conditions for quitting game if user quits in middle
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
sys.exit()
for car in cars:
#Condition to change the upcoming pattern / input source to Neural Network
if patterns[index].get_peak_y() > car[0].y + 100:
# If car passed all patterns successfully, end game and display Winning text
if index == len(patterns) - 1:
pass
SCREEN.fill([107,108,115])
show_text("win", SCREEN)
time.sleep(5)
sys.exit()
#Otherwise increase index for next pattern, increase car's fitness since it passed previous pattern successfully
else:
index += 1
car[1].fitness += 4
SCREEN.fill([107,108,115])
#Checks for collision of car and obstacle; if collision, remove the specific car list from cars
for car in cars:
if collision(car[0], patterns[index]):
car[1].fitness -= 2
cars.pop(cars.index(car))
#Pattern display
for pattern in patterns:
pattern.display(SCREEN)
#Car display and neural network inputs
for car in cars:
car_centerX = car[0].x + 23
car_centerY = car[0].y + 50
dist = dist_closest_obstacle(patterns[index],car_centerX, car_centerY)
"""
NEAT NN Inputs:
For upcoming pattern,
1. Y Distance from top of pattern to center of car
2. X Distance from first opening of pattern to center of car
3. X Distance from second opening of pattern to center of car (Mainly for offset square)
4. Distance from car to closest obstacle, positive or negative
depending on relative positioning (left or right)
"""
value = car[2].activate((patterns[index].get_peak_y() - car_centerY,
patterns[index].OP_1 - car_centerX,
patterns[index].OP_2 - car_centerX,
dist))
# Car takes action depending on value returned by Neural Network
if value[0] > 0.5:
car[0].display('right',SCREEN)
elif value[0] < -0.5:
car[0].display('left',SCREEN)
else:
car[0].display('still',SCREEN)
show_text('stats', SCREEN)
pygame.display.update()
global generation
generation += 1 # Increases generation number for display
def run(config_file):
# Load configuration file
config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
neat.DefaultSpeciesSet, neat.DefaultStagnation,
config_file)
# Create population
p = neat.Population(config)
# Add statistic reporters to show progress for each generation in terminal
p.add_reporter(neat.StdOutReporter(True))
stats = neat.StatisticsReporter()
p.add_reporter(stats)
p.add_reporter(neat.Checkpointer(5))
#Run the NEAT algorithm over 25 generations
p.run(eval_genomes, 25)
if __name__ == '__main__':
local_dir = os.path.dirname(__file__)
config_path = os.path.join(local_dir, 'config.txt')
run(config_path)