I misinterpreted what you were looking for because of the context in which it was asked. But I thought this was a nice idea anyway, so here is iso-permaOn-gen.py
It's a bit of a hack based on isotropic-rule.py and as such the comments are a bit dodgy and sparse.
It generates a 3 state rule with permaOn cells and then optionally draws a boundary of permaOn cells on the border of a bounded grid.
Code: Select all
# iso-permaOn-gen.py
#
# Derived from isotropic-rule.py / isotropicRulegen.py
# which itself was derived from apgsearch v1.0
#
# Generate a rule table for an isotropic rule using Alan Hensel's
# isotropic, non-totalistic rule format for CA on the Moore neighbourhood
# Adds a third state representing permanently On cells
#
# Includes code written by: Eric Goldstein, Adam P. Goucher, Dave Green, Arie Paap, and Andrew Trevorrow
import golly as g
import os
# Generates the helper rules for apgsearch-isotropic, given a base isotropic
# rule in Hensel's notation.
class RuleGenerator:
# notationdict adapted from Eric Goldstein's HenselNotation->Ruletable(1.3).py
# Modified for neighbourhood2 version of Hensel's notation
notationdict = {
"0" : [0,0,0,0,0,0,0,0], #
"1e" : [1,0,0,0,0,0,0,0], # N
"1c" : [0,1,0,0,0,0,0,0], # NE
"2a" : [1,1,0,0,0,0,0,0], # N, NE
"2e" : [1,0,1,0,0,0,0,0], # N, E
"2k" : [1,0,0,1,0,0,0,0], # N, SE
"2i" : [1,0,0,0,1,0,0,0], # N, S
"2c" : [0,1,0,1,0,0,0,0], # NE, SE
"2n" : [0,1,0,0,0,1,0,0], # NE, SW
"3a" : [1,1,1,0,0,0,0,0], # N, NE, E
"3n" : [1,1,0,1,0,0,0,0], # N, NE, SE
"3r" : [1,1,0,0,1,0,0,0], # N, NE, S
"3q" : [1,1,0,0,0,1,0,0], # N, NE, SW
"3j" : [1,1,0,0,0,0,1,0], # N, NE, W
"3i" : [1,1,0,0,0,0,0,1], # N, NE, NW
"3e" : [1,0,1,0,1,0,0,0], # N, E, S
"3k" : [1,0,1,0,0,1,0,0], # N, E, SW
"3y" : [1,0,0,1,0,1,0,0], # N, SE, SW
"3c" : [0,1,0,1,0,1,0,0], # NE, SE, SW
"4a" : [1,1,1,1,0,0,0,0], # N, NE, E, SE
"4r" : [1,1,1,0,1,0,0,0], # N, NE, E, S
"4q" : [1,1,1,0,0,1,0,0], # N, NE, E, SW
"4i" : [1,1,0,1,1,0,0,0], # N, NE, SE, S
"4y" : [1,1,0,1,0,1,0,0], # N, NE, SE, SW
"4k" : [1,1,0,1,0,0,1,0], # N, NE, SE, W
"4n" : [1,1,0,1,0,0,0,1], # N, NE, SE, NW
"4z" : [1,1,0,0,1,1,0,0], # N, NE, S, SW
"4j" : [1,1,0,0,1,0,1,0], # N, NE, S, W
"4t" : [1,1,0,0,1,0,0,1], # N, NE, S, NW
"4w" : [1,1,0,0,0,1,1,0], # N, NE, SW, W
"4e" : [1,0,1,0,1,0,1,0], # N, E, S, W
"4c" : [0,1,0,1,0,1,0,1], # NE, SE, SW, NW
"5i" : [1,1,1,1,1,0,0,0], # N, NE, E, SE, S
"5j" : [1,1,1,1,0,1,0,0], # N, NE, E, SE, SW
"5n" : [1,1,1,1,0,0,1,0], # N, NE, E, SE, W
"5a" : [1,1,1,1,0,0,0,1], # N, NE, E, SE, NW
"5q" : [1,1,1,0,1,1,0,0], # N, NE, E, S, SW
"5c" : [1,1,1,0,1,0,1,0], # N, NE, E, S, W
"5r" : [1,1,0,1,1,1,0,0], # N, NE, SE, S, SW
"5y" : [1,1,0,1,1,0,1,0], # N, NE, SE, S, W
"5k" : [1,1,0,1,0,1,1,0], # N, NE, SE, SW, W
"5e" : [1,1,0,1,0,1,0,1], # N, NE, SE, SW, NW
"6a" : [1,1,1,1,1,1,0,0], # N, NE, E, SE, S, SW
"6c" : [1,1,1,1,1,0,1,0], # N, NE, E, SE, S, W
"6k" : [1,1,1,1,0,1,1,0], # N, NE, E, SE, SW, W
"6e" : [1,1,1,1,0,1,0,1], # N, NE, E, SE, SW, NW
"6n" : [1,1,1,0,1,1,1,0], # N, NE, E, S, SW, W
"6i" : [1,1,0,1,1,1,0,1], # N, NE, SE, S, SW, NW
"7c" : [1,1,1,1,1,1,1,0], # N, NE, E, SE, S, SW, W
"7e" : [1,1,1,1,1,1,0,1], # N, NE, E, SE, S, SW, NW
"8" : [1,1,1,1,1,1,1,1], # N, NE, E, SE, S, SW, W, NW
}
allneighbours = [
["0"],
["1e", "1c"],
["2a", "2e", "2k", "2i", "2c", "2n"],
["3a", "3n", "3r", "3q", "3j", "3i", "3e", "3k", "3y", "3c"],
["4a", "4r", "4q", "4i", "4y", "4k", "4n", "4z", "4j", "4t", "4w", "4e", "4c"],
["5i", "5j", "5n", "5a", "5q", "5c", "5r", "5y", "5k", "5e"],
["6a", "6c", "6k", "6e", "6n", "6i"],
["7c", "7e"],
["8"],
]
allneighbours_flat = [n for x in allneighbours for n in x]
numneighbours = len(notationdict)
# Use dict to store rule elements, initialised by setrule():
bee = {}
ess = {}
alphanumeric = ""
rulename = ""
# Save the isotropic rule
def saveAllRules(self):
self.saveIsotropicRule()
# Interpret birth or survival string
def ruleparts(self, part):
inverse = False
nlist = []
totalistic = True
rule = { k: False for k, v in self.notationdict.iteritems() }
# Reverse the rule string to simplify processing
part = part[::-1]
for c in part:
if c.isdigit():
d = int(c)
if totalistic:
# Add all the neighbourhoods for this value
for neighbour in self.allneighbours[d]:
rule[neighbour] = True
elif inverse:
# Add all the neighbourhoods not in nlist for this value
for neighbour in self.allneighbours[d]:
if neighbour[1] not in nlist:
rule[neighbour] = True
else:
# Add all the neighbourhoods in nlist for this value
for n in nlist:
neighbour = c + n
if neighbour in rule:
rule[neighbour] = True
else:
# Error
return {}
inverse = False
nlist = []
totalistic = True
elif (c == '-'):
inverse = True
else:
totalistic = False
nlist.append(c)
return rule
# Set isotropic, non-totalistic rule
# Adapted from Eric Goldstein's HenselNotation->Ruletable(1.3).py
def setrule(self, rulestring):
# neighbours_flat = [n for x in neighbours for n in x]
b = {}
s = {}
sep = ''
birth = ''
survive = ''
rulestring = rulestring.lower()
if '/' in rulestring:
sep = '/'
elif '_' in rulestring:
sep = '_'
elif (rulestring[0] == 'b'):
sep = 's'
else:
sep = 'b'
survive, birth = rulestring.split(sep)
if (survive[0] == 'b'):
survive, birth = birth, survive
survive = survive.replace('s', '')
birth = birth.replace('b', '')
b = self.ruleparts(birth)
s = self.ruleparts(survive)
if b and s:
self.alphanumeric = 'B' + birth + 'S' + survive
self.rulename = 'B' + birth + 'S' + survive
self.bee = b
self.ess = s
else:
# Error
g.note("Unable to process rule definition.\n" +
"b = " + str(b) + "\ns = " + str(s))
g.exit()
# Save a rule file:
def saverule(self, name, comments, table, colours):
ruledir = g.getdir("rules")
filename = ruledir + name + ".rule"
results = "@RULE " + name + "\n\n"
results += "*** File autogenerated by saverule. ***\n\n"
results += comments
results += "\n\n@TABLE\n\n"
results += table
results += "\n\n@COLORS\n\n"
results += colours
results += "\n\n@ICONS\n\n"
results += "circles\n"
try:
f = open(filename, 'w')
f.write(results)
f.close()
except:
g.warn("Unable to create rule table:\n" + filename)
# Defines a variable:
def newvar(self, name, vallist):
line = "var "+name+"={"
for i in xrange(len(vallist)):
if (i > 0):
line += ','
line += str(vallist[i])
line += "}\n"
return line
# Defines a block of equivalent variables:
def newvars(self, namelist, vallist):
block = "\n"
for name in namelist:
block += self.newvar(name, vallist)
return block
def scoline(self, chara, charb, left, right, amount):
line = str(left) + ","
for i in xrange(8):
if (i < amount):
line += chara
else:
line += charb
line += chr(97 + i)
line += ","
line += str(right) + "\n"
return line
def isotropicline(self, chara, charb, left, right, n):
line = str(left) + ","
neighbours = self.notationdict[n]
for i in xrange(8):
if neighbours[i]:
line += chara
else:
line += charb
line += chr(97 + i)
line += ","
line += str(right) + "\n"
return line
def savePermaOnRule(self):
comments = """
This is a two state, isotropic, non-totalistic rule on the Moore neighbourhood.
This rules add a third state which acts as a permanently On cell.
"""
table = """
n_states:3
neighborhood:Moore
symmetries:rotate4reflect
"""
table += self.newvars(["a","b","c","d","e","f","g","h"], [0, 1, 2])
table += self.newvars(["da","db","dc","dd","de","df","dg","dh"], [0])
table += self.newvars(["la","lb","lc","ld","le","lf","lg","lh"], [1, 2])
table += "\n# Birth\n"
for n in self.allneighbours_flat:
if self.bee[n]:
table += self.isotropicline('l', 'd', 0, 1, n)
table += "\n# Survival\n"
for n in self.allneighbours_flat:
if self.ess[n]:
table += self.isotropicline('l', 'd', 1, 1, n)
table += "\n# Death\n"
table += self.scoline("","",1,0,0)
colours = """
1 255 0 0
2 0 64 255
"""
self.saverule(self.rulename, comments, table, colours)
# Adapted from Golly's draw-lines.py by Andrew Trevorrow
def drawline(x1, y1, x2, y2, drawstate):
# draw a line of cells from x1,y1 to x2,y2 using Bresenham's algorithm;
if x1 == x2 and y1 == y2:
return
dx = x2 - x1
ax = abs(dx) * 2
sx = 1
if dx < 0: sx = -1
dy = y2 - y1
ay = abs(dy) * 2
sy = 1
if dy < 0: sy = -1
if ax > ay:
d = ay - (ax / 2)
while x1 != x2:
g.setcell(x1, y1, drawstate)
if d >= 0:
y1 += sy
d -= ax
x1 += 2*sx
d += ay
else:
d = ax - (ay / 2)
while y1 != y2:
g.setcell(x1, y1, drawstate)
if d >= 0:
x1 += sx
d -= ay
y1 += 2*sy
d += ax
g.setcell(x2, y2, drawstate)
return
# Test the current rule to see if it can be used
bounds = ''
if g.getalgo() in ['QuickLife', 'HashLife']:
rulestring = g.getrule()
if ':' in rulestring:
rulestring, bounds = rulestring.split(':')
else:
rulestring = 'B3S23'
rulestring = g.getstring("This script generates a rule table for an isotropic 2-state CA\n" +
"with an added third state which act as permanently On cells\n" +
"Enter rule string in Alan Hensel's isotropic rule notation",
rulestring)
rg = RuleGenerator()
rg.setrule(rulestring)
rg.rulename = "PermaOn_" + rg.rulename
rg.savePermaOnRule()
if bounds and (bounds[0] == 'P'):
wd, ht = map(int, bounds[1:].split(','))
g.show(str([wd,ht]))
else:
size = g.getstring("Enter size of bounded grid to create, 0 to skip:", "0")
try:
wd = int(size)
ht = wd
except:
wd, ht = 0, 0
if wd and ht:
g.setrule("%s:P%d,%d" % (rg.rulename, wd, ht))
# Draw a boundary of PermaOn cells
left, right, top, bottom = -wd/2+wd%2, wd/2-1+wd%2, -ht/2+ht%2, ht/2-1+ht%2
drawline(left, top, right, top, 2)
drawline(right, top, right, bottom, 2)
drawline(right, bottom, left, bottom, 2)
drawline(left, bottom, left, top, 2)
else:
g.setrule(rg.rulename)
To modify this to get the originally requested script would probably only require a few changes:
Some comments and other bits of text should also be updated if it were to be published, and then I realised it made more sense for the script to create a four state rule with both permaOn and permaOff cells. Then one script could simulate bounded grids with outer space being either all On cells or all Off cells without needing a bounded grid in Golly.