Skip to content

Instantly share code, notes, and snippets.

@peverett
Last active January 4, 2019 11:56
Show Gist options
  • Save peverett/dbcabd80a0a5f5d0236c03faa8839162 to your computer and use it in GitHub Desktop.
Save peverett/dbcabd80a0a5f5d0236c03faa8839162 to your computer and use it in GitHub Desktop.
Conway's Game of Life in Python 2.7 using TKinter
#!/usr/bin/python
"""
Conway's Game of Live
Model: Matrix of cells - x, y
View: Tkinter Canvas with grid of cells (rectangles).
Controller: Puts them together.
"""
from Tkinter import *
from random import randint
import time
GRID_SIZE = 50 # How big is the Matrix of cells
SCALE = 5 # Size of a cell in pixels
BORDER = 4 # Border around Cell Matrix
DELAY = 1
GENERATIONS = 500
class Model(object):
"""
Models the cell matrix and implements Conway's cellular automation rules.
"""
ALIVE = 1
DEAD = 0
def __init__(self, size):
"""Initialise the grid, set up the state."""
self.size = size
self.matrix = [[Model.DEAD for x in xrange(size)] for y in xrange(size)]
def random_seed(self):
"""Initialise the grid of cells randomly"""
self.matrix = [[randint(Model.DEAD, Model.ALIVE)
for x in xrange(self.size)]
for y in xrange(self.size)]
def test_seed(self):
"""Intialise to a known pattern for test purposes.
The four dots disappear, the 4-block circle remains stable.
"""
self.matrix[1][1] = Model.ALIVE
self.matrix[8][8] = Model.ALIVE
self.matrix[4][3] = Model.ALIVE
self.matrix[3][4] = Model.ALIVE
self.matrix[5][4] = Model.ALIVE
self.matrix[4][5] = Model.ALIVE
self.matrix[1][8] = Model.ALIVE
self.matrix[8][1] = Model.ALIVE
def glider_seed(self):
"""Intialise to a single glider"""
# Now add a known pattern of cells
self.matrix[18][3] = Model.ALIVE
self.matrix[19][4] = Model.ALIVE
self.matrix[17][5] = Model.ALIVE
self.matrix[18][5] = Model.ALIVE
self.matrix[19][5] = Model.ALIVE
def neighbour_count(self, x, y):
"""
Count the number of live neighbours surrounding the cell at x, y.
"""
count = 0
for dy in xrange(y-1, y+2):
for dx in xrange(x-1, x+2):
xx = dx % self.size
yy = dy % self.size
if not (xx == x and yy == y) and self.matrix[yy][xx]:
count += 1
return count
def tick(self):
"""
Execute the rule across the whole matrix.
1) A live cell with fewer than two neighbours dies.
2) A live cell with two or three neighbours lives.
3) A live cell with more than three neighbours dies.
4) A dead cell with three neighbours becomes a live cell.
"""
nmatrix=[]
for y in xrange(self.size):
row=[]
for x in xrange(self.size):
nc = self.neighbour_count(x, y)
#print "cell({},{})={}".format(x, y, nc)
if self.matrix[y][x] == Model.ALIVE and (nc<2 or nc>3):
row.append(Model.DEAD)
elif self.matrix[y][x] == Model.DEAD and nc == 3:
row.append(Model.ALIVE)
else:
row.append(self.matrix[y][x])
nmatrix.append(row)
self.matrix = nmatrix
class NewModel(Model):
NEIGHBOURS = ((-1, -1), (-1, 0), (-1, 1),
( 0, -1), ( 0, 1),
( 1, -1), ( 1, 0), ( 1, 1))
def neighbour_count(self, x, y):
return sum( self.matrix[(y+j)%self.size][(x+i)%self.size] \
for (i, j) in NewModel.NEIGHBOURS )
def tick(self):
nmatrix=[]
for y in xrange(self.size):
row = []
for x in xrange(self.size):
row.append(
int(self.neighbour_count(x, y)) in (
(2,3) if self.matrix[y][x] else (3,)
)
)
nmatrix.append(row)
self.matrix = nmatrix
class View(object):
"""
Draw a canvas object representing the cell matrix.
Implementation:
The grid is made up of rectangles of SCALE.
The state of the each cell is indicated by it's colour.
"""
ALIVE = '#00FF00' # RGB for green
DEAD = '#000000' # RGB for black
DIED = '#0000FF' # RGB for blue.
def __init__(self, parent, size, scale):
"""
Initialise the canvas object and draw all it's rectangles.
"""
self.parent = parent
self.size = size
self.scale = scale
# Create a Canvas to draw on.
self.canvas = Canvas(
self.parent,
height = size * scale,
width = size * scale
)
self.canvas.config(bg='black')
self.canvas.pack()
# Now create the boxes in that grid.
self.boxes = []
for y in range(size):
row = []
for x in range(size):
tlx = x * scale
brx = x * scale + scale
tly = y * scale
bry = y * scale + scale
row.append(
self.canvas.create_rectangle(
tlx, tly, brx, bry,
fill='black',
width=0
)
)
self.boxes.append(row)
def draw(self, matrix):
"""Draw the display grid for the state of the cells in the matrix."""
for y in xrange(self.size):
for x in xrange(self.size):
tid = self.boxes[y][x]
if matrix[y][x] == Model.DEAD:
self.canvas.itemconfig(tid, fill=View.DEAD)
else:
self.canvas.itemconfig(tid, fill=View.ALIVE)
def update(self, matrix):
"""Update the display grid for the state of the cells in the matrix.
This only updates changed cells.
When a cell 'dies' it's corpse turns blue and slowly decays to black.
"""
for y in xrange(self.size):
for x in xrange(self.size):
tid = self.boxes[y][x]
fillc = self.canvas.itemcget(tid, 'fill')
if fillc == View.ALIVE and matrix[y][x] == Model.DEAD:
self.canvas.itemconfig(tid, fill=View.DIED)
elif fillc != View.ALIVE and matrix[y][x] == Model.ALIVE:
self.canvas.itemconfig(tid, fill=View.ALIVE)
elif fillc != View.ALIVE and fillc != View.DEAD: # DIED
self.canvas.itemconfig(tid, fill="#0000{:02X}".format(
int(fillc[1:],16)-5)
)
class Controller(object):
"""
This is the controller, responsible for connecting the Model with the View
and controlling both.
"""
def __init__(self):
"""Init the Tk GUI framework"""
self.root = Tk()
self.root.title("Conway's Game of Life")
self.model = NewModel(GRID_SIZE)
self.view = View(self.root, GRID_SIZE, SCALE)
self.gen = 0
self.model.random_seed()
self.view.draw(self.model.matrix)
self.start_time = time.time()
self.root.after(DELAY, self.process)
def process(self):
self.model.tick()
self.view.update(self.model.matrix)
# if self.gen < GENERATIONS:
# self.gen += 1
self.root.after(DELAY, self.process)
# else:
# print "Elapsed time: {}".format(time.time() - self.start_time)
def run(self):
"""Go into the Tk main processing loop."""
self.root.mainloop()
if __name__ == "__main__":
controller = Controller()
controller.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment