Add the original Conway rules.
Use it as the demo. Add the size as an argument option of the script. Beautify.
This commit is contained in:
parent
deaf9bd78c
commit
1e34699004
1 changed files with 76 additions and 17 deletions
99
life.py
99
life.py
|
|
@ -6,32 +6,76 @@ import random
|
||||||
def count( node, states, board, graph ):
|
def count( node, states, board, graph ):
|
||||||
"""Count the number of neighbours in each given states, in a single pass."""
|
"""Count the number of neighbours in each given states, in a single pass."""
|
||||||
nb = {s:0 for s in states}
|
nb = {s:0 for s in states}
|
||||||
|
|
||||||
for neighbor in graph[node]:
|
for neighbor in graph[node]:
|
||||||
for state in states:
|
for state in states:
|
||||||
if board[neighbor] == state:
|
if board[neighbor] == state:
|
||||||
nb[state] += 1
|
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
|
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
|
class State: # Should be an Enum in py3k
|
||||||
ground = 0
|
ground = 0
|
||||||
head = 1
|
head = 1
|
||||||
tail = 2
|
tail = 2
|
||||||
wing = 3
|
wing = 3
|
||||||
|
|
||||||
# Available states, the first one is the default "empty" (or "dead") one.
|
|
||||||
states = [ State.ground, State.head, State.tail, State.wing ]
|
states = [ State.ground, State.head, State.tail, State.wing ]
|
||||||
|
|
||||||
def __call__(self, node, current, graph ):
|
def __call__(self, node, current, graph ):
|
||||||
"""This is the Goucher 4-states rule.
|
"""Summarized as:
|
||||||
From: Adam P. Goucher, "Gliders in cellular automata on Penrose tilings", J. Cellular Automata, 2012
|
|
||||||
Summarized as:
|
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
| Current state | Neighbour condition | Next state |
|
| Current state | Neighbour condition | Next state |
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
|
|
@ -51,7 +95,11 @@ class Goucher:
|
||||||
|
|
||||||
if current[node] is a.ground:
|
if current[node] is a.ground:
|
||||||
# Count the number of neighbors of each state in one pass.
|
# 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:
|
if nb[a.head] >= 1 and nb[a.tail] >= 1:
|
||||||
next = a.wing
|
next = a.wing
|
||||||
elif nb[a.head] >= 1 and nb[a.wing] >= 3:
|
elif nb[a.head] >= 1 and nb[a.wing] >= 3:
|
||||||
|
|
@ -60,6 +108,8 @@ class Goucher:
|
||||||
elif current[node] is a.head:
|
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.
|
# 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 )
|
nb = count( node, [a.wing], current, graph )
|
||||||
|
assert( all(nb[s] <= 11 for s in [a.wing]) )
|
||||||
|
|
||||||
if nb[a.wing] >= 1:
|
if nb[a.wing] >= 1:
|
||||||
next = a.tail
|
next = a.tail
|
||||||
else:
|
else:
|
||||||
|
|
@ -103,29 +153,38 @@ def step( current, graph, rule ):
|
||||||
return next
|
return next
|
||||||
|
|
||||||
|
|
||||||
def play( board, graph, nb_gen, rule ):
|
def play( board, graph, rule, nb_gen ):
|
||||||
for i in range(nb_gen):
|
for i in range(nb_gen):
|
||||||
board = step( board, graph, rule )
|
board = step( board, graph, rule )
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
|
||||||
# Simple demo on a square grid torus.
|
# Simple demo on a square grid torus.
|
||||||
graph = {}
|
graph = {}
|
||||||
size = 10
|
size = 5
|
||||||
|
if len(sys.argv) >= 2:
|
||||||
|
size = int(sys.argv[1])
|
||||||
|
|
||||||
for i in range(size):
|
for i in range(size):
|
||||||
for j 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.
|
# Fill a board with random states.
|
||||||
board = make_board( graph, lambda x : random.choice(rule.states) )
|
board = make_board( graph, lambda x : random.choice(rule.states) )
|
||||||
|
|
||||||
# Play and print.
|
# Play and print.
|
||||||
for i in range(5):
|
for i in range(size):
|
||||||
print i
|
print i
|
||||||
for i in range(size):
|
for i in range(size):
|
||||||
for j in range(size):
|
for j in range(size):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue