Adds a cellular automata with Goucher 4-states rules.
And a simple demo on a square torus grid.
This commit is contained in:
parent
0cbfe6da92
commit
013c004807
1 changed files with 135 additions and 0 deletions
135
life.py
Normal file
135
life.py
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
from copy import copy
|
||||
from itertools import permutations
|
||||
import random
|
||||
|
||||
|
||||
def count( node, states, game, 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 game[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 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:
|
||||
------------------------------------------------------
|
||||
| Current state | Neighbour condition | Next state |
|
||||
------------------------------------------------------
|
||||
| 0 | n1>=1 | n2>=1 | * | 3 |
|
||||
| 0 | n1>=1 | * | n3>=2 | 3 |
|
||||
| 1 | * | * | n3>=1 | 2 |
|
||||
| 1 | * | * | * | 1 |
|
||||
| 2 | * | * | * | 3 |
|
||||
| * | * | * | * | 0 |
|
||||
------------------------------------------------------
|
||||
"""
|
||||
# "a" is just a shortcut.
|
||||
a = self.State()
|
||||
|
||||
# Default state, if nothing matches.
|
||||
next = a.ground
|
||||
|
||||
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 )
|
||||
if nb[a.head] >= 1 and nb[a.tail] >= 1:
|
||||
next = a.wing
|
||||
elif nb[a.head] >= 1 and nb[a.wing] >= 3:
|
||||
next = a.wing
|
||||
|
||||
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 )
|
||||
if nb[a.wing] >= 1:
|
||||
next = a.tail
|
||||
else:
|
||||
next = a.head
|
||||
|
||||
elif current[node] is a.tail:
|
||||
next = a.wing
|
||||
|
||||
# Default to ground, as stated above.
|
||||
# else:
|
||||
# next = a.ground
|
||||
|
||||
return next
|
||||
|
||||
|
||||
def make_game( graph, state = lambda x: 0 ):
|
||||
"""Create a new game board, filled with the results of the calls to the given state function.
|
||||
The given graph should be an iterable with all the nodes.
|
||||
The given state function should take a node and return a state.
|
||||
The default state function returns zero.
|
||||
"""
|
||||
game = {}
|
||||
for node in graph:
|
||||
game[node] = state(node)
|
||||
return game
|
||||
|
||||
|
||||
def step( current, graph, rule ):
|
||||
"""Compute one generation of the game.
|
||||
i.e. apply the given rule function on each node of the given graph board.
|
||||
The given current board should associate a state to a node.
|
||||
The given graph should associate each node with its neighbors.
|
||||
The given rule is a function that takes a node, the current board and the graph and return the next state of the node."""
|
||||
|
||||
# Defaults to the first state of the rule.
|
||||
next = make_game(graph, lambda x : rule.states[0])
|
||||
|
||||
for node in graph:
|
||||
next[node] = rule( node, current, graph )
|
||||
|
||||
return next
|
||||
|
||||
|
||||
def play( game, graph, nb_gen, rule ):
|
||||
for i in range(nb_gen):
|
||||
game = step( game, graph, rule )
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Simple demo on a square grid torus.
|
||||
graph = {}
|
||||
size = 10
|
||||
for i in range(size):
|
||||
for j in range(size):
|
||||
graph[(i,j)] = []
|
||||
# 2-permutations of 1-axis neighbors directions == all Moore neighborhood vectors around a coordinate.
|
||||
for di,dj in permutations( (-1,0,1), 2 ):
|
||||
# Use modulo to avoid limits and create a torus.
|
||||
graph[ (i,j) ].append( ( (i+di)%size, (j+dj)%size ) )
|
||||
|
||||
rule = Goucher()
|
||||
# Fill a board with random states.
|
||||
game = make_game( graph, lambda x : random.choice(rule.states) )
|
||||
|
||||
# Play and print.
|
||||
for i in range(5):
|
||||
print i
|
||||
for i in range(size):
|
||||
for j in range(size):
|
||||
print game[(i,j)],
|
||||
print ""
|
||||
game = step(game,graph,rule)
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue