Conway’s Game Of “Life”#
Summary#
The Game of Life, also known simply as “Life”, is a cellular automaton devised by the British mathematician John Horton Conway in 1970.
The “game” is a zero-player game, meaning that its evolution is determined by its initial state, requiring no further input by a human. One interacts with the Game of “Life” by creating an initial configuration and observing how it evolves, or, for advanced “players”, by creating patterns with particular properties.
How to Run#
To run the model interactively you can use either the streamlit or solara version. For solara, you use
$ solara run app.py
For streamlit, you need
$ streamlit run st_app.py
This will open your browser and show you the controls. You can start the model by hitting the run button.
Files#
agents.py
: Defines the behavior of an individual cell, which can be in two states: DEAD or ALIVE.model.py
: Defines the model itself, initialized with a random configuration of alive and dead cells.app.py
: Defines an interactive visualization using solara.st_app.py
: Defines an interactive visualization using Streamlit.
Optional#
For the streamlit version, you need to have streamlit installed (can be done via pip install streamlit)
Further Reading#
Agents#
from mesa import Agent
class Cell(Agent):
"""Represents a single ALIVE or DEAD cell in the simulation."""
DEAD = 0
ALIVE = 1
def __init__(self, pos, model, init_state=DEAD):
"""Create a cell, in the given state, at the given x, y position."""
super().__init__(model)
self.x, self.y = pos
self.state = init_state
self._next_state = None
@property
def is_alive(self):
return self.state == self.ALIVE
@property
def neighbors(self):
return self.model.grid.iter_neighbors((self.x, self.y), True)
def determine_state(self):
"""Compute if the cell will be dead or alive at the next tick. This is
based on the number of alive or dead neighbors. The state is not
changed here, but is just computed and stored in self._nextState,
because our current state may still be necessary for our neighbors
to calculate their next state.
"""
# Get the neighbors and apply the rules on whether to be alive or dead
# at the next tick.
live_neighbors = sum(neighbor.is_alive for neighbor in self.neighbors)
# Assume nextState is unchanged, unless changed below.
self._next_state = self.state
if self.is_alive:
if live_neighbors < 2 or live_neighbors > 3:
self._next_state = self.DEAD
else:
if live_neighbors == 3:
self._next_state = self.ALIVE
def assume_state(self):
"""Set the state to the new computed state -- computed in step()."""
self.state = self._next_state
Model#
from mesa import Model
from mesa.examples.basic.conways_game_of_life.agents import Cell
from mesa.space import SingleGrid
class ConwaysGameOfLife(Model):
"""Represents the 2-dimensional array of cells in Conway's Game of Life."""
def __init__(self, width=50, height=50, initial_fraction_alive=0.2, seed=None):
"""Create a new playing area of (width, height) cells."""
super().__init__(seed=seed)
# Use a simple grid, where edges wrap around.
self.grid = SingleGrid(width, height, torus=True)
# Place a cell at each location, with some initialized to
# ALIVE and some to DEAD.
for _contents, (x, y) in self.grid.coord_iter():
cell = Cell((x, y), self)
if self.random.random() < initial_fraction_alive:
cell.state = cell.ALIVE
self.grid.place_agent(cell, (x, y))
self.running = True
def step(self):
"""Perform the model step in two stages:
- First, all cells assume their next state (whether they will be dead or alive)
- Then, all cells change state to their next state.
"""
self.agents.do("determine_state")
self.agents.do("assume_state")
App#
from mesa.examples.basic.conways_game_of_life.model import ConwaysGameOfLife
from mesa.visualization import (
SolaraViz,
make_space_component,
)
def agent_portrayal(agent):
return {
"color": "white" if agent.state == 0 else "black",
"marker": "s",
"size": 25,
}
def post_process(ax):
ax.set_aspect("equal")
ax.set_xticks([])
ax.set_yticks([])
model_params = {
"seed": {
"type": "InputText",
"value": 42,
"label": "Random Seed",
},
"width": {
"type": "SliderInt",
"value": 50,
"label": "Width",
"min": 5,
"max": 60,
"step": 1,
},
"height": {
"type": "SliderInt",
"value": 50,
"label": "Height",
"min": 5,
"max": 60,
"step": 1,
},
"initial_fraction_alive": {
"type": "SliderFloat",
"value": 0.2,
"label": "Cells initially alive",
"min": 0,
"max": 1,
"step": 0.01,
},
}
# Create initial model instance
model1 = ConwaysGameOfLife()
# Create visualization elements. The visualization elements are solara components
# that receive the model instance as a "prop" and display it in a certain way.
# Under the hood these are just classes that receive the model instance.
# You can also author your own visualization elements, which can also be functions
# that receive the model instance and return a valid solara component.
SpaceGraph = make_space_component(
agent_portrayal, post_process=post_process, draw_grid=False
)
# Create the SolaraViz page. This will automatically create a server and display the
# visualization elements in a web browser.
# Display it using the following command in the example directory:
# solara run app.py
# It will automatically update and display any changes made to this file
page = SolaraViz(
model1,
components=[SpaceGraph],
model_params=model_params,
name="Game of Life",
)
page # noqa