Skip to content

Commit fc0e1ab

Browse files
authored
Flappy_bird.py
1 parent c0b68a7 commit fc0e1ab

File tree

1 file changed

+282
-0
lines changed

1 file changed

+282
-0
lines changed
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
#AI-driven AI Flappy Bird
2+
import pygame
3+
import neat
4+
import os
5+
import random
6+
7+
# Game window size
8+
WINDOW_WIDTH = 500
9+
WINDOW_HEIGHT = 800
10+
11+
# Bird images
12+
BIRD_IMAGES = [
13+
pygame.transform.scale2x(pygame.image.load(os.path.join("images", "bird1.png"))),
14+
pygame.transform.scale2x(pygame.image.load(os.path.join("images", "bird2.png"))),
15+
pygame.transform.scale2x(pygame.image.load(os.path.join("images", "bird3.png")))
16+
]
17+
18+
# Pipe image
19+
PIPE_IMAGE = pygame.transform.scale2x(pygame.image.load(os.path.join("images", "pipe.png")))
20+
21+
# Base image
22+
BASE_IMAGE = pygame.transform.scale2x(pygame.image.load(os.path.join("images", "base.png")))
23+
24+
# Background image
25+
BG_IMAGE = pygame.transform.scale2x(pygame.image.load(os.path.join("images", "bg.png")))
26+
27+
28+
class Bird:
29+
IMAGES = BIRD_IMAGES
30+
MAX_ROTATION = 25
31+
ROTATION_VELOCITY = 20
32+
ANIMATION_TIME = 5
33+
34+
def __init__(self, x, y):
35+
self.x = x
36+
self.y = y
37+
self.tilt = 0
38+
self.tick_count = 0
39+
self.velocity = 0
40+
self.height = self.y
41+
self.image_count = 0
42+
self.image = self.IMAGES[0]
43+
44+
def jump(self):
45+
self.velocity = -10.5
46+
self.tick_count = 0
47+
self.height = self.y
48+
49+
def move(self):
50+
self.tick_count += 1
51+
displacement = self.velocity * self.tick_count + 1.5 * self.tick_count ** 2
52+
53+
if displacement >= 16:
54+
displacement = 16
55+
56+
if displacement < 0:
57+
displacement -= 2
58+
59+
self.y = self.y + displacement
60+
61+
if displacement < 0 or self.y < self.height + 50:
62+
if self.tilt < self.MAX_ROTATION:
63+
self.tilt = self.MAX_ROTATION
64+
else:
65+
if self.tilt > -90:
66+
self.tilt -= self.ROTATION_VELOCITY
67+
68+
def draw(self, win):
69+
self.image_count += 1
70+
71+
if self.image_count < self.ANIMATION_TIME:
72+
self.image = self.IMAGES[0]
73+
elif self.image_count < self.ANIMATION_TIME * 2:
74+
self.image = self.IMAGES[1]
75+
elif self.image_count < self.ANIMATION_TIME * 3:
76+
self.image = self.IMAGES[2]
77+
elif self.image_count < self.ANIMATION_TIME * 4:
78+
self.image = self.IMAGES[1]
79+
elif self.image_count == self.ANIMATION_TIME * 4 + 1:
80+
self.image = self.IMAGES[0]
81+
self.image_count = 0
82+
83+
if self.tilt <= -80:
84+
self.image = self.IMAGES[1]
85+
self.image_count = self.ANIMATION_TIME * 2
86+
87+
rotated_image = pygame.transform.rotate(self.image, self.tilt)
88+
new_rect = rotated_image.get_rect(center=self.image.get_rect(topleft=(self.x, self.y)).center)
89+
win.blit(rotated_image, new_rect.topleft)
90+
91+
def get_mask(self):
92+
return pygame.mask.from_surface(self.image)
93+
94+
95+
class Pipe:
96+
GAP = 200
97+
VEL = 5
98+
99+
def __init__(self, x):
100+
self.x = x
101+
self.height = 0
102+
103+
self.top = 0
104+
self.bottom = 0
105+
self.PIPE_TOP = pygame.transform.flip(PIPE_IMAGE, False, True)
106+
self.PIPE_BOTTOM = PIPE_IMAGE
107+
108+
self.passed = False
109+
self.set_height()
110+
111+
def set_height(self):
112+
self.height = random.randrange(50, 450)
113+
self.top = self.height - self.PIPE_TOP.get_height()
114+
self.bottom = self.height + self.GAP
115+
116+
def move(self):
117+
self.x -= self.VEL
118+
119+
def draw(self, win):
120+
win.blit(self.PIPE_TOP, (self.x, self.top))
121+
win.blit(self.PIPE_BOTTOM, (self.x, self.bottom))
122+
123+
def collide(self, bird):
124+
bird_mask = bird.get_mask()
125+
top_mask = pygame.mask.from_surface(self.PIPE_TOP)
126+
bottom_mask = pygame.mask.from_surface(self.PIPE_BOTTOM)
127+
128+
top_offset = (self.x - bird.x, self.top - round(bird.y))
129+
bottom_offset = (self.x - bird.x, self.bottom - round(bird.y))
130+
131+
t_point = bird_mask.overlap(top_mask, top_offset)
132+
b_point = bird_mask.overlap(bottom_mask, bottom_offset)
133+
134+
if t_point or b_point:
135+
return True
136+
137+
return False
138+
139+
140+
class Base:
141+
VEL = 5
142+
WIDTH = BASE_IMAGE.get_width()
143+
IMG = BASE_IMAGE
144+
145+
def __init__(self, y):
146+
self.y = y
147+
self.x1 = 0
148+
self.x2 = self.WIDTH
149+
150+
def move(self):
151+
self.x1 -= self.VEL
152+
self.x2 -= self.VEL
153+
154+
if self.x1 + self.WIDTH < 0:
155+
self.x1 = self.x2 + self.WIDTH
156+
157+
if self.x2 + self.WIDTH < 0:
158+
self.x2 = self.x1 + self.WIDTH
159+
160+
def draw(self, win):
161+
win.blit(self.IMG, (self.x1, self.y))
162+
win.blit(self.IMG, (self.x2, self.y))
163+
164+
165+
def draw_window(win, birds, pipes, base, score):
166+
win.blit(BG_IMAGE, (0, 0))
167+
for pipe in pipes:
168+
pipe.draw(win)
169+
170+
text = STAT_FONT.render("Score: " + str(score), 1, (255, 255, 255))
171+
win.blit(text, (WINDOW_WIDTH - 10 - text.get_width(), 10))
172+
173+
base.draw(win)
174+
for bird in birds:
175+
bird.draw(win)
176+
pygame.display.update()
177+
178+
179+
def main(genomes, config):
180+
nets = []
181+
ge = []
182+
birds = []
183+
184+
for _, g in genomes:
185+
net = neat.nn.FeedForwardNetwork.create(g, config)
186+
nets.append(net)
187+
birds.append(Bird(230, 350))
188+
g.fitness = 0
189+
ge.append(g)
190+
191+
base = Base(730)
192+
pipes = [Pipe(700)]
193+
win = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
194+
clock = pygame.time.Clock()
195+
score = 0
196+
197+
run = True
198+
while run:
199+
clock.tick(30)
200+
for event in pygame.event.get():
201+
if event.type == pygame.QUIT:
202+
run = False
203+
pygame.quit()
204+
quit()
205+
206+
pipe_ind = 0
207+
if len(birds) > 0:
208+
if len(pipes) > 1 and birds[0].x > pipes[0].x + pipes[0].PIPE_TOP.get_width():
209+
pipe_ind = 1
210+
else:
211+
run = False
212+
break
213+
214+
for x, bird in enumerate(birds):
215+
bird.move()
216+
ge[x].fitness += 0.1
217+
218+
output = nets[x].activate((bird.y, abs(bird.y - pipes[pipe_ind].height), abs(bird.y - pipes[pipe_ind].bottom)))
219+
220+
if output[0] > 0.5:
221+
bird.jump()
222+
223+
add_pipe = False
224+
remove_pipes = []
225+
for pipe in pipes:
226+
for x, bird in enumerate(birds):
227+
if pipe.collide(bird):
228+
ge[x].fitness -= 1
229+
birds.pop(x)
230+
nets.pop(x)
231+
ge.pop(x)
232+
233+
if not pipe.passed and pipe.x < bird.x:
234+
pipe.passed = True
235+
add_pipe = True
236+
237+
if pipe.x + pipe.PIPE_TOP.get_width() < 0:
238+
remove_pipes.append(pipe)
239+
240+
pipe.move()
241+
242+
if add_pipe:
243+
score += 1
244+
for g in ge:
245+
g.fitness += 5
246+
pipes.append(Pipe(700))
247+
248+
for pipe in remove_pipes:
249+
pipes.remove(pipe)
250+
251+
for x, bird in enumerate(birds):
252+
if bird.y + bird.image.get_height() >= 730 or bird.y < 0:
253+
birds.pop(x)
254+
nets.pop(x)
255+
ge.pop(x)
256+
257+
base.move()
258+
draw_window(win, birds, pipes, base, score)
259+
260+
261+
def run_neat():
262+
local_dir = os.path.dirname(__file__)
263+
config_path = os.path.join(local_dir, "config-feedforward.txt")
264+
config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
265+
neat.DefaultSpeciesSet, neat.DefaultStagnation,
266+
config_path)
267+
268+
population = neat.Population(config)
269+
population.add_reporter(neat.StdOutReporter(True))
270+
stats = neat.StatisticsReporter()
271+
population.add_reporter(stats)
272+
273+
winner = population.run(main, 50)
274+
275+
print("\nBest genome:\n{!s}".format(winner))
276+
277+
278+
if __name__ == "__main__":
279+
pygame.init()
280+
STAT_FONT = pygame.font.SysFont("comicsans", 50)
281+
run_neat()
282+

0 commit comments

Comments
 (0)