pythonpygameneat

Built a game using Pygame, using NEAT to teach an AI, but pygame all the sudden does not draw anything


I am in the process of building an AI to play a game I created using pygame, I built the simple game and it worked as expected. I started implementing NEAT and finally got my code to not give any errors but when I run it the pygame window is only filled with black and i'm not sure why. I am new to NEAT and pygame for that matter so the code is probably ugly to most standards, but thanks for any help.

            import pygame
            import neat
            import math
            import os

            BLACK = (0, 0, 0)
            WHITE = (255, 255, 255)
            BLUE = (0, 0, 255)
            GREEN = (0, 255, 0)
            RED = (255, 0, 0)
            PURPLE = (255, 0, 255)
            YELLOW = (255, 255, 0)

            screen = pygame.display.set_mode([1500, 1000])
            pygame.display.set_caption('Maze Runner')


            class Wall(pygame.sprite.Sprite):

              def __init__(self, x, y, width, height, color):

                super().__init__()

                self.image = pygame.Surface([width, height])
                self.image.fill(color)

                self.rect = self.image.get_rect()
                self.rect.y = y 
                self.rect.x = x

              def get_x(self):
                return self.rect.x

            class Enemy(pygame.sprite.Sprite):

              def __init__(self, x, y, width, height, color, speed_x, speed_y):

                super().__init__()
                self.image = pygame.Surface([width, height])
                self.image.fill(color)

                self.rect = self.image.get_rect()
                self.rect.y = y 
                self.rect.x = x 

                self.speed_x = speed_x
                self.speed_y = speed_y

              def get_x(self):
                return self.rect.x

              def get_y(self):
                return self.rect.y

              def enemymove(self, walls, dt):
                self.rect.x += self.speed_x * dt

                block_hit_list = pygame.sprite.spritecollide(self, walls, False)
                for block in block_hit_list:
                  if self.speed_x > 0:
                    self.rect.right = block.rect.left
                    self.speed_x = -self.speed_x
                  else:
                    self.rect.left = block.rect.right
                    self.speed_x = -self.speed_x

                self.rect.y += self.speed_y * dt

                block_hit_list = pygame.sprite.spritecollide(self, walls, False)
                for block in block_hit_list:

                  if self.speed_y > 0:
                    self.rect.bottom = block.rect.top
                    self.speed_y = -self.speed_y
                  else:
                    self.rect.top = block.rect.bottom
                    self.speed_y = -self.speed_y

            class Player(pygame.sprite.Sprite):

              def __init__(self, x, y):

                super().__init__()

                self.image = pygame.Surface([15, 15])
                self.image.fill(WHITE)

                self.rect = self.image.get_rect()
                self.rect.y = y
                self.rect.x = x

                self.deaths = 0

                self.change_x = 10
                self.change_y = 10

              def num_deaths(self):
                return self.deaths

              def get_x(self):
                return self.rect.x

              def get_y(self):
                return self.rect.y

              def get_closest_wall(self, walls):
                wall_list = walls
                min = -1000
                distance = 0
                for wall in wall_list:
                  distace = self.rect.x - wall.get_x()
                  if distance < min:
                    min = distance
                return abs(distance)

              def get_closest_enemy(self, enemies):
                enemy_list = enemies
                min = -1000
                distance = 0
                for enemy in enemy_list:
                  distace = self.rect.x - enemy.get_x()
                  if distance < min:
                    min = distance
                return abs(distance)

              def collide_with_enemy(self, enemies):
                return pygame.sprite.spritecollide(self, enemies, False)

              def move_right(self, walls, enemies, starting_x, starting_y):

                self.rect.x += self.change_x

                block_hit_list = pygame.sprite.spritecollide(self, walls, False)

                for block in block_hit_list:
                  if self.change_x > 0:
                    self.rect.right = block.rect.left

                if self.collide_with_enemy(enemies):
                  self.rect.x = starting_x
                  self.rect.y = starting_y

              def move_left(self, walls, enemies, starting_x, starting_y):

                self.rect.x -= self.change_x

                block_hit_list = pygame.sprite.spritecollide(self, walls, False)

                for block in block_hit_list:
                  if self.change_x < 0:
                    self.rect.left = block.rect.right

                if self.collide_with_enemy(enemies):
                  self.rect.x = starting_x
                  self.rect.y = starting_y

              def move_up(self, walls, enemies, starting_x, starting_y):

                self.rect.y -= self.change_y

                block_hit_list = pygame.sprite.spritecollide(self, walls, False)

                for block in block_hit_list:
                  if self.change_y > 0:
                    self.rect.top = block.rect.bottom

                if self.collide_with_enemy(enemies):
                  self.rect.x = starting_x
                  self.rect.y = starting_y

              def move_down(self, walls, enemies, starting_x, starting_y):

                self.rect.y += self.change_y

                block_hit_list = pygame.sprite.spritecollide(self, walls, False)

                for block in block_hit_list:
                  if self.change_y < 0:
                    self.rect.bottom = block.rect.top

                if self.collide_with_enemy(enemies):
                  self.rect.x = starting_x
                  self.rect.y = starting_y

            class Level(object):

              wall_list = None
              enemy_list = None
              starting_x = 0
              starting_y = 0

              def __init__(self):
                self.wall_list = pygame.sprite.Group()
                self.enemy_list = pygame.sprite.Group()

            class Level1(Level):
              def __init__(self):
                super().__init__()

                starting_x = 210 
                starting_y = 775 

                walls = [[0, 0, 20, 1000, WHITE],
                        [1480, 0, 20, 1000, WHITE],
                        [20, 0, 1500, 20, WHITE],
                        [0, 980, 1500, 20, WHITE],
                        [100, 100, 3, 700, WHITE],
                        [100, 800, 1200, 3, YELLOW],
                        [1300, 100, 3, 700, YELLOW],
                        [100, 100, 250, 3, YELLOW],
                        [350, 100, 3, 550, YELLOW],
                        [350, 650, 90, 3, YELLOW],
                        [450, 100, 3, 550, YELLOW],
                        [450, 100, 850, 3, YELLOW]
                        ]
                enemies = [[500, 410, 20, 375, BLUE, 300, 0],
                          ]

                for item in walls:
                  wall = Wall(item[0], item[1], item[2], item[3], item[4])
                  self.wall_list.add(wall)

                for item in enemies:
                  enemy = Enemy(item[0], item[1], item[2], item[3], item[4], item[5],
                    item[6])
                self.enemy_list.add(enemy)

            class Level2(Level):
              def __init__(self):
                super().__init__()

                starting_x = 225 
                starting_y = 775 

                walls = [[0, 0, 20, 1000, WHITE],
                        [1480, 0, 20, 1000, WHITE],
                        [20, 0, 1500, 20, WHITE],
                        [0, 980, 1500, 20, WHITE],
                        [150, 150, 3, 250, YELLOW],
                        [150, 150, 1150, 3, YELLOW],
                        [1300, 150, 3, 750, YELLOW],
                        [150, 900, 1150, 3, YELLOW],
                        [150, 650, 3, 250, YELLOW],
                        [150, 650, 900, 3, YELLOW],
                        [1050, 400, 3, 250, YELLOW],
                        [150, 400, 900, 3, YELLOW]
                        ]
                enemies = [[350, 770, 20, 20, BLUE, 0, -400],
                          [500, 770, 20, 20, BLUE, 0, 400],
                          [650, 770, 20, 20, BLUE, 0, -400],
                          [800, 770, 20, 20, BLUE, 0, 400],
                          [950, 770, 20, 20, BLUE, 0, -400],
                          [350, 250, 20, 20, BLUE, 0, 400],
                          [500, 250, 20, 20, BLUE, 0, -400],
                          [650, 250, 20, 20, BLUE, 0, 400],
                          [800, 250, 20, 20, BLUE, 0, -400],
                          [950, 250, 20, 20, BLUE, 0, 400],
                          ]
                for item in walls:
                  wall = Wall(item[0], item[1], item[2], item[3], item[4])
                  self.wall_list.add(wall)

                for item in enemies:
                  enemy = Enemy(item[0], item[1], item[2], item[3], item[4], item[5],
                    item[6])
                  self.enemy_list.add(enemy)

            class Level3(Level):
              def __init__(self):
                super().__init__()

                starting_x = 200 
                starting_y = 895 

                walls = [[0, 0, 20, 1000, WHITE],
                        [1480, 0, 20, 1000, WHITE],
                        [20, 0, 1500, 20, WHITE],
                        [0, 980, 1500, 20, WHITE],
                        [150, 950, 1200, 6, YELLOW],
                        [1350, 200, 6, 750, YELLOW],
                        [1350, 200, 100, 6, YELLOW],
                        [1450, 100, 6, 100, YELLOW],
                        [250, 100, 1200, 6, YELLOW],
                        [250, 100, 6, 750, YELLOW],
                        [150, 850, 100, 6, YELLOW],
                        [150, 850, 6, 100, YELLOW]
                        ]

                enemies = [[780, 220, 40, 118, BLUE, -1000, 0],
                          [780, 340, 40, 118, BLUE, 1000, 0],
                          [780, 460, 40, 118, BLUE, -1000, 0],
                          [780, 580, 40, 118, BLUE, 1000, 0],
                          [780, 700, 40, 118, BLUE, -1000, 0],
                          ]
                for item in walls:
                  wall = Wall(item[0], item[1], item[2], item[3], item[4])
                  self.wall_list.add(wall)
                for item in enemies:
                  enemy = Enemy(item[0], item[1], item[2], item[3], item[4], item[5],
                    item[6])
                  self.enemy_list.add(enemy)

            def main(genomes, config):

              pygame.init()

              levels = []

              level = Level1()
              levels.append(level)

              level = Level2()
              levels.append(level)

              level = Level3()
              levels.append(level)

              current_level_num = 0
              current_level = levels[current_level_num]

              movingsprites = pygame.sprite.Group()

              clock = pygame.time.Clock()

              done = False

              players = []
              ge = []
              nets = []

              for genome_id, g in genomes:
                g.fitness = 0
                net = neat.nn.FeedForwardNetwork.create(g, config)
                nets.append(net)
                players.append(Player(15,15))
                ge.append(g)
                
              for player in players:
                movingsprites.add(player)

              while not done:
                dt = clock.tick(60) / 1000

                for enemy in current_level.enemy_list:
                  enemy.enemymove(current_level.wall_list, dt)

              for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    done = True
                    pygame.QUIT()
                    quit()
                    break

                for x, player in enumerate(players):

                  ge[x].fitness += .01
                  
                  output = nets[players.index(player)].activate((player.rect.x, player.rect.y, player.get_closest_enemy(current_level.enemy_list), player.get_closest_wall(current_level.wall_list)))
                  
                  if output[0] > .5:
                    player.move_right(current_level.wall_list, current_level.enemy_list, current_level.starting_x, current_level.starting_y)
                  elif output[1] > .5:
                    player.move_left(current_level.wall_list, current_level.enemy_list, current_level.starting_x, current_level.starting_y)
                  elif output[2] > .5:
                    player.move_down(current_level.wall_list, current_level.enemy_list, current_level.starting_x, current_level.starting_y)
                  elif output[3] > .5:
                    player.move_up(current_level.wall_list, current_level.enemy_list, current_level.starting_x, current_level.starting_y)

                for player in players:
                  movingsprites.add(player)
                  if player.collide_with_enemy(current_level.enemy_list):
                    ge[players.index(player)].fitness -= 1
                    player.num_deaths += 1
                  
                  if player.num_deaths() > 5:
                    ge[players.index(player)].fitness -= 5
                    nets.pop(players.index(player))
                    ge.pop(players.index(player))
                    players.pop(players.index(player))

              screen.fill(BLACK)

              player_sprites.draw(screen)
              current_level.wall_list.draw(screen)
              current_level.enemy_list.draw(screen) 
              pygame.display.flip()

              clock.tick(60)

              pygame.quit()

            def run(config_file):

              config = neat.config.Config(neat.DefaultGenome, neat.DefaultReproduction,
                                          neat.DefaultSpeciesSet, neat.DefaultStagnation,
                                          config_file)

              p = neat.Population(config)

              p.add_reporter(neat.StdOutReporter(True))
              stats = neat.StatisticsReporter()
              p.add_reporter(stats)

              winner = p.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)

