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