# Jogo da Cobrinha - Atividade avaliativa Aprendizado de Máquina - COM10792 Inteligência Artificial
# Grupo 02: Kaique Solano dos Reis, <NAME>, <NAME> e <NAME>.
# Autor original do projeto: <NAME> (<EMAIL>)

"""
snake.py
~~~~~~~~~~

Este módulo é para construir a própria cobra no jogo da cobra

A cobra:
- Está na forma de uma lista, cada elemento é um bloco de corpo (contendo suas coordenadas)
- Tem uma cabeça apontando no primeiro bloco, uma direção e também uma rede neural (cérebro)
- Possui visão dada pelo mapa (método Map.scan)
- Encarrega-se de mover seus blocos, envelhecendo, crescendo adicionando um bloco no lugar certo
  e toma decisões com rede neural
- Dá sua aptidão com base na idade e comprimento próprios

"""

from neural_network import *


class Snake:
    """Snake Class"""

    def __init__(self, neural_net=None):
        """
        :param neural_net: NeuralNet dado à cobra encarregada das decisões (AI)
        """
        #self.body = [[10, 10], [9, 10]]                         # a cobra é na verdade uma lista de coordenadas
        self.body = [[10, 10], [9, 10], [9, 11], [9, 12], [9, 13], [9,14], [9,15], [9,16], [9,17], [9,18]]     #adição de dificuldade: a cobra começa o jogo com o tamanho de 10 blocos
        self.head = self.body[0][:]                             # primeiro bloco de corpo
        self.old_tail = self.head[:]                            # útil para crescer
        self.direction = RIGHT
        self.age = 0
        self.starve = 500                                        # útil para evitar o loop de cobras AI
        self.alive = True
        self.neural_net = neural_net
        self.vision = []                                        # contém o map.scan () e é usado pela rede neural

    def update(self):
        """
        Atualize a cobra ao longo do tempo, tornando-a mais velha e mais faminta a cada iteração do jogo,
        desculpe snek

        """
        self.age += 1
        self.starve -= 1
        if self.starve < 1:
            self.alive = False
        self.move()

    def grow(self):
        """
        Faz a cobra crescer um quarteirão a mais
        Chamado por map.update () quando a cabeça da cobra está em colisão com a comida

        """
        self.starve = 500                   # útil para evitar o loop de cobras de IA (elas morrem mais jovens -> má preparação)
        self.body.append(self.old_tail)     # é por isso que mantenho old_tail

    def move(self):
        """
        Faz a cobra se mover, a cabeça se move na direção atual e cada bloco substitui seu antecessor
        
        """
        self.old_tail = self.body[-1][:]        # salvar posição antiga do último bloco
        self.head[0] += self.direction[0]       # move a cabeça
        self.head[1] += self.direction[1]
        if self.head in self.body[1:]:          # se a cobra se atingir
            self.alive = False
        self.body.insert(0, self.body.pop())    # cada bloco é substituído pelo predecessor
        self.body[0] = self.head[:]             # primeiro bloco é head

    def turn_right(self):
        """
        Faz a direção da cobra à direita da direção atual
        Direção atual = [x, y], turn_right dá [-y, x]

        Exemplo:
        Se [0,1] (para baixo) é a direção atual, [-1,0] (direita) é a nova direção

        """
        temp = self.direction[0]
        self.direction[0] = -self.direction[1]
        self.direction[1] = temp

    def turn_left(self):
        """
        Faz a direção da cobra à direita da direção atual
        Direção atual = [x, y], turn_right dá [y, -x]

        """
        temp = self.direction[0]
        self.direction[0] = self.direction[1]
        self.direction[1] = -temp

    def AI(self):
        """
        Decide a direção da cobra de acordo com sua visão atual
        A visão é dada ao NeuralNetwork e a maioria dos neurônios de saída ativados é considerada uma decisão

        """
        decision = np.argmax(self.neural_net.feed_forward(self.vision))
        if decision == 1:
            self.turn_right()
        elif decision == 2:
            self.turn_left()

    def fitness(self):
        """
        Mede o quão bem a cobra está em função de seu comprimento e idade

        Observação:

        - Você pode ser criativo com a fórmula e encontrar uma solução melhor
        - Tem um grande impacto no algoritmo genético

        : return: inteiro representando o quão bem a cobra está se saindo

        """
        return (len(self.body)**2) * self.age

    def render(self, window):
        """
        Renderiza o mapa (fundo, paredes e comida) na superfície da janela e chama render () de snake
        Muito, muito não otimizado, uma vez que a renderização não afeta o algoritmo genético

        :param window: janela de superfície
        """
        body = pygame.image.load(IMAGE_SNAKE).convert_alpha()                   # carregando imagem
        for block in self.body:
            window.blit(body, (block[0]*SPRITE_SIZE, block[1]*SPRITE_SIZE))     # pintando um belo sorriso
        if self.neural_net:                                                     # chamadas para renderização da rede neural
            self.neural_net.render(window, self.vision)

