User:Danf/TurtleGraphics
Jump to navigation
Jump to search
# 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()