AC: Jogo da Vida

De Física Computacional
Revisão de 08h12min de 22 de julho de 2022 por Jhordan (discussão | contribs)
(dif) ← Edição anterior | Revisão atual (dif) | Versão posterior → (dif)
Ir para navegação Ir para pesquisar

Anterior: Modelos Logísticos | Índice: Ecologia | Próximo: Modelo de Lotka-Volterra

Modelos conceituais

O "Jogo da Vida" é um jogo criado em 1970 pelo matemático Horton Conway usando autômatos celulares. Na proposta original o jogo da vida é composto por uma grade de células quaradas, onde cada célula pode estar morta ou viva, e seu estado é atualizado a cada passo finito de tempo baseado no estado atual da própria célula e das 8 células vizinhas (vizinhança de Moore). Estas regras podem ser enunciadas da seguinte forma:

  • Reprodução: Qualquer célula morta com exatamente três vizinhos vivos será uma célula viva no próximo passo.
  • Sobrevivência: Qualquer célula viva com 2 ou 3 vizinhos vivos permanece vivo no próximo passo.
  • Subpopulação: A célula morre se tiver menos de 2 vizinhos vivos na vizinhança.
  • Superpopulação: A célula morre se tiver mais de 3 vizinhos na vizinhança.

Um autômato celular probabilístico foi proposto como uma extensão no jogo da vida. A intenção é possuir um modelo mais flexível com a possibilidade de alterar os limites do Jogo da Vida clássico. A ideia básica do PCAEGOL (Probabilistic Cellular Automata, Extension of the Game Of Life) é que os estados das células vizinhas de uma dada célula não são conhecidos com exatidão, dessa forma o estado resultante não é determinístico e há uma certa probabilidade de uma célula que teria determinado resultado deterministicamente, agora tenha outro. A ideia é que o sistema nem sempre se comporte conforme é esperado. As probabilidades podem ser definidas individualmente para cada célula vizinha de uma determinada célula. Por exemplo, tendo a célula central C0 e chamando as 8 células vizinhas de Ci (onde i=1,2,...,8), então temos os vetores:

  • pl = (pl0,pl1,...,pl8), onde pli é a probabilidade da célula Ci ser considerada viva quando está viva.
  • pd = (pd0,pd1,...,pd8), onde pdi é a probabilidade da célula Ci ser considerada viva quando está morta.

Podemos recuperar o jogo da vida facilmente fazendo pli=1$ e pdi=0. Além disso as regras clássicas do jogo da vida podem ser estendidas fazendo:

  • MAXO = Número máximo de células vizinhas vivas para que não ocorre superpopulação
  • MINU = Número máximo de células vizinhas vivas para que não ocorre superpopulação
  • MAXB = Número máximo de células vizinhas vivas para que a célula nasça no próximo passo
  • MINB = Número mínimo de células vizinhas vivas para que a célula nasça no próximo passo

Novamente podemos recuperar o Jogo da Vida clássico fazendo MAXO=3,MINU=2, MAXB=3 e MINB=3.

Código do Jogo da Vida

Mesa é um framework em Python para desenvolvimento de modelos baseados em agentes, porém também podemos simular autômatos celulares e visualizar o resultado, por isto o mesmo foi utilizado para desenvolver ambos os códigos a seguir. Algun tutoriais podem ser consultados aqui e aqui.

Para começar, precisamos definir duas classes: a de cada agent (no nosso caso cada célula) e a própria do modelo. Cada classe tem algumas obrigatórias relacionadas a forma com que avançamos o modelo. Como é assíncrono, o agente tem uma função para preparar as mudanças (step) e outra para aplicar as mudanças (advance). Além disso precisamos passar algumas informações para o modelo: que as células estão distribuídas em grade, a largura e altura da grade, e que as alterações ocorrem de forma assíncrona.

#Bibliotecas necessárias
from mesa import Agent, Model                                      #Importamos as classes Agente e Modelo
from mesa.time import SimultaneousActivation                       #Importamos o agendador por ordem aleatória
from mesa.space import MultiGrid                                   #Importamos a malha
from mesa.visualization.modules import CanvasGrid                  #Gerar o JSON
from mesa.visualization.ModularVisualization import ModularServer  #Ler o JSON
import random                                                      #Biblioteca para números aleatórios

#O Modelo em si

class Planta(Agent):
    """Dinâmica da planta inspirada no jogo da vida"""
    def __init__(self, ide, modelo,frac):
        super().__init__(ide, modelo)       #Requisito da biblioteca
        self.vivo = random.random() < frac  #Guardar o estado atual
        self.passo=self.vivo                #Guardar o próximo estado

    def step(self):                         # Método que aplica as mudanças a cada passo
        #Pegamos a coordenada da vizinhança
        vizinhos = self.model.grid.get_neighborhood(self.pos,moore=True,include_center=False)
        nviz=0     
        agentes = self.model.grid.get_cell_list_contents(vizinhos) #E o conteúdo de cada célula
        for planta in agentes:
            if (planta.vivo==True):
                nviz+=1
        if (self.vivo==True):
            if   (nviz<2 or nviz>3):
                self.passo = False
        elif (nviz==3):
                self.passo=True

    def advance(self): #E no simultâneo precisa de um método avance() que é o que aplica as mudanças assíncronas
        self.vivo=self.passo
                
