Last active
January 4, 2019 11:56
-
-
Save peverett/dbcabd80a0a5f5d0236c03faa8839162 to your computer and use it in GitHub Desktop.
Conway's Game of Life in Python 2.7 using TKinter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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