Difference between revisions of "User:Danf/TurtleGraphics"

From Noisebridge
Jump to: navigation, search
('x' to re-randomize ... lulz)
(features & refactoring: cycle detection, separation of concerns)
 
Line 1: Line 1:
python turtle module -> Conway's Game of Life ... and stuff.
 
 
experimental work in progress, needs to be cleaned up and refactored a bit ...
 
 
 
<pre>
 
<pre>
 
# turtlife.py - Artistic License w/ Attribution -> "(evil) Dan of MOISEBRIDGE"
 
# turtlife.py - Artistic License w/ Attribution -> "(evil) Dan of MOISEBRIDGE"
# note: press 'n' to advance frame, 'r' to run, 'p' to pause, 'x' to re-randomize
+
# note: press 'n' to advance frame, 'r' to run, 'p' to pause
  
 
from turtle import Screen, Turtle, mainloop
 
from turtle import Screen, Turtle, mainloop
from itertools import islice, product, repeat, starmap
+
from itertools import count, islice, product, repeat, starmap
from random import randint
+
from collections import deque
 +
from random import choice, randint
 
from time import sleep
 
from time import sleep
 +
 +
class Logic(object):
 +
  def __init__(self, rules=None):
 +
    self.rules = rules
 +
  def evaluate(self, o=None):
 +
    ret = None
 +
    if self.rules == 'life':
 +
      ret = self.evaluate_life(o)
 +
    elif self.rules == 'prime':
 +
      ret = self.evaluate_prime(o)
 +
    else:  # random 
 +
      ret = (choice((0, 1)), choice(range(0, 9)))
 +
    return ret
 +
  def evaluate_life(self, o):
 +
    n = o.neighborsum()
 +
    if n == 2:
 +
      destiny = o.binary_val
 +
    elif n == 3:
 +
      destiny = 1
 +
    else:
 +
      destiny = 0
 +
    return (destiny, n)
 +
  def evaluate_prime(self, o):
 +
    n = o.neighborsum()
 +
    if n in (2, 3):
 +
      destiny = 1
 +
    elif n in (5, 7):
 +
      destiny = o.binary_val
 +
    else:
 +
      destiny = 0
 +
    return (destiny, n)
 +
  def toggle(self):
 +
    if self.rules == 'life':
 +
      self.rules = 'prime'
 +
    elif self.rules == 'prime':
 +
      self.rules = 'life'
 +
    else:
 +
      self.rules = choice(('prime', 'life'))
  
 
class Cell(object):
 
class Cell(object):
Line 17: Line 52:
 
     self.row = row
 
     self.row = row
 
     self.col = col
 
     self.col = col
     self.val = 0
+
     self.binary_val = 0
     self.extra = 0
+
     self.cause = 0
     self.rules = colony.rules
+
     self.logic = colony.logic
 
     self._neighbors = None
 
     self._neighbors = None
 
   def neighbors(self):
 
   def neighbors(self):
 
     if self._neighbors is None:
 
     if self._neighbors is None:
       self._neighbors = list(self.colony.neighbormap(self, self.colony.cells))
+
       self._neighbors = list(starmap(
 +
        lambda x, y: self.colony.cells[x * self.colony.cols + y],
 +
        self.colony.neighborhood(self.row, self.col) ))
 
     return self._neighbors
 
     return self._neighbors
 
   def neighborsum(self):
 
   def neighborsum(self):
     return sum(o.val for o in self.neighbors())
+
     return sum(o.binary_val for o in self.neighbors())
 
   def destiny(self):
 
   def destiny(self):
     n = self.neighborsum()
+
     effect, cause = self.logic.evaluate(self)
    if self.rules == 'prime':
+
     return (effect, cause)
      return ((self.val if (n == 5 or n == 7) else 1 if (n == 2 or n == 3) else 0), n)  
+
   def update(self, effect=None, cause=None):
     elif self.rules == 'life':
+
     if effect is not None:
      return ((self.val if (n == 2) else 1 if (n == 3) else 0), n)  
+
       self.binary_val = 1 if effect else 0
   def value(self, val=None, extra=None):
+
     if cause is not None:
     if val is not None:
+
       self.cause = cause
       self.val = val
+
     return self.binary_val
     if extra is not None:
 
       self.extra = extra
 
     return self.val
 
  def valchar(self):
 
    return (' ', 'o')[self.val]
 
  
class Raster(object):
+
class Colony(object):
 
   def __init__(self, rules, displaymode, rows, cols):
 
   def __init__(self, rules, displaymode, rows, cols):
     self.rules = rules
+
     self.logic = Logic(rules)
    self.displaymode = displaymode
 
 
     self.rows = rows
 
     self.rows = rows
 
     self.cols = cols
 
     self.cols = cols
Line 51: Line 82:
 
       lambda x, y: Cell(self, x, y),
 
       lambda x, y: Cell(self, x, y),
 
       product(range(rows), range(cols)) ))
 
       product(range(rows), range(cols)) ))
     self.turtles = None
+
     self.state = State(self)
  def rowslice(self, r):
+
     self.display = ColonyDisplay(self, displaymode)
     i = r * self.cols
 
    return islice(self.cells, i, i + self.cols)
 
 
   def neighborhood(self, row, col):
 
   def neighborhood(self, row, col):
 
     up = row - 1 if row else self.rows - 1
 
     up = row - 1 if row else self.rows - 1
Line 63: Line 92:
 
             (row, left),            (row, right),
 
             (row, left),            (row, right),
 
             (down, left), (down, col), (down, right) )
 
             (down, left), (down, col), (down, right) )
   def neighbormap(self, o, sq):
+
   def randomize(self):
     return starmap(
+
    orig_rules = self.logic.rules
       lambda x, y: sq[x * self.cols + y],
+
    self.logic.rules = 'random'
       self.neighborhood(o.row, o.col) )
+
    self.update()
 +
    self.logic.rules = orig_rules
 +
  def update(self, sync=True):
 +
    self.state.update()
 +
    self.display.update()
 +
 
 +
class ColonyDisplay(object):
 +
  def __init__(self, colony, displaymode):
 +
     self.displaymode = displaymode
 +
    self.cells = colony.cells
 +
    self.textdisplay = lambda: colony.state.displaystring()
 +
    self.turtles = list(starmap(
 +
       lambda x, y: ColonialTurtle(colony, x, y),
 +
       product(range(colony.rows), range(colony.cols)) ))
 +
  def update(self):
 +
    print(self.textdisplay())
 +
    self.turtledisplay()  
 
   def turtledisplay(self):
 
   def turtledisplay(self):
    if self.turtles is None:
 
      self.turtles = list(CellularTurtle(self, row, col) for row in range(self.rows) for col in range(self.cols))
 
 
     for c, t in zip(self.cells, self.turtles):
 
     for c, t in zip(self.cells, self.turtles):
       if c.val:
+
       if c.binary_val:
         if c.extra == 2:
+
         if c.cause == 2:
           t.rgb = list(t.colors['blue'])
+
           t.set_rgb('blue')
         elif c.extra == 3:
+
         elif c.cause == 3:
           t.rgb = list(t.colors['green'])
+
           t.set_rgb('green')
         elif c.extra == 5:
+
         elif c.cause == 5:
           t.rgb = list(t.colors['yellow'])
+
           t.set_rgb('yellow')
         elif c.extra == 7:
+
         elif c.cause == 7:
           t.rgb = list(t.colors['red'])
+
           t.set_rgb('red')
 
       else:
 
       else:
 
         if self.displaymode == 'ambient':
 
         if self.displaymode == 'ambient':
           t.rgb = list(t.ambience())
+
           t.set_rgb(t.ambience())
 
         elif self.displaymode == 'fade':
 
         elif self.displaymode == 'fade':
           for i in range(3):
+
           t.set_rgb(0.618)
            t.rgb[i] *= 0.618
 
 
         else:
 
         else:
           t.rgb = list(t.colors['black'])
+
           t.set_rgb('black')
       t.color(t.rgb)
+
 
   def textdisplay(self):
+
class State(object):
     for r in range(self.rows):
+
  def __init__(self, colony):
       print(''.join(map(lambda x: x.valchar(), self.rowslice(r))))
+
    self.colony = colony
   def display(self):
+
    self.context = None
     self.turtledisplay()  
+
    self.lookback = 1000
     self.textdisplay()  
