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