class Modelo(Model):
    """Modelo geral"""
    def __init__(self, frac, largura, altura):
        self.frac = frac
        self.grid = MultiGrid(largura, altura, True)    #Grade
        self.schedule = SimultaneousActivation(self)    #Assíncrono
        self.running = True                             #Sem codições de parada
        # Criar agentes
        i=0
        for X in range(largura):
            for Y in range(altura):
                a = Planta(i, self,self.frac)
                self.schedule.add(a)
                self.grid.place_agent(a, (X, Y))
                i+=1

    def step(self):                                    #Avança o modelo
        self.schedule.step()

Para exibir a simulação na tela, ainda precisamos definir como gerar o JSON em que especificamos como cada célula deve ser exibida na tela. No caso omo retângulos 1x1 onde a cor depende se a célula esta viva ou morta.

#Define como gerar esse JSON
def retrato(planta):         
    """Definir o retrato do agente"""
    portrayal = {"Shape": "rect","Filled": "true","h": 1.0,"w":1.0,"Layer":1}    
    if (planta.vivo == True):
        portrayal["Color"] = "green" 
    else:
        portrayal["Color"] = "grey"
    return portrayal

Definimos então a grade para exibir na tela:

#Definimos uma grade de 10x10 células e 500x500 píxeis
grade = CanvasGrid(retrato, 10, 10, 500, 500)

E lançamos o servidor:

#Lançamo o servidor
servidor = ModularServer(Modelo, [grade], "Jogo da Vida",{"frac":0.1, "largura":10, "altura":10}) 
servidor.port = 8521                                        
servidor.launch()                                              #Lançamos o servidor

Código do PCAEGOL

O código do PCAEGL segue a mesma ideia do anterior, com as alterações pertinentes devido às diferenças de modelo.

#Bibliotecas necessárias
from mesa import Agent, Model                                      #Classes Agente e Modelo
from mesa.time import SimultaneousActivation                       #Agendador simultâneo
from mesa.space import MultiGrid                                   #Malha multigrid
from mesa.visualization.modules import CanvasGrid                  #Gerar o JSON
from mesa.visualization.ModularVisualization import ModularServer  #Ler o JSON
import random                                                      #Número aleatórios

#Classe do agente
class Agente(Agent):
    """Classe do agente"""
    def __init__(self, ide, modelo):
        """Função incializadora"""
        super().__init__(ide, modelo)     #Necessário para funcionar o modelo
        F = 0.5                           #Fração de plantas vivas
        self.vivo  = random.random() < F  #Definimos o estado inicial da planta
        self.passo = self.vivo            #E já definimos a situação no próximo estado

    def step(self):
        """Método obrigatório que prepara as mudanças"""
        #pl = 1;pd = 0;MAXO=3;MINU=2;MAXB=3;MINB=3
        pl = 1;pd = 0;MAXO=6;MINU=2;MAXB=3;MINB=3                                                    #Parâmetros do PCAEGOL
        vizinhos = self.model.grid.get_neighborhood(self.pos,moore=True,include_center=False)        #Coordenada da vizinhança
        nviz=0                                                                                       #Contador de vizinhos
        agentes = self.model.grid.get_cell_list_contents(vizinhos)                                   #Agentes na vizinhança
        for planta in agentes:                                                                       #Percorre o agentes
            estado = (random.random() <pl) if (planta.vivo) else (random.random() <pd)               #Se está vivo ou não
            nviz = (nviz+1) if (estado) else (nviz)                                                  #Soma o mais um vizinho
        estado = (random.random() <pl) if (self.vivo) else (random.random() <pd)
        if (estado):
            if (nviz<MINU or nviz>MAXO):                                                             #Sub ou super população
                self.passo = False
        elif (nviz >= MINB and nviz<=MAXB):                                                          #Reprodução
                self.passo = True
                
    def advance(self):
        """Método obrigatório que aplica as mudanças"""
        self.vivo=self.passo   #Aplica as mudanças
                
class Modelo(Model):
    """Modelo geral"""
    def __init__(self, largura, altura):
        """Função chamada quando o modelo é inicializazdo"""
        self.grid     = MultiGrid(largura, altura, True)         #Configura a grade
        self.schedule = SimultaneousActivation(self)             #Configura o agendador
        self.running = True                                      #Condiçao para seguir executando o modelo
        # Distribuir agentes em toda grade 
        i=0
        for X in range(largura):
            for Y in range(altura):
                a = Agente(i, self)
                self.schedule.add(a)
                self.grid.place_agent(a, (X, Y))
                i+=1
    def step(self):
        """Avançar um passo do modelo"""
        self.schedule.step()

#Define como gerar esse JSON
def retrato(agente):         
    """Definir o retrato do agente"""
    portrayal = {"Shape": "rect","Filled": "true","h": 1.0,"w":1.0,"Layer":1}    
    if (agente.vivo == True):
        portrayal["Color"] = "green" 
    else:
        portrayal["Color"] = "grey"
    return portrayal

#Lançamo o servidor
grade = CanvasGrid(retrato, 20, 20, 500, 500)
servidor = ModularServer(Modelo, [grade], "Jogo da Vida",{"largura":20, "altura":20}) 
servidor.port = 8521 
servidor.launch()      


Principal material utilizado

A probabilistic extension to Conway’s Game of Life (Gabriel Aguilera-Venegas e outros,)

Anterior: Modelos Logísticos | Índice: Ecologia | Próximo: Modelo de Lotka-Volterra