+
    self.gen = 0
 +
    self.history = deque(maxlen=self.lookback)
 +
    self.cycle_detector = None
 +
  def bitvals(self):
 +
    bits = 0
 +
    for o in self.colony.cells:
 +
      bits <<= 1
 +
      bits += o.binary_val
 +
    return bits
 +
  def update_context(self):
 +
    prev_context = self.context
 +
    self.context = (self.colony.logic.rules, self.colony.rows, self.colony.cols)
 +
    return (self.context, prev_context)
 +
  def memorialize(self):
 +
    current, prev = self.update_context()
 +
    if current != prev:
 +
      self.history.append((self.gen, (current, prev)))
 +
    self.history.append((self.gen, self.bitvals()))
 +
  def update(self, sync=True):
 +
    dst = (x.destiny() for x in self.colony.cells)
 +
    for c, d in zip(self.colony.cells, list(dst) if sync else dst):
 +
      effect, cause = d
 +
      c.update(effect, cause)
 +
    self.memorialize()
 +
    self.gen += 1
 +
  def smash(self):
 +
    self.history.clear()
 +
    self.gen = 0
 +
  def dump(self):
 +
    cur, prev = None, None
 +
    print('history:')
 +
    for gen, entry in self.history:
 +
      if type(entry) == tuple:  # context
 +
        cur, prev = entry
 +
      else:
 +
        print(cur, gen)
 +
        print(self.bitstring(entry)) 
 +
  def cycler(self):
 +
    if self.cycle_detector:  # toggle
 +
       self.cycle_detector = None
 +
      ret = None
 +
    else:
 +
      self.cycle_detector = dict()
 +
      ret = self.cycler_check_history()
 +
    return ret
 +
   def cycler_check_history(self):
 +
    cycle_found = None
 +
     for gen, entry in self.history:
 +
      if type(entry) == tuple:  # context
 +
        continue
 +
      regen = self.cycler_check_entry(gen, entry)
 +
      if gen != regen:
 +
        cycle_found = regen
 +
        break
 +
    return cycle_found
 +
  def cycler_check_entry(self, gen, entry):
 +
    if entry in self.cycle_detector:  # key via history <- self.bitvals()
 +
      ret = self.cycle_detector[entry]
 +
    else:
 +
      self.cycle_detector[entry] = gen
 +
      ret = gen
 +
    return ret
 +
  def bitstring(self, bits=None, wrapped=True):
 +
    if bits is None:
 +
       bits = self.bitvals()
 +
    s = '{0:0{n}b}'.format(bits, n=len(self.colony.cells))
 +
    if wrapped:
 +
      s = '\n'.join(map(
 +
        lambda x: s[x:x+self.colony.cols],
 +
        range(0, len(s), self.colony.cols) ))
 +
    return s
 +
   def displaystring(self, which=-1):
 +
     gen, entry = self.history[which]
 +
    if which == -1:
 +
      cur = self.context
 +
    while type(entry) == tuple:  # context
 +
      cur, prev = entry
 +
      which -= 1
 +
      if which + len(d) < 0:
 +
        return ""
 +
      gen, entry = self.history[which]
 +
     s = ' '.join((str(cur), str(gen)))
 +
    return '\n'.join((s, self.bitstring(entry).replace('1','o').replace('0',' ')))
  
class CellularTurtle(Turtle):
+
class ColonialTurtle(Turtle):
 
   def __init__(self, colony, row, col):
 
   def __init__(self, colony, row, col):
 
     Turtle.__init__(self)
 
     Turtle.__init__(self)
Line 103: Line 227:
 
     self.col = col
 
     self.col = col
 
     self.speed(0)
 
     self.speed(0)
    # self.hideturtle()
+
     self.shape("turtle")
     self.shape("circle")
+
     self.settiltangle(45)
     # self.settiltangle(90)
 
 
     self.resizemode("user")
 
     self.resizemode("user")
     self.shapesize(2, 2, 0)
+
     self.shapesize(1, 1, 0)
     self.pu()
+
     self.penup()
 
     self.setx(col)
 
     self.setx(col)
 
     self.sety(row)
 
     self.sety(row)
