patternpythonMinor
Pythonic implementation of Conway's Game of Life
Viewed 0 times
pythonicconwaygamelifeimplementation
Problem
I've just finished with my python implementation of Conway's Game of Life. Can I get some opinions on it?
```
import pygame
import sys
import time
import random
rules = '''
1. Any live cell with fewer than two live neighbours dies, as if caused by under-population.
2. Any live cell with two or three live neighbours lives on to the next generation.
3. Any live cell with more than three live neighbours dies, as if by overcrowding.
4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.'''
#Initializations:
#CONSTANTS
#-------------------
global thesize
thesize = 100
global pixelsize
pixelsize = 10
global FPS
FPS = 100
global windowsizes
windowsize = thesize*pixelsize
global generation
generation = 0
#-------------------
#Define a 2D board (List containing lists)
global board
board = [[False for x in range(thesize)] for x in range(thesize)]
#Set up colors for ease of use
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
#Set up pygame
pygame.init()
global surface
surface = pygame.display.set_mode((windowsize, windowsize)) # Define the surface for the simulation to run on
pygame.display.set_caption('Conway\'s Game of Life')
surface.fill(WHITE) # Fill the screen white
pygame.display.update()
clock = pygame.time.Clock()
#Function to round to the nearest base
def myround(x, base=5):
return int(base * round(float(x)/base))
#Function for returning the segment that a number is in
def whichSlot(x, groupsize=pixelsize):
return x // groupsize
#Function for returning which row and column the mouse is in
def where():
x, y = pygame.mouse.get_pos()
return (whichSlot(x), whichSlot(y))
#Function to find number of live neighbors
def neighbors(row, column):
adjacents = 0
#Horizontally adjacent
if row > 0:
if board[row-1][column]:
adjacents += 1
if column > 0:
if board[row][column-1]:
adjacents += 1
if row 0 and column > 0:
if board[row-1][column
```
import pygame
import sys
import time
import random
rules = '''
1. Any live cell with fewer than two live neighbours dies, as if caused by under-population.
2. Any live cell with two or three live neighbours lives on to the next generation.
3. Any live cell with more than three live neighbours dies, as if by overcrowding.
4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.'''
#Initializations:
#CONSTANTS
#-------------------
global thesize
thesize = 100
global pixelsize
pixelsize = 10
global FPS
FPS = 100
global windowsizes
windowsize = thesize*pixelsize
global generation
generation = 0
#-------------------
#Define a 2D board (List containing lists)
global board
board = [[False for x in range(thesize)] for x in range(thesize)]
#Set up colors for ease of use
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
#Set up pygame
pygame.init()
global surface
surface = pygame.display.set_mode((windowsize, windowsize)) # Define the surface for the simulation to run on
pygame.display.set_caption('Conway\'s Game of Life')
surface.fill(WHITE) # Fill the screen white
pygame.display.update()
clock = pygame.time.Clock()
#Function to round to the nearest base
def myround(x, base=5):
return int(base * round(float(x)/base))
#Function for returning the segment that a number is in
def whichSlot(x, groupsize=pixelsize):
return x // groupsize
#Function for returning which row and column the mouse is in
def where():
x, y = pygame.mouse.get_pos()
return (whichSlot(x), whichSlot(y))
#Function to find number of live neighbors
def neighbors(row, column):
adjacents = 0
#Horizontally adjacent
if row > 0:
if board[row-1][column]:
adjacents += 1
if column > 0:
if board[row][column-1]:
adjacents += 1
if row 0 and column > 0:
if board[row-1][column
Solution
There are quite a few comments relating to the style guide. If you are following a different one, please include a link to it in your question. So, from the top:
Information about the module should be in a module-level docstring (and the line length limit still applies), e.g.:
If you ever need to access this string, you can do so via
Constants should be
It is conventional to indicate values that won't be used with an underscore, i.e.:
Function names should
becomes:
The current global
The GUI loop itself should be in a function, rather than running at the top level of the module. You can then call this function like:
to prevent anything happening if you later
You have the logic for creating a new, random starting point buried deep inside the GUI loop; instead, if you had a
Overall, I would suggest a structure like:
If
imports should be in alphabetical order, and third-party should be separated from standard library:import random
import sys
import time
import pygameInformation about the module should be in a module-level docstring (and the line length limit still applies), e.g.:
"""Pythonic implementation of Conway's Game of Life.
Rules:
1. Any live cell with fewer than two live neighbours dies, as if caused
by under-population.
2. Any live cell with two or three live neighbours lives on to the next
generation.
3. Any live cell with more than three live neighbours dies, as if by
overcrowding.
4. Any dead cell with exactly three live neighbours becomes a live cell,
as if by reproduction.
"""If you ever need to access this string, you can do so via
__doc__.Constants should be
UPPERCASE_WITH_UNDERSCORES. Also, there's no need for global - it doesn't do anything here, and isn't necessary for read-only access. Note that I have moved in BLACK and WHITE, as these are also constant, and rearranged to alphabetical order (this is a personal preference; it makes it easier to find things as the list gets longer).BLACK = (0, 0, 0)
FPS = 100
PIXEL_SIZE = 10
THE_SIZE = 100
WHITE = (255, 255, 255)
WINDOW_SIZE = THE_SIZE * PIXEL_SIZEgeneration is not constant, so should not be with these other values (also, it doesn't seem to actually do anything).It is conventional to indicate values that won't be used with an underscore, i.e.:
board = [[False for _ in range(THE_SIZE)] for _ in range(THE_SIZE)]Function names should
lowercase_with_underscores, or at the very least consistent (why myround but whichSlot?) Functions should also have docstrings explaining what they do, what parameters are expected, etc. In some cases you have comments above the function definition; move these inside the function and switch to multiline strings, e.g.:#Turn a space of the grid on
def giveLife(ro, col):
...becomes:
def giveLife(ro, col):
"""Turn a space of the grid on."""
...The current global
board should be encapsulated in a class. This should also hold the logic to implement the rules and draw the board, rather than have this part of the main GUI loop. Rather than drawing each square as you giveLife and killRuthlessly, have a single method draw that iterates over the board instance attribute and creates each square accordingly. You can also have a simple __str__ method that allows you to look at the board directly, to easily test the simulation separately from the GUI.The GUI loop itself should be in a function, rather than running at the top level of the module. You can then call this function like:
if __name__ == '__main__':
run_gui()to prevent anything happening if you later
import this functionality elsewhere. generations should be managed entirely within run_gui, rather than existing as global state.You have the logic for creating a new, random starting point buried deep inside the GUI loop; instead, if you had a
GameOfLife class, you could create a class method to do this in a much more transparent manner. Alternatively, add an instance method to randomly reset the board for an existing instance.Overall, I would suggest a structure like:
"""Module docstring"""
# import statements
# CONSTANTS
class GameOfLife(object):
"""Hold the state of a single run of the Game of Life."""
def __init__(self, size=THE_SIZE):
"""Create the starting board as an instance attribute."""
...
def __str__(self):
"""Print out the board as a simple array of integers."""
...
def step(self):
"""Run one step of the simulation, updating the board."""
...
def draw(self, surface):
"""Draw the board's squares on the supplied surface."""
...
@classmethod
def create_random(cls, size=THE_SIZE):
"""Create a new instance and set up the board randomly."""
...
def run_gui(game):
"""Run the GUI loop with the supplied game."""
# Initialise pygame and the surface
while True:
for event in pygame.event.get():
# Handle any events
...
if event.type == pygame.KEYDOWN:
...
if event.key == pygame.K_r:
game = GameOfLife.create_random()
game.draw(surface)
game.step()
clock.tick(FPS)
pygame.display.flip()
if __name__ == '__main__':
run_gui(GameOfLife())If
GameOfLife.step starts to get a bit too long, you can always split out some helper methods (e.g. GameOfLife._count_neighbours, etc.; note the "private-by-convention" leading underscore on the method name).Code Snippets
import random
import sys
import time
import pygame"""Pythonic implementation of Conway's Game of Life.
Rules:
1. Any live cell with fewer than two live neighbours dies, as if caused
by under-population.
2. Any live cell with two or three live neighbours lives on to the next
generation.
3. Any live cell with more than three live neighbours dies, as if by
overcrowding.
4. Any dead cell with exactly three live neighbours becomes a live cell,
as if by reproduction.
"""BLACK = (0, 0, 0)
FPS = 100
PIXEL_SIZE = 10
THE_SIZE = 100
WHITE = (255, 255, 255)
WINDOW_SIZE = THE_SIZE * PIXEL_SIZEboard = [[False for _ in range(THE_SIZE)] for _ in range(THE_SIZE)]#Turn a space of the grid on
def giveLife(ro, col):
...Context
StackExchange Code Review Q#85356, answer score: 5
Revisions (0)
No revisions yet.