From 1e346990048b826451eb6f46134bb09f9b57f3d3 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Wed, 24 Dec 2014 15:42:15 +0100 Subject: [PATCH] Add the original Conway rules. Use it as the demo. Add the size as an argument option of the script. Beautify. --- life.py | 99 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 79 insertions(+), 20 deletions(-) diff --git a/life.py b/life.py index a35ae57..7f3b7e7 100644 --- a/life.py +++ b/life.py @@ -6,32 +6,76 @@ import random def count( node, states, board, graph ): """Count the number of neighbours in each given states, in a single pass.""" nb = {s:0 for s in states} - for neighbor in graph[node]: for state in states: if board[neighbor] == state: nb[state] += 1 - - # This is the max size of the neighborhood on a rhomb Penrose tiling (P2) - assert( all(nb[s] <= 11 for s in states) ) - return nb -class Goucher: +class Rule: + """The template to create a rule for a game of life. + + A rule is just a set of states and a function to compute the state of a given node, + given the current board states and a neighborhood represented by an adjacency graph.""" + + class State: + default = 0 + + # Available states, the first one should be the default "empty" (or "dead") one. + states = [State.default] + + def __call__(self, node, board, graph ): + raise NotImplemented + + +class Conway(Rule): + """The original rules for Conway's game of life on square grid.""" + + class State: + dead = 0 + live = 1 + + states = [State.dead, State.live] + + def __call__(self, node, board, graph ): + # "a" is just a shortcut. + a = self.State() + next = a.dead + + nb = count( node, [a.live], board, graph ) + if board[node] is a.dead: + if nb[a.live] == 3: # reproduction + next = a.live + else: + assert(board[node] is a.live) + + if nb[a.live] < 2: # under-population + next = a.dead + elif nb[a.live] > 3: # over-population + next = a.dead + else: + assert( 2 <= nb[a.live] <= 3 ) + next = a.live + + return next + + +class Goucher(Rule): + """This is the Goucher 4-states rule. + It permits gliders on Penrose tiling. + From: Adam P. Goucher, "Gliders in cellular automata on Penrose tilings", J. Cellular Automata, 2012""" + class State: # Should be an Enum in py3k ground = 0 head = 1 tail = 2 wing = 3 - # Available states, the first one is the default "empty" (or "dead") one. states = [ State.ground, State.head, State.tail, State.wing ] def __call__(self, node, current, graph ): - """This is the Goucher 4-states rule. - From: Adam P. Goucher, "Gliders in cellular automata on Penrose tilings", J. Cellular Automata, 2012 - Summarized as: + """Summarized as: ------------------------------------------------------ | Current state | Neighbour condition | Next state | ------------------------------------------------------ @@ -51,7 +95,11 @@ class Goucher: if current[node] is a.ground: # Count the number of neighbors of each state in one pass. - nb = count( node, [a.head,a.tail,a.wing], current, graph ) + stated = [a.head,a.tail,a.wing] + nb = count( node, stated, current, graph ) + # This is the max size of the neighborhood on a rhomb Penrose tiling (P2) + assert( all(nb[s] <= 11 for s in stated) ) + if nb[a.head] >= 1 and nb[a.tail] >= 1: next = a.wing elif nb[a.head] >= 1 and nb[a.wing] >= 3: @@ -60,6 +108,8 @@ class Goucher: elif current[node] is a.head: # It is of no use to compute the number of heads and tails if the current state is not ground. nb = count( node, [a.wing], current, graph ) + assert( all(nb[s] <= 11 for s in [a.wing]) ) + if nb[a.wing] >= 1: next = a.tail else: @@ -103,29 +153,38 @@ def step( current, graph, rule ): return next -def play( board, graph, nb_gen, rule ): +def play( board, graph, rule, nb_gen ): for i in range(nb_gen): board = step( board, graph, rule ) if __name__ == "__main__": + import sys + # Simple demo on a square grid torus. graph = {} - size = 10 + size = 5 + if len(sys.argv) >= 2: + size = int(sys.argv[1]) + for i in range(size): for j in range(size): - graph[(i,j)] = [] - # All Moore neighborhood around a coordinate. - for di,dj in set(permutations( [0]+[-1,1]*2, 2)): - # Use modulo to avoid limits and create a torus. - graph[ (i,j) ].append( ( (i+di)%size, (j+dj)%size ) ) - rule = Goucher() + # All Moore neighborhood around a coordinate. + neighborhood = set(permutations( [0]+[-1,1]*2, 2)) # FIXME ugly + assert( len(neighborhood) == 8 ) + + graph[(i,j)] = [] + for di,dj in neighborhood: + # Use modulo to avoid limits and create a torus. + graph[ (i,j) ].append(( (i+di)%size, (j+dj)%size )) + + rule = Conway() # Fill a board with random states. board = make_board( graph, lambda x : random.choice(rule.states) ) # Play and print. - for i in range(5): + for i in range(size): print i for i in range(size): for j in range(size):