Line 119: Line 242:
 
       ( 'green', (0.0, 0.7, 0.0) ),
 
       ( 'green', (0.0, 0.7, 0.0) ),
 
       ( 'blue', (0.0, 0.0, 0.7) ) ) )
 
       ( 'blue', (0.0, 0.0, 0.7) ) ) )
     self.rgb = list(self.colors['black'])
+
     self.set_rgb('black')
    self.color(self.rgb)
 
 
     self._neighbors = None
 
     self._neighbors = None
 
   def neighbors(self):
 
   def neighbors(self):
 
     if self._neighbors is None:
 
     if self._neighbors is None:
       self._neighbors = list(self.colony.neighbormap(self, self.colony.turtles))
+
       self._neighbors = list(starmap(
 +
        lambda x, y: self.colony.display.turtles[x * self.colony.cols + y],
 +
        self.colony.neighborhood(self.row, self.col) ))
 
     return self._neighbors
 
     return self._neighbors
 +
  def set_rgb(self, c):
 +
    otype = type(c)
 +
    if otype is float:
 +
      for i in range(3):
 +
        self.rgb[i] *= c
 +
    else:
 +
      if otype is str:
 +
        c = self.colors[c]
 +
      self.rgb = list(c)
 +
    self.color(self.rgb)
 
   def avg_rgb(self, turtles):
 
   def avg_rgb(self, turtles):
 
     rgb = [0.0, 0.0, 0.0]
 
     rgb = [0.0, 0.0, 0.0]
Line 135: Line 269:
 
   def ambience(self):
 
   def ambience(self):
 
     return self.avg_rgb(self.neighbors())
 
     return self.avg_rgb(self.neighbors())
 
class CellRunner(object):
 
  def __init__(self, rules, displaymode, rows, cols):
 
    self.raster = Raster(rules, displaymode, rows, cols)
 
    self.randomize()
 
  def randomize(self):
 
    list(map(lambda x: x.value(randint(0, 1)), self.raster.cells))
 
  def update(self, sync=True):
 
    dst = map(lambda x: x.destiny(), self.raster.cells)
 
    if sync:
 
      dst = list(dst)
 
    list(starmap(
 
      lambda x, y: x.value(*y),
 
      zip(self.raster.cells, dst) ))
 
    self.raster.display()
 
  def run(self, n=0, delay=0.1):
 
    f = (lambda: repeat(1, n)) if n else (lambda: repeat(1))
 
    for x in f():
 
      try:
 
        self.update(sync=True)
 
        if(delay):
 
          sleep(delay)
 
      except:
 
        break
 
  
 
class ScreenRunner(object):
 
class ScreenRunner(object):
   def __init__(self, rules='prime', displaymode='ambient', rows=16, cols=32):
+
   def __init__(self, rules='prime', displaymode='ambient', delay=0, rows=16, cols=32):
 +
    self.delay_milliseconds = delay
 
     self.screen = self.initscreen(rows, cols)
 
     self.screen = self.initscreen(rows, cols)
     self.cellrunner = CellRunner(rules, displaymode, rows, cols)
+
     self.colony = Colony(rules, displaymode, rows, cols)  
     self.running = False
+
     self.randomize()
     self.next()
+
     self.run()
 
   def initscreen(self, rows, cols):
 
   def initscreen(self, rows, cols):
 
     screen = Screen()
 
     screen = Screen()
Line 176: Line 287:
 
     return screen
 
     return screen
 
   def bindkeys(self, screen):
 
   def bindkeys(self, screen):
     screen.onkey(self.randomize, 'x')
+
     screen.onkey(self.cycle_detection, 'c')
 +
    screen.onkey(self.dump, 'd')
 +
    screen.onkey(self.mode, 'm')
 
     screen.onkey(self.next, 'n')
 
     screen.onkey(self.next, 'n')
 +
    screen.onkey(self.pause, 'p')
 
     screen.onkey(self.run, 'r')
 
     screen.onkey(self.run, 'r')
     screen.onkey(self.save, 's')
+
     screen.onkey(self.smash, 's')
     screen.onkey(self.pause, 'p')
+
     screen.onkey(self.randomize, 'x')
 
     screen.onkey(self.quit, 'q')
 
     screen.onkey(self.quit, 'q')
 
     screen.listen()
 
     screen.listen()
   def randomize(self):
