Skip to content

Instantly share code, notes, and snippets.

@SebastianJarsve
Created February 5, 2014 21:00
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save SebastianJarsve/8832970 to your computer and use it in GitHub Desktop.
Save SebastianJarsve/8832970 to your computer and use it in GitHub Desktop.
GameOfLife
# -*- coding: utf-8 -*-
# Conway's game of life.
# Touch cells to give them life.
# Tap screen with two fingers to pause/un-pause.
# Tap screen with three fingers to give life to random cells.
from scene import *
from PIL import Image, ImageDraw
import random
class Cell (object):
def __init__(self, x, y, frame):
self.frame = frame
self.pos = (x, y)
self.alive = 0
self.neighbors = []
self.background = Color(0,0,0)
def update(self, b=None):
if b is not None:
self.alive = b
self.background = Color(1,1,1) if self.alive else Color(0,0,0)
def living_neighbors(self):
return [c for c in self.neighbors if c.alive]
def draw(self):
fill(*self.background)
rect(*self.frame)
class Grid (object):
def __init__(self, w, h):
self.size = Size(w, h)
w, h = (screen.w/w, screen.h/h)
self.cells = {(x, y):Cell(x, y, Rect(x*w, y*h, w, h)) for x in xrange(self.size.w) for y in xrange(self.size.h)}
for c in self.cells.values():
self.adjacent_cells(c)
grid_img = Image.new('RGBA', [int(i) for i in screen.as_tuple()])
grid_draw = ImageDraw.Draw(grid_img)
for x in xrange(self.size.w): grid_draw.line((x*w, 0, x*w, screen.h))
for y in xrange(self.size.h): grid_draw.line((0, y*h, screen.w, y*h))
self.grid_img = load_pil_image(grid_img)
del grid_img, grid_draw
def __iter__(self):
return iter(sorted(self.cells.keys()))
def __getitem__(self, key):
return self.cells[key]
def itervalues(self):
return iter(self.cells.values())
def create_life(self, pos_list=None):
if pos_list is None:
w, h = self.size
for i in xrange(int((w*h)**0.5)*2):
self.cells[(random.randint(0,w-1), random.randint(0,h-1))].update(1)
else:
for p in pos_list:
self.cells[p].update(1)
def living_cells(self):
return [c for c in self.cells.values() if c.alive]
def dead_cells(self):
return [c for c in self.cells.values() if not c.alive]
def adjacent_cells(self, cell):
w, h = self.size
cx, cy = cell.pos
for y in (-1,0,1):
for x in (-1,0,1):
nx = cx+x
ny = cy+y
if x == y == 0:
continue
if nx>=0 and nx<w and ny>=0 and ny<h:
cell.neighbors.append(self[(nx, ny)])
def update(self):
kill = []
life = []
for cell in self.dead_cells():
if len(cell.living_neighbors()) == 3:
life.append(cell)
for cell in self.living_cells():
if len(cell.living_neighbors()) < 2:
kill.append(cell)
elif len(cell.living_neighbors()) > 3:
kill.append(cell)
else:
life.append(cell)
for k in kill:
self.cells[k.pos].update(0)
for l in life:
self.cells[l.pos].update(1)
def draw(self):
stroke(1,1,1)
stroke_weight(1)
image(self.grid_img, 0, 0)
class MyScene (Scene):
def setup(self):
global screen
screen = self.size
self.paused = True
self.grid = Grid(50, 35)
for c in self.grid.itervalues():
c.update()
def draw(self):
background(0,0,0)
for c in self.grid.living_cells():
c.draw()
self.grid.draw()
if not self.paused:
if len(self.grid.living_cells()) == 0:
self.paused = True
if self.grid.living_cells() > 0:
self.grid.update()
for touch in self.touches.values():
if len(self.touches) == 1:
for cell in self.grid.itervalues():
if touch.location in cell.frame:
cell.update(1)
def touch_began(self, touch):
if len(self.touches) > 1:
self.paused = True if not self.paused else False
if len(self.touches) == 3:
self.grid.create_life()
run(MyScene())
@cclauss
Copy link

cclauss commented Feb 8, 2014

Awesome to see again after all these years!! Beautiful code. Thanks... To consider:

  1. Use self.grid = Grid(50, 35, self.size) to eliminate the global variable 'screen'.
  2. Use a generator instead of a list comprehension in dead_cells() which may deliver faster performance with less memory when processing all the blank squares.
  3. self.paused = not self.paused instead of self.paused = True if not self.paused else False

@cclauss
Copy link

cclauss commented Feb 8, 2014

Optimize draw() to call living_cells() once instead of 3 times. Then make living_cells a generator. A nice speed up for iPhones.

    def draw(self):
        scene.background(0,0,0)
        i = 0
        for i, c in enumerate(self.grid.living_cells()):
            c.draw()
        self.grid.draw()
        if not self.paused:
            if i:  # number of living cells
                self.grid.update()
            else:
                self.paused = True

@franciswu
Copy link

Hi Sebastian,

Thanks a lot for posting your code. I am a newbee to Pythonista and I have a question for you. I have downloaded your code to use with the latest Pythonista on my iPad, but receive an error message for row 120 that the MyScene object has no "grid" attribute. Do you know how I can fix it? Thanks a lot!

Regards,
Francis

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment