Итак, в сегодняшнем блоге мы реализуем игру Flappy Bird, в которую будет играть ИИ. Мы добьемся этого, используя NEAT, что означает NeuroEvolution of Augmenting Topologies. Одна из главных фантазий каждого инженера по машинному обучению — сделать игру, которая может научиться играть на себе. В сегодняшнем блоге мы увидим, как мы можем это сделать. Так что, не теряя времени…
Полную статью с исходным кодом читайте здесь —https://machinelearningprojects.net/ai-learns-to-play-flappy-bird-game/
Давай сделаем это…
В основном нам понадобятся следующие 2 библиотеки для реализации этого:
- пигейм
- аккуратный питон
Код для игры Flappy Bird…
import pygame import neat import os import random pygame.font.init() # some initialization to use font in pygame WIN_HEIGHT = 800 WIN_WIDTH = 500 DRAW_LINES = True GEN = 0 # declaring generation variable pygame.display.set_caption("Flappy Bird") # importing the images of birds, pipes, background and base BIRD_IMGS = [pygame.transform.scale2x(pygame.image.load('imgs\\bird1.png')), pygame.transform.scale2x(pygame.image.load('imgs\\bird2.png')), pygame.transform.scale2x(pygame.image.load('imgs\\bird3.png'))] BASE_IMG = pygame.transform.scale2x(pygame.image.load('imgs\\base.png')) BG_IMG = pygame.transform.scale2x(pygame.image.load('imgs\\bg1.png')) PIPE_IMG = pygame.transform.scale2x(pygame.image.load('imgs\\pipe.png')) # declaring font STAT_FONT = pygame.font.SysFont('comicsans', 50) # CLASS BIRD class Bird: ROTATION_VEL = 20 MAX_ROTATION = 25 ANIMATION_TIME = 5 IMGS = BIRD_IMGS def __init__(self, x, y): # constructor for class bird self.x = x self.y = y self.height = self.y self.frame_count = 0 self.tilt = 0 self.vel = 0 self.img_number = 0 # image in which the bird's wings are upwards self.img = self.IMGS[0] # image in which the bird's wings are upwards def jump(self): self.vel = -10.5 # negative velocity refers to jump upwards self.frame_count = 0 # reset the frames self.height = self.y # reset the height def move(self): self.frame_count += 1 # update the frames when the bird moves # d > 0 means downwards and d<0 means upwards and same for velocity also # and also bird is just moving in y direction and not in x direction d = self.vel * self.frame_count + 1.5 * self.frame_count ** 2 # frame count also works as time if d >= 16: # if the bird is falling and it falls more than 16 pixels then stop falling and face straight moving d = 16 # if d < 0: # just for tuning so that upward movement is seen clear # d -= 2 self.y = self.y + d if d < 0 or self.y < self.height + 50: # (d<0) this is the case when when bird is going upward if self.tilt < self.MAX_ROTATION: # so making a tilt angle of max rotation self.tilt = self.MAX_ROTATION else: if self.tilt > -90: # if the tilt is greater than -90 then keep on reducing it till it reaches -90 to show self.tilt -= self.ROTATION_VEL # the arc like falling def draw(self, win): self.img_number += 1 # below work is done to show the flapping of the bird # animation time is that for how much time bird should be in one image state if self.img_number < self.ANIMATION_TIME: self.img = self.IMGS[0] elif self.img_number < self.ANIMATION_TIME * 2: self.img = self.IMGS[1] elif self.img_number < self.ANIMATION_TIME * 3: self.img = self.IMGS[2] elif self.img_number < self.ANIMATION_TIME * 4: self.img = self.IMGS[1] elif self.img_number < self.ANIMATION_TIME * 4 + 1: self.img = self.IMGS[0] self.img_number = 0 if self.tilt < -80: # when the bird is nose diving it should not flap its wings self.img = self.IMGS[1] self.img_number = self.ANIMATION_TIME * 2 # reset the image number so that next image should be IMG[2] rotated_image = pygame.transform.rotate(self.img, self.tilt) # just rotating the image around its center new_rect = rotated_image.get_rect(center=self.img.get_rect(topleft=(self.x, self.y)).center) win.blit(rotated_image, new_rect) def get_mask(self): # getting the mask of the bird means the contour of bird to check its collision with any pipe return pygame.mask.from_surface(self.img) class Pipe: GAP = 200 VEL = 5 def __init__(self, x): self.x = x self.height = 0 # for random purpose self.top = 0 # y coordinates of top pipe self.bottom = 0 # y coordinates of bottom pipe self.TOP_PIPE = pygame.transform.flip(PIPE_IMG, False, True) self.BOTTOM_PIPE = PIPE_IMG self.passed = False self.set_height() def set_height(self): # randomly setting the heights of both pipes self.height = random.randrange(50, 450) self.bottom = self.height + self.GAP self.top = self.height - self.TOP_PIPE.get_height() def move(self): self.x -= self.VEL def draw(self, win): win.blit(self.TOP_PIPE, (self.x, self.top)) win.blit(self.BOTTOM_PIPE, (self.x, self.bottom)) def collide(self, bird): # for checking collision of the bird with the pipes bird_mask = bird.get_mask() top_pipe_mask = pygame.mask.from_surface(self.TOP_PIPE) bottom_pipe_mask = pygame.mask.from_surface(self.BOTTOM_PIPE) top_offset = (self.x - bird.x, self.top - round(bird.y)) bottom_offset = (self.x - bird.x, self.bottom - round(bird.y)) top_overlap = bird_mask.overlap(top_pipe_mask, top_offset) bottom_overlap = bird_mask.overlap(bottom_pipe_mask, bottom_offset) if top_overlap or bottom_overlap: return True return False # BASE Class for showing base class Base: VEL = 5 IMG = BASE_IMG WIDTH = BASE_IMG.get_width() def __init__(self, y): # base will be shown moving by taking two images of the same base and putting it one after other self.y = y self.x1 = 0 # here x1 and x2 are the 2 images where x1 comes first and then x2 self.x2 = self.WIDTH def move(self): self.x1 -= self.VEL self.x2 -= self.VEL if self.x1 < -self.WIDTH: self.x1 = self.x2 + self.WIDTH if self.x2 < -(self.WIDTH): self.x2 = self.x1 + self.WIDTH def draw(self, win): win.blit(self.IMG, (self.x1, self.y)) win.blit(self.IMG, (self.x2, self.y)) # Our main drawing function def draw_window(win, birds, pipes, base, score, GEN, pipe_ind): global DRAW_LINES win.blit(BG_IMG, (0, 0)) # drawing the background for pipe in pipes: # drawing the pipe or pipes as there can be more than one pipes also in one window pipe.draw(win) base.draw(win) # drawing the base for bird in birds: # draw lines from bird to pipe if DRAW_LINES: try: pygame.draw.line(win, (255, 0, 0), (bird.x + bird.img.get_width() / 2, bird.y + bird.img.get_height() / 2), (pipes[pipe_ind].x + pipes[pipe_ind].TOP_PIPE.get_width() / 2, pipes[pipe_ind].height), 5) pygame.draw.line(win, (255, 0, 0), (bird.x + bird.img.get_width() / 2, bird.y + bird.img.get_height() / 2), ( pipes[pipe_ind].x + pipes[pipe_ind].BOTTOM_PIPE.get_width() / 2, pipes[pipe_ind].bottom), 5) except: pass bird.draw(win) text = STAT_FONT.render('Score : ' + str(score), 1, (255, 255, 255)) # printing the score on screen win.blit(text, (WIN_WIDTH - 10 - text.get_width(), 10)) text = STAT_FONT.render('Gen : ' + str(GEN), 1, (255, 255, 255)) # printing the generation on screen win.blit(text, (10, 10)) text = STAT_FONT.render('Alive : ' + str(len(birds)), 1, (255, 255, 255)) # printing the alive on screen win.blit(text, (10, 50)) pygame.display.update() def main(genomes, config): global GEN GEN += 1 birds = [] ge = [] neural_networks = [] # everytime new generation comes make new birds and neural networks for _, g in genomes: net = neat.nn.FeedForwardNetwork.create(g, config) neural_networks.append(net) birds.append(Bird(230, 350)) g.fitness = 0 ge.append(g) pipes = [Pipe(500)] # list of pipe objects base_object = Base(630) win = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT)) clock = pygame.time.Clock() run = True score = 0 # OUR main running loop while run: clock.tick(30) # fps for event in pygame.event.get(): if event.type == pygame.QUIT: # if the user click on the red cross button then quit the game run = False pygame.quit() quit() pipe_ind = 0 # this part is done to check in the case when 2 pipes appear on the screen that which is the pipe we are evaluating on if len(birds) > 0: if len(pipes) > 1 and birds[0].x > pipes[0].x + pipes[0].TOP_PIPE.get_width(): pipe_ind = 1 else: # if all the birds are dead then exit the loop break for x, bird in enumerate(birds): # traversing every bird bird.move() ge[x].fitness += 0.1 # incrementing little fitness to keep them moving # this is the output list which the nn is giving for all the birds whether to jump or not output = neural_networks[x].activate( (bird.y, abs(bird.y - pipes[pipe_ind].height), abs(bird.y - pipes[pipe_ind].bottom))) if output[0] > 0.5: bird.jump() for pipe in pipes: for x, bird in enumerate(birds): # checking if the bird has hit the ground or if the pipe and bird collide or if the bird has touched te top boundary if pipe.collide(bird) or (bird.y + bird.img.get_height() >= 630 or bird.y < 0): ge[x].fitness -= 1 # remove all things of that bird from that bird's position birds.pop(x) ge.pop(x) neural_networks.pop(x) if not pipe.passed and bird.x > pipe.x: # if the passed is not set to true and bird has passed the pipe then set it to true pipe.passed = True for g in ge: g.fitness += 5 # adding fitness to birds which passed score += 1 pipes.append(Pipe(500)) # add another pipe if pipe.x + pipe.TOP_PIPE.get_width() < 0: # if pipe passed the screen remove it pipes.remove(pipe) pipe.move() base_object.move() draw_window(win, birds, pipes, base_object, score, GEN, pipe_ind) def run(config_path): config = neat.config.Config(neat.DefaultGenome, neat.DefaultReproduction, neat.DefaultSpeciesSet, neat.DefaultStagnation, config_path) population = neat.Population(config) population.add_reporter(neat.StdOutReporter(True)) stats = neat.StatisticsReporter() population.add_reporter(stats) winner = population.run(main, 50) print('\nBest genome:\n{!s}'.format(winner)) if __name__ == '__main__': local_dir = os.path.dirname(__file__) config_path = os.path.join(local_dir, 'config-feedforward.txt') run(config_path) # This was the code for Flappy Bird Game
- Строка 1–4 — Импорт необходимых библиотек для игры Flappy Bird.
- Строка 8–11 — Некоторые константы.
- Строка 16–21 — Импорт изображений птиц, труб, фона и основы.
- Строка 28–95 — Создание класса Bird для Flappy Bird Game.
- Строка 98–137 — Создание класса Pipe для Flappy Bird Game.
- Строка 141–163 — Создание базы классов для игры Flappy Bird.
- Строка 167–200 — Наше главное окно, в котором будет отображаться наша игра.
- Строка 203–276 — Собираем все вместе и делаем игру.
- Строка 279–288 — эта функция запустит весь код. Эта функция также скажет нам, какое поколение было лучшим.
- Строка 291–294 — Просто вызов функции запуска выше и передача пути к файлу конфигурации вместе с ней.
Дайте мне знать, если у вас есть какие-либо вопросы относительно игры Flappy Bird, связавшись со мной по электронной почте или через LinkedIn. Вы также можете прокомментировать ниже любые вопросы.
Чтобы узнать больше о машинном обучении, глубоком обучении, компьютерном зрении, НЛП и проектах Flask, посетите мой блог — Проекты машинного обучения
Для дальнейшего объяснения кода и исходного кода посетите здесь — https://machinelearningprojects.net/ai-learns-to-play-flappy-bird-game/
Итак, это все для этого блога, ребята, спасибо за то, что прочитали его, и я надеюсь, что вы возьмете что-то с собой после прочтения этого и до следующего раза 👋…
Прочитайте мой предыдущий пост: ОБНАРУЖЕНИЕ ПОЖАРА И ДЫМА С ИСПОЛЬЗОВАНИЕМ CNN С KERAS