首页 > 解决方案 > Snake 一直在循环或重复循环路径移动。尝试使用 NEAT 制作一个在 Python 中玩蛇游戏的 AI

问题描述

我正在尝试使用 Python 中的 NEAT 库制作一个玩蛇游戏的 AI。我已经弄清楚了大部分事情。我为我的神经网络选择了 6 个输入 ->

  1. 前进方向是否清晰?(1-是/0-否)
  2. 左方向清楚吗?(1-是/0-否)
  3. 方向是否明确?(1-是/0-否)
  4. 食物在前方吗?(1-是/0-否)
  5. 左边有食物吗?(1-是/0-否)
  6. 那里的食物方向正确吗?(1-是/0-否)

输出只是一个由三个数字组成的数组 -> (LEFT, FORWARD, RIGHT),它告诉蛇是继续直行,还是向左或向右移动。我给出的健身积分如下->

即使我的代码运行了 150 代,它仍然循环如下 ->

GIF图片在这里

我还添加了蛇死后的最大移动次数。吃完食物后,它会获得一些额外的动作。但它似乎仍然是零效果。

我对神经网络完全陌生,这是我的第一个项目之一。到目前为止,我完全一无所知。请帮忙。下面是我的完整代码

import pygame
import os
import time
import random
import neat


BLOCK_SIZE = 300
GRID_SIZE = 20
SIZE = BLOCK_SIZE//GRID_SIZE

GREY = (150, 150, 150)
BLACK = (0,0,0)
RED = (250, 50, 50)
ORANGE = (250, 120, 10)

GREEN = (23, 250, 40)
LIGHT_GREEN = (23, 150, 40)

GRID = [(0,0), (0, BLOCK_SIZE), (0, 2*BLOCK_SIZE), (0, 3*BLOCK_SIZE), (0, 4*BLOCK_SIZE),
        (BLOCK_SIZE, 0), (BLOCK_SIZE, BLOCK_SIZE), (BLOCK_SIZE, 2*BLOCK_SIZE), (BLOCK_SIZE, 3*BLOCK_SIZE), (BLOCK_SIZE, 4*BLOCK_SIZE)]

