Currently it is in a 'junk script' stage and doesn't actually find anything, but I'm posting it here just in case someone gets interested and develops it.
The idea was from Jason Summers, who (I think) wrote his own C program to search for spark assisted oscs.
It emulates Life with sparks with this rule (SparkyLife.rule)
Code: Select all
@RULE SparkyLife
# This rule Emulates Life perturbation with sparks.
# Two gens in SparkyLife equals to one gen in normal Life.
@TABLE
# States
# 0: OFF cell
# 1: ON cell on even gens
# 2: ON cell on odd gens (auxillary state)
# 3: ON spark
# 4: OFF spark
n_states:5
neighborhood:Moore
symmetries:rotate8
var on1 = {1,3}
var on2 = {1,3}
var on3 = {1,3}
var off1 = {0,4}
var off2 = {0,4}
var off3 = {0,4}
var off4 = {0,4}
var off5 = {0,4}
var off6 = {0,4}
var off7 = {0,4}
var off8 = {0,4}
var a1 = {0,1,2,3,4}
var a2 = {0,1,2,3,4}
var a3 = {0,1,2,3,4}
var a4 = {0,1,2,3,4}
var a5 = {0,1,2,3,4}
var a6 = {0,1,2,3,4}
var a7 = {0,1,2,3,4}
var a8 = {0,1,2,3,4}
var b = {0,3,4}
# Gen 2k -> 2k+1
# Evolve ON cells as normal Life, considering ON sparks.
# The states change from 1 to 2.
0,off1,off2,off3,off4,off5,on1,on2,on3,2
0,off1,off2,off3,off4,on1,off5,on2,on3,2
0,off1,off2,off3,off4,on1,on2,off5,on3,2
0,off1,off2,off3,on1,off4,off5,on2,on3,2
0,off1,off2,off3,on1,off4,on2,off5,on3,2
0,off1,off2,off3,on1,on2,off4,off5,on3,2
0,off1,off2,on1,off3,off4,on2,off5,on3,2
1,off1,off2,off3,off4,off5,off6,on1,on2,2
1,off1,off2,off3,off4,off5,on1,off6,on2,2
1,off1,off2,off3,off4,on1,off5,off6,on2,2
1,off1,off2,off3,on1,off4,off5,off6,on2,2
1,off1,off2,off3,off4,off5,on1,on2,on3,2
1,off1,off2,off3,off4,on1,off5,on2,on3,2
1,off1,off2,off3,off4,on1,on2,off5,on3,2
1,off1,off2,off3,on1,off4,off5,on2,on3,2
1,off1,off2,off3,on1,off4,on2,off5,on3,2
1,off1,off2,off3,on1,on2,off4,off5,on3,2
1,off1,off2,on1,off3,off4,on2,off5,on3,2
1,off1,off2,off3,off4,off5,off6,off7,a1,0
1,a1,a2,a3,a4,1,1,1,1,0
1,off1,a1,a2,1,off2,1,1,1,0
1,off1,a1,a2,1,1,off2,1,1,0
1,off1,off2,off3,1,1,1,off4,1,0
1,off1,off2,1,off3,off4,1,1,1,0
1,off1,off2,1,off3,1,off4,1,1,0
1,off1,off2,1,off3,1,1,off4,1,0
1,off1,off2,1,1,off3,off4,1,1,0
1,off1,off2,1,1,off3,1,off4,1,0
1,off1,1,off2,1,off3,1,off4,1,0
# Gen 2k+1 -> 2k+2
# ON cells don't change, but their states change from 2 to 1.
# ON sparks turn off iff there is an ON cell nearby.
2,a1,a2,a3,a4,a5,a6,a7,a8,1
3,a1,a2,a3,a4,a5,a6,a7,2,4
# OFF sparks turn ON again.
4,0,0,0,0,0,0,0,b,3
@COLORS
1 255 255 255
2 128 128 128
3 255 255 0
4 128 128 0
And generates random soups and tests oscillation with this script(SparkyLifeSearch):
Code: Select all
import golly as g
import random
# List of symmetries
C1 = lambda (x, y): [(x, y)]
C21 = lambda (x, y): [(x, y), (-x, -y)]
C22 = lambda (x, y): [(x, y), (-x, -y-1)]
C24 = lambda (x, y): [(x, y), (-x-1, -y-1)]
C41 = lambda (x, y): [(x, y), (-y, x), (-x, -y), (y, -x)]
C44 = lambda (x, y): [(x, y), (-y-1, x), (-x-1, -y-1), (y, -x-1)]
D21 = lambda (x, y): [(x, y), (x, -y)]
D22 = lambda (x, y): [(x, y), (x, -y-1)]
D2X = lambda (x, y): [(x, y), (y, x)]
D41 = lambda (x, y): [(x, y), (x, -y), (-x, y), (-x, -y)]
D42 = lambda (x, y): [(x, y), (x, -y-1), (-x, y), (-x, -y-1)]
D44 = lambda (x, y): [(x, y), (x, -y-1), (-x-1, y), (-x-1, -y-1)]
D4X1 = lambda (x, y): [(x, y), (y, x), (-x, -y), (-y, -x)]
D4X4 = lambda (x, y): [(x, y), (y, x), (-x-1, -y-1), (-y-1, -x-1)]
D81 = lambda (x, y): [(x, y), (x, -y), (-x, y), (-x, -y), (y, x), (y, -x), (-y, x), (-y, -x)]
D84 = lambda (x, y): [(x, y), (x, -y-1), (-x-1, y), (-x-1, -y-1), (y, x), (y, -x-1), (-y-1, x), (-y-1, -x-1)]
class SparkSoup(object):
symmetrydict = {'C1':C1, 'C21':C21, 'C22':C22, 'C24':C24, 'C41':C41, 'C44':C44,\
'D21':D21, 'D22':D22, 'D2X':D2X, 'D41':D41, 'D42':D42, 'D44':D44,\
'D4X1':D4X1, 'D4X4':D4X4, 'D81':D81, 'D84':D84}
def __init__(self, width, height, symstring, sparkdist):
self.halfwidth = width/2
self.halfheight = height/2
self.widthparity = width%2
self.heightparity = height%2
self.symmetry = self.symmetrydict[symstring]
self.canonicalcells = self.getcanonicalcells(width, height)
self.sparkdist = sparkdist
def getcanonicalcells(self, width, height):
hrange = xrange((-height+1)/2, (height+1)/2)
wrange = xrange((-width+1)/2, (width+1)/2)
cells = [(w, h) for w in wrange for h in hrange]
canonical = []
dependent = []
for cell in cells:
if cell not in dependent:
canonical.append(cell)
dependent += self.symmetry(cell)
return canonical
def randfill(self):
# Generate random soup
n = len(self.canonicalcells)
canonicalbits = random.getrandbits(n)
oncells = []
oncelllist = []
for i in xrange(n):
if canonicalbits & (1<<i):
oncells += self.symmetry(self.canonicalcells[i])
for oncell in oncells:
oncelllist.append(oncell[0])
oncelllist.append(oncell[1])
oncelllist.append(1)
# Add sparks
rand = random.getrandbits(2)
if rand&1:
xmin = 0
xmax = self.halfwidth
ymin = self.halfheight+2
ymax = self.halfheight+self.sparkdist
else:
xmin = self.halfwidth+2
xmax = self.halfwidth+self.sparkdist
ymin = 0
ymax = self.halfheight
x = random.randint(xmin,xmax)
y = random.randint(ymin,ymax)
for (u,v) in self.symmetry((x, y)):
oncelllist.append(u)
oncelllist.append(v)
oncelllist.append(3)
# For domino sparks
if rand&2:
if rand&1:
for (u,v) in self.symmetry((x+1, y)):
oncelllist.append(u)
oncelllist.append(v)
oncelllist.append(3)
else:
for (u,v) in self.symmetry((x, y+1)):
oncelllist.append(u)
oncelllist.append(v)
oncelllist.append(3)
if len(oncelllist) %2 == 0:
oncelllist.append(0)
return oncelllist
def symtest():
g.new('Symmetry Test')
g.setrule('SparkyLife')
g.setstep(1)
g.setbase(2)
symmetries = [['C1'], ['C21', 'C22', 'C24'], ['C41', 'C44'], ['D21', 'D22', 'D2X'],\
['D41', 'D42', 'D44', 'D4X1', 'D4X4'],['D81', 'D84']]
for i in range(len(symmetries)):
for j in range(len(symmetries[i])):
samplesoup = SparkSoup(16, 16, symmetries[i][j], 5)
g.putcells(samplesoup.randfill(), 40*j, 40*i)
g.fit()
#symtest()
# Code from oscar.py
class Oscar(object):
def __init__(self):
# initialize lists
self.hashlist = [] # for pattern hash values
self.genlist = [] # corresponding generation counts
self.poplist = [] # corresponding population counts
self.period = None
def reset(self):
# initialize lists
self.hashlist = [] # for pattern hash values
self.genlist = [] # corresponding generation counts
self.poplist = [] # corresponding population counts
self.period = None
def oscillating(self, prect):
# Test if pattern (without sparks) is empty:
cells = g.getcells(prect)
curpop = len(cells) # Current population * 3 (optionally +1)
for i in cells[::3]:
if i==1 or i==2:
break
else:
return True
curgen = int(g.getgen())
# Get current pattrn and create hash
h = g.hash(prect)
# determine where to insert h into self.hashlist
pos = 0
listlen = len(self.hashlist)
while pos < listlen:
if h > self.hashlist[pos]:
pos += 1
elif h < self.hashlist[pos]:
# shorten lists and append info below
del self.hashlist[pos : listlen]
del self.genlist[pos : listlen]
del self.poplist[pos : listlen]
break
else:
# h == self.hashlist[pos] so pattern is probably oscillating, but just in
# case this is a hash collision we also compare pop count and box size
if (curpop == self.poplist[pos]):
self.period = (curgen - self.genlist[pos])/2 # 2 SparkyLife gens = 1 Life gen
return True
else:
# look at next matching hash value or insert if no more
pos += 1
# store hash/gen/pop/box info at same position in various lists
self.hashlist.insert(pos, h)
self.genlist.insert(pos, curgen)
self.poplist.insert(pos, curpop)
return False
def main():
g.setrule('SparkyLife')
sym = g.getstring('SparkSoup symmetry?\n C1, C21, C22, C24, C41, C44, D21, D22, D2X, D41, D42, D44, D4X1, D4X4, D81, D84', 'C1').upper()
width = int(g.getstring('SparkSoup width?', '8'))
if 'C4' in sym or 'D4' in sym or 'D8' in sym or 'X' in sym: # Soup should be square
height = width
else:
height = int(g.getstring('SparkSoup height?', '8'))
sparkdist = int(g.getstring('Spark distance?','5'))
soup = SparkSoup(width, height, sym, sparkdist)
oscar = Oscar()
# Detect whether the soup is oscillating:
while True:
g.new('SparkyLifeSearch')
g.setbase(2)
g.setstep(1)
g.putcells(soup.randfill())
souprect = g.getrect()
oscar.reset()
while not oscar.oscillating(souprect):
g.step()
if oscar.period > 3:
g.reset()
g.setstep(1)
g.show("Oscillator detected (period = " + str(oscar.period) + ")")
break
main()
I am posting it here as soon as I finished the script and saw it working, so I haven't tested it thoroughly.
It seems that it can't search C1, C2, C4, and D2 too well.
And it seems that it reports periods of stabilized patterns, but I don't know why.
Any comments/optimizations to the code are welcome.