# Jogo da Cobrinha - Atividade avaliativa Aprendizado de Máquina - COM10792 Inteligência Artificial
# Grupo 02: Kaique Solano dos Reis, <NAME>, <NAME> e <NAME>.
# Autor original do projeto: <NAME> (<EMAIL>)

"""
snake.py
~~~~~~~~~~

Este módulo é para construir a própria cobra no jogo da cobra

A cobra:
- Está na forma de uma lista, cada elemento é um bloco de corpo (contendo suas coordenadas)
- Tem uma cabeça apontando no primeiro bloco, uma direção e também uma rede neural (cérebro)
- Possui visão dada pelo mapa (método Map.scan)
- Encarrega-se de mover seus blocos, envelhecendo, crescendo adicionando um bloco no lugar certo
  e toma decisões com rede neural
- Dá sua aptidão com base na idade e comprimento próprios

"""

from neural_network import *


class Snake:
    """Snake Class"""

    def __init__(self, neural_net=None):
        """
        :param neural_net: NeuralNet dado à cobra encarregada das decisões (AI)
        """
        #self.body = [[10, 10], [9, 10]]                         # a cobra é na verdade uma lista de coordenadas
        self.body = [[10, 10], [9, 10], [9, 11], [9, 12], [9, 13], [9,14], [9,15], [9,16], [9,17], [9,18]]     #adição de dificuldade: a cobra começa o jogo com o tamanho de 10 blocos
        self.head = self.body[0][:]                             # primeiro bloco de corpo
        self.old_tail = self.head[:]                            # útil para crescer
        self.direction = RIGHT
        self.age = 0
        self.starve = 500                                        # útil para evitar o loop de cobras AI
        self.alive = True
        self.neural_net = neural_net
        self.vision = []                                        # contém o map.scan () e é usado pela rede neural

    def update(self):
        """
        Atualize a cobra ao longo do tempo, tornando-a mais velha e mais faminta a cada iteração do jogo,
        desculpe snek

        """
        self.age += 1
        self.starve -= 1
        if self.starve < 1:
            self.alive = False
        self.move()

    def grow(self):
        """
        Faz a cobra crescer um quarteirão a mais
        Chamado por map.update () quando a cabeça da cobra está em colisão com a comida

        """
        self.starve = 500                   # útil para evitar o loop de cobras de IA (elas morrem mais jovens -> má preparação)
        self.body.append(self.old_tail)     # é por isso que mantenho old_tail

    def move(self):
        """
        Faz a cobra se mover, a cabeça se move na direção atual e cada bloco substitui seu antecessor
        
        """
        self.old_tail = self.body[-1][:]        # salvar posição antiga do último bloco
        self.head[0] += self.direction[0]       # move a cabeça
        self.head[1] += self.direction[1]
        if self.head in self.body[1:]:          # se a cobra se atingir
            self.alive = False
        self.body.insert(0, self.body.pop())    # cada bloco é substituído pelo predecessor
        self.body[0] = self.head[:]             # primeiro bloco é head

    def turn_right(self):
        """
        Faz a direção da cobra à direita da direção atual
        Direção atual = [x, y], turn_right dá [-y, x]

        Exemplo:
        Se [0,1] (para baixo) é a direção atual, [-1,0] (direita) é a nova direção

        """
        temp = self.direction[0]
        self.direction[0] = -self.direction[1]
        self.direction[1] = temp

    def turn_left(self):
        """
        Faz a direção da cobra à direita da direção atual
        Direção atual = [x, y], turn_right dá [y, -x]

        """
        temp = self.direction[0]
        self.direction[0] = self.direction[1]
        self.direction[1] = -temp

    def AI(self):
        """
        Decide a direção da cobra de acordo com sua visão atual
        A visão é dada ao NeuralNetwork e a maioria dos neurônios de saída ativados é considerada uma decisão

        """
        decision = np.argmax(self.neural_net.feed_forward(self.vision))
        if decision == 1:
            self.turn_right()
        elif decision == 2:
            self.turn_left()

    def fitness(self):
        """
        Mede o quão bem a cobra está em função de seu comprimento e idade

        Observação:

        - Você pode ser criativo com a fórmula e encontrar uma solução melhor
        - Tem um grande impacto no algoritmo genético

        : return: inteiro representando o quão bem a cobra está se saindo

        """
        return (len(self.body)**2) * self.age

    def render(self, window):
        """
        Renderiza o mapa (fundo, paredes e comida) na superfície da janela e chama render () de snake
        Muito, muito não otimizado, uma vez que a renderização não afeta o algoritmo genético

        :param window: janela de superfície
        """
        body = pygame.image.load(IMAGE_SNAKE).convert_alpha()                   # carregando imagem
        for block in self.body:
            window.blit(body, (block[0]*SPRITE_SIZE, block[1]*SPRITE_SIZE))     # pintando um belo sorriso
        if self.neural_net:                                                     # chamadas para renderização da rede neural
            self.neural_net.render(window, self.vision)