+
   def cycle_detection(self):
     self.cellrunner.randomize()
+
     detected = self.colony.state.cycler()
     self.next()
+
    if detected is not None:
 +
      print('cycle detected - gen ', detected)
 +
  def dump(self):
 +
    self.colony.state.dump()
 +
  def mode(self):
 +
     self.colony.logic.toggle()
 +
    self.colony.update()
 
   def next(self):
 
   def next(self):
     self.cellrunner.run(1, 0)
+
     self.colony.update()
 +
  def pause(self):
 +
    self.running = False
 
   def run(self):
 
   def run(self):
 
     self.running = True
 
     self.running = True
 
     self.timer()
 
     self.timer()
   def save(self):
+
   def randomize(self):
     self.pause(r)
+
     self.colony.randomize()
    s = ''.join(str(o.val) for o in self.cellrunner.raster.cells)
+
   def smash(self):
    print(s)
+
     self.colony.state.smash()
   def pause(self):
 
     self.running = False
 
 
   def quit(self):
 
   def quit(self):
 
     exit()
 
     exit()
   def timer(self, delay=100):
+
   def timer(self, delay=None):
 +
    if delay is None:
 +
      delay = self.delay_milliseconds
 
     if self.running:
 
     if self.running:
 
       self.next()
 
       self.next()
Line 205: Line 327:
  
 
def main():
 
def main():
   sr = ScreenRunner(rules='life', displaymode='fade', rows=7, cols=11)
+
   ScreenRunner(rules='prime', displaymode='ambient', delay=0, rows=17, cols=23)   # delay in milliseconds
 
   return "EVENTLOOP"
 
   return "EVENTLOOP"
  
Line 212: Line 334:
 
   print(msg)
 
   print(msg)
 
   mainloop()
 
   mainloop()
 +
 +
</pre>

Latest revision as of 02:54, 24 January 2015

# turtlife.py - Artistic License w/ Attribution -> "(evil) Dan of MOISEBRIDGE"
# note: press 'n' to advance frame, 'r' to run, 'p' to pause

from turtle import Screen, Turtle, mainloop
from itertools import count, islice, product, repeat, starmap
from collections import deque
from random import choice, randint
from time import sleep

class Logic(object):
  def __init__(self, rules=None):
    self.rules = rules
  def evaluate(self, o=None):
    ret = None
    if self.rules == 'life':
      ret = self.evaluate_life(o)
    elif self.rules == 'prime':
      ret = self.evaluate_prime(o)
    else:  # random  
      ret = (choice((0, 1)), choice(range(0, 9))) 
    return ret
  def evaluate_life(self, o):
    n = o.neighborsum()
    if n == 2:
      destiny = o.binary_val
    elif n == 3:
      destiny = 1
    else:
      destiny = 0
    return (destiny, n)
  def evaluate_prime(self, o):
    n = o.neighborsum()
    if n in (2, 3):
      destiny = 1
    elif n in (5, 7):
      destiny = o.binary_val
    else:
      destiny = 0
    return (destiny, n)
  def toggle(self):
    if self.rules == 'life':
      self.rules = 'prime'
    elif self.rules == 'prime':
      self.rules = 'life'
    else:
      self.rules = choice(('prime', 'life')) 

class Cell(object):
  def __init__(self, colony, row, col):
    self.colony = colony
    self.row = row
    self.col = col
    self.binary_val = 0
    self.cause = 0
    self.logic = colony.logic
    self._neighbors = None
  def neighbors(self):
    if self._neighbors is None:
      self._neighbors = list(starmap(
        lambda x, y: self.colony.cells[x * self.colony.cols + y],
        self.colony.neighborhood(self.row, self.col) ))
    return self._neighbors
  def neighborsum(self):
    return sum(o.binary_val for o in self.neighbors())
  def destiny(self):
    effect, cause = self.logic.evaluate(self)
    return (effect, cause)
  def update(self, effect=None, cause=None):
    if effect is not None:
      self.binary_val = 1 if effect else 0
    if cause is not None:
      self.cause = cause
    return self.binary_val

