HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Python - Game Of Life

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
lifegamepython

Problem

Here is my implementation of John Conway's Game of Life in Python:

```
from copy import deepcopy

def countSurrounding(board, row, cell):
SURROUNDING = ((row - 1, cell - 1),
(row - 1, cell ),
(row - 1, cell + 1),
(row , cell - 1),
(row , cell + 1),
(row + 1, cell - 1),
(row + 1, cell ),
(row + 1, cell + 1))
count = 0
for row, cell in SURROUNDING:
if isInRange(board, row, cell) and board[row][cell] == True:
count += 1
return count

def isInRange(board, row, cell):
return 0 <= row < len(board) and 0 <= cell < len(board[0])

def nextGeneration(board):
nextBoard = deepcopy(board)
for row in range(len(board)):
for cell in range(len(board[0])):
if board[row][cell] == True and countSurrounding(board, row, cell) not in (2, 3):
nextBoard[row][cell] = False
elif board[row][cell] == False and countSurrounding(board, row, cell) == 3:
nextBoard[row][cell] = True
return nextBoard

def printBoard(board):
for row in board:
for cell in row:
if cell == True:
print("#", end = "")
else:
print(".", end = "")
print()
print()

def main():
board = [[False, False, False, False, False, False, False, False, False],
[False, False, False, True , False, False, False, False, False],
[False, True , False, True , False, False, False, False, False],
[False, False, True , True , False, False, False, False, False],
[False, False, False, False, False, False, False, False, False],
[False, False, False, False, False, False, False, False, False],
[False, False, False, False, False, False, False, False, False],
[False, False, False, False, False, False, False, False, False],

Solution


  1. Comments on your code



-
You have no guard on the call to main(). This makes it harder to debug the program using the interactive interpreter: as soon as the module is imported, it call main() and starts running. Better to put a guard in place:

if __name__ == '__main__':
    main()


-
This program consists of persistent state (the board) together with a set of operations on that state. It is therefore crying out to be implemented in the form of a class. See section 3 for how you might do this.

-
It's horribly tedious to watch the progress of the cellular automaton coming out on the terminal. This is the kind of program that really requires the use of a graphics toolkit like PyGame to draw and animate it. See section 4 for how you might do this.

-
It's almost never necessary to compare values explicitly against True or False. Instead of:

if cell == True:


you can write:

if cell:


and instead of:

if board[row][cell] == False:


you can write:

if not board[row][cell]:


-
Giant blocks of values like this are hard to input and hard to check.

board = [[False, False, False, False, False, False, False, False, False],
         [False, False, False, True , False, False, False, False, False],
         [False, True , False, True , False, False, False, False, False],
         [False, False, True , True , False, False, False, False, False],
         [False, False, False, False, False, False, False, False, False],
         [False, False, False, False, False, False, False, False, False],
         [False, False, False, False, False, False, False, False, False],
         [False, False, False, False, False, False, False, False, False],
         [False, False, False, False, False, False, False, False, False]]


It would be better to draw a "picture" and write a bit of code to decode it into the values you want:

glider = '''..o
            o.o
            .oo'''


See the paste method in section 3 below for how to decode this kind of picture.

  1. Suggestions



-
When you have a program like this that operates on a two-dimensional array of cells, it often simplifies matters if you represent the two-dimensional array as a one-dimensional list. Then a cell can be represented by a single number instead of a pair; lookups are of the form array[cell] instead of array[x][y], and adjacent cells may be found by simple arithmetic operations.

-
It's a feature of the game of Life that, most of the time, very few cells are changing. You can take advantage of this fact to speed up the update operation. Instead of examining all cells to see if they need updating, maintain a set of cells that need to be looked at. Whenever you change a cell from live to dead or vice versa, add it, and all its neighbours, to the set. In the update operation, look only at those cells that were added to the set in the course of the previous update operation.

This means that you don't have to copy out the whole world each update: you only need a copy of the information (liveness and neighbour count) relating to the cells in the set.

-
Similarly, rather than count the number of live neighbours each time you update a cell, you could maintain an array containing these neighbour counts and update the array whenever a cell changes.

  1. Revised code



```
from itertools import product

class Life(object):
def __init__(self, width, height):
self.width = width
self.height = height
cells = (width + 2) * (height + 2)
self.live = [False] * cells
self.in_bounds = [True] * cells
self.neighbours = [0] * cells
for i in range(width + 2):
self.in_bounds[i] = self.in_bounds[-i] = False
for j in range(height):
k = (j + 1) * (width + 2)
self.in_bounds[k - 1] = self.in_bounds[k] = False
self.neighbourhood = [y * (width + 2) + x
for x, y in product((-1, 0, 1), repeat=2)
if x or y]
self.needs_update = set()

def cell(self, x, y):
"""Return the cell number corresponding to the coordinates (x, y)."""
return (self.width + 2) * (y + 1) + x + 1

def set(self, p, value):
"""Set cell number 'p' to 'value' (True=live, False=dead)."""
if value != self.live[p] and self.in_bounds[p]:
self.live[p] = value
self.needs_update.add(p)
adjust = 1 if value else -1
for n in self.neighbourhood:
n += p
if self.in_bounds[n]:
self.neighbours[n] += adjust
self.needs_update.add(n)

def update(self, steps=1):
"""Update the world by 'steps' generations (default: 1)."""
for _ in range(steps):
u = [(p, self.live[p], self.neighbours[p]) for p in self.needs_update]
self.needs_update = set()
for p, live, neighbours in u:
if live

Code Snippets

if __name__ == '__main__':
    main()
if cell == True:
if board[row][cell] == False:
if not board[row][cell]:
board = [[False, False, False, False, False, False, False, False, False],
         [False, False, False, True , False, False, False, False, False],
         [False, True , False, True , False, False, False, False, False],
         [False, False, True , True , False, False, False, False, False],
         [False, False, False, False, False, False, False, False, False],
         [False, False, False, False, False, False, False, False, False],
         [False, False, False, False, False, False, False, False, False],
         [False, False, False, False, False, False, False, False, False],
         [False, False, False, False, False, False, False, False, False]]

Context

StackExchange Code Review Q#31817, answer score: 5

Revisions (0)

No revisions yet.