sexta-feira, 12 de dezembro de 2025

Jogo da Vida de Conway - exemplo usando o Scilab


O Jogo da Vida (em inglês: Conway's Game of Life) é um dos experimentos mais famosos da história da computação e da matemática recreativa. Criado em 1970 pelo matemático britânico John Horton Conway, ele não é um jogo no sentido convencional, pois não tem jogadores, pontuação ou um objetivo claro. É na verdade um autômato celular: uma simulação que evolui sozinha a partir de regras extremamente simples.

Curiosidade: Conway manteve o jogo em segredo por meses, testando configurações até ter certeza de que era "interessante". Quando publicado na revista Scientific American (Gardner, Martin. “Mathematical Games: The fantastic combinations of John Conway’s new solitaire game ‘life’.” Scientific American, vol. 223, no. 4, p. 120–123, outubro 1970.), explodiu em popularidade e nunca mais parou!

Como funciona?

Tudo acontece em uma grade infinita de células quadradas (em simulações usamos grades finitas, como 50×50). Cada célula pode estar em dois estados:

  • Viva (geralmente representada por 1 ou cor preta)
  • Morta (0 ou branca)

A cada geração (passo de tempo), todas as células são atualizadas simultaneamente com base em seus 8 vizinhos. As regras são apenas estas quatro:

  1. Uma célula viva com menos de 2 vizinhos vivos → morre (solidão)
  2. Uma célula viva com 2 ou 3 vizinhos vivos → sobrevive
  3. Uma célula viva com mais de 3 vizinhos vivos → morre (superpopulação)
  4. Uma célula morta com exatamente 3 vizinhos vivos → nasce!

E só isso. Não há mais regras. 

Por que é tão fascinante?

Apesar da simplicidade, surgem comportamentos incrivelmente complexos:

  • Padrões estáticos: como o "bloco" (2×2) que nunca muda
  • Osciladores: como o "blinker" (pisca a cada geração)
  • Naves espaciais: como o famoso glider que se move diagonalmente para sempre
  • Construções universais: é possível criar portas lógicas, memórias e até computadores completos dentro do jogo!
Fato impressionante: O Jogo da Vida é Turing-completo, ou seja, pode simular qualquer computador real. Teoricamente, daria para rodar Windows dentro dele!

Exemplo de padrão famoso: o Glider

Uma das menores "naves espaciais", que se move uma célula a cada 4 gerações:

// Glider (3x3) - uma das estruturas mais icônicas
glider = [0 1 0;
          0 0 1;
          1 1 1];

Onde ver em ação?

O Jogo da Vida não é só diversão: ele inspira estudos em biologia, inteligência artificial, complexidade e até filosofia sobre o que significa "vivo".´ E tudo isso com apenas quatro regras.

Exemplo de código Scilab:

// ===========================================
// JOGO DA VIDA DE CONWAY (Conway's Game of Life)
// Implementação básica no Scilab
// ===========================================

// Limpa tudo:
clf();
close(winsid());

// --- 1. Parâmetros e Inicialização ---

// Dimensão da grade (matriz)
linhas = 70; 
colunas = 70; 

// Número de gerações a serem simuladas
numGeracoes = 1000;

// Probabilidade inicial de uma célula estar viva (entre 0 e 1)
probVida = 0.25; 

// Cria a grade inicial (Estado 0) preenchida com 0s (mortas) ou 1s (vivas)
// rand(linhas, colunas) gera números aleatórios uniformes entre 0 e 1.
// O teste '< probVida' transforma em matriz booleana (verdadeiro/falso).
// *1 converte a matriz booleana para números (1 para verdadeiro, 0 para falso).
rand('seed',35);
grade = (rand(linhas, colunas) < probVida)*1; 
grade = zeros(linhas, colunas);
m=20;
grade(40:40+m-1,40:40+m-1) = (rand(m,m) < probVida)*1; 

// 1. NAVE ESPACIAL (Glider) - 3x3 - Posição: linhas 5-7, cols 5-7
glider = [0 1 0;   // Forma clássica do Glider
          0 0 1;
          1 1 1];
grade(5:7, 5:7) = glider;
grade(5:7, 35:37) = glider;

// 2. R-PENTOMINO - Evolui de forma interessante - 3x3 - Pos: lin 5-7, col 20-22
r_pentomino = [0 1 1;
               1 1 0;
               0 1 0];
grade(5:7, 20:22) = r_pentomino;

// 3. BLINKER VERTICAL (oscilador período 2) - 3x1 - Pos: lin 20-22, col 40
blinker_v = [1; 1; 1];
grid(20:22, 40) = blinker_v;

// 4. BLOCK (estático) - 2x2 - Pos: lin 25-26, col 25-26
block = [1 1;
         1 1];
grade(26:27, 28:29) = block;

// 5. TOAD (oscilador período 2) - 2x4 - Pos: lin 10-11, col 30-33
toad = [0 1 1 1;
        1 1 1 0];
grade(10:11, 30:33) = toad;

// 6. BEACON (bloco pulsante, período 2) - 4x4 - Pos: lin 35-38, col 10-13
beacon = [1 1 0 0;
          1 1 0 0;
          0 0 1 1;
          0 0 1 1];
grade(35:38, 10:13) = beacon;

// Matriz LWSS (5 linhas x 4 colunas)
lwss = [0 1 1 1 1;   // Linha 1 (topo)
        1 0 0 0 1;   // Linha 2
        0 0 0 0 1;   // Linha 3
        1 0 0 1 0];  // Linha 4 (base)
grade(15:18, 50:54) = lwss;
        
// --- 2. Função para Calcular a Próxima Geração ---
// Define uma função que aplica as regras do Jogo da Vida
function novaGrade=proximaGeracao(gradeAtual)
    
    // Obtém as dimensões da grade
    [r, c] = size(gradeAtual); 
    
    // Inicializa a nova grade (todos mortos)
    novaGrade = zeros(r, c); 
    
    // Loop sobre cada célula da grade
    for i = 1:r
        for j = 1:c
            
            // --- A. Contagem de Vizinhos Vivos ---
            
            // Inicializa a contagem de vizinhos
            vizinhosVivos = 0; 
            
            // Loop de 3x3 em torno da célula (i, j), 
            // ignorando a própria célula
            for di = -1:1
                for dj = -1:1
                    
                    // Se (di, dj) = (0, 0), é a própria célula, 
                    // então pule.
                    if di == 0 & dj == 0 then
                        continue
                    end
                    
                    // Coordenadas do vizinho
                    ni = i + di;
                    nj = j + dj;
                    
                    // Checa se o vizinho está dentro dos limites da grade
                    // Usa a topologia de grade finita (não toroidal/cíclica)
                    if ni >= 1 & ni <= r & nj >= 1 & nj <= c then
                        // Adiciona o estado do vizinho (1 se vivo, 0 se morto)
                        vizinhosVivos = vizinhosVivos + gradeAtual(ni, nj);
                    end
                end
            end
            
            // --- B. Aplicação das Regras (4 Regras Fundamentais) ---
            
            // Estado atual da célula
            estadoAtual = gradeAtual(i, j);
            
            if estadoAtual == 1 then
                // 1. Sobrevivência: Se viva e tem 2 ou 3 vizinhos vivos, continua viva.
                if vizinhosVivos == 2 | vizinhosVivos == 3 then
                    novaGrade(i, j) = 1;
                // 2. Subpopulação/Superpopulação: Caso contrário (0, 1, ou >3), morre.
                else
                    novaGrade(i, j) = 0; // Morre
                end
            else // estadoAtual == 0 (Célula Morta)
                // 3. Nascimento: Se morta e tem EXATAMENTE 3 vizinhos vivos, nasce (fica viva).
                if vizinhosVivos == 3 then
                    novaGrade(i, j) = 1; // Nasce
                // 4. Continua Morta: Caso contrário, permanece morta.
                else
                    novaGrade(i, j) = 0; // Permanece morta
                end
            end
            
        end
    end 
endfunction

// --- 3. Simulação e Visualização (Loop Principal) ---

disp("Iniciando Jogo da Vida...")

    f = scf();
    f.color_map = graycolormap(32);

uicontrol(f, ...
    "style", "pushbutton", ...
    "string", "Fim", ...
    "units", "normalized", ...
    "position", [0.88 0.05 0.1 0.06], ...
    "fontsize", 14, ...
    "fontweight", "bold", ...
    "backgroundcolor", [0.9 0.3 0.3], ...
    "foregroundcolor", [1 1 1], ...
    "callback", "close(gcf()); abort;", ...  // fecha a figura atual
    "callback_type", 10);  // permite usar comandos Scilab diretamente

W = 0;
g = 0;
while W < 1
    g = g + 1;
    if g>2500 then W = 2; end; 
    // Calcula a próxima geração
    grade = proximaGeracao(grade); 
    
    // --- Visualização (Plotagem) ---
    
    // Plota a matriz 'grade' como uma imagem
    // image(grade) mostra a matriz como pixels, onde o valor (0 ou 1)
    // é mapeado para uma cor. 
    // Usamos 'grayplot' para maior controle sobre a visualização de matrizes.
    
    // Define os limites dos eixos
    x = 1:colunas; 
    y = 1:linhas; 

    // O comando grayplot plota a matriz de dados (grade)
    // O 'colmap' (mapa de cores) é definido para ter apenas duas cores:
    // 0 (morto) -> Cor 1 (Branco - 'wh')
    // 1 (vivo) -> Cor 2 (Preto - 'bl')
    
    // Matplot: Plota uma matriz de valores, mapeando os valores diretamente.
    // Usamos -grade para inverter a escala de cores, se necessário.
    // Melhor usar 'Matplot' e definir a paleta:
      
    // Plota a grade usando as cores definidas
    // Matplot(grade) mapeia: 0 -> Cor 1 (Branco), 1 -> Cor 2 (Preto)
    Matplot(32*grade);
    
    // Configurações estéticas para parecer um autômato celular
    a = gca(); // Pega a alça para os eixos atuais
    a.box = "on"; // Desenha a moldura
    //a.x_location = "top"; // Eixo X em cima (opcional)
    //a.y_reverse = "on"; // Inverte o eixo Y para começar no canto superior esquerdo
    a.x_label.text = ""; // Remove o rótulo X
    a.y_label.text = ""; // Remove o rótulo Y
    a.title.text = "Geração " + string(g); // Título com o número da geração
    
    // Pausa para visualizar a animação. O 0.1 é em segundos.
    sleep(0.01); 
end

disp("Simulação concluída.")
// =========================================

Nenhum comentário:

Postar um comentário