Conway’s Game of Life

This version of Conway’s Game of Life speeds everything up by using controlling a cell’s visibility through its alpha value, and handing the drawing logic off to the graphics card.

Grid-based games can take a while to render the program uses classic raster-based graphics. Every cell has to be re-drawn every single frame. If the cells are complex at all, that adds to the rendering time.

In this program, we create all cells in the grid to begin with. (This does causes the program to pause a while at start-up.)

After the sprites are created, we turn the cells on and off by their alpha value. We can update the entire grid by simply sending a list of alpha values to the graphics card. This significantly improves drawing time.

conway_alpha.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 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
"""
Conway's Game of Life

This code shows how to set up sprites in a grid, and then use their
'alpha' value to quickly turn them on and off.

After installing the "arcade" package version 2.4.4+, this program can be run by
typing:
python -m arcade.examples.conway_alpha
"""
import arcade
import random

# Set how many rows and columns we will have
ROW_COUNT = 70
COLUMN_COUNT = 128

# This sets the WIDTH and HEIGHT of each grid location
CELL_WIDTH = 15
CELL_HEIGHT = 15

# This sets the margin between each cell
# and on the edges of the screen.
CELL_MARGIN = 0

# Do the math to figure out our screen dimensions
SCREEN_WIDTH = (CELL_WIDTH + CELL_MARGIN) * COLUMN_COUNT + CELL_MARGIN
SCREEN_HEIGHT = (CELL_HEIGHT + CELL_MARGIN) * ROW_COUNT + CELL_MARGIN
SCREEN_TITLE = "Conway's Game of Life"

# Colors and alpha values
ALIVE_COLOR = arcade.color.BISTRE
BACKGROUND_COLOR = arcade.color.ANTIQUE_WHITE
ALPHA_ON = 255
ALPHA_OFF = 0


def create_grids():
    """
    Create a 2D and 1D grid of sprites. We use the 1D SpriteList for drawing,
    and the 2D list for accessing via grid. Both lists point to the same set of
    sprites.
    """
    # One dimensional list of all sprites in the two-dimensional sprite list
    grid_sprites_one_dim = arcade.SpriteList()

    # This will be a two-dimensional grid of sprites to mirror the two
    # dimensional grid of numbers. This points to the SAME sprites that are
    # in grid_sprite_list, just in a 2d manner.
    grid_sprites_two_dim = []

    # Create a list of sprites to represent each grid location
    for row in range(ROW_COUNT):
        grid_sprites_two_dim.append([])

        for column in range(COLUMN_COUNT):

            # Make the sprite as a soft circle
            sprite = arcade.SpriteCircle(CELL_WIDTH // 2, ALIVE_COLOR, soft=True)

            # Position the sprite
            x = column * (CELL_WIDTH + CELL_MARGIN) + (CELL_WIDTH / 2 + CELL_MARGIN)
            y = row * (CELL_HEIGHT + CELL_MARGIN) + (CELL_HEIGHT / 2 + CELL_MARGIN)
            sprite.center_x = x
            sprite.center_y = y

            # Add the sprite to both lists
            grid_sprites_one_dim.append(sprite)
            grid_sprites_two_dim[row].append(sprite)

    return grid_sprites_one_dim, grid_sprites_two_dim


def randomize_grid(grid: arcade.SpriteList):
    """ Randomize the grid to alive/dead """
    for cell in grid:
        pick = random.randrange(2)
        if pick:
            cell.alpha = ALPHA_ON
        else:
            cell.alpha = ALPHA_OFF


class MyGame(arcade.Window):
    """
    Main application class.
    """

    def __init__(self, width: int, height: int, title: str):
        """
        Set up the application.
        """
        super().__init__(width, height, title)

        self.background_color = BACKGROUND_COLOR

        # We need two layers. One holds the current state of our grid, the other
        # holds the next frame's state. We flip back and forth between the two.
        grid_sprites_one_dim1, grid_sprites_two_dim1 = create_grids()
        grid_sprites_one_dim2, grid_sprites_two_dim2 = create_grids()

        self.layers_grid_sprites_one_dim = [grid_sprites_one_dim1, grid_sprites_one_dim2]
        self.layers_grid_sprites_two_dim = [grid_sprites_two_dim1, grid_sprites_two_dim2]

        self.cur_layer = 0
        randomize_grid(self.layers_grid_sprites_one_dim[0])

    def on_draw(self):
        """ Render the screen. """
        # Clear all pixels in the window
        self.clear()
        self.layers_grid_sprites_one_dim[0].draw()

    def on_update(self, delta_time: float):
        """ Update the grid """

        # Flip layers
        if self.cur_layer == 0:
            layer1 = self.layers_grid_sprites_two_dim[0]
            layer2 = self.layers_grid_sprites_two_dim[1]
            self.cur_layer = 1
        else:
            layer1 = self.layers_grid_sprites_two_dim[1]
            layer2 = self.layers_grid_sprites_two_dim[0]
            self.cur_layer = 0

        # Count the neighbors that are alive
        for row in range(ROW_COUNT):
            for column in range(COLUMN_COUNT):
                live_neighbors = 0
                # -1 -1
                if row > 0 and column > 0 \
                        and layer1[row - 1][column - 1].alpha == ALPHA_ON:
                    live_neighbors += 1
                # -1  0
                if row > 0 and layer1[row - 1][column].alpha == ALPHA_ON:
                    live_neighbors += 1
                # -1 +1
                if row > 0 and column < COLUMN_COUNT - 1\
                        and layer1[row - 1][column + 1].alpha == ALPHA_ON:
                    live_neighbors += 1
                #  0 +1
                if column < COLUMN_COUNT - 1 \
                        and layer1[row][column + 1].alpha == ALPHA_ON:
                    live_neighbors += 1
                # +1 +1
                if row < ROW_COUNT - 1 \
                        and column < COLUMN_COUNT - 1 \
                        and layer1[row + 1][column + 1].alpha == ALPHA_ON:
                    live_neighbors += 1
                # +1  0
                if row < ROW_COUNT - 1 and layer1[row + 1][column].alpha == ALPHA_ON:
                    live_neighbors += 1
                # +1 -1
                if row < ROW_COUNT - 1 and column > 0 \
                        and layer1[row + 1][column - 1].alpha == ALPHA_ON:
                    live_neighbors += 1
                #  0 -1
                if column > 0 and layer1[row][column - 1].alpha == ALPHA_ON:
                    live_neighbors += 1

                """
                Implement Conway's game of life rules

                Any live cell with two or three live neighbours survives.
                Any dead cell with three live neighbours becomes a live cell.
                All other live cells die in the next generation. Similarly, all other dead cells stay dead.
                """
                if layer1[row][column].alpha == ALPHA_ON and (live_neighbors == 2 or live_neighbors == 3):
                    if layer2[row][column].alpha == ALPHA_OFF:
                        layer2[row][column].alpha = ALPHA_ON
                elif layer1[row][column].alpha == ALPHA_OFF and live_neighbors == 3:
                    if layer2[row][column].alpha == ALPHA_OFF:
                        layer2[row][column].alpha = ALPHA_ON
                else:
                    if layer2[row][column].alpha == ALPHA_ON:
                        layer2[row][column].alpha = ALPHA_OFF


def main():
    """ Main function - starting point to the program """
    window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
    window.center_window()
    arcade.run()


if __name__ == "__main__":
    main()