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"""
  2Conway's Game of Life
  3
  4This code shows how to set up sprites in a grid, and then use their
  5'alpha' value to quickly turn them on and off.
  6
  7After installing the "arcade" package version 2.4.4+, this program can be run by
  8typing:
  9python -m arcade.examples.conway_alpha
 10"""
 11from __future__ import annotations
 12
 13import arcade
 14import random
 15
 16# Set how many rows and columns we will have
 17ROW_COUNT = 70
 18COLUMN_COUNT = 128
 19
 20# This sets the WIDTH and HEIGHT of each grid location
 21CELL_WIDTH = 15
 22CELL_HEIGHT = 15
 23
 24# This sets the margin between each cell
 25# and on the edges of the screen.
 26CELL_MARGIN = 0
 27
 28# Do the math to figure out our screen dimensions
 29SCREEN_WIDTH = (CELL_WIDTH + CELL_MARGIN) * COLUMN_COUNT + CELL_MARGIN
 30SCREEN_HEIGHT = (CELL_HEIGHT + CELL_MARGIN) * ROW_COUNT + CELL_MARGIN
 31SCREEN_TITLE = "Conway's Game of Life"
 32
 33# Colors and alpha values
 34ALIVE_COLOR = arcade.color.BISTRE
 35BACKGROUND_COLOR = arcade.color.ANTIQUE_WHITE
 36ALPHA_ON = 255
 37ALPHA_OFF = 0
 38
 39
 40def create_grids():
 41    """
 42    Create a 2D and 1D grid of sprites. We use the 1D SpriteList for drawing,
 43    and the 2D list for accessing via grid. Both lists point to the same set of
 44    sprites.
 45    """
 46    # One dimensional list of all sprites in the two-dimensional sprite list
 47    grid_sprites_one_dim = arcade.SpriteList()
 48
 49    # This will be a two-dimensional grid of sprites to mirror the two
 50    # dimensional grid of numbers. This points to the SAME sprites that are
 51    # in grid_sprite_list, just in a 2d manner.
 52    grid_sprites_two_dim = []
 53
 54    # Create a list of sprites to represent each grid location
 55    for row in range(ROW_COUNT):
 56        grid_sprites_two_dim.append([])
 57
 58        for column in range(COLUMN_COUNT):
 59
 60            # Make the sprite as a soft circle
 61            sprite = arcade.SpriteCircle(CELL_WIDTH // 2, ALIVE_COLOR, soft=True)
 62
 63            # Position the sprite
 64            x = column * (CELL_WIDTH + CELL_MARGIN) + (CELL_WIDTH / 2 + CELL_MARGIN)
 65            y = row * (CELL_HEIGHT + CELL_MARGIN) + (CELL_HEIGHT / 2 + CELL_MARGIN)
 66            sprite.center_x = x
 67            sprite.center_y = y
 68
 69            # Add the sprite to both lists
 70            grid_sprites_one_dim.append(sprite)
 71            grid_sprites_two_dim[row].append(sprite)
 72
 73    return grid_sprites_one_dim, grid_sprites_two_dim
 74
 75
 76def randomize_grid(grid: arcade.SpriteList):
 77    """ Randomize the grid to alive/dead """
 78    for cell in grid:
 79        pick = random.randrange(2)
 80        if pick:
 81            cell.alpha = ALPHA_ON
 82        else:
 83            cell.alpha = ALPHA_OFF
 84
 85
 86class MyGame(arcade.Window):
 87    """
 88    Main application class.
 89    """
 90
 91    def __init__(self, width: int, height: int, title: str):
 92        """
 93        Set up the application.
 94        """
 95        super().__init__(width, height, title)
 96
 97        self.background_color = BACKGROUND_COLOR
 98
 99        # We need two layers. One holds the current state of our grid, the other
100        # holds the next frame's state. We flip back and forth between the two.
101        grid_sprites_one_dim1, grid_sprites_two_dim1 = create_grids()
102        grid_sprites_one_dim2, grid_sprites_two_dim2 = create_grids()
103
104        self.layers_grid_sprites_one_dim = [grid_sprites_one_dim1, grid_sprites_one_dim2]
105        self.layers_grid_sprites_two_dim = [grid_sprites_two_dim1, grid_sprites_two_dim2]
106
107        self.cur_layer = 0
108        randomize_grid(self.layers_grid_sprites_one_dim[0])
109
110    def on_draw(self):
111        """ Render the screen. """
112        # Clear all pixels in the window
113        self.clear()
114        self.layers_grid_sprites_one_dim[0].draw()
115
116    def on_update(self, delta_time: float):
117        """ Update the grid """
118
119        # Flip layers
120        if self.cur_layer == 0:
121            layer1 = self.layers_grid_sprites_two_dim[0]
122            layer2 = self.layers_grid_sprites_two_dim[1]
123            self.cur_layer = 1
124        else:
125            layer1 = self.layers_grid_sprites_two_dim[1]
126            layer2 = self.layers_grid_sprites_two_dim[0]
127            self.cur_layer = 0
128
129        # Count the neighbors that are alive
130        for row in range(ROW_COUNT):
131            for column in range(COLUMN_COUNT):
132                live_neighbors = 0
133                # -1 -1
134                if row > 0 and column > 0 \
135                        and layer1[row - 1][column - 1].alpha == ALPHA_ON:
136                    live_neighbors += 1
137                # -1  0
138                if row > 0 and layer1[row - 1][column].alpha == ALPHA_ON:
139                    live_neighbors += 1
140                # -1 +1
141                if row > 0 and column < COLUMN_COUNT - 1\
142                        and layer1[row - 1][column + 1].alpha == ALPHA_ON:
143                    live_neighbors += 1
144                #  0 +1
145                if column < COLUMN_COUNT - 1 \
146                        and layer1[row][column + 1].alpha == ALPHA_ON:
147                    live_neighbors += 1
148                # +1 +1
149                if row < ROW_COUNT - 1 \
150                        and column < COLUMN_COUNT - 1 \
151                        and layer1[row + 1][column + 1].alpha == ALPHA_ON:
152                    live_neighbors += 1
153                # +1  0
154                if row < ROW_COUNT - 1 and layer1[row + 1][column].alpha == ALPHA_ON:
155                    live_neighbors += 1
156                # +1 -1
157                if row < ROW_COUNT - 1 and column > 0 \
158                        and layer1[row + 1][column - 1].alpha == ALPHA_ON:
159                    live_neighbors += 1
160                #  0 -1
161                if column > 0 and layer1[row][column - 1].alpha == ALPHA_ON:
162                    live_neighbors += 1
163
164                """
165                Implement Conway's game of life rules
166
167                Any live cell with two or three live neighbours survives.
168                Any dead cell with three live neighbours becomes a live cell.
169                All other live cells die in the next generation. Similarly, all other dead cells stay dead.
170                """
171                if layer1[row][column].alpha == ALPHA_ON and (live_neighbors == 2 or live_neighbors == 3):
172                    if layer2[row][column].alpha == ALPHA_OFF:
173                        layer2[row][column].alpha = ALPHA_ON
174                elif layer1[row][column].alpha == ALPHA_OFF and live_neighbors == 3:
175                    if layer2[row][column].alpha == ALPHA_OFF:
176                        layer2[row][column].alpha = ALPHA_ON
177                else:
178                    if layer2[row][column].alpha == ALPHA_ON:
179                        layer2[row][column].alpha = ALPHA_OFF
180
181
182def main():
183    """ Main function - starting point to the program """
184    window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
185    window.center_window()
186    arcade.run()
187
188
189if __name__ == "__main__":
190    main()