class Colony(object):
  def __init__(self, rules, displaymode, rows, cols):
    self.logic = Logic(rules)
    self.rows = rows
    self.cols = cols
    self.cells = list(starmap(
      lambda x, y: Cell(self, x, y),
      product(range(rows), range(cols)) ))
    self.state = State(self)
    self.display = ColonyDisplay(self, displaymode)
  def neighborhood(self, row, col):
    up = row - 1 if row else self.rows - 1
    down = row + 1 if row < self.rows - 1 else 0
    left = col - 1 if col else self.cols - 1
    right = col + 1 if col < self.cols - 1 else 0
    return ( (up, left), (up, col), (up, right),
             (row, left),             (row, right),
             (down, left), (down, col), (down, right) )
  def randomize(self):
    orig_rules = self.logic.rules
    self.logic.rules = 'random'
    self.update() 
    self.logic.rules = orig_rules
  def update(self, sync=True):
    self.state.update()
    self.display.update()

class ColonyDisplay(object):
  def __init__(self, colony, displaymode):
    self.displaymode = displaymode
    self.cells = colony.cells
    self.textdisplay = lambda: colony.state.displaystring()
    self.turtles = list(starmap(
      lambda x, y: ColonialTurtle(colony, x, y),
      product(range(colony.rows), range(colony.cols)) ))
  def update(self):
    print(self.textdisplay()) 
    self.turtledisplay() 
  def turtledisplay(self):
    for c, t in zip(self.cells, self.turtles):
      if c.binary_val:
        if c.cause == 2:
          t.set_rgb('blue')
        elif c.cause == 3:
          t.set_rgb('green')
        elif c.cause == 5:
          t.set_rgb('yellow')
        elif c.cause == 7:
          t.set_rgb('red')
      else:
        if self.displaymode == 'ambient':
          t.set_rgb(t.ambience())
        elif self.displaymode == 'fade':
          t.set_rgb(0.618)
        else:
          t.set_rgb('black')

class State(object):
  def __init__(self, colony):
    self.colony = colony
    self.context = None
    self.lookback = 1000
    self.gen = 0
    self.history = deque(maxlen=self.lookback)
    self.cycle_detector = None
  def bitvals(self):
    bits = 0
    for o in self.colony.cells:
      bits <<= 1
      bits += o.binary_val
    return bits
  def update_context(self):
    prev_context = self.context
    self.context = (self.colony.logic.rules, self.colony.rows, self.colony.cols)
    return (self.context, prev_context)
  def memorialize(self):
    current, prev = self.update_context()
    if current != prev:
      self.history.append((self.gen, (current, prev)))
    self.history.append((self.gen, self.bitvals()))
  def update(self, sync=True):
    dst = (x.destiny() for x in self.colony.cells)
    for c, d in zip(self.colony.cells, list(dst) if sync else dst):
      effect, cause = d
      c.update(effect, cause)
    self.memorialize()
    self.gen += 1
  def smash(self):
    self.history.clear()
    self.gen = 0
  def dump(self):
    cur, prev = None, None
    print('history:')
    for gen, entry in self.history:
      if type(entry) == tuple:  # context
        cur, prev = entry
      else:
        print(cur, gen)
        print(self.bitstring(entry))  
  def cycler(self):
    if self.cycle_detector:  # toggle
      self.cycle_detector = None
      ret = None
    else:
      self.cycle_detector = dict()
      ret = self.cycler_check_history()
    return ret
  def cycler_check_history(self):
    cycle_found = None
    for gen, entry in self.history:
      if type(entry) == tuple:  # context
        continue
      regen = self.cycler_check_entry(gen, entry)
      if gen != regen:
        cycle_found = regen
        break
    return cycle_found
  def cycler_check_entry(self, gen, entry):
    if entry in self.cycle_detector:  # key via history <- self.bitvals()
      ret = self.cycle_detector[entry]
    else:
      self.cycle_detector[entry] = gen
      ret = gen
    return ret
  def bitstring(self, bits=None, wrapped=True):
    if bits is None:
      bits = self.bitvals()
    s = '{0:0{n}b}'.format(bits, n=len(self.colony.cells))
    if wrapped:
      s = '\n'.join(map(
        lambda x: s[x:x+self.colony.cols],
        range(0, len(s), self.colony.cols) ))
    return s
  def displaystring(self, which=-1):
    gen, entry = self.history[which]
    if which == -1:
      cur = self.context
    while type(entry) == tuple:  # context
      cur, prev = entry
      which -= 1
      if which + len(d) < 0:
        return ""
      gen, entry = self.history[which]
    s = ' '.join((str(cur), str(gen)))
    return '\n'.join((s, self.bitstring(entry).replace('1','o').replace('0',' ')))