# Jogo da Cobrinha - Atividade avaliativa Aprendizado de Máquina - COM10792 Inteligência Artificial # Grupo 02: Kaique Solano dos Reis, <NAME>, <NAME> e <NAME>. # Autor original do projeto: <NAME> (<EMAIL>) snake.py ~~~~~~~~~~  Este módulo é para construir a própria cobra no jogo da cobra  A cobra: - Está na forma de uma lista, cada elemento é um bloco de corpo (contendo suas coordenadas) - Tem uma cabeça apontando no primeiro bloco, uma direção e também uma rede neural (cérebro) - Possui visão dada pelo mapa (método Map.scan) - Encarrega-se de mover seus blocos, envelhecendo, crescendo adicionando um bloco no lugar certo   e toma decisões com rede neural - Dá sua aptidão com base na idade e comprimento próprios Snake Class :param neural_net: NeuralNet dado à cobra encarregada das decisões (AI) #self.body = [[10, 10], [9, 10]]                         # a cobra é na verdade uma lista de coordenadas #adição de dificuldade: a cobra começa o jogo com o tamanho de 10 blocos # primeiro bloco de corpo # útil para crescer # útil para evitar o loop de cobras AI # contém o map.scan () e é usado pela rede neural Atualize a cobra ao longo do tempo, tornando-a mais velha e mais faminta a cada iteração do jogo,         desculpe snek Faz a cobra crescer um quarteirão a mais         Chamado por map.update () quando a cabeça da cobra está em colisão com a comida # útil para evitar o loop de cobras de IA (elas morrem mais jovens -> má preparação) # é por isso que mantenho old_tail Faz a cobra se mover, a cabeça se move na direção atual e cada bloco substitui seu antecessor # salvar posição antiga do último bloco # move a cabeça # se a cobra se atingir # cada bloco é substituído pelo predecessor # primeiro bloco é head Faz a direção da cobra à direita da direção atual         Direção atual = [x, y], turn_right dá [-y, x]          Exemplo:         Se [0,1] (para baixo) é a direção atual, [-1,0] (direita) é a nova direção Faz a direção da cobra à direita da direção atual         Direção atual = [x, y], turn_right dá [y, -x] Decide a direção da cobra de acordo com sua visão atual         A visão é dada ao NeuralNetwork e a maioria dos neurônios de saída ativados é considerada uma decisão Mede o quão bem a cobra está em função de seu comprimento e idade          Observação:          - Você pode ser criativo com a fórmula e encontrar uma solução melhor         - Tem um grande impacto no algoritmo genético          : return: inteiro representando o quão bem a cobra está se saindo Renderiza o mapa (fundo, paredes e comida) na superfície da janela e chama render () de snake         Muito, muito não otimizado, uma vez que a renderização não afeta o algoritmo genético          :param window: janela de superfície # carregando imagem # pintando um belo sorriso # chamadas para renderização da rede neural