Sempre tive curiosidade de entender como uma IA aprende de verdade, por dentro, sem ser só chamando uma API. A inspiração pra este projeto veio do canal Universo Programado, que eu admiro muito: ele coloca IA pra aprender a jogar Dino, Flappy Bird, Snake. (Quem curte o tema provavelmente conhece também o Code Bullet, que faz a versão gringa disso.
Aí resolvi fazer a minha. Uma IA que aprende a jogar Snake sozinha, sem nenhuma biblioteca de IA, só JavaScript, treinando em 4 VPS ao mesmo tempo. Ela saiu de "bate na parede" pra zerar o jogo: 397 maçãs, tabuleiro 20×20 completamente cheio. Só que o resultado mais interessante foi o que aconteceu quando eu fui conferir esse zeramento. E no caminho eu caí em algumas boas ciladas, que deixei todas numa seção no final.
A ideia: Neuroevolução
Nada de backpropagation, gradiente ou PyTorch.
- Cria 500 cobras, cada uma com uma rede neural de pesos aleatórios
- Todas jogam a mesma partida
- Quem come mais maçã ganha mais "aptidão"
- As melhores viram pais da próxima geração. Os filhos misturam os pesos dos pais (cruzamento) e ganham pequenas alterações aleatórias (mutação)
- Repete.
Ninguém programa como jogar. O comportamento surge da seleção, e essa foi a parte que mais me pegou no projeto.
A rede neural
A rede é minúscula:
- 17 entradas → 16 neurônios ocultos (tanh) → 3 saídas
- As saídas são seguir reto, virar à esquerda e virar à direita, e a maior nota decide a jogada. São 339 pesos contando os bias, então o modelo inteiro cabe em 1,4 KB (sim, KB haha).
O forward
O forward é só isso:
function forward(g, input, bufs) {
bufs[0]. set(input);
let off = 0;
const last = SIZES. length - 2;
for (let l = 0; l <= last; l++) {
const a = bufs[l], b = bufs[l + 1], ni = SIZES[l], no = SIZES[l + 1];
for (let j = 0; j < no; j++) {
let s = g[off + ni]; // bias
for (let i = 0; i < ni; i++) s += g[off + i] * a[i];
off += ni + 1;
b[j] = l < last?
### Mutação
Mutação é somar um ruído gaussiano em alguns deles.
### O que ela enxerga
A versão final tem 17 entradas, todas relativas à direção da cabeça:
- 5 raios (frente, esquerda, direita e as duas diagonais da frente) dizendo a distância até o obstáculo mais próximo e se tem maçã naquela linha
- Onde está a maçã em relação à cabeça
- Onde está a cauda em relação à cabeça
- Quanto espaço livre ela ainda alcança se for pra frente, pra esquerda ou pra direita (um flood fill pra cada direção)
### O que aconteceu com as três últimas entradas
As três últimas não existiam no começo, e a história de por que elas entraram é a primeira cilada lá embaixo.
### A aptidão
A aptidão é const fitness = g => g.
### Rodando em 4 VPS: modelo de ilhas
O jeito clássico de distribuir algoritmo genético é o modelo de ilhas. Cada máquina evolui a própria população e, de tempos em tempos, as melhores cobras migram entre elas. Isso mantém a diversidade (cada ilha vai por um caminho) e ao mesmo tempo espalha as descobertas boas.
### Cada ilha avalia a população com worker_threads (3 workers por máquina)
De 90 em 90 segundos manda as 10 melhores pra uma central e recebe as das outras
### As migrantes entram no lugar dos piores filhos, e as elites ficam intactas
Checkpoint a cada 10 minutos.
### Conclusão
Uma IA de 19 neurônios em JavaScript que aprendeu a zerar o jogo Snake usando neuroevolução e 4 VPS. O que mais me pegou foi a ideia de que um modelo de IA é literalmente um arquivo de matrizes, com 339 números sendo uma matriz 16×17, uma 3×16 e dois vetores de bias. Um LLM é a mesma natureza de coisa, só que com bilhões de números em vez de 339 e um treino completamente diferente. 
Comentários