Solution

  • I can't get your code to work at all, I'm not familiar with the neat module to debug it off the top of my head. However, based on the problem description, it looks to me like indentation errors in the main() function. I expect the while not done: loop needs to include the drawing to the screen, and the handling of events, which it currently does not.

    It would behoove you to run your code through something like reindent or autopep8 which make your code easier to read, and therefore debug.

    Anyway, your current main loop is:

    while not done:
        dt = clock.tick(60) / 1000
    
        for enemy in current_level.enemy_list:
            enemy.enemymove(current_level.wall_list, dt)
    

    Whereas it probably was intended to be something like the following. If the indentation had been more standard, this would have been fairly obvious. There are good reasons why the vast majority of professional Software Engineers use an indentation of strictly 4 spaces. It's not about the code being "ugly" or "pretty", it's about maintaining efficiency and correctness.

    while not done:
        dt = clock.tick(60) / 1000
    
        for enemy in current_level.enemy_list:
            enemy.enemymove(current_level.wall_list, dt)
    
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
                pygame.QUIT()
                quit()
                break
    
            for x, player in enumerate(players):
    
                ge[x].fitness += .01
    
                output = nets[players.index(player)].activate((player.rect.x, player.rect.y, player.get_closest_enemy(current_level.enemy_list), player.get_closest_wall(current_level.wall_list)))
    
                if output[0] > .5:
                    player.move_right(current_level.wall_list, current_level.enemy_list, current_level.starting_x, current_level.starting_y)
                elif output[1] > .5:
                    player.move_left(current_level.wall_list, current_level.enemy_list, current_level.starting_x, current_level.starting_y)
                elif output[2] > .5:
                    player.move_down(current_level.wall_list, current_level.enemy_list, current_level.starting_x, current_level.starting_y)
                elif output[3] > .5:
                    player.move_up(current_level.wall_list, current_level.enemy_list, current_level.starting_x, current_level.starting_y)
    
            for player in players:
                movingsprites.add(player)
                if player.collide_with_enemy(current_level.enemy_list):
                    ge[players.index(player)].fitness -= 1
                    player.num_deaths += 1
    
                if player.num_deaths() > 5:
                    ge[players.index(player)].fitness -= 5
                    nets.pop(players.index(player))
                    ge.pop(players.index(player))
                    players.pop(players.index(player))
    
        screen.fill(BLACK)
    
        player_sprites.draw(screen)
        current_level.wall_list.draw(screen)
        current_level.enemy_list.draw(screen)
        pygame.display.flip()
    
        clock.tick(60)