Skip to content

Game of Life

dissmodel.models.ca.game_of_life.GameOfLife

Bases: CellularAutomaton

Spatial cellular automaton implementation of Conway's Game of Life.

Cells live or die based on the number of live neighbors according to the following rules:

  • A live cell with fewer than 2 or more than 3 live neighbors dies.
  • A live cell with 2 or 3 live neighbors survives.
  • A dead cell with exactly 3 live neighbors becomes alive.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame with geometries and a state attribute.

required
**kwargs Any

Extra keyword arguments forwarded to :class:~dissmodel.geo.CellularAutomaton.

{}

Examples:

>>> from dissmodel.geo import regular_grid
>>> from dissmodel.core import Environment
>>> gdf = regular_grid(dimension=(5, 5), resolution=1, attrs={"state": 0})
>>> env = Environment(end_time=3)
>>> gol = GameOfLife(gdf=gdf)
>>> gol.initialize()
Source code in dissmodel/models/ca/game_of_life.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
class GameOfLife(CellularAutomaton):
    """
    Spatial cellular automaton implementation of Conway's Game of Life.

    Cells live or die based on the number of live neighbors according to
    the following rules:

    - A live cell with fewer than 2 or more than 3 live neighbors dies.
    - A live cell with 2 or 3 live neighbors survives.
    - A dead cell with exactly 3 live neighbors becomes alive.

    Parameters
    ----------
    gdf : geopandas.GeoDataFrame
        GeoDataFrame with geometries and a ``state`` attribute.
    **kwargs :
        Extra keyword arguments forwarded to
        :class:`~dissmodel.geo.CellularAutomaton`.

    Examples
    --------
    >>> from dissmodel.geo import regular_grid
    >>> from dissmodel.core import Environment
    >>> gdf = regular_grid(dimension=(5, 5), resolution=1, attrs={"state": 0})
    >>> env = Environment(end_time=3)
    >>> gol = GameOfLife(gdf=gdf)
    >>> gol.initialize()
    """

    def setup(self) -> None:
        """Build the Queen neighborhood for the grid."""
        self.create_neighborhood(strategy=Queen, use_index=True)

    def initialize(self) -> None:
        """
        Fill the grid with a random initial state.

        Uses a 60/40 live/dead split with a fixed seed for reproducibility.
        Override this method to define a custom initial state.
        """
        fill(
            strategy=FillStrategy.RANDOM_SAMPLE,
            gdf=self.gdf,
            attr="state",
            data={1: 0.6, 0: 0.4},
            seed=42,
        )

    def initialize_patterns(
        self,
        patterns: list[str] | None = None,
    ) -> None:
        """
        Place classic Game of Life patterns at random positions on the grid.

        Parameters
        ----------
        patterns : list of str, optional
            Pattern names to place. If ``None``, all patterns in
            :data:`PATTERNS` are used. Available keys: ``"blinker"``,
            ``"toad"``, ``"beacon"``, ``"pulsar"``, ``"glider"``,
            ``"lwss"``, ``"block"``, ``"beehive"``, ``"loaf"``.

        Notes
        -----
        Assumes a square grid. The ``"pulsar"`` pattern requires a grid
        of at least 15x15 to avoid out-of-range placement.
        """
        selected = (
            {k: PATTERNS[k] for k in patterns if k in PATTERNS}
            if patterns
            else PATTERNS
        )

        grid_dim = int(len(self.gdf) ** 0.5)

        for pattern in selected.values():
            start_x = random.randint(0, grid_dim - len(pattern[0]))   # col _ offset bounded by n_cols
            start_y = random.randint(0, grid_dim - len(pattern))     # row offset bounded by n_rows

            fill(
                strategy=FillStrategy.PATTERN,
                gdf=self.gdf,
                attr="state",
                pattern=pattern,
                start_x=start_x,
                start_y=start_y,
            )

    def rule(self, idx: Any) -> int:
        """
        Apply the Game of Life transition rule to cell ``idx``.

        Parameters
        ----------
        idx : any
            Index of the cell being evaluated.

        Returns
        -------
        int
            ``1`` if the cell is alive after the transition, ``0`` if dead.
        """
        state = self.gdf.loc[idx, self.state_attr]
        live_neighbors = (self.neighbor_values(idx, self.state_attr)).sum()

        if state == 1:
            return 1 if 2 <= live_neighbors <= 3 else 0
        return 1 if live_neighbors == 3 else 0

initialize()

Fill the grid with a random initial state.

Uses a 60/40 live/dead split with a fixed seed for reproducibility. Override this method to define a custom initial state.

Source code in dissmodel/models/ca/game_of_life.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def initialize(self) -> None:
    """
    Fill the grid with a random initial state.

    Uses a 60/40 live/dead split with a fixed seed for reproducibility.
    Override this method to define a custom initial state.
    """
    fill(
        strategy=FillStrategy.RANDOM_SAMPLE,
        gdf=self.gdf,
        attr="state",
        data={1: 0.6, 0: 0.4},
        seed=42,
    )

initialize_patterns(patterns=None)

Place classic Game of Life patterns at random positions on the grid.

Parameters:

Name Type Description Default
patterns list of str

Pattern names to place. If None, all patterns in :data:PATTERNS are used. Available keys: "blinker", "toad", "beacon", "pulsar", "glider", "lwss", "block", "beehive", "loaf".

None
Notes

Assumes a square grid. The "pulsar" pattern requires a grid of at least 15x15 to avoid out-of-range placement.

Source code in dissmodel/models/ca/game_of_life.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
def initialize_patterns(
    self,
    patterns: list[str] | None = None,
) -> None:
    """
    Place classic Game of Life patterns at random positions on the grid.

    Parameters
    ----------
    patterns : list of str, optional
        Pattern names to place. If ``None``, all patterns in
        :data:`PATTERNS` are used. Available keys: ``"blinker"``,
        ``"toad"``, ``"beacon"``, ``"pulsar"``, ``"glider"``,
        ``"lwss"``, ``"block"``, ``"beehive"``, ``"loaf"``.

    Notes
    -----
    Assumes a square grid. The ``"pulsar"`` pattern requires a grid
    of at least 15x15 to avoid out-of-range placement.
    """
    selected = (
        {k: PATTERNS[k] for k in patterns if k in PATTERNS}
        if patterns
        else PATTERNS
    )

    grid_dim = int(len(self.gdf) ** 0.5)

    for pattern in selected.values():
        start_x = random.randint(0, grid_dim - len(pattern[0]))   # col _ offset bounded by n_cols
        start_y = random.randint(0, grid_dim - len(pattern))     # row offset bounded by n_rows

        fill(
            strategy=FillStrategy.PATTERN,
            gdf=self.gdf,
            attr="state",
            pattern=pattern,
            start_x=start_x,
            start_y=start_y,
        )

rule(idx)

Apply the Game of Life transition rule to cell idx.

Parameters:

Name Type Description Default
idx any

Index of the cell being evaluated.

required

Returns:

Type Description
int

1 if the cell is alive after the transition, 0 if dead.

Source code in dissmodel/models/ca/game_of_life.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
def rule(self, idx: Any) -> int:
    """
    Apply the Game of Life transition rule to cell ``idx``.

    Parameters
    ----------
    idx : any
        Index of the cell being evaluated.

    Returns
    -------
    int
        ``1`` if the cell is alive after the transition, ``0`` if dead.
    """
    state = self.gdf.loc[idx, self.state_attr]
    live_neighbors = (self.neighbor_values(idx, self.state_attr)).sum()

    if state == 1:
        return 1 if 2 <= live_neighbors <= 3 else 0
    return 1 if live_neighbors == 3 else 0

setup()

Build the Queen neighborhood for the grid.

Source code in dissmodel/models/ca/game_of_life.py
112
113
114
def setup(self) -> None:
    """Build the Queen neighborhood for the grid."""
    self.create_neighborhood(strategy=Queen, use_index=True)