CELLS = [(x//GRID_SIZE, x - (x//GRID_SIZE)*GRID_SIZE) for x in range(GRID_SIZE*GRID_SIZE)]

def drawGrid(world):
    pygame.draw.line(world, GREY, (BLOCK_SIZE, 0), (BLOCK_SIZE, 2*BLOCK_SIZE), 3)
    pygame.draw.line(world, GREY, (2*BLOCK_SIZE, 0), (2*BLOCK_SIZE, 2*BLOCK_SIZE), 3)
    pygame.draw.line(world, GREY, (3*BLOCK_SIZE, 0), (3*BLOCK_SIZE, 2*BLOCK_SIZE), 3)
    pygame.draw.line(world, GREY, (4*BLOCK_SIZE, 0), (4*BLOCK_SIZE, 2*BLOCK_SIZE), 3)
    pygame.draw.line(world, GREY, (0, BLOCK_SIZE), (5*BLOCK_SIZE, BLOCK_SIZE), 3)

    pygame.draw.rect(world, GREY, (0, 0, 5*BLOCK_SIZE, 2*BLOCK_SIZE), 3)
    pygame.display.update()


class Snake:
    MAX_MOVES = 250
    INIT_MOVES = 100
    ADD_MOVES = 50
    def __init__(self,x, y, GRID_POS, world):
        self.X = GRID_POS[0]
        self.Y = GRID_POS[1]
        self.body = [(x, y), (x, y+1), (x, y+2)]
        self.WALLS = []
        self.tail = (-1, -1)
        self.moves = self.INIT_MOVES
        self.defineWalls(world)
        self.draw(world)

    def defineWalls(self, world):
        WALLS1 = [(-1 + self.X, x + self.Y) for x in range(-1, 21)]
        WALLS2 = [(20 + self.X, x + self.Y) for x in range(-1, 21)]
        WALLS3 = [(x + self.X, -1 + self.Y) for x in range(-1, 21)]
        WALLS4 = [(x + self.X, 20 + self.Y) for x in range(-1, 21)]

        self.WALLS = WALLS1 + WALLS2 + WALLS3 + WALLS4

    def draw(self, world):
        for part in self.body:
            pygame.draw.rect(world, RED, (part[1]*SIZE + self.Y, part[0]* SIZE + self.X, SIZE, SIZE), 0)
            pygame.draw.rect(world, BLACK, (part[1]*SIZE + self.Y, part[0]* SIZE + self.X, SIZE, SIZE), 1)

        pygame.draw.rect(world, ORANGE, (self.body[0][1]*SIZE + self.Y, self.body[0][0]* SIZE + self.X, SIZE, SIZE), 0)
        pygame.draw.rect(world, BLACK, (self.body[0][1]*SIZE + self.Y, self.body[0][0]* SIZE + self.X, SIZE, SIZE), 1)

        pygame.draw.rect(world, BLACK, (self.tail[1]*SIZE + self.Y, self.tail[0]* SIZE + self.X, SIZE, SIZE), 0)

        pygame.display.update()

    def collideSelf(self, dirs):
        snake_direction = (self.body[1][0] - self.body[0][0], self.body[1][1] - self.body[0][1])
        

    
        Dir = 0
        if dirs == (1, 0, 0):
            Dir = -1
        elif dirs == (0, 1, 0):
            Dir = 0
        elif dirs == (0, 0, 1):
            Dir = 1

        direction = 0

        if snake_direction[0] == 0:
            if snake_direction[1] == 1:
                direction = 3
            elif snake_direction[1] == -1:
                direction = 1

        elif snake_direction[1] == 0:
            if snake_direction[0] == 1:
                direction = 0
            elif snake_direction[0] == -1:
                direction = 2

        dirs = [(-1, 0), (0, 1), (1, 0), (0, -1)]

        move = dirs[(direction + Dir) % 4]
        head = (self.body[0][0] + move[0], self.body[0][1] + move[1])

        if head in self.body:
            return True

        return False

    def collideWall(self, dirs):
        snake_direction = (self.body[1][0] - self.body[0][0], self.body[1][1] - self.body[0][1])
        

    
        Dir = 0
        if dirs == (1, 0, 0):
            Dir = -1
        elif dirs == (0, 1, 0):
            Dir = 0
        elif dirs == (0, 0, 1):
            Dir = 1

        direction = 0

        if snake_direction[0] == 0:
            if snake_direction[1] == 1:
                direction = 3
            elif snake_direction[1] == -1:
                direction = 1

        elif snake_direction[1] == 0:
            if snake_direction[0] == 1:
                direction = 0
            elif snake_direction[0] == -1:
                direction = 2

        dirs = [(-1, 0), (0, 1), (1, 0), (0, -1)]

        move = dirs[(direction + Dir) % 4]
        head = (self.body[0][0] + move[0] + self.X, self.body[0][1] + move[1] + self.Y)

        # print(head)

        if head in self.WALLS:
            return True
        return False


    def move(self, output):
        snake_direction = (self.body[1][0] - self.body[0][0], self.body[1][1] - self.body[0][1])

        Dir = next(x for x, op in enumerate(output) if op == 1)

        if Dir == 0:
            Dir = -1
        elif Dir == 1:
            Dir = 0
        elif Dir == 2:
            Dir = 1

        direction = 0

        if snake_direction[0] == 0:
            if snake_direction[1] == 1:
                direction = 3
            elif snake_direction[1] == -1:
                direction = 1

        elif snake_direction[1] == 0:
            if snake_direction[0] == 1:
                direction = 0
            elif snake_direction[0] == -1:
                direction = 2

        dirs = [(-1, 0), (0, 1), (1, 0), (0, -1)]

        move = dirs[(direction + Dir) % 4]

        self.tail = self.body[len(self.body) - 1]

        for i in range(len(self.body)-1, 0, -1):
            self.body[i] = self.body[i-1]

        self.body[0] = (self.body[0][0] + move[0], self.body[0][1] + move[1])

        self.moves -= 1

    def grow(self):
        tail = self.body[len(self.body) - 1]

        self.body.append(tail)

        self.moves += self.ADD_MOVES

        if self.moves > self.MAX_MOVES:
            self.moves = self.MAX_MOVES


class Food:
    def __init__(self, snake):
        self.snake = snake.body
        self.x = 0
        self.y = 0
        self.X = snake.X
        self.Y = snake.Y
        self.distance = 0
        self.prX = -1
        self.prY = -1
        self.setPos(snake)
        self.setDist(snake)

    def setPos(self, snake):
        self.snake = snake.body

        possibilities = list(set(CELLS) - set(self.snake))

        position = random.choice(possibilities)

        self.prX = self.x
        self.prY = self.y

        self.x = position[0] 
        self.y = position[1] 

    def draw(self, world):
        pygame.draw.rect(world, LIGHT_GREEN, (self.y*SIZE + self.Y, self.x* SIZE + self.X, SIZE, SIZE), 0)
        pygame.draw.rect(world, GREEN, (self.y*SIZE + self.Y, self.x* SIZE + self.X, SIZE, SIZE), 2)

        pygame.draw.rect(world, BLACK, (self.prY*SIZE + self.Y, self.prX* SIZE + self.X, SIZE, SIZE), 0)
        pygame.draw.rect(world, BLACK, (self.prY*SIZE + self.Y, self.prX* SIZE + self.X, SIZE, SIZE), 2)

        pygame.display.update()

    def setDist(self, snake):
        self.snake = snake.body

        distance = ((self.snake[0][0] - self.x)**2 + (self.snake[0][1] - self.y)**2)**0.5

        self.distance = distance

def isClear(snake, Dir):
    snake_direction = (snake.body[1][0] - snake.body[0][0], snake.body[1][1] - snake.body[0][1])
        
    direction = 0

    if snake_direction[0] == 0:
        if snake_direction[1] == 1:
            direction = 3
        elif snake_direction[1] == -1:
            direction = 1

    elif snake_direction[1] == 0:
        if snake_direction[0] == 1:
            direction = 0
        elif snake_direction[0] == -1:
            direction = 2

    dirs = [(-1, 0), (0, 1), (1, 0), (0, -1)]

    move = dirs[(direction + Dir) % 4]
    head = (snake.body[0][0] + move[0], snake.body[0][1] + move[1])

    if snake.collideSelf(head) or snake.collideWall(head):
        return 1

    return 0

def whereIsFood(snake, food):
    snake_direction = (snake.body[1][0] - snake.body[0][0], snake.body[1][1] - snake.body[0][1])

    forward = 0
    right = 1

    direction = 0

    if snake_direction[0] == 0:
        if snake_direction[1] == 1:
            direction = 3
        elif snake_direction[1] == -1:
            direction = 1

    elif snake_direction[1] == 0:
        if snake_direction[0] == 1:
            direction = 0
        elif snake_direction[0] == -1:
            direction = 2

    dirs = [(-1, 0), (0, 1), (1, 0), (0, -1)]

    Left = dirs[(direction + left) % 4]
    Right = dirs[(direction + right) % 4]
    Forward = dirs[(direction + forward) % 4]

    head = snake.body[0]
    headLeft = (head[0] + Left[0], head[1] + Left[1])
    headRight = (head[0] + Right[0], head[1] + Right[1])
    headForward = (head[0] + Forward[0], head[1] + Forward[1])

    distLeft = ((headLeft[0] - food.x)**2 + (headLeft[1] - food.y)**2)**0.5
    distRight = ((headRight[0] - food.x)**2 + (headRight[1] - food.y)**2)**0.5
    distForward = ((headForward[0] - food.x)**2 + (headForward[1] - food.y)**2)**0.5

    minDist = min(distLeft, distRight, distForward)

    Foods = []
    if minDist == distLeft:
        Foods = [1, 0, 0]

    elif minDist == distRight:
        Foods = [0, 0, 1]

    elif minDist == distForward:
        Foods = [0, 1, 0]

    return Foods

def finalDirection(snake, maxOutput):
    if maxOutput == 0:
        Dir = -1
    elif maxOutput == 1:
        Dir = 0

    elif maxOutput == 2:
        Dir = 1

    snake_direction = (snake.body[1][0] - snake.body[0][0], snake.body[1][1] - snake.body[0][1])

    direction = 0

    if snake_direction[0] == 0:
        if snake_direction[1] == 1:
            direction = 3
        elif snake_direction[1] == -1:
            direction = 1

    elif snake_direction[1] == 0:
        if snake_direction[0] == 1:
            direction = 0
        elif snake_direction[0] == -1:
            direction = 2

    dirs = [(-1, 0), (0, 1), (1, 0), (0, -1)]

    move = dirs[(direction + Dir) % 4]
    head = (snake.body[0][0] + move[0], snake.body[0][1] + move[1])

    return head


def main(genomes, config):
    world = pygame.display.set_mode((5*BLOCK_SIZE, 2*BLOCK_SIZE))
    drawGrid(world)

    nets = []
    ge = []
    snakes = []
    foods = []

    for _, g in genomes:
        net = neat.nn.FeedForwardNetwork.create(g, config)
        nets.append(net)
        g.fitness = 0
        ge.append(g)

    for i in range(10):
        snakes.append(Snake(6, 10, GRID[i % 10], world))

    for snake in snakes:
        foods.append(Food(snake))

    for x, food in enumerate(foods):
        if x < 10:
            food.draw(world)

    run = True
    while run:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
                pygame.quit()

        for x, snake in enumerate(snakes):
            time.sleep(0.005)

            inputs = whereIsFood(snake, food)
            inputs.append(isClear(snake, -1))
            inputs.append(isClear(snake, 0))
            inputs.append(isClear(snake, 1))
            # inputs.append(foods[x].x)
            # inputs.append(foods[x].y)

            output = nets[x].activate(inputs)

            maxOutput = next(x for x, op in enumerate(output) if op == max(output))

            if maxOutput == 0:
                output = (1, 0, 0)

            elif maxOutput == 1:
                output = (0, 1, 0)

            elif maxOutput == 2:
                output = (0, 0, 1)

            headed = finalDirection(snake, maxOutput)

            rem = []
            if snake.collideWall(output) or snake.collideSelf(output):
                ge[x].fitness -= 10
                rem.append(snake)

            else:
                snake.move(output)
                if x < 10:
                    snake.draw(world)
                    drawGrid(world)

                # ge[x].fitness += 0.1

                if snake.moves <= 0:
                    rem.append(snake)
                    ge[x].fitness -= 10

                if foods[x].distance >= ((snake.body[0][0] - foods[x].x)**2 + (snake.body[0][1] - foods[x].y)**2)**0.5:
                    # print(foods[x].distance ,((snake.body[0][0] - foods[x].x)**2 + (snake.body[0][1] - foods[x].y)**2)**0.5)
                    ge[x].fitness += 1

                else:
                    ge[x].fitness -= 1.5

                foods[x].setDist(snake)

                if (foods[x].x, foods[x].y) == snake.body[0]:
                    ge[x].fitness += 30
                    snake.grow()
                    foods[x].setPos(snake)
                    if x < 10:
                        foods[x].draw(world)

        for x, snake in enumerate(snakes):
            if snake in rem:
                nets.pop(x)
                ge.pop(x)
                foods.pop(x)
                snakes.pop(x)

        if len(snakes) == 0:
            run = False
            break

def run(config_path):
    config = neat.config.Config(neat.DefaultGenome, neat.DefaultReproduction,
                        neat.DefaultSpeciesSet, neat.DefaultStagnation,
                        config_path)

    p = neat.Population(config)

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

    winner = p.run(main,150)


if __name__ == "__main__":
    local_dir = os.path.dirname(__file__)
    config_path = os.path.join(local_dir, "config-FeedForward.txt")
    run(config_path)

这是 NEAT 配置文件 ->


[NEAT]
fitness_criterion     = max
fitness_threshold     = 1000
pop_size              = 1000
reset_on_extinction   = False

[DefaultGenome]
# node activation options
activation_default      = relu
activation_mutate_rate  = 0.0
activation_options      = tanh

# node aggregation options
aggregation_default     = random
aggregation_mutate_rate = 0.05
aggregation_options     = sum product min max mean median maxabs

# node bias options
bias_init_mean          = 0.05
bias_init_stdev         = 1.0
bias_max_value          = 30.0
bias_min_value          = -30.0
bias_mutate_power       = 0.5
bias_mutate_rate        = 0.7
bias_replace_rate       = 0.1

# genome compatibility options
compatibility_disjoint_coefficient = 1.0
compatibility_weight_coefficient   = 0.5

# connection add/remove rates
conn_add_prob           = 0.5
conn_delete_prob        = 0.1 

# connection enable options
enabled_default         = False
enabled_mutate_rate     = 0.2 

feed_forward            = False
initial_connection      = full

# node add/remove rates
node_add_prob           = 0.5
node_delete_prob        = 0.1

# network parameters
num_hidden              = 0 
num_inputs              = 6
num_outputs             = 3

# node response options
response_init_mean      = 1.0
response_init_stdev     = 0.05
response_max_value      = 30.0
response_min_value      = -30.0
response_mutate_power   = 0.1
response_mutate_rate    = 0.75
response_replace_rate   = 0.1

# connection weight options
weight_init_mean        = 0.1
weight_init_stdev       = 1.0
weight_max_value        = 30
weight_min_value        = -30
weight_mutate_power     = 0.5
weight_mutate_rate      = 0.8
weight_replace_rate     = 0.1

[DefaultSpeciesSet]
compatibility_threshold = 2.5

[DefaultStagnation]
species_fitness_func = max
max_stagnation       = 20
species_elitism      = 1

[DefaultReproduction]
elitism            = 8
survival_threshold = 0.3

请让我知道我做错了什么。先感谢您 !!

标签: pythonneural-networkpygameartificial-intelligence

解决方案


推荐阅读