
Distância do cano:
Altura da passagem:
Velocidade vertical do pássaro:
Altura do pássaro:
Tempo:
Geração:
Pontos:
Este documento descreve, de forma didática, a parte central do agente que joga Flappy Bird neste repositório: o perceptron de uma camada (que decide quando pular) e o algoritmo genético (que evolui os pesos representados por genes). A implementação está em TypeScript e usa Matter.js para a física.
Repositório: includeDaniel/PlayAI
Arquivos principais (caminhos no repositório):
Cada indivíduo tem um vetor de bits chamado Genes (no código Genes = boolean[]).
Trecho (implementação principal, veja o arquivo logicas/ia.ts):
// converte 22 bits para número no intervalo [-1, 1]
function bitsParaNumero(bits: boolean[]): number {
// (implementação: soma de bit shifts, normalização e map para [-1,1])
}
// extrai os 4 pesos a partir dos 88 bits
function extrairPesos(genes: boolean[]): number[] {
const pesos = [];
for (let i = 0; i < 4; i++) {
const slice = genes.slice(i * 22, (i + 1) * 22);
pesos.push(bitsParaNumero(slice));
}
return pesos;
}Observação: usar bits permite representar com granularidade controle direto da codificação genética e realizar cruzamentos bit-a-bit facilmente.
O perceptron é uma única camada linear sem função de ativação complexa — a saída é apenas a soma ponderada das entradas comparada a zero.
Entradas usadas no perceptron (ordem usada no código):
Decisão (pseudocódigo):
const pesos = extrairPesos(genes); // [w0, w1, w2, w3]
const entradas = [distancia, canoY, velocidade, passaroY];
const soma = entradas.reduce((acc, v, i) => acc + v * pesos[i], 0);
const devePular = soma >= 0; // true => aplica impulso de puloVeja a função real: logicas/ia.ts
Dica prática: a normalização das entradas (escala e offset) afeta muito o comportamento do perceptron — verifique como as entradas são calculadas em logicas/jogo.ts para entender a escala usada no experimento.
Visão geral do ciclo de vida genético:
criarGenesAleatorios).tempo de sobrevivência).Pontos-chave do código:
criarGenesAleatorios() — gera 88 bits aleatórios.cruzarGenes(g1, g2) — para cada bit há probabilidade de herdar de pai A, pai B ou sofrer mutação.Trecho ilustrativo de cruzamento (pseudocódigo):
function cruzarGenes(g1: Genes, g2: Genes): Genes {
const filho = [];
for (let i = 0; i < 88; i++) {
const p = Math.random();
if (p < 0.45) filho.push(g1[i]);
else if (p < 0.9) filho.push(g2[i]);
else filho.push(Math.random() < 0.5); // mutação
}
return filho;
}Implementação completa e parâmetros: logicas/ia.ts
Como a aptidão é medida (fitness):
function obterFitness(individuo) {
// Exemplo do projeto — usa tempo de sobrevivência
return individuo.tempo - TEMPO_TREINAMENTO;
}O TEMPO_TREINAMENTO aparece no código de orquestração (jogo.ts) e é usado para calibrar a função de fitness.
TAMANHO_POPULACAO): maior população explora mais, mas custa performance.Parâmetros estão em: logicas/jogo.ts