File: EmulateMargolus.py

package info (click to toggle)
golly 3.2-2
  • links: PTS
  • area: main
  • in suites: buster
  • size: 19,516 kB
  • sloc: cpp: 69,819; ansic: 25,894; python: 7,921; sh: 4,267; objc: 3,721; java: 2,781; xml: 1,362; makefile: 530; perl: 69
file content (157 lines) | stat: -rw-r--r-- 7,627 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# We emulate Margolus and related partitioning neighborhoods by making a 2-layer CA, with a
# background layer (0,1) that alternates a sort-of-checker pattern, and the normal states on top.
#
# For the Margolus neighborhood there is only one 'phase'; one background pattern, that alternates
# between top-left and bottom-right of a 2x2 region.
#
# For the square4_* neighborhoods we need two phases since the background shifts in different
# directions to make a figure-8 or cyclic pattern.
#
# By looking at the nearby background states, each cell can work out where in the partition it is
# and what the current phase is. It can therefore update the cell states layer correctly, and also
# update the background layer for the next phase.

import golly
import os
from glife.RuleTree import *

# state s (0,N-1) on background state b (0,1) becomes 1+b+2s with 0 = off-grid
def encode(s,b): # (s is a list)
    return [ 1+b+2*se for se in s ]

# The BackgroundInputs array tells us where we are in the block:
#
# Phase 1:   0  1   (the lone 0 is the top-left of the block to be updated)
#            1  1
#
# Phase 2:   1  0   (the lone 1 is the top-left of the block to be updated)
#            0  0
#
# (imagine the blocks stacked together to see why this gives the values below)
#
BackgroundInputs = [ # each C,S,E,W,N,SE,SW,NE,NW
    [0,1,1,1,1,1,1,1,1], # this pattern of background states means that this is top-left, phase 1
    [1,1,0,0,1,1,1,1,1], # top-right, phase 1
    [1,0,1,1,0,1,1,1,1], # bottom-left, phase 1
    [1,1,1,1,1,0,0,0,0], # bottom-right, phase 1
    [1,0,0,0,0,0,0,0,0], # top-left, phase 2
    [0,0,1,1,0,0,0,0,0], # top-right, phase 2
    [0,1,0,0,1,0,0,0,0], # bottom-left, phase 2
    [0,0,0,0,0,1,1,1,1], # bottom-right, phase 2
]

# The BackgroundOutputs array tells us how the background pattern changes:
#
# Margolus:     0  1   become   1  1   (block moves diagonally)
#               1  1            1  0
#
# square4_figure8v:     0  1   becomes   0  0   (block moves diagonally)
#                       1  1             0  1
#
#                       1  0   becomes   1  0   (block moves horizontally)
#                       0  0             1  1
#
# square4_figure8h:     0  1   becomes   0  0   (block moves diagonally)
#                       1  1             0  1
#
#                       1  0   becomes   1  1   (block moves vertically)
#                       0  0             0  1
#
# square4_cyclic  :     0  1   becomes   0  0   (block moves vertically)
#                       1  1             1  0
#
#                       1  0   becomes   1  0   (block moves horizontally)
#                       0  0             1  1
#
# (Following Tim Tyler: http://www.cell-auto.com/neighbourhood/square4/index.html)
#
BackgroundOutputs = { # (top-left, top-right, bottom-left, bottom-right)*2 (or *1 for Margolus)
    "Margolus":        [1,1,1,0], # phase 1 -> phase 1
    "square4_figure8v":[0,0,0,1,1,0,1,1], # phase 1 <--> phase 2
    "square4_figure8h":[0,0,0,1,1,1,0,1], # phase 1 <--> phase 2
    "square4_cyclic":  [0,0,1,0,1,0,1,1], # phase 1 <--> phase 2
}

# The ForegroundInputs array tells us, for each of the 4 positions (0=top-left, 1=top-right,
# 2=bottom-left, 3=bottom-right), where the other positions are found in our Moore neighborhood
# e.g. if we're 0 (top-left) then the first row tells us that to our South is position 2 (bottom-left)
#      and to our South-East is position 3 (bottom-right).
# (N.B. these values don't depend on the phase or the background pattern)
ForegroundInputs = [
    [0,2,1,-1,-1,3,-1,-1,-1], # what surrounds this entry?
    [1,3,-1,0,-1,-1,2,-1,-1], # (C,S,E,W,N,SE,SW,NE,NW)
    [2,-1,3,-1,0,-1,-1,1,-1], # (-1 = anything)
    [3,-1,-1,2,1,-1,-1,-1,0]  # (0,1,2,3 = index of Margolus input)
]