class ColonialTurtle(Turtle):
  def __init__(self, colony, row, col):
    Turtle.__init__(self)
    self.colony = colony
    self.row = row
    self.col = col
    self.speed(0)
    self.shape("turtle")
    self.settiltangle(45)
    self.resizemode("user")
    self.shapesize(1, 1, 0)
    self.penup()
    self.setx(col)
    self.sety(row)
    self.colors = dict( (
      ( 'black', (0.0, 0.0, 0.0) ),
      ( 'grey50', (0.5, 0.5, 0.5) ),
      ( 'white', (1.0, 1.0, 1.0) ),
      ( 'red', (0.7, 0.0, 0.0) ),
      ( 'yellow', (0.7, 0.7, 0.0) ),
      ( 'green', (0.0, 0.7, 0.0) ),
      ( 'blue', (0.0, 0.0, 0.7) ) ) )
    self.set_rgb('black')
    self._neighbors = None
  def neighbors(self):
    if self._neighbors is None:
      self._neighbors = list(starmap(
        lambda x, y: self.colony.display.turtles[x * self.colony.cols + y],
        self.colony.neighborhood(self.row, self.col) ))
    return self._neighbors
  def set_rgb(self, c):
    otype = type(c)
    if otype is float:
      for i in range(3):
        self.rgb[i] *= c
    else:
      if otype is str:
        c = self.colors[c]
      self.rgb = list(c)
    self.color(self.rgb)
  def avg_rgb(self, turtles):
    rgb = [0.0, 0.0, 0.0]
    n = len(turtles)
    for t in turtles:
      for i in range(3):
        rgb[i] += t.rgb[i]
    return map(lambda x: x/n, rgb)
  def ambience(self):
    return self.avg_rgb(self.neighbors())

class ScreenRunner(object):
  def __init__(self, rules='prime', displaymode='ambient', delay=0, rows=16, cols=32):
    self.delay_milliseconds = delay
    self.screen = self.initscreen(rows, cols)
    self.colony = Colony(rules, displaymode, rows, cols) 
    self.randomize()
    self.run()
  def initscreen(self, rows, cols):
    screen = Screen()
    screen.delay(0)
    offset = map(lambda x: x - 0.3, (0, rows, cols, 0))
    screen.setworldcoordinates(*offset)
    screen.bgcolor(0.0, 0.0, 0.0)
    screen.tracer(n=rows*cols)
    self.bindkeys(screen)
    return screen
  def bindkeys(self, screen):
    screen.onkey(self.cycle_detection, 'c')
    screen.onkey(self.dump, 'd')
    screen.onkey(self.mode, 'm')
    screen.onkey(self.next, 'n')
    screen.onkey(self.pause, 'p')
    screen.onkey(self.run, 'r')
    screen.onkey(self.smash, 's')
    screen.onkey(self.randomize, 'x')
    screen.onkey(self.quit, 'q')
    screen.listen()
  def cycle_detection(self):
    detected = self.colony.state.cycler()
    if detected is not None:
      print('cycle detected - gen ', detected)
  def dump(self):
    self.colony.state.dump()
  def mode(self):
    self.colony.logic.toggle()
    self.colony.update()
  def next(self):
    self.colony.update()
  def pause(self):
    self.running = False
  def run(self):
    self.running = True
    self.timer()
  def randomize(self):
    self.colony.randomize()
  def smash(self):
    self.colony.state.smash()
  def quit(self):
    exit()
  def timer(self, delay=None):
    if delay is None:
      delay = self.delay_milliseconds
    if self.running:
      self.next()
      self.screen.ontimer(lambda: self.timer(delay), delay)

def main():
  ScreenRunner(rules='prime', displaymode='ambient', delay=0, rows=17, cols=23)   # delay in milliseconds 
  return "EVENTLOOP"

if __name__ == "__main__":
  msg = main()
  print(msg)
  mainloop()