Desenvolvendo o jogo Snake [parte 3]

9 minute read

Neste post vamos modificar o código para que tenhamos de fato uma cobra com os seus segmentos.

Criando segmentos para a cobra

Até agora a nossa cobra possui apenas a cabeça, mas precisamos adicionar a possibilidade de ela ter mais segmentos. Vamos agora introduzir então a noção de listas.

Listas

Listas são estruturas que permitem que armazenemos vários valores. Diferente de variáveis simples, que podem apenas armazenar um valor por vez. Podemos declarar e inicializar a lista de forma bem simples. Veja um exemplo:

1
lista1 = ['a', 'b', 'c']

Neste exemplo bem simples a variável lista1 recebendo uma lista com 3 elementos. Esses elementos são acessíveis a partir de seu índice, que em Python iniciam em 0. Para acessar o valor ‘b’ você faria lista1[1], para obter o valor na posição 1. Existe um mundo de coisas para aprender sobre listas, e não dá pra ensinar tudo de uma vez, então vamos aprendendo alguns conceitos à medida que vamos precisando. Se você já quiser se antecipar um pouco, olhe este link No nosso caso, vamos usar uma lista para armazenar as posições de cada segmento do nosso ofídio. Poderíamos criar cada posição como uma tupla (leia mais sobre tuplas), mas fica mais legal se criarmos uma classe especifica pra isso. Vamos tentar fazer isso agora.

1
2
3
4
class Posicao:
  def __init__(self, x, y):
    self.x = x
    self.y = y

É isso, agora podemos referenciar a posicão x e y facilmente e de forma bem clara. Como ficaria isso no código que já temos?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import pyxel

#direcoes da cobra
CIMA = 0
DIREITA = 1
BAIXO = 2
ESQUERDA = 3

class Posicao:
  def __init__(self, x, y):
    self.x = x
    self.y = y

class Game:
    def __init__(self):
      pyxel.init(208, 208)
      self.cabeca = Posicao(pyxel.width/2, pyxel.height/2)
      self.direcao = DIREITA
      self.tamanho_sprite = 8
      pyxel.run(self.update, self.draw)

    def update(self):
      #atualiza a direção atual de acordo com entrada do usuario
      if pyxel.btn(pyxel.KEY_UP) and self.direcao != BAIXO:
        self.direcao = CIMA
      elif pyxel.btn(pyxel.KEY_RIGHT) and self.direcao != ESQUERDA:
        self.direcao = DIREITA
      elif pyxel.btn(pyxel.KEY_DOWN) and self.direcao != CIMA:
        self.direcao = BAIXO
      elif pyxel.btn(pyxel.KEY_LEFT) and self.direcao != DIREITA:
        self.direcao = ESQUERDA

      if pyxel.frame_count % 15 == 0:
        if self.direcao == CIMA:
            if self.cabeca.y - self.tamanho_sprite < 0:
                self.cabeca.y =  pyxel.height - self.tamanho_sprite
            else:
                self.cabeca.y -= 8
        elif self.direcao == DIREITA:
            if self.cabeca.x + self.tamanho_sprite > pyxel.width - self.tamanho_sprite:
                self.cabeca.x = 0
            else:
                self.cabeca.x += 8
        elif self.direcao == BAIXO:
            if self.cabeca.y + self.tamanho_sprite > pyxel.height - self.tamanho_sprite:
                self.cabeca.y = 0
            else:
                self.cabeca.y += 8
        elif self.direcao == ESQUERDA:
            if self.cabeca.x - self.tamanho_sprite < 0:
                self.cabeca.x = pyxel.width - self.tamanho_sprite
            else:
                self.cabeca.x -= 8

    def draw(self):
        pyxel.cls(0)
        pyxel.rect(self.cabeca.x, self.cabeca.y, self.tamanho_sprite, self.tamanho_sprite, 7)

Game()

O código deve funcionar da mesma forma, mas agora ele está mais legível, e um pouco mais fácil de estender para as próximas partes.

Novos segmentos

O próximo passo vai ser adicionar mais segmentos para que nosso amigo da casa Sonserina pareça de fato uma cobra. Agora entrarmos de fato com a idéia das listas, porque vamos precisar armazenar todos os segmentos da cobra, cada um com sua posição individual. Vamos atualizar o método __init__ para incluir esse conceito:

1
2
3
4
5
6
7
8
9
10
11
12
13
def __init__(self):
  pyxel.init(208, 208)
  self.tamanho_sprite = 8
  self.segmentos = []
  cabeca = Posicao(pyxel.width/2, pyxel.height/2)
  self.segmentos.append(cabeca)
  self.segmentos.append(Posicao(pyxel.width/2 - self.tamanho_sprite, pyxel.height/2))
  self.segmentos.append(Posicao(pyxel.width/2 - self.tamanho_sprite*2, pyxel.height/2))
  self.segmentos.append(Posicao(pyxel.width/2 - self.tamanho_sprite*3, pyxel.height/2))
  self.segmentos.append(Posicao(pyxel.width/2 - self.tamanho_sprite*4, pyxel.height/2))
  self.segmentos.append(Posicao(pyxel.width/2 - self.tamanho_sprite*5, pyxel.height/2))
  self.direcao = DIREITA
  pyxel.run(self.update, self.draw)

A linha 4 cria essa lista, armazenando-a no atributo segmentos. Linhas 6 a 11 criam novos segmentos além da cabeça e adicionam na lista de segmentos.

Fazer tudo se mexer de maneira ordenada

Agora a nossa tarefa é faze tudo se mexer junto, de forma que lembre o movimento de uma cobra. Nós poderíamos, a cada rodada de atualização, percorrer toda a lista e atualizar as posições de cada elemento. Mas isso seria muito custoso em comparação com a estratégia que eu empreguei. Se você parar para observar, de toda a cobra, apenas o primeiro e o último segmentos de fato se movem de uma atualização para outra, então é muito mais fácil mexer apenas neles.

A estratégia é inserir um novo segmento na direção para onde o usuário quer se mover, e remover um segmento da cauda. Simples não? Dessa forma você vai passar a impressão de que a cobra está se movendo, mas na realidade, você está sempre adicionando e removendo um elemento a cada rodada do algoritmo. Vamos atualizar o método update para implementar essa estratégia.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def update(self):
    # atualiza a direção atual de acordo com entrada do usuario
    if pyxel.btn(pyxel.KEY_UP) and self.direcao != BAIXO:
        self.direcao = CIMA
    elif pyxel.btn(pyxel.KEY_RIGHT) and self.direcao != ESQUERDA:
        self.direcao = DIREITA
    elif pyxel.btn(pyxel.KEY_DOWN) and self.direcao != CIMA:
        self.direcao = BAIXO
    elif pyxel.btn(pyxel.KEY_LEFT) and self.direcao != DIREITA:
        self.direcao = ESQUERDA

    if pyxel.frame_count % 5 == 0:
        # adicionar uma nova cabeca e remover a cauda eh mais facil
        # do que atualizar todos os segmentos
        cabeca = self.segmentos[0]

        if self.direcao == CIMA:
            if cabeca.y - self.tamanho_sprite < 0: #fazer o wrap
                cabeca = Posicao(cabeca.x, pyxel.height - self.tamanho_sprite)
            else:
                cabeca = Posicao(cabeca.x, cabeca.y - self.tamanho_sprite)
        elif self.direcao == DIREITA:
            if cabeca.x + self.tamanho_sprite > pyxel.width - self.tamanho_sprite:
                cabeca = Posicao(0, cabeca.y)
            else:
                cabeca = Posicao(cabeca.x + self.tamanho_sprite, cabeca.y)
        elif self.direcao == BAIXO:
            if cabeca.y + self.tamanho_sprite > pyxel.height - self.tamanho_sprite:
                cabeca = Posicao(cabeca.x, 0)
            else:
                cabeca = Posicao(cabeca.x, cabeca.y + self.tamanho_sprite)
        elif self.direcao == ESQUERDA:
            if cabeca.x - self.tamanho_sprite < 0:
                cabeca = Posicao(pyxel.width - self.tamanho_sprite, cabeca.y)
            else:
                cabeca = Posicao(cabeca.x - self.tamanho_sprite, cabeca.y)

        self.segmentos.pop()
        self.segmentos.insert(0, cabeca)

Na linha 15 agora pegamos a referência para o primeiro segmento, que sempre será a cabeça. Toda a lógica para movimentação é feita sobre ela (linhas 17 a 36), inclusive as checagens pra saber se a cobra está tentando “escapar” por alguma borda da tela. Na linha 38 fazemos a remoção do elemento na última posição da lista usando o método pop(). Com este método é possível remover qualquer elemento da lista a partir de seu índice, mas quando o chamamos sem nenhum parâmetro ele remove o último elemento. Finalmente na linha 39 inserimos um novo elemento para ser a cabeça da cobra, na posição 0.

Alterei também a linha 12 para que o jogo fique um pouco mais rápido, fazendo a atualização a cada 5 frames (antes eram 15 frames).

Este é o resultado final para esta parte.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import pyxel

#direcoes da cobra
CIMA = 0
DIREITA = 1
BAIXO = 2
ESQUERDA = 3

class Posicao:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Game:
    def __init__(self):
        pyxel.init(208, 208)
        self.tamanho_sprite = 8
        self.segmentos = []
        cabeca = Posicao(pyxel.width/2, pyxel.height/2)
        self.segmentos.append(cabeca)
        self.segmentos.append(Posicao(pyxel.width/2 - self.tamanho_sprite, pyxel.height/2))
        self.segmentos.append(Posicao(pyxel.width/2 - self.tamanho_sprite*2, pyxel.height/2))
        self.segmentos.append(Posicao(pyxel.width/2 - self.tamanho_sprite*3, pyxel.height/2))
        self.segmentos.append(Posicao(pyxel.width/2 - self.tamanho_sprite*4, pyxel.height/2))
        self.segmentos.append(Posicao(pyxel.width/2 - self.tamanho_sprite*5, pyxel.height/2))
        self.direcao = DIREITA
        pyxel.run(self.update, self.draw)

    def update(self):
        # atualiza a direção atual de acordo com entrada do usuario
        if pyxel.btn(pyxel.KEY_UP) and self.direcao != BAIXO:
            self.direcao = CIMA
        elif pyxel.btn(pyxel.KEY_RIGHT) and self.direcao != ESQUERDA:
            self.direcao = DIREITA
        elif pyxel.btn(pyxel.KEY_DOWN) and self.direcao != CIMA:
            self.direcao = BAIXO
        elif pyxel.btn(pyxel.KEY_LEFT) and self.direcao != DIREITA:
            self.direcao = ESQUERDA

        if pyxel.frame_count % 5 == 0:
            # adicionar uma nova cabeca e remover a cauda eh mais facil
            # do que atualizar todos os segmentos
            cabeca = self.segmentos[0]

            if self.direcao == CIMA:
                if cabeca.y - self.tamanho_sprite < 0: #fazer o wrap
                    cabeca = Posicao(cabeca.x, pyxel.height - self.tamanho_sprite)
                else:
                    cabeca = Posicao(cabeca.x, cabeca.y - self.tamanho_sprite)
            elif self.direcao == DIREITA:
                if cabeca.x + self.tamanho_sprite > pyxel.width - self.tamanho_sprite:
                    cabeca = Posicao(0, cabeca.y)
                else:
                    cabeca = Posicao(cabeca.x + self.tamanho_sprite, cabeca.y)
            elif self.direcao == BAIXO:
                if cabeca.y + self.tamanho_sprite > pyxel.height - self.tamanho_sprite:
                    cabeca = Posicao(cabeca.x, 0)
                else:
                    cabeca = Posicao(cabeca.x, cabeca.y + self.tamanho_sprite)
            elif self.direcao == ESQUERDA:
                if cabeca.x - self.tamanho_sprite < 0:
                    cabeca = Posicao(pyxel.width - self.tamanho_sprite, cabeca.y)
                else:
                    cabeca = Posicao(cabeca.x - self.tamanho_sprite, cabeca.y)

            self.segmentos.pop()
            self.segmentos.insert(0, cabeca)


    def draw(self):
        pyxel.cls(0)
        for segmento in self.segmentos:
            pyxel.rectb(segmento.x, segmento.y, self.tamanho_sprite, self.tamanho_sprite, 7)

Game()

Você pode ver este código fonte no GitHub, tag v0.3.

Leave a comment