Code: Select all
#collisrc.py version 0.3
import sys
sys.path.append("/.../path/to/lifelib/")
from random import randint, choice
from time import clock, sleep
import lifelib
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-n", "--n-gliders", help="number of gliders", default=2, type=int)
parser.add_argument("-r", "--rule", help="rule to use (must allow gliders)",
default="B3/S23")
parser.add_argument("-s", "--sl",
help="still life to serve as the basis for generated components",
default="2o$2o!")
parser.add_argument("-x", "--x", help="x displacement of still life", default=0, type=int)
parser.add_argument("-y", "--y", help="y displacement of still life", default=0, type=int)
parser.add_argument("-b", "--backtrack", help="distance to backtrack gliders (in cells)",
default=10, type=int)
parser.add_argument("-d", "--directions",
help="directions of arriving gliders (as string containing any or all of characters '0123' representing standard quadrants (with 0 being used in place of 4, as if it wasn't confusing enough))",
default="0123")
parser.add_argument("-u", "--area-width",
help="half of x width of area of glider generation", default=5,
type=int)
parser.add_argument("-v", "--area-height",
help="half of y height of area of glider generation", default=5,
type=int)
parser.add_argument("-f", "--first-interact-before",
help="maximum time of first interaction", default=50, type=int)
parser.add_argument("-i", "--max-interact-time",
help="maximum time between beginning and end of interaction",
default=30, type=int)
parser.add_argument("-p", "--max-int-pop", help="maximum intermediate population",
default=40, type=int)
parser.add_argument("-z", "--min-stator-size",
help="minimum number of inactive cells in still life", default=0,
type=int)
parser.add_argument("-t", "--time", help="for profiling purposes", action="store_true")
parser.add_argument("-c", "--cpu-save", help="slow cpu usage to 50%", action="store_true")
args = parser.parse_args()
lt = lifelib.load_rules(args.rule).lifetree()
gliders = map(lt.pattern("3o$o$bo!").centre()(args.backtrack, args.backtrack).__getitem__, xrange(4))
ngliders = args.n_gliders
dirs = sorted(map(int, list(set(args.directions))))
sl = lt.pattern(args.sl).centre()(args.x, args.y)
gencol_t = 0
def gencol():
return reduce(lambda x, y: x+y, map(lambda _: gliders[randint(0, 3)](("rot"+str(choice(dirs)*90)).replace("rot0", "identity"), randint(-args.area_width, args.area_width), randint(-args.area_height, args.area_height)), [0]*ngliders)) + sl
testsane_t = 0
def testsane(col):
return len(set(map(lambda x:col[x].population, xrange(5)) + [ngliders * 5 + sl.population])) == 1
results = []
rcols = []
orientations = ["rot270", "rot180", "rot90", "identity", "flip_x", "flip_y", "swap_xy", "swap_xy_flip"]
testres_t = 0
def testres(res, update=True):
global results
for q in results:
if not update and q == res: continue
for i in map(q, orientations):
#Rudimentary and not 100% functional form of object separation
z = res.match(i, halo="3o$3o$3o!").convolve(i)
y = res-z
if y[1] == y: res = y
if not res.population: return False
return res
def updateres(res):
global results
newresults = [res]
for i in xrange(len(results)):
if results[i].population < res.population + 3:
q = testres(results[i], False)
if q:
newresults = [q] + newresults
else:
newresults = [results[i]] + newresults
results = sorted(newresults, key=lambda x: x.population)
print map(lambda x: x.population, results)
testcol_t = 0
def testcol():
global gencol_t, testsane_t, testres_t
t = clock()
col = gencol()
gencol_t += clock() - t
col0 = col.__copy__()
t = clock()
if not testsane(col):
testsane_t += clock() - t
return None
testsane_t += clock() - t
gens = 5
for i in xrange(args.first_interact_before/5):
col = col[5]
if not col or col == col[1]: return None
if col.population > args.max_int_pop: return None
if sl - col:
gens += i*5
break
else:
return None
gens0 = gens
for i in xrange(args.max_interact_time/5):
if not sl & col: return None
if col.population > args.max_int_pop: return None
if col == col[1]:
gens += i*5
break
col = col[5]
else:
return None
t = clock()
q = testres(col)
if q and (not args.min_stator_size or reduce(lambda x, y: x & y, map(col0.__getitem__, xrange(gens0-5, gens))).population >= args.min_stator_size):
updateres(q)
testres_t += clock() - t
return q.apgcode, col0.rle_string()
testres_t += clock() - t
return None
def search():
global testcol_t
cct = 0
while 1:
t = clock()
q = testcol()
testcol_t += clock() - t
if q is not None:
print "Object %s produced by the following collision: \n%s" % q
rcols.append(q[1])
cct += 1
if not cct%500:
print cct, "collisions searched,", len(rcols), "components found."
if args.time:
print "Time per collision spent in testres():", testres_t / cct * 1000
print "Time per collision spent elsewhere in testcol():", (testcol_t - testres_t - gencol_t - testsane_t) / cct * 1000
print "Time per collision spent in gencol():", gencol_t / cct * 1000
print "Time per collision spent in testsane():", testsane_t / cct * 1000
if args.cpu_save and not cct%5000:
sleep(testcol_t / cct * 5000)
try:
search()
except KeyboardInterrupt:
for i in rcols:
print i
I'm sure there are a lot of improvements that could be made — for one, the script is a lot slower than I would like, and I suspect there are major enhancements that I'm missing due to inexperience with lifelib, and for another object separation doesn't work particularly well in certain cases. I apologize in advance that I won't have much time to answer questions/work on this more due to a busy personal schedule, but anyone who wants to improve/make modifications is welcome to do so.