def EmulateMargolus(neighborhood,n_states,transitions,input_filename):
    '''Emulate a Margolus or square4_* neighborhood rule table with a Moore neighborhood rule tree.'''
    rule_name = os.path.splitext(os.path.split(input_filename)[1])[0]
    total_states = 1+2*n_states
    tree = RuleTree(total_states,8)
    # now work through the transitions
    for tr in transitions:
        for iOutput,background_output in enumerate(BackgroundOutputs[neighborhood]):
            bg_inputs = BackgroundInputs[iOutput]
            iEntry = iOutput % 4  # (0=top-left, 1=top-right, 2=bottom-left, 3=bottom-right)
            rule_inputs = []
            for i in range(9):
                if ForegroundInputs[iEntry][i]==-1:
                    rule_inputs.append(encode( range(n_states), bg_inputs[i] ) + [0]) # wildcard
                else:
                    rule_inputs.append(encode( tr[ForegroundInputs[iEntry][i]], bg_inputs[i] ))
            tree.add_rule( rule_inputs, encode( tr[iEntry+4], background_output )[0] )
    # supply default behaviour: background still changes even if the state doesn't
    for iState in range(n_states):
        for iOutput,background_output in enumerate(BackgroundOutputs[neighborhood]):
            bg_inputs = BackgroundInputs[iOutput]
            tree.add_rule( [ encode( [iState], bg_inputs[0] ) ] +
                           [ encode( range(n_states), bg_inputs[i] )+[0] for i in range(1,9) ], # wildcard
                           encode( [iState], background_output )[0] )

    # output the rule tree
    golly.show("Compressing rule tree and saving to file...")
    tree.write(golly.getdir('rules') + rule_name + '.tree')

    # also save a .colors file
    golly.show("Generating colors...")

    # read rule_name+'.colors' file if it exists
    cfn = os.path.split(input_filename)[0] + '/' + os.path.splitext(os.path.split(input_filename)[1])[0] + ".colors"
    try:
        cf = open(cfn,'r')
    except IOError:
        # use Golly's default random colours
        random_colors=[[90,90,90],[0,255,127],[127,0,255],[148,148,148],[128,255,0],[255,0,128],[0,128,255],[1,159,0],
            [159,0,1],[255,254,96],[0,1,159],[96,255,254],[254,96,255],[126,125,21],[21,126,125],[125,21,126],
            [255,116,116],[116,255,116],[116,116,255],[228,227,0],[28,255,27],[255,27,28],[0,228,227],
            [227,0,228],[27,28,255],[59,59,59],[234,195,176],[175,196,255],[171,194,68],[194,68,171],
            [68,171,194],[72,184,71],[184,71,72],[71,72,184],[169,255,188],[252,179,63],[63,252,179],
            [179,63,252],[80,9,0],[0,80,9],[9,0,80],[255,175,250],[199,134,213],[115,100,95],[188,163,0],
            [0,188,163],[163,0,188],[203,73,0],[0,203,73],[73,0,203],[94,189,0],[189,0,94]]
        colors = dict(zip(range(len(random_colors)),random_colors))
    else:
        # read from the .colors file
        colors = {}
        for line in cf:
            if line[0:5]=='color':
                entries = map(int,line[5:].replace('=',' ').replace('\n',' ').split())
                if len(entries)<4:
                    continue # too few entries, ignore
                colors.update({entries[0]:[entries[1],entries[2],entries[3]]})
        # (TODO: support gradients in .colors)

    # provide a deep blue background if none provided
    if not 0 in colors:
        colors.update({0:[0,0,120]})

    c = open(golly.getdir('rules')+rule_name+".colors",'w')
    for col in colors.items()[:n_states]:
        c.write('color='+str(col[0]*2+1)+' '+' '.join(map(str,col[1]))+'\n')
        c.write('color='+str(col[0]*2+2)+' '+' '.join([ str(int(x*0.7)) for x in col[1] ])+'\n')  # (darken slightly)
    c.flush()
    c.close()

    # use rule_name.tree and rule_name.colors to create rule_name.rule (no icon info)
    ConvertTreeToRule(rule_name, total_states, [])
    return rule_name