Snake game basics e como implementar do zero
Se você já tentou programar um jogo da cobrinha em alguma linguagem, sabe que a ideia parece simples até o momento em que precisa tratar colisão, grid e frame rate ao mesmo tempo. O clássico jogo da minhoca que come maçã é basicamente uma grade onde um personagem se move por coordenadas, cresce quando coleta um item e morre se bater nas paredes ou no próprio corpo.
Como funciona o jogo da minhoca que come maçã na prática
A lógica central é um loop que reposiciona a cabeça da cobra na direção indicada pelo input do jogador. A cauda é atualizada removendo o último segmento, a menos que a cobra tenha comido algo. Cada frame, você verifica duas coisas: se a cabeça sobrepujou a maçã na mesma célula, e se a cabeça colidiu com alguma parte do corpo ou com as bordas. Uma coisa que muitos não percebem logo de cara é a questão do timing. Se você usar simplesmente o evento de tecla pressionada para mover a cobra, vai enfrentar o problema clássico de virada em 180 graus. O jogador aperta esquerda e imediatamente direita antes do próximo frame renderizar, e a cobra colide consigo mesma. A solução elegante é manter um buffer de direção e só aplicar uma mudança de ângulo se ela não for oposta à direção atual.
No meu caso, quando desenvolvi uma versão em JavaScript para rodar no navegador, me deparei com um bug curioso: em resoluções maiores, a cobra parecia acelerar sem motivo. Descobri que o setInterval do navegador não é determinístico em abas que não estão em primeiro plano. Quando a aba perde o foco, o timer desacelera, e quando volta, ele tenta compensar disparando callbacks acumulados de forma irregular. A workaround foi simples: abandonei setInterval e passei a usar requestAnimationFrame com cálculo de delta time entre frames. Isso estabilizou o jogo independentemente da taxa de atualização do monitor ou do estado da aba.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Estrutura de dados para a cobra
Você pode representar a cobra de várias formas. A mais intuitiva é um array de objetos com coordenadas x e y, onde o primeiro elemento é a cabeça. Outra abordagem, menos óbvia mas eficiente, usa uma estrutura de deque onde você insere na frente e remove de trás a cada movimento. Ambas funcionam, mas a do deque evita o custo de deslocamento de elementos que o splice gera em arrays grandes. Para a grade, um array unidimensional é suficiente se você calcular o índice como index = y * largura + x. Isso facilita a visualização e otimiza acesso. Não complica nada e economiza memória comparado a uma matriz 2D real.
Pegadinhas comuns
Gerar a maçã em uma posição válida exige verificar se ela não caiu sobre o corpo da cobra. A maneira ingênua é sortear coordenadas aleatórias e repetir até encontrar um espaço livre. Isso funciona bem quando a cobra é curta. Mas conforme ela cresce e ocupa grande parte da grade, o processo de tentativa e erro fica caro. O workaround é manter uma lista de células livres e sortear dessa lista. Quando a cobra cresce, você adiciona a célula da cauda antiga à lista de livres. Quando ela come, remove a célula da maçã. Isso torna a geração sempre O(1), independente do tamanho da cobra. Outro detalhe que todo mundo esquece é a sensação de peso. Uma cobra que se move exatamente uma célula por frame parece robótica. Adicionar um leve atraso entre movimentos ou interpolando a posição visual entre os nós da grade melhora muito a fluidez. Em Python com pygame, por exemplo, você pode renderizar a cobra na posição intermediária calculada pelo tempo decorrido desde o último move, enquanto a lógica permanece discreta na grade.
Tecnologias viáveis
Para quem quer algo rápido, uma implementação em Python com pygame ou em JavaScript com canvas dá resultado em poucas horas. Se o objetivo é Deploy em browser, uma versão vanilla em HTML5 + JS com cerca de duzentas linhas cobre tudo. Bibliotecas como Phaser ou p5.js aceleram o desenvolvimento mas adicionam dependência desnecessária para um jogo tão simples. O problema real começa quando você quer salvar recorde, adicionar níveis ou multiplayer. Aí a arquitetura pura do jogo vira um empecilho. Recomendo estruturar o código em camadas desde o início: camada de lógica (grade, cobra, comida), camada de renderização (o que aparece na tela) e camada de input (teclado, toque). Isso evita que, no meio do caminho, você fique refatorando metade do projeto porque input e renderização estavam misturados.
Considerações finais
O jogo da minhoca que come maçã é um exercício básico que muitas pessoas subestimam. Parece simples, mas esconde armadilhas reais de performance e UX que só aparecem quando você coloca o código para rodar de verdade. A maioria dos tutoriais ensina a versão mais ingênua e para por aí. Se o seu objetivo é realmente entender o que acontece por baixo do capô, vale a pena investir nos detalhes: buffer de direção, delta time, geração inteligente de comida e separação de camadas. Sem isso, o projeto vira um amontoado de código que funciona em condições ideais mas quebra da pior forma possível quando alguém testa de verdade.