Code: Select all
# apgsearch-blocksoup v0.3
# Search program which collides a glider into a random field of blonks
# (blocks). Only tested with Conway's Life, but should work for
# any rule which supports the glider, block and blinker.
#
# == Based on apgsearch v0.4 by Adam P. Goucher ==
#
# Optimised soup searcher, v0.4 (beta release).
#
# -- Processes roughly 100 soups per (second . core . GHz).
#
# -- Can perfectly identify oscillators with period < 1000, well-separated
# spaceships of low period, and certain infinite-growth patterns (such
# guns and puffers, including both naturally-occurring types of switch
# engine).
#
# -- Separates most pseudo-objects into their constituent parts, including
# all pseudo-still-lifes of 18 or fewer live cells (which is the maximum
# theoretically possible, given there is a 19-cell pseudo-still-life
# with two distinct decompositions).
#
# -- Correctly separates non-interacting standard spaceships, irrespective
# of their proximity. In particular, a LWSS-on-LWSS is registered as two
# LWSSes, whereas an LWSS-on-HWSS is registered as a single spaceship
# (since they interact by suppressing sparks).
#
# -- At least 99.9999999% reliable at identifying objects.
#
# -- Scores soups based on the total excitement of the ash objects.
#
# -- Preliminary support for other outer-totalistic rules.
#
# By Adam P. Goucher, with contributions by Andrew Trevorrow,
# Tom Rokicki, Nathaniel Johnston and Dave Greene.
#
# == Modifications by Dave Green ==
# apgsearch v0.4.5
#
# - Extend list of common names of life objects
# - Store all soupids for rare objects as well as first occurrence for others
# - Save rare-object soupids to the progress folder:
# new sections in search*.txt, @SAMPLE_SOUPIDS and @RARE_OBJ_SOUPIDS
#
# == Modifications by Arie Paap ==
# apgsearch-blocksoup v0.3
#
# - Replace hashsoup() with blocksoup() to search result of glider collisions
# with random blonk fields.
# - Run soup for sufficient time to have first collision.
# - Increase security in stabilise3() to prevent escaping gliders colliding
# with surrounding blonks after periodicity is detected.
# - Update save_progress() and display_census() to show blocksoup starting
# conditions.
# - Add @ SEARCH and @RULE lines to progress file when saving
# - Save all soupids for any infinite growth pattern even if included in list
# of common names (i.e. switch engines).
# - Add diehard detection - starting conditions which result in an empty universe
# (extremely unlikely for blocksoup)
# - TODO: Add method to check if the intersection of two cell lists is empty, and
# modify blocksoup() to return a cell list instead of pasting into layer
# - TODO: refactor apgsearch code into separate libraries
import golly as g
from glife import rect, pattern, flip
import glife.base as glb
import time
import math
import operator
import hashlib
import datetime
import os
# Creates a random ash field and runs a glider into it. Ash can consist of
# blonks: blocks or blinkers with sufficient spacing between them to prevent
# interaction. Incoming glider placed to increase probability of an interesting
# reaction occuring.
# Blonks are distributed on a diagonal rectangle of size 128 x 256
#
# Ideally should create pattern and return it instead of placing directly
# in the universe, but there's no function to perform cell list intersection
# which is needed when checking the blonk mask for ON cells.
# Tried "len(a) + len(b) == golly.join(a, b)", but this always evaluates to true
# because golly.join simply appends b to a
def blonksoup(instring):
# Glider lanes which give interesting reactions
block_offsets = [-4, -3, -3, -3, 4, 4, 4, 5]
blinker_offsets = [-4, -5, -2, 1, 0, 3, 6, 5] # Even - phase0; Odd - phase1
buffer = 10
# Masks to check that region around blonk location is empty
block_mask = g.parse("3b2o$2b4o$b6o$8o$8o$b6o$2b4o$3b2o!", -3, -3)
blinker_mask = g.parse("3b2o$2b4o$b6o$8o$8o$b6o$2b4o$3b2o!", -3, -3)
# This gives an array of 64 random bytes to be used in construction
s = hashlib.sha256(instring).digest()
s += hashlib.sha256(instring + instring).digest()
ii = 0 # Index into list of random bytes
# Place a collection of up to twenty five blonks and incoming glider
# Blonks are placed within a shifted and rotated rectangle <u,v> -> <x,y>
# u E [ 0, 255 ], v E [ 0, 128 ]
# x = u - v + 64
# y = u + v - 64
# This transform always places an object on cells of a single colour,
# so adjust colour as required
# Place the first blonk and glider
obj = ord(s[ii])
u = ord(s[ii+1])
v = ord(s[ii+2])
ii += 3
colour = v & 1
v = v >> 1
offset = obj & 7
# Location of blonk
xblonk = u - v + 64 + colour
yblonk = u + v - 64
if (obj & 8):
glb.block.put(xblonk, yblonk)
lane = block_offsets[offset] + xblonk - yblonk
else:
# Blinker phase determined by LSB of offset
glb.block[offset & 1].put(xblonk, yblonk)
lane = block_offsets[offset] + xblonk - yblonk
# Add glider to soup
xglider = (lane + 1) // 2 - buffer
yglider = - (lane//2) - buffer
glb.glider.put(xglider, yglider, flip)
# Use next 54 bytes to place the next twenty four blonks.
# First 6 bytes determine object types: 1/2 block; 1/2 blinker (of either phase)
# Use two bits for each blonk
objects = []
for jj in xrange(0, 6):
obj = ord(s[ii])
ii += 1
for kk in xrange(0, 4):
objects.append(obj & 3)
obj >> 2
# Remaining 48 bytes determine location of each blonk
for jj in xrange(0, blocknumber):
u = ord(s[ii])
v = ord(s[ii+1])
color = v & 1
v = v >> 1
obj = objects[jj]
ii += 2
xblonk = u - v + 64 + colour
yblonk = u + v - 64
if (obj & 2):
blonk = glb.block
mask = g.transform(block_mask, xblonk, yblonk)
else:
blonk = glb.block[obj & 1]
mask = g.transform(blinker_mask, xblonk, yblonk)
occupied = False
for kk in xrange(0, len(mask), 2):
if g.getcell(mask[kk], mask[kk+1]):
occupied = True
if not occupied:
blonk.put(xblonk, yblonk)
# return thesoup
# Obtains a canonical representation of any oscillator/spaceship that (in
# some phase) fits within a 40-by-40 bounding box. This representation is
# alphanumeric and lowercase, and so much more compact than RLE. Compare:
#
# Common name: pentadecathlon
# Canonical representation: 4r4z4r4
# Equivalent RLE: 2bo4bo$2ob4ob2o$2bo4bo!
#
# It is a generalisation of a notation created by Allan Weschler in 1992.
def canonise(duration):
representation = "#"
# We need to compare each phase to find the one with the smallest
# description:
for t in xrange(duration):
rect = g.getrect()
if (len(rect) == 0):
return "0"
if ((rect[2] <= 40) & (rect[3] <= 40)):
# Fits within a 40-by-40 bounding box, so eligible to be canonised.
# Choose the orientation which results in the smallest description:
representation = compare_representations(representation, canonise_orientation(rect[2], rect[3], rect[0], rect[1], 1, 0, 0, 1))
representation = compare_representations(representation, canonise_orientation(rect[2], rect[3], rect[0]+rect[2]-1, rect[1], -1, 0, 0, 1))
representation = compare_representations(representation, canonise_orientation(rect[2], rect[3], rect[0], rect[1]+rect[3]-1, 1, 0, 0, -1))
representation = compare_representations(representation, canonise_orientation(rect[2], rect[3], rect[0]+rect[2]-1, rect[1]+rect[3]-1, -1, 0, 0, -1))
representation = compare_representations(representation, canonise_orientation(rect[3], rect[2], rect[0], rect[1], 0, 1, 1, 0))
representation = compare_representations(representation, canonise_orientation(rect[3], rect[2], rect[0]+rect[2]-1, rect[1], 0, -1, 1, 0))
representation = compare_representations(representation, canonise_orientation(rect[3], rect[2], rect[0], rect[1]+rect[3]-1, 0, 1, -1, 0))
representation = compare_representations(representation, canonise_orientation(rect[3], rect[2], rect[0]+rect[2]-1, rect[1]+rect[3]-1, 0, -1, -1, 0))
g.run(1)
return representation
# A subroutine used by canonise:
def canonise_orientation(length, breadth, ox, oy, a, b, c, d):
representation = ""
chars = "0123456789abcdefghijklmnopqrstuvwxyz"
for v in xrange(int((breadth-1)/5)+1):
zeroes = 0
if (v != 0):
representation += "z"
for u in xrange(length):
baudot = 0
for w in xrange(5):
x = ox + a*u + b*(5*v + w)
y = oy + c*u + d*(5*v + w)
baudot = (baudot >> 1) + 16*g.getcell(x, y)
if (baudot == 0):
zeroes += 1
else:
if (zeroes > 0):
if (zeroes == 1):
representation += "0"
elif (zeroes == 2):
representation += "w"
elif (zeroes == 3):
representation += "x"
else:
representation += "y"
representation += chars[zeroes - 4]
zeroes = 0
representation += chars[baudot]
return representation
# Compares strings first by length, then by lexicographical ordering.
# A hash character is worse than anything else.
def compare_representations(a, b):
if (a == "#"):
return b
elif (b == "#"):
return a
elif (len(a) < len(b)):
return a
elif (len(b) < len(a)):
return b
elif (a < b):
return a
else:
return b
# Gets the period of an interleaving of degree-d polynomials:
def deepperiod(sequence, maxperiod, degree):
for p in xrange(1, maxperiod, 1):
good = True
for i in xrange(maxperiod):
diffs = [0] * (degree + 2)
for j in xrange(degree + 2):
diffs[j] = sequence[i + j*p]
# Produce successive differences:
for j in xrange(degree + 1):
for k in xrange(degree + 1):
diffs[k] = diffs[k] - diffs[k + 1]
if (diffs[0] != 0):
good = False
break
if (good):
return p
return -1
# Analyses a linear-growth pattern, returning a hash:
def linearlyse(maxperiod):
poplist = [0]*(3*maxperiod)
for i in xrange(3*maxperiod):
g.run(1)
poplist[i] = int(g.getpop())
p = deepperiod(poplist, maxperiod, 1)
if (p == -1):
return "unidentified"
difflist = [0]*(2*maxperiod)
for i in xrange(2*maxperiod):
difflist[i] = poplist[i + p] - poplist[i]
q = deepperiod(difflist, maxperiod, 0)
moments = [0, 0, 0]
for i in xrange(p):
moments[0] += (poplist[i + q] - poplist[i])
moments[1] += (poplist[i + q] - poplist[i]) ** 2
moments[2] += (poplist[i + q] - poplist[i]) ** 3
prehash = str(moments[1]) + "#" + str(moments[2])
# Linear-growth patterns with growth rate zero are clearly errors!
if (moments[0] == 0):
return "unidentified"
return "yl" + str(p) + "_" + str(q) + "_" + str(moments[0]) + "_" + hashlib.md5(prehash).hexdigest()
# This explodes pseudo-still-lifes and pseudo-oscillators into their
# constituent parts.
#
# -- Requires the period (if oscillatory) and graph-theoretic diameter
# to not exceed 4096.
# -- Never mistakenly separates a true object.
# -- Correctly separates most pseudo-still-lifes, including the famous:
# http://www.conwaylife.com/wiki/Quad_pseudo_still_life
# -- Works perfectly for all still-lifes of up to 17 bits.
# -- Doesn't separate 'locks', of which the smallest example has 18
# bits and is unique:
#
# ** **
# ** **
#
# * *** *
# ** * **
#
# To use this function (standalone), merely copy it into a script of
# the following form:
#
# import golly as g
#
# def pseudo_bangbang():
#
# [...]
#
# pseudo_bangbang()
#
# and execute it in Golly with a B3/S23 universe containing any still-
# lifes or oscillators you want to separate. Pure objects correspond to
# connected components in the final state of the universe.
#
# This has dependencies on the rules ContagiousLife, PercolateInfection
# and EradicateInfection.
#
# Not to be confused with the Unix shell instruction for repeating the
# previous instruction as a superuser (sudo !!), or indeed with any
# parodies of this song: https://www.youtube.com/watch?v=YswhUHH6Ufc
#
# Adam P. Goucher, 2014-08-25
def pseudo_bangbang(alpharule):
g.setrule("APG_ContagiousLife_" + alpharule)
g.setbase(2)
g.setstep(12)
g.step()
celllist = g.getcells(g.getrect())
for i in xrange(0, len(celllist)-1, 3):
# Only infect cells that haven't yet been infected:
if (g.getcell(celllist[i], celllist[i+1]) <= 2):
# Seed an initial 'infected' (red) cell:
g.setcell(celllist[i], celllist[i+1], g.getcell(celllist[i], celllist[i+1]) + 2)
prevpop = 0
currpop = int(g.getpop())
# Continue infecting until the entire component has been engulfed:
while (prevpop != currpop):
# Percolate the infection to every cell in the island:
g.setrule("APG_PercolateInfection")
g.setbase(2)
g.setstep(12)
g.step()
# Transmit the infection across any bridges.
g.setrule("APG_ContagiousLife_" + alpharule)
g.setbase(2)
g.setstep(12)
g.step()
prevpop = currpop
currpop = int(g.getpop())
g.fit()
g.update()
# Red becomes green:
g.setrule("APG_EradicateInfection")
g.step()
# Counts the number of live cells of each degree:
def degreecount():
celllist = g.getcells(g.getrect())
counts = [0,0,0,0,0,0,0,0,0]
for i in xrange(0, len(celllist), 2):
x = celllist[i]
y = celllist[i+1]
degree = -1
for ux in xrange(x - 1, x + 2):
for uy in xrange(y - 1, y + 2):
degree += g.getcell(ux, uy)
counts[degree] += 1
return counts
# Counts the number of live cells of each degree in generations 1 and 2:
def degreecount2():
g.run(1)
a = degreecount()
g.run(1)
b = degreecount()
return (a + b)
# If the universe consists only of disjoint *WSSes, this will return
# a triple (l, w, h) giving the quantities of each *WSS. Otherwise,
# this function will return (-1, -1, -1).
#
# This should only be used to separate period-4 moving objects which
# may contain multiple *WSSes.
def countxwsses():
degcount = degreecount2()
if (degreecount2() != degcount):
# Degree counts are not period-2:
return (-1, -1, -1)
# Degree counts of each standard spaceship:
hwssa = [1,4,6,2,0,0,0,0,0,0,0,0,4,4,6,1,2,1]
mwssa = [2,2,5,2,0,0,0,0,0,0,0,0,4,4,4,1,2,0]
lwssa = [1,2,4,2,0,0,0,0,0,0,0,0,4,4,2,2,0,0]
hwssb = [0,0,0,4,4,6,1,2,1,1,4,6,2,0,0,0,0,0]
mwssb = [0,0,0,4,4,4,1,2,0,2,2,5,2,0,0,0,0,0]
lwssb = [0,0,0,4,4,2,2,0,0,1,2,4,2,0,0,0,0,0]
# Calculate the number of standard spaceships in each phase:
hacount = degcount[17]
macount = degcount[16]/2 - hacount
lacount = (degcount[15] - hacount - macount)/2
hbcount = degcount[8]
mbcount = degcount[7]/2 - hbcount
lbcount = (degcount[6] - hbcount - mbcount)/2
# Determine the expected degcount given the calculated quantities:
pcounts = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
pcounts = map(lambda x, y: x + y, pcounts, map(lambda x: hacount*x, hwssa))
pcounts = map(lambda x, y: x + y, pcounts, map(lambda x: macount*x, mwssa))
pcounts = map(lambda x, y: x + y, pcounts, map(lambda x: lacount*x, lwssa))
pcounts = map(lambda x, y: x + y, pcounts, map(lambda x: hbcount*x, hwssb))
pcounts = map(lambda x, y: x + y, pcounts, map(lambda x: mbcount*x, mwssb))
pcounts = map(lambda x, y: x + y, pcounts, map(lambda x: lbcount*x, lwssb))
# Compare the observed and expected degcounts (to eliminate nonstandard spaceships):
if (pcounts != degcount):
# Expected and observed values do not match:
return (-1, -1, -1)
# Return the combined numbers of *WSSes:
return(lacount + lbcount, macount + mbcount, hacount + hbcount)
# Generates the helper rules for apgsearch, given a base outer-totalistic rule.
class RuleGenerator:
# Unless otherwise specified, assume standard B3/S23 rule:
bee = [False, False, False, True, False, False, False, False, False]
ess = [False, False, True, True, False, False, False, False, False]
alphanumeric = "B3S23"
slashed = "B3/S23"
ruletype = True
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]
ntbee = {}
ntess = {}
# Save all helper rules:
def saveAllRules(self):
self.saveClassifyObjects()
self.saveCoalesceObjects()
self.saveExpungeObjects()
self.saveExpungeGliders()
self.saveIdentifyGliders()
self.saveHandlePlumes()
self.savePercolateInfection()
self.saveEradicateInfection()
self.saveContagiousLife()
self.savePropagateClassifications()
self.saveDecayer()
self.saveTreeMaker()
if self.t:
self.saveIdentifyTs()
self.saveAdvanceTs()
self.saveAssistTs()
self.saveExpungeTs()
def testPattern(self, clist, period, moving):
g.new("Test pattern")
g.setalgo("QuickLife")
g.setrule(self.slashed)
g.putcells(clist)
r = g.getrect()
h = g.hash(r)
g.run(period)
f = g.getrect()
if int(g.getpop()) == 0:
return False
return h == g.hash(f) and (moving and f != r) or (not moving and f == r)
#Assumes that blinkers exist and are p1 or p2, and that we still don't support B0 rules
def findBlinkerApgcode(self):
g.new("Test pattern")
g.setalgo("QuickLife")
g.setrule(self.slashed)
g.setcell(0,0,1)
g.setcell(0,1,1)
g.setcell(0,2,1)
g.step()
if int(g.getpop()) == 2:
return "xp2_5"
elif int(g.getpop()) == 3 and g.getcell(0,0):
return "xs3_7"
elif int(g.getpop()) == 3:
return "xp2_7"
else:
return None
#To use this standalone, just copy this into a separate file and add the lines
'''import golly as g
class Foo:
slashed = g.getstring("Enter name of rule to test", "Life")'''
#before it and the lines
'''foo = Foo()
g.show(foo.testHensel())'''
#after it, and run it in Golly.
def testHensel(self):
#Dict containing all possible transitions:
dict = {
"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
"5a" : "0,0,0,1,1,1,1,1", # SE, S, SW, W, NW
"5n" : "0,0,1,0,1,1,1,1", # E, S, SW, W, NW
"5r" : "0,0,1,1,0,1,1,1", # E, SE, SW, W,
"5q" : "0,0,1,1,1,0,1,1", # E, SE, S, W, NW
"5j" : "0,0,1,1,1,1,0,1", # E, SE, S, SW, NW
"5i" : "0,0,1,1,1,1,1,0", # E, SE, S, SW, W
"5e" : "0,1,0,1,0,1,1,1", # NE, SE, SW, W, NW,
"5k" : "0,1,0,1,1,0,1,1", # NE, SE, S, W, NW
"5y" : "0,1,1,0,1,0,1,1", # NE, E, S, W, NW
"5c" : "1,0,1,0,1,0,1,1", # N, E, S, W, NW
"6a" : "0,0,1,1,1,1,1,1", # E, SE, S, SW, W, NW
"6e" : "0,1,0,1,1,1,1,1", # NE, SE, S, SW, W, NW
"6k" : "0,1,1,0,1,1,1,1", # NE, E, S, SW, W, NW
"6i" : "0,1,1,1,0,1,1,1", # NE, E, SE, SW, W, NW
"6c" : "1,0,1,0,1,1,1,1", # N, E, S, SW, W, NW
"6n" : "1,0,1,1,1,0,1,1", # N, E, SE, S, W, NW
"7e" : "0,1,1,1,1,1,1,1", # NE, E, SE, S, SW, W, NW
"7c" : "1,0,1,1,1,1,1,1", # N, E, SE, S, SW, W, NW
"8" : "1,1,1,1,1,1,1,1",
}
#Represents the encoding in dict:
neighbors = [(-1,0),(-1,1),(0,1),(1,1),(1,0),(1,-1),(0,-1),(-1,-1)]
#Will store transitions temporarily:
d2 = [{},{}]
#Used to help a conversion later:
lnums = []
for i in xrange(9):
lnums.append([j for j in dict if int(j[0]) == i])
#Self-explanatory:
g.setrule(self.slashed)
#Test each transition in turn:
for i in xrange(2):
for j in dict:
j2 = dict[j].split(",")
g.new("Testing Hensel notation...")
for k in xrange(len(j2)):
k2 = int(j2[k])
g.setcell(neighbors[k][0], neighbors[k][1], k2)
g.setcell(0, 0, i)
g.run(1)
d2[i][j] = int(g.getcell(0, 0)) == 1
#Will become the main table of transitions:
trans_ = [[],[]]
#Will become the final output string:
not_ = "B"
for i in xrange(2):
#Convert d2 to a more usable form
for j in xrange(9):
trans_[i].append({})
for k in lnums[j]:
trans_[i][j][k] = d2[i][k]
#Make each set of transitions:
for j in xrange(9):
#Number of present transitions for B/S[[j]]
sum = 0
for k in trans_[i][j]:
if trans_[i][j][k]:
sum += 1
#No transitions present:
if sum == 0:
continue
#All transitions present:
if sum == len(trans_[i][j]):
not_ += str(j)
continue
str_ = str(j) #Substring for current set of transitions
#Minus sign needed if more than half of
#current transition set is present.
minus = (sum >= len(trans_[i][j])/2)
if minus:
str_ += "-"
str2 = "" #Another substring for current transition set
#Write transitions:
for k in trans_[i][j]:
if trans_[i][j][k] != minus:
str2 += k[1:]
#Append transitions:
not_ += str_ + "".join(sorted(str2))
if i == 0:
not_ += "S"
g.new("Test finished.")
return not_
# Interpret birth or survival string
def ruleparts(self, part):
inverse = False
nlist = []
totalistic = True
rule = {}
for k in self.notationdict:
rule[k] = False
# 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 something adapted from Eric Goldstein's HenselNotation->Ruletable(1.3).py
def nt_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.slashed = 'B' + birth + 'S' + survive
self.hensel = 'B' + birth + 'S' + survive
self.ntbee = b
self.ntess = s
self.rulepath = g.getdir("rules") + self.alphanumeric + ".rule"
else:
# Error
g.note("Unable to process rule definition.\n" +
"b = " + str(b) + "\ns = " + str(s))
g.exit()
# Set outer-totalistic or isotropic non-totalistic rule:
def setrule(self, rulestring):
# Prevent annoying Golly warnings that pause the script and make it nearly
# impossible to exit.
rulestring = rulestring.replace("b", "B").replace("s", "S")
mode = 0 #
s = [False]*9
b = [False]*9
#Outer-totalistic
#if '/' in rulestring:
if not len(filter(lambda c: c in "acdefghijklmnopqrtuvwxyz", rulestring)):
for c in rulestring:
if ((c == 's') | (c == 'S')):
mode = 0
if ((c == 'b') | (c == 'B')):
mode = 1
if (c == '/'):
mode = 1 - mode
if ((ord(c) >= 48) & (ord(c) <= 56)):
d = ord(c) - 48
if (mode == 0):
s[d] = True
else:
b[d] = True
prefix = "B"
suffix = "S"
for i in xrange(9):
if (b[i]):
prefix += str(i)
if (s[i]):
suffix += str(i)
self.alphanumeric = prefix + suffix
self.slashed = prefix + "/" + suffix
self.hensel = self.alphanumeric
self.bee = b
self.ess = s
self.t = False
self.g = self.ess[2] & self.ess[3] & (not self.ess[1]) & (not self.ess[4])
self.g = self.g & (not (self.bee[4] | self.bee[5]))
self.bl = True #Maybe not, but we can get away with assuming so.
self.blcode = "xp2_7"
#Non-totalistic
else:
rulestring = rulestring.replace("/", "_")
self.ruletype = False
self.t = self.testPattern([1,0,0,1,1,1,2,1], 5, True)
self.g = self.testPattern([0,0,1,0,2,0,0,1,1,2], 4, True)
self.bl = self.testPattern([0,0,0,1,0,2], 2, False)
if self.bl:
self.blcode = self.findBlinkerApgcode()
if self.blcode is None:
self.bl = False
if os.path.exists(g.getdir("app") + "Rules/" + rulestring + ".rule"):
self.rulepath = g.getdir("app") + "Rules/" + rulestring + ".rule"
elif os.path.exists(g.getdir("rules") + rulestring + ".rule"):
self.rulepath = g.getdir("rules") + rulestring + ".rule"
else:
self.nt_setrule(rulestring)
self.saveIsotropicRule()
return
self.alphanumeric = rulestring
self.slashed = rulestring
self.hensel = self.testHensel()
#Leave bee and ess alone; we don't know what we're dealing with, so default to Life.
# 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
# Only create a rule file if it doesn't already exist; this avoids
# concurrency issues when booting an instance of apgsearch whilst
# one is already running.
if not os.path.exists(filename):
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 = ""
for name in namelist:
block += self.newvar(name, vallist)
block += "\n"
return block
def scoline(self, chara, charb, left, right, amount): #Second and third parameters not to be confused with Beta Canum Venaticorum and the main victim of a 2015 Paris terrorist attack, respectively.
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 saveIsotropicRule(self):
comments = """
This is a two state, isotropic, non-totalistic rule on the Moore neighbourhood.
The notation used to define the rule was originally proposed by Alan Hensel.
See http://www.ibiblio.org/lifepatterns/neighbors2.html for details
"""
table = """
n_states:2
neighborhood:Moore
symmetries:rotate4reflect
"""
table += self.newvars(["a","b","c","d","e","f","g","h"], [0, 1])
table += "\n# Birth\n"
for n in self.allneighbours_flat:
if self.ntbee[n]:
table += "0,"
table += str(self.notationdict[n])[1:-1].replace(' ','')
table += ",1\n"
table += "\n# Survival\n"
for n in self.allneighbours_flat:
if self.ntess[n]:
table += "1,"
table += str(self.notationdict[n])[1:-1].replace(' ','')
table += ",1\n"
table += "\n# Death\n"
table += self.scoline("","",1,0,0)
colours = ""
self.saverule(self.alphanumeric, comments, table, colours)
def saveHandlePlumes(self):
comments = """
This post-processes the output of ClassifyObjects to remove any
unwanted clustering of low-period objects appearing in puffer
exhaust.
state 0: vacuum
state 7: ON, still-life
state 8: OFF, still-life
state 9: ON, p2 oscillator
state 10: OFF, p2 oscillator
state 11: ON, higher-period object
state 12: OFF, higher-period object
"""
table = """
n_states:18
neighborhood:Moore
symmetries:permute
var da={0,2,4,6,8,10,12,14,16}
var db={0,2,4,6,8,10,12,14,16}
var dc={0,2,4,6,8,10,12,14,16}
var dd={0,2,4,6,8,10,12,14,16}
var de={0,2,4,6,8,10,12,14,16}
var df={0,2,4,6,8,10,12,14,16}
var dg={0,2,4,6,8,10,12,14,16}
var dh={0,2,4,6,8,10,12,14,16}
var a={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var b={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var c={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var d={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var e={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var f={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var g={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var h={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
8,da,db,dc,dd,de,df,dg,dh,0
10,da,db,dc,dd,de,df,dg,dh,0
9,a,b,c,d,e,f,g,h,1
10,a,b,c,d,e,f,g,h,2
"""
colours = """
1 255 255 255
2 127 127 127
7 0 0 255
8 0 0 127
9 255 0 0
10 127 0 0
11 0 255 0
12 0 127 0
"""
self.saverule("APG_HandlePlumesCorrected", comments, table, colours)
def saveExpungeGliders(self):
comments = """
This removes unwanted gliders.
It is mandatory that one first runs the rules CoalesceObjects,
IdentifyGliders and ClassifyObjects.
Run this for two generations, and observe the population
counts after 1 and 2 generations. This will give the
following data:
number of gliders = (p(1) - p(2))/5
"""
table = """
n_states:18
neighborhood:Moore
symmetries:rotate4reflect
var a={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var b={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var c={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var d={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var e={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var f={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var g={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var h={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
13,a,b,c,d,e,f,g,h,14
14,a,b,c,d,e,f,g,h,0
"""
colours = """
0 0 0 0
1 255 255 255
2 127 127 127
7 0 0 255
8 0 0 127
9 255 0 0
10 127 0 0
11 0 255 0
12 0 127 0
13 255 255 0
14 127 127 0
"""
self.saverule("APG_ExpungeGliders", comments, table, colours)
def saveIdentifyGliders(self):
comments = """
Run this after CoalesceObjects to find any gliders.
state 0: vacuum
state 1: ON
state 2: OFF
"""
table = """
n_states:18
neighborhood:Moore
symmetries:rotate4reflect
var a={0,2}
var b={0,2}
var c={0,2}
var d={0,2}
var e={0,2}
var f={0,2}
var g={0,2}
var h={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var i={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var j={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var k={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var l={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var m={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var n={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var o={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var p={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
var q={3,4}
var r={9,10}
var s={11,12}
1,1,a,1,1,b,1,c,d,3
d,1,1,1,1,a,b,1,c,4
3,i,j,k,l,m,n,o,p,5
4,i,j,k,l,m,n,o,p,6
1,q,i,j,a,b,c,k,l,7
d,q,i,j,a,b,c,k,l,8
1,i,a,b,c,d,e,j,q,7
f,i,a,b,c,d,e,j,q,8
5,7,8,7,7,8,7,8,8,9
6,7,7,7,7,8,8,7,8,10
5,i,j,k,l,m,n,o,p,15
6,i,j,k,l,m,n,o,p,16
15,i,j,k,l,m,n,o,p,1
16,i,j,k,l,m,n,o,p,2
7,i,j,k,l,m,n,o,p,11
8,i,j,k,l,m,n,o,p,12
9,i,j,k,l,m,n,o,p,13
10,i,j,k,l,m,n,o,p,14
11,r,j,k,l,m,n,o,p,13
11,i,r,k,l,m,n,o,p,13
12,r,j,k,l,m,n,o,p,14
12,i,r,k,l,m,n,o,p,14
11,i,j,k,l,m,n,o,p,1
12,i,j,k,l,m,n,o,p,2
"""
colours = """
0 0 0 0
1 255 255 255
2 127 127 127
7 0 0 255
8 0 0 127
9 255 0 0
10 127 0 0
11 0 255 0
12 0 127 0
13 255 255 0
14 127 127 0
"""
self.saverule("APG_IdentifyGliders", comments, table, colours)
def saveIdentifyTs(self):
comments = """
To identify the common spaceship xq4_27, also known as the T.
state 0: vacuum
state 11: p3+ on
state 12: p3+ off
state 13: T on
state 14: T off
state 15: not-T on
state 16: not-T off
"""
table = """
n_states:18
neighborhood:Moore
symmetries:rotate4reflect
var a={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17}
var aa=a
var ab=a
var ac=a
var ad=a
var ae=a
var af=a
var ag=a
var o={0,2,12,14}
var oa=o
var ob=o
var oc=o
var od=o
var s={5,6,17}
var sa=s
var sb=s
var sc=s
var n={7,8,9,10,11,15,16}
var xo={2,4,6,14}
var xn={1,3,5,13,17}
var i={11,12}
var io={0,1,2,11,12}
var ioa=io
var b={0,12}
11,11,o,12,11,12,11,12,oa,1
11,11,12,o,oa,io,oc,od,12,1
11,12,11,o,oa,ob,oc,12,12,1
11,12,12,12,12,12,12,12,12,1
11,12,11,o,oa,ob,oc,od,11,1
11,11,o,11,oa,11,io,io,io,1
11,11,11,11,11,12,o,oa,ob,1
11,11,11,o,oa,io,ob,oc,11,1
11,11,11,o,oa,ob,oc,12,12,1
11,11,o,11,oa,11,io,ioa,io,1
11,11,12,11,12,11,12,o,0,1
11,11,11,11,io,ioa,o,oa,ob,1
11,11,11,o,oa,ob,oc,od,12,1
12,11,o,11,oa,11,12,12,12,2
12,11,11,o,oa,ob,oc,11,12,2
12,11,12,12,11,12,11,12,12,2
12,11,12,12,11,i,o,oa,ob,2
12,11,12,12,o,oa,ob,12,12,2
b,11,11,o,io,oa,ioa,ob,11,2
12,11,11,12,o,o,oa,ob,ob,2
12,11,11,11,11,12,o,oa,ob,2
b,11,11,11,o,oa,ob,oc,od,2
1,1,2,1,2,1,2,1,2,15
1,2,1,o,oa,ob,oc,od,1,3
1,1,o,2,1,2,1,2,oa,3
1,1,2,o,oa,io,oc,od,2,3
1,2,1,o,oa,ob,oc,2,2,3
1,2,2,2,2,2,o,oa,ob,3
1,1,o,1,oa,1,io,ioa,io,3
1,1,2,1,2,1,12,2,0,3
1,1,2,1,2,1,2,o,0,3
1,1,1,1,io,ioa,o,oa,ob,3
1,1,1,2,o,oa,ob,2,1,3
1,1,1,o,oa,ob,oc,od,2,3
1,1,1,2,2,1,2,2,1,3
2,1,2,2,1,2,1,2,2,4
2,1,2,2,1,12,12,12,2,12
2,1,12,2,1,2,12,12,12,12
2,1,o,12,a,aa,ab,12,oa,12
2,2,1,2,12,1,o,o,o,12
2,1,o,1,oa,1,2,2,2,4
2,1,1,o,oa,ob,oc,1,2,4
2,1,2,1,2,1,2,2,2,4
2,1,2,2,1,io,o,oa,ob,4
2,1,2,2,o,oa,ob,oc,od,4
2,1,1,o,io,oa,ioa,ob,1,4
2,1,1,2,o,oa,ob,oc,od,4
2,1,1,1,o,oa,ob,oc,od,4
2,1,1,1,1,2,o,oa,ob,4
4,3,3,4,3,4,3,4,3,6
6,3,3,4,3,4,3,4,3,4
4,3,4,3,4,3,4,4,4,6
6,3,4,3,4,3,4,4,4,4
3,3,3,3,3,3,4,3,4,5
5,3,3,3,3,3,4,3,4,3
3,3,4,3,4,3,4,4,4,5
5,3,4,3,4,3,4,4,4,3
3,3,3,4,4,3,4,4,3,5
5,3,3,4,4,3,4,4,3,3
3,5,5,4,12,12,12,4,4,17
3,s,a,aa,ab,ac,ad,ae,af,5
4,s,a,aa,ab,ac,ad,ae,af,6
3,a,s,aa,ab,ac,ad,ae,af,5
4,a,s,aa,ab,ac,ad,ae,af,6
6,12,6,5,5,6,o,oa,ob,12
6,s,sa,sb,a,aa,ab,ac,ad,14
6,s,sa,a,aa,ab,ac,ad,sb,14
5,s,sa,a,aa,ab,ac,ad,sb,13
5,s,sa,sb,a,aa,ac,ac,ad,13
6,13,o,oa,oa,14,13,6,14,12
17,14,14,13,13,14,12,12,12,13
14,17,13,14,o,oa,12,12,12,12
xn,n,a,aa,ab,ac,ad,ae,af,15
xo,n,a,aa,ab,ac,ad,ae,af,16
xn,a,n,aa,ab,ac,ad,ae,af,15
xo,a,n,aa,ab,ac,ad,ae,af,16
1,a,aa,ab,ac,ad,ae,af,ag,15
2,a,aa,ab,ac,ad,ae,af,ag,16
"""
colours = """
0 0 0 0
1 255 255 255
2 127 127 127
7 0 0 255
8 0 0 127
9 255 0 0
10 127 0 0
11 0 255 0
12 0 127 0
13 255 255 0
14 127 127 0
17 127 0 127
"""
self.saverule("APG_IdentifyTs", comments, table, colours)
def saveAdvanceTs(self):
comments = """
To filter out extraneous results from the output of APG_IdentifyTs.
state 0: vacuum
state 11: p3+ on
state 12: p3+ off
state 13: T on
state 14: T off
state 15: not-T on
state 16: not-T off
"""
table = """
n_states:18
neighborhood:Moore
symmetries:rotate4reflect
var a={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17}
var aa=a
var ab=a
var ac=a
var ad=a
var ae=a
var af=a
var ag=a
var in={1,3,5,15}
var io={2,4,6,16}
var i={1,2,3,4,5,6,15,16}
var o={0,12,14}
var oa=o
var ob=o
var oc=o
var od=o
var oe=o
var of=o
var og=o
var oo={12,14}
var c={0,12,13,14}
var ca=c
var t={13,14}
in,a,aa,ab,ac,ad,ae,af,ag,11
io,a,aa,ab,ac,ad,ae,af,ag,12
#Birth
o,13,13,13,oa,ob,oc,od,oe,13
o,13,13,oa,ob,13,oc,od,oe,13
o,13,13,oa,ob,oc,od,13,oe,13
o,13,13,oa,ob,oc,od,oe,13,13
o,13,oa,13,ob,13,oc,od,oe,13
o,13,oa,ob,13,oc,13,od,oe,13
#Inert
o,13,13,c,ca,oa,ob,oc,od,o
o,13,oa,c,ob,oc,od,oe,of,o
o,13,13,oa,13,ob,13,oc,13,o
o,oa,13,ob,c,oc,od,oe,of,o
o,13,oa,ob,13,oc,od,oe,of,o
o,13,oa,ob,oc,13,od,oe,of,o
o,13,13,oa,13,13,ob,oc,od,o
o,oa,13,ob,13,oc,13,od,13,o
o,oa,13,ob,oc,od,13,oe,of,o
#Survival
13,13,13,o,oa,ob,oc,od,c,13
13,13,o,13,oa,13,ob,oc,od,13
13,13,13,13,o,oa,ob,oc,od,13
13,c,o,oa,13,ob,13,oc,od,13
#Death
13,13,13,13,13,o,oa,ob,oc,14
13,13,13,13,13,13,o,c,oa,14
13,13,o,oa,ob,c,oc,od,oe,14
13,o,13,oa,ob,oc,od,oe,of,14
13,13,13,o,oa,13,ob,oc,13,14
13,o,oa,ob,oc,od,oe,of,og,14
#Not T
0,o,oa,ob,oc,od,oe,of,og,0
oo,o,oa,ob,oc,od,oe,of,og,12
o,a,aa,ab,ac,ad,ae,af,t,16
o,a,aa,ab,ac,ad,ae,t,af,16
13,a,aa,ab,ac,ad,ae,af,t,15
13,a,aa,ab,ac,ad,ae,t,af,15
"""
colours = """
0 0 0 0
1 255 255 255
2 127 127 127
7 0 0 255
8 0 0 127
9 255 0 0
10 127 0 0
11 0 255 0
12 0 127 0
13 255 255 0
14 127 127 0
17 127 0 127
"""
self.saverule("APG_AdvanceTs", comments, table, colours)
def saveExpungeTs(self):
comments = """
To filter out extraneous results from the output of APG_IdentifyTs.
state 0: vacuum
state 11: p3+ on
state 12: p3+ off
state 13: T on
state 14: T off
state 17: about to die
"""
table = """
n_states:18
neighborhood:Moore
symmetries:rotate8reflect
var a={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17}
var aa=a
var ab=a
var ac=a
var ad=a
var ae=a
var af=a
var ag=a
var o={0,12,14}
var oa=o
var ob=o
var oc=o
var od=o
var oe=o
var of=o
var og=o
var t={13,14}
13,o,oa,ob,oc,od,oe,of,og,17
13,13,o,oa,ob,oc,od,oe,of,17
13,13,13,o,oa,ob,oc,od,oe,17
13,13,13,o,oa,ob,oc,od,13,17
13,13,o,13,oa,13,ob,oc,od,17
13,13,13,13,13,13,o,13,oa,17
14,o,oa,ob,oc,od,oe,of,og,12
17,a,aa,ab,ac,ad,ae,af,ag,0
t,17,a,aa,ab,ac,ad,ae,af,17
"""
colours = """
0 0 0 0
1 255 255 255
2 127 127 127
7 0 0 255
8 0 0 127
9 255 0 0
10 127 0 0
11 0 255 0
12 0 127 0
13 255 255 0
14 127 127 0
17 127 0 127
"""
self.saverule("APG_ExpungeTs", comments, table, colours)
def saveAssistTs(self):
comments = """
To help filter out extraneous results from the output of APG_IdentifyTs.
state 0: vacuum
state 11: p3+ on
state 12: p3+ off
state 13: T on
state 14: T off
state 15: not-T on
state 15: not-T off
"""
table = """
n_states:18
neighborhood:Moore
symmetries:rotate8reflect
var a={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17}
var aa=a
var ab=a
var ac=a
var ad=a
var ae=a
var af=a
var ag=a
var t={13,14}
var nt={15,16}
13,nt,a,ab,ac,ad,ae,af,ag,15
14,nt,a,ab,ac,ad,ae,af,ag,16
12,t,a,ab,ac,nt,ad,ae,af,16
"""
colours = """
0 0 0 0
1 255 255 255
2 127 127 127
7 0 0 255
8 0 0 127
9 255 0 0
10 127 0 0
11 0 255 0
12 0 127 0
13 255 255 0
14 127 127 0
17 127 0 127
"""
self.saverule("APG_AssistTs", comments, table, colours)
def saveEradicateInfection(self):
comments = """
To run after ContagiousLife to disinfect any cells in states 3, 4, 7, and 8.
state 0: vacuum
state 1: ON
state 2: OFF
"""
table = """
n_states:7
neighborhood:Moore
symmetries:permute
var a={0,1,2,3,4,5,6}
var b={0,1,2,3,4,5,6}
var c={0,1,2,3,4,5,6}
var d={0,1,2,3,4,5,6}
var e={0,1,2,3,4,5,6}
var f={0,1,2,3,4,5,6}
var g={0,1,2,3,4,5,6}
var h={0,1,2,3,4,5,6}
var i={0,1,2,3,4,5,6}
4,a,b,c,d,e,f,g,h,6
3,a,b,c,d,e,f,g,h,5
"""
colours = """
0 0 0 0
1 0 0 255
2 0 0 127
3 255 0 0
4 127 0 0
5 0 255 0
6 0 127 0
7 255 0 255
8 127 0 127
"""
self.saverule("APG_EradicateInfection", comments, table, colours)
def savePercolateInfection(self):
comments = """
Percolates any infection to all cells of that particular island.
state 0: vacuum
state 1: ON
state 2: OFF
"""
table = """
n_states:7
neighborhood:Moore
symmetries:permute
var a={0,1,2,3,4,5,6}
var b={0,1,2,3,4,5,6}
var c={0,1,2,3,4,5,6}
var d={0,1,2,3,4,5,6}
var e={0,1,2,3,4,5,6}
var f={0,1,2,3,4,5,6}
var g={0,1,2,3,4,5,6}
var h={0,1,2,3,4,5,6}
var i={0,1,2,3,4,5,6}
var q={3,4}
var da={2,4,6}
var la={1,3,5}
da,q,b,c,d,e,f,g,h,4
la,q,b,c,d,e,f,g,h,3
"""
colours = """
0 0 0 0
1 0 0 255
2 0 0 127
3 255 0 0
4 127 0 0
5 0 255 0
6 0 127 0
7 255 0 255
8 127 0 127
"""
self.saverule("APG_PercolateInfection", comments, table, colours)
def saveExpungeObjects(self):
comments = """
This removes unwanted monominos, blocks, blinkers and beehives.
It is mandatory that one first runs the rule ClassifyObjects.
Run this for four generations, and observe the population
counts after 0, 1, 2, 3 and 4 generations. This will give the
following data:
number of monominos = p(1) - p(0)
number of blocks = (p(2) - p(1))/4
number of blinkers = (p(3) - p(2))/5
number of beehives = (p(4) - p(3))/8
"""
table = "n_states:18\n"
table += "neighborhood:Moore\n"
table += "symmetries:rotate4reflect\n\n"
table += self.newvars(["a","b","c","d","e","f","g","h","i"], range(0, 17, 1))
table += """
# Monomino
6,0,0,0,0,0,0,0,0,0
# Death
6,a,b,c,d,e,f,g,h,0
a,6,b,c,d,e,f,g,h,0
# Block
7,7,7,7,0,0,0,0,0,1
1,1,1,1,0,0,0,0,0,0
1,a,b,c,d,e,f,g,h,7
# Blinker
10,0,0,0,9,9,9,0,0,2
9,9,10,0,0,0,0,0,10,3
2,a,b,c,d,e,f,g,h,10
3,a,b,c,d,e,f,g,h,9
9,2,0,3,0,2,0,3,0,6
# Beehive
7,0,7,8,7,0,0,0,0,1
7,0,0,7,8,8,7,0,0,1
8,7,7,8,7,7,0,7,0,4
4,1,1,4,1,1,0,1,0,5
4,a,b,c,d,e,f,g,h,8
5,5,b,c,d,e,f,g,h,6
5,a,b,c,d,e,f,g,h,15
15,a,b,c,d,e,f,g,h,8
"""
colours = """
0 0 0 0
1 255 255 255
2 127 127 127
7 0 0 255
8 0 0 127
9 255 0 0
10 127 0 0
11 0 255 0
12 0 127 0
13 255 255 0
14 127 127 0
"""
self.saverule("APG_ExpungeObjects", comments, table, colours)
def saveCoalesceObjects(self):
comments = """
A variant of HistoricalLife which separates a field of ash into
distinct objects.
state 0: vacuum
state 1: ON
state 2: OFF
"""
table = "n_states:3\n"
table += "neighborhood:Moore\n"
if self.ruletype: #Outer-totalistic
table += "symmetries:permute\n\n"
table += self.newvars(["a","b","c","d","e","f","g","h","i"], [0, 1, 2])
table += self.newvars(["da","db","dc","dd","de","df","dg","dh","di"], [0, 2])
table += self.newvars(["la","lb","lc","ld","le","lf","lg","lh","li"], [1])
minperc = 10
for i in xrange(9):
if (self.bee[i]):
if (minperc == 10):
minperc = i
table += self.scoline("l","d",0,1,i)
table += self.scoline("l","d",2,1,i)
if (self.ess[i]):
table += self.scoline("l","d",1,1,i)
table += "\n# Bridge inductors\n"
for i in xrange(9):
if (i >= minperc):
table += self.scoline("l","d",0,2,i)
table += self.scoline("","",1,2,0)
else: #Isotropic non-totalistic
rule1 = open(self.rulepath, "r")
lines = rule1.read().split("\n")
lines1 = []
for i in lines:
l1 = i.split("\r")
for j in l1:
lines1.append(j)
rule1.close()
for q in xrange(len(lines1)-1):
if lines1[q].startswith("@TABLE"):
lines1 = lines1[q:]
break
vars = []
for q in xrange(len(lines1)-1): #Copy symmetries and vars
i = lines1[q]
if i[:2] == "sy" or i[:1] == "sy":
table += i + "\n\n"
if i[:2] == "va" or i[:1] == "va":
'''table += self.newvar(i[4:5].replace("=", ""), [0, 1, 2])
vars.append(i[4:5].replace("=", ""))'''
if i != "":
if i[0] == "0" or i[0] == "1":
break
alpha = "abcdefghijklmnopqrstuvwxyz"
vars2 = []
'''for i in alpha:
if not i in [n[0] for n in vars]: #Create new set of vars for OFF cells
table += self.newvars([i + j for j in alpha[:9]], [0, 2])
vars2 = [i + j for j in alpha[:9]]
break
for i in alpha:
if not i in [n[0] for n in vars] and not i in [n[0] for n in vars2]:
for j in xrange(5-len(vars)):
table += self.newvar(i + alpha[j], [0, 1, 2])
vars.append(i + alpha[j])
break'''
vars = ["aa", "ab", "ac", "ad", "ae", "af", "ag", "ah"]
vars2 = ["ba", "bb", "bc", "bd", "be", "bf", "bg", "bh"]
table += self.newvars(vars, [0, 1, 2])
table += self.newvars(vars2, [0, 2])
for i in lines1:
q = i.split("#")[0].replace(" ", "").split(",")
if len(q[0]) > 1:
if len(q) == 1 and (q[0][1] == "0" or q[0][1] == "1"):
q = list(q[0])
if len(q) > 1 and not i.startswith("var"):
vn = 0
vn2 = 0
for j in q[:-1]:
if j == "0":
table += vars2[vn2]
vn2 += 1
elif j == "1":
table += "1"
elif j != "#":
table += vars[vn]
vn += 1
table += ","
table += str(2-int(q[len(q)-1]))
table += "\n"
for i in xrange(256): #Get all B3+ rules
ncells = 0
for j in xrange(8):
if (i & 2**j) > 0:
ncells += 1
if ncells == 3:
q = "0,"
vn = 0
for j in xrange(8):
if i & 2**j > 0:
q += str((i & 2**j)/2**j) + ","
else:
q += vars[vn] + ","
vn += 1
q += "2\n"
table += q
colours = """
0 0 0 0
1 255 255 255
2 127 127 127
"""
self.saverule("APG_CoalesceObjects_"+self.alphanumeric, comments, table, colours)
def saveDecayer(self):
comments = """
A multipurpose rule used to assist with decomposition.
"""
table = """
n_states:9
neighborhood:vonNeumann
symmetries:permute
var a={0,1,2,3,4,5,6,7,8}
var aa=a
var ab=a
var ac=a
8,a,aa,ab,ac,0
7,a,aa,ab,ac,0
6,a,aa,ab,ac,2
5,a,aa,ab,ac,1
4,a,aa,ab,ac,2
3,a,aa,ab,ac,1
2,a,aa,ab,ac,0
1,a,aa,ab,ac,0
0,a,aa,ab,ac,0
"""
colours = ""
self.saverule("APG_Decayer", comments, table, colours)
def saveTreeMaker(self):
comments = """
A surprisingly simple rule used to prepare objects for decomposition.
"""
'''table = """
n_states:3
neighborhood:Moore
symmetries:permute
var a={0,1,2}
var aa=a
var ab=a
var ac=a
var ad=a
var ae=a
var af=a
var ag=a
var b={0,2}
var ba=b
var bb=b
var bc=b
var bd=b
var be=b
var bf=b
var bg=b
0,1,1,1,b,ba,bb,bc,bd,2"""'''
table = "n_states:3\n"
table += "neighborhood:Moore\n"
if self.ruletype: #Outer-totalistic
table += "symmetries:permute\n\n"
table += self.newvars(["da","db","dc","dd","de","df","dg","dh","di"], [0,2])
table += self.newvars(["la","lb","lc","ld","le","lf","lg","lh","li"], [1])
minperc = 10
for i in xrange(9):
if (self.bee[i]):
table += self.scoline("l","d",0,2,i)
else: #Isotropic non-totalistic
rule1 = open(self.rulepath, "r")
lines = rule1.read().split("\n")
lines1 = []
for i in lines:
l1 = i.split("\r")
for j in l1:
lines1.append(j)
rule1.close()
for q in xrange(len(lines1)-1):
if lines1[q].startswith("@TABLE"):
lines1 = lines1[q:]
break
vars = []
for q in xrange(len(lines1)-1): #Copy symmetries and vars
i = lines1[q]
if i[:2] == "sy" or i[:1] == "sy":
table += i + "\n\n"
if i[:2] == "va" or i[:1] == "va":
'''table += self.newvar(i[4:5].replace("=", ""), [0, 1, 2])
vars.append(i[4:5].replace("=", ""))'''
if i != "":
if i[0] == "0" or i[0] == "1":
break
alpha = "abcdefghijklmnopqrstuvwxyz"
vars2 = []
'''for i in alpha:
if not i in [n[0] for n in vars]: #Create new set of vars for OFF cells
table += self.newvars([i + j for j in alpha[:9]], [0, 2])
vars2 = [i + j for j in alpha[:9]]
break
for i in alpha:
if not i in [n[0] for n in vars] and not i in [n[0] for n in vars2]:
for j in xrange(5-len(vars)):
table += self.newvar(i + alpha[j], [0, 1, 2])
vars.append(i + alpha[j])
break'''
vars = ["aa", "ab", "ac", "ad", "ae", "af", "ag", "ah"]
vars2 = ["ba", "bb", "bc", "bd", "be", "bf", "bg", "bh"]
table += self.newvars(vars, [0, 1, 2])
table += self.newvars(vars2, [0, 2])
for i in lines1:
q = i.split("#")[0].replace(" ", "").split(",")
if len(q[0]) > 1:
if len(q) == 1 and (q[0][1] == "0" or q[0][1] == "1"):
q = list(q[0])
if len(q) > 1 and not i.startswith("var") and q[0] != "1":
vn = 0
vn2 = 0
for j in q[:-1]:
if j == "0":
table += vars2[vn2]
vn2 += 1
elif j == "1":
table += "1"
elif j != "#":
table += ("0",vars[vn])[j!=0]
vn += 1
table += ","
table += "2\n"
colours = """
0 0 0 0
1 255 255 255
2 127 127 127
"""
self.saverule("APG_TreeMaker_"+self.alphanumeric, comments, table, colours)
def saveClassifyObjects(self):
comments = """
This passively classifies objects as either still-lifes, p2 oscillators
or higher-period oscillators. It is mandatory that one first runs the
rule CoalesceObjects.
state 0: vacuum
state 1: input ON
state 2: input OFF
state 3: ON, will die
state 4: OFF, will remain off
state 5: ON, will survive
state 6: OFF, will become alive
state 7: ON, still-life
state 8: OFF, still-life
state 9: ON, p2 oscillator
state 10: OFF, p2 oscillator
state 11: ON, higher-period object
state 12: OFF, higher-period object
"""
table = "n_states:18\n"
table += "neighborhood:Moore\n"
if self.ruletype: #Outer-totalistic
table += "symmetries:permute\n\n"
table += self.newvars(["a","b","c","d","e","f","g","h","i"], range(0, 17, 1))
table += self.newvars(["la","lb","lc","ld","le","lf","lg","lh","li"], range(1, 17, 2))
table += self.newvars(["da","db","dc","dd","de","df","dg","dh","di"], range(0, 17, 2))
table += self.newvars(["pa","pb","pc","pd","pe","pf","pg","ph","pi"], [0, 3, 4])
table += self.newvars(["qa","qb","qc","qd","qe","qf","qg","qh","qi"], [5, 6])
#Serious modifications necessary:
for i in xrange(9):
if (self.bee[i]):
table += self.scoline("l","d",2,6,i)
table += self.scoline("q","p",3,9,i)
table += self.scoline("q","p",4,12,i)
if (self.ess[i]):
table += self.scoline("l","d",1,5,i)
table += self.scoline("q","p",5,7,i)
table += self.scoline("q","p",6,12,i)
table += self.scoline("","",2,4,0)
table += self.scoline("","",1,3,0)
table += self.scoline("","",5,11,0)
table += self.scoline("","",3,11,0)
table += self.scoline("","",4,8,0)
table += self.scoline("","",6,10,0)
else: #Isotropic non-totalistic
rule1 = open(self.rulepath, "r")
lines = rule1.read().split("\n")
lines1 = []
for i in lines:
l1 = i.split("\r")
for j in l1:
lines1.append(j)
rule1.close()
for q in xrange(len(lines1)-1):
if lines1[q].startswith("@TABLE"):
lines1 = lines1[q:]
break
if lines1[0].startswith("@TREE"):
g.warn("apgsearch v.0.54+0.1i does not support rule trees")
vars = []
for q in xrange(len(lines1)-1): #Copy symmetries and vars
i = lines1[q]
if i[:2] == "sy" or i[:1] == "sy":
table += i + "\n\n"
if i[:2] == "va" or i[:1] == "va":
'''table += self.newvar(i[4:5].replace("=", ""), [0, 1, 2, 3, 4, 5, 6])
vars.append(i[4:5].replace("=", ""))'''
if i != "":
if i[0] == "0" or i[0] == "1":
break
alpha = "abcdefghijklmnopqrstuvwxyz"
ovars = []
'''for i in alpha:
if not i in [n[0] for n in vars]: #Create new set of vars for ON cells
table += self.newvars([i + j for j in alpha[:9]], [1, 5, 6])
ovars = [i + j for j in alpha[:9]]
break'''
dvars = []
vars = ["aa", "ab", "ac", "ad", "ae", "af", "ag", "ah"]
dvars = ["ba", "bb", "bc", "bd", "be", "bf", "bg", "bh"]
ovars = ["ca", "cb", "cc", "cd", "ce", "cf", "cg", "ch"]
table += self.newvars(vars, xrange(7))
table += self.newvars(dvars, [0, 2, 3, 4])
table += self.newvars(ovars, [1, 5, 6])
'''for i in alpha:
if not i in [n[0] for n in vars] and not i in [n[0] for n in ovars]: #Create new set of vars for OFF cells
table += self.newvars([i + j for j in alpha[:9]], [0, 2, 3, 4])
dvars = [i + j for j in alpha[:9]]
break
for i in alpha:
if not i in [n[0] for n in vars] and not i in [n[0] for n in ovars] and not i in [n[0] for n in dvars]:
for j in xrange(8-len(vars)):
table += self.newvar(i + alpha[j], [0, 1, 2, 3, 4, 5, 6])
vars.append(i + alpha[j])
break'''
for i in lines1:
q = i.split("#")[0].replace(" ", "").split(",")
if len(q[0]) > 1:
if len(q) == 1 and (q[0][1] == "0" or q[0][1] == "1"):
q = list(q[0])
if len(q) > 1:
vn = 0
ovn = 0
dvn = 0
if q[0] == "0" or q[0] == "1":
if q[0] == "0":
table += "2"
elif q[0] == "1":
table += "1"
elif q[0] != "#":
table += vars[vn]
vn += 1
table += ","
for j in q[1:-1]:
if j == "0":
table += dvars[dvn]
dvn += 1
elif j == "1":
table += "1"
elif j != "#":
table += vars[vn]
vn += 1
table += ","
table += str(4-int(q[0])+2*int(q[len(q)-1]))
table += "\n"
elif not i.startswith("var"): #Line starts with a variable.
table += vars[vn] + ","
vn += 1
for j in q[1:-1]:
if j == "0":
table += dvars[dvn]
dvn += 1
elif j == "1":
table += "1"
elif j != "#":
table += vars[vn]
vn += 1
table += ","
table += str(4+2*int(q[len(q)-1]))
table += "\n1,"
vn = 0
for j in q[1:-1]:
if j == "0":
table += "2"
elif j == "1":
table += "1"
elif j != "#":
table += vars[vn]
vn += 1
table += ","
table += str(3+2*int(q[len(q)-1]))
table += "\n"
table += "2," + ",".join(vars[:8]) + ",4\n"
table += "1," + ",".join(vars[:8]) + ",5\n"
for i in lines1:
q = i.split("#")[0].replace(" ", "").split(",")
if len(q[0]) > 1:
if len(q) == 1 and (q[0][1] == "0" or q[0][1] == "1"):
q = list(q[0])
if len(q) > 1:
vn = 0
ovn = 0
dvn = 0
if q[0] == "0" or q[0] == "1":
table += str(4+2*int(q[0])) + ","
for j in q[1:-1]:
if j == "0":
table += dvars[dvn]
dvn += 1
elif j == "1":
table += ovars[ovn]
ovn += 1
elif j != "#":
table += vars[vn]
vn += 1
table += ","
if q[0] == "0" and q[len(q)-1] == "0":
table += "8"
if q[0] == "1" and q[len(q)-1] == "0":
table += "10"
if q[0] == "0" and q[len(q)-1] == "1":
table += "12"
if q[0] == "1" and q[len(q)-1] == "1":
table += "12"
table += "\n"
elif not i.startswith("var"): #Line starts with a variable.
table += "5,"
for j in q[1:-1]:
if j == "0":
table += dvars[dvn]
dvn += 1
elif j == "1":
table += ovars[ovn]
ovn += 1
elif j != "#":
table += vars[vn]
vn += 1
table += ","
if q[len(q)-1] == "0":
table += "7"
if q[len(q)-1] == "1":
table += "11"
table += "\n3,"
for j in q[1:-1]:
if j == "0":
table += dvars[dvn]
dvn += 1
elif j == "1":
table += ovars[ovn]
ovn += 1
elif j != "#":
table += vars[vn]
vn += 1
table += ","
if q[len(q)-1] == "0":
table += "9"
if q[len(q)-1] == "1":
table += "11"
table += "\n"
for i in lines1:
q = i.split("#")[0].replace(" ", "").split(",")
if len(q[0]) > 1:
if len(q) == 1 and (q[0][1] == "0" or q[0][1] == "1"):
q = list(q[0])
if len(q) > 1:
vn = 0
ovn = 0
dvn = 0
if q[0] == "0" or q[0] == "1":
table += str(3+2*int(q[0])) + ","
for j in q[1:-1]:
if j == "0":
table += dvars[dvn]
dvn += 1
elif j == "1":
table += ovars[ovn]
ovn += 1
elif j != "#":
table += vars[vn]
vn += 1
table += ","
if q[0] == "0" and q[len(q)-1] == "0":
table += "11"
if q[0] == "1" and q[len(q)-1] == "0":
table += "11"
if q[0] == "0" and q[len(q)-1] == "1":
table += "9"
if q[0] == "1" and q[len(q)-1] == "1":
table += "7"
table += "\n"
elif not i.startswith("var"): #Line starts with a variable.
table += "6,"
for j in q[1:-1]:
if j == "0":
table += dvars[dvn]
dvn += 1
elif j == "1":
table += ovars[ovn]
ovn += 1
elif j != "#":
table += vars[vn]
vn += 1
table += ","
if q[len(q)-1] == "0":
table += "12"
if q[len(q)-1] == "1":
table += "10"
table += "\n4,"
for j in q[1:-1]:
if j == "0":
table += dvars[dvn]
dvn += 1
elif j == "1":
table += ovars[ovn]
ovn += 1
elif j != "#":
table += vars[vn]
vn += 1
table += ","
if q[len(q)-1] == "0":
table += "8"
if q[len(q)-1] == "1":
table += "12"
table += "\n"
table += "4," + ",".join(vars[:8]) + ",8\n"
table += "3," + ",".join(vars[:8]) + ",11\n"
table += "6," + ",".join(vars[:8]) + ",12\n"
table += "5," + ",".join(vars[:8]) + ",7\n"
colours = """
0 0 0 0
1 255 255 255
2 127 127 127
7 0 0 255
8 0 0 127
9 255 0 0
10 127 0 0
11 0 255 0
12 0 127 0
13 255 255 0
14 127 127 0
"""
self.saverule("APG_ClassifyObjects_"+self.alphanumeric, comments, table, colours)
def savePropagateClassifications(self):
comments = """This propagates the result of running ClassifyObjects for two generations.
"""
table = "n_states:18\n"
table += "neighborhood:Moore\n"
table += "symmetries:permute\n\n"
table += self.newvars(["a","b","c","d","e","f","g","h","i"], range(0, 17, 1))
table += """
7,11,b,c,d,e,f,g,h,11
7,12,b,c,d,e,f,g,h,11
7,9,b,c,d,e,f,g,h,9
7,10,b,c,d,e,f,g,h,9
8,11,b,c,d,e,f,g,h,12
8,12,b,c,d,e,f,g,h,12
8,9,b,c,d,e,f,g,h,10
8,10,b,c,d,e,f,g,h,10
7,13,b,c,d,e,f,g,h,11
7,14,b,c,d,e,f,g,h,11
8,13,b,c,d,e,f,g,h,14
8,14,b,c,d,e,f,g,h,14
9,13,b,c,d,e,f,g,h,11
9,14,b,c,d,e,f,g,h,11
10,13,b,c,d,e,f,g,h,14
10,14,b,c,d,e,f,g,h,14
9,11,b,c,d,e,f,g,h,11
9,12,b,c,d,e,f,g,h,11
10,11,b,c,d,e,f,g,h,12
10,12,b,c,d,e,f,g,h,12
13,11,b,c,d,e,f,g,h,11
13,12,b,c,d,e,f,g,h,11
14,11,b,c,d,e,f,g,h,12
14,12,b,c,d,e,f,g,h,12
13,9,b,c,d,e,f,g,h,11
14,9,b,c,d,e,f,g,h,12
"""
colours = """
0 0 0 0
1 255 255 255
2 127 127 127
7 0 0 255
8 0 0 127
9 255 0 0
10 127 0 0
11 0 255 0
12 0 127 0
13 255 255 0
14 127 127 0
"""
self.saverule("APG_PropagateClassification", comments, table, colours)
#foo = "" + 2
def saveContagiousLife(self):
comments = """
A variant of HistoricalLife used for detecting dependencies between
islands.
state 0: vacuum
state 1: ON
state 2: OFF
"""
table = "n_states:7\n"
table += "neighborhood:Moore\n"
if self.ruletype:
table += "symmetries:permute\n\n"
table += self.newvars(["a","b","c","d","e","f","g","h","i"], range(0, 7, 1))
table += self.newvars(["la","lb","lc","ld","le","lf","lg","lh","li"], range(1, 7, 2))
table += self.newvars(["da","db","dc","dd","de","df","dg","dh","di"], range(0, 7, 2))
table += self.newvar("p",[3, 4])
table += self.newvars(["ta","tb","tc","td","te","tf","tg","th","ti"], [3])
table += self.newvars(["qa","qb","qc","qd","qe","qf","qg","qh","qi"], [0, 1, 2, 4, 5, 6])
for i in xrange(9):
if (self.bee[i]):
table += self.scoline("l","d",4,3,i)
table += self.scoline("l","d",2,1,i)
table += self.scoline("l","d",0,1,i)
table += self.scoline("l","d",6,5,i)
table += self.scoline("t","q",0,4,i)
if (self.ess[i]):
table += self.scoline("l","d",3,3,i)
table += self.scoline("l","d",5,5,i)
table += self.scoline("l","d",1,1,i)
table += "# Default behaviour (death):\n"
table += self.scoline("","",1,2,0)
table += self.scoline("","",5,6,0)
table += self.scoline("","",3,4,0)
else:
rule1 = open(self.rulepath, "r")
lines = rule1.read().split("\n")
lines1 = []
for i in lines:
l1 = i.split("\r")
for j in l1:
lines1.append(j)
rule1.close()
for q in xrange(len(lines1)-1):
if lines1[q].startswith("@TABLE"):
lines1 = lines1[q:]
break
vars = []
for q in xrange(len(lines1)-1): #Copy symmetries and vars
i = lines1[q]
if i[:2] == "sy" or i[:1] == "sy":
table += i + "\n\n"
if i[:2] == "va" or i[:1] == "va":
'''table += self.newvar(i[4:5].replace("=", ""), [0, 1, 2, 3, 4, 5, 6])
vars.append(i[4:5].replace("=", ""))'''
if i != "":
if i[0] == "0" or i[0] == "1":
break
alpha = "abcdefghijklmnopqrstuvwxyz"
ovars = []
'''for i in alpha:
if not i in [n[0] for n in vars]: #Create new set of vars for ON cells
table += self.newvars([i + j for j in alpha[:9]], [1, 3, 5])
ovars = [i + j for j in alpha[:9]]
break'''
dvars = []
'''for i in alpha:
if not i in [n[0] for n in vars] and not i in [n[0] for n in ovars]: #Create new set of vars for OFF cells
table += self.newvars([i + j for j in alpha[:9]], [0, 2, 4, 6])
dvars = [i + j for j in alpha[:9]]
break
for i in alpha:
if not i in [n[0] for n in vars] and not i in [n[0] for n in ovars] and not i in [n[0] for n in dvars]:
for j in xrange(8-len(vars)):
table += self.newvar(i + alpha[j], [0, 1, 2, 3, 4, 5, 6])
vars.append(i + alpha[j])
break'''
qvars = []
'''for i in alpha:
if not i in [n[0] for n in vars] and not i in [n[0] for n in ovars] and not i in [n[0] for n in dvars]:
table += self.newvars([i + j for j in alpha[:9]], [0, 1, 2, 4, 5, 6])
qvars = [i + j for j in alpha[:9]]
break'''
vars = ["aa", "ab", "ac", "ad", "ae", "af", "ag", "ah"]
dvars = ["ba", "bb", "bc", "bd", "be", "bf", "bg", "bh"]
ovars = ["ca", "cb", "cc", "cd", "ce", "cf", "cg", "ch"]
qvars = ["da", "db", "dc", "dd", "de", "df", "dg", "dh"]
table += self.newvars(vars, xrange(7))
table += self.newvars(dvars, [0, 2, 4, 6])
table += self.newvars(ovars, [1, 3, 5])
table += self.newvars(qvars, [0, 1, 2, 4, 5, 6])
for i in lines1:
q = i.split("#")[0].replace(" ", "").split(",")
if len(q[0]) > 1:
if len(q) == 1 and (q[0][1] == "0" or q[0][1] == "1"):
q = list(q[0])
if len(q) > 1 and not i.startswith("var"):
vn = 0
ovn = 0
dvn = 0
qvn = 0
table += str(2-int(q[0])) + ","
for j in q[1:-1]:
if j == "0":
table += dvars[dvn]
dvn += 1
elif j == "1":
table += ovars[ovn]
ovn += 1
elif j != "#":
table += vars[vn]
vn += 1
table += ","
if q[len(q)-1] == "0":
table += "2"
if q[len(q)-1] == "1":
table += "1"
table += "\n"
vn = 0
ovn = 0
dvn = 0
qvn = 0
table += str(4-int(q[0])) + ","
for j in q[1:-1]:
if j == "0":
table += dvars[dvn]
dvn += 1
elif j == "1":
table += ovars[ovn]
ovn += 1
elif j != "#":
table += vars[vn]
vn += 1
table += ","
if q[len(q)-1] == "0":
table += "4"
if q[len(q)-1] == "1":
table += "3"
table += "\n"
vn = 0
ovn = 0
dvn = 0
qvn = 0
table += str(6-int(q[0])) + ","
for j in q[1:-1]:
if j == "0":
table += dvars[dvn]
dvn += 1
elif j == "1":
table += ovars[ovn]
ovn += 1
elif j != "#":
table += vars[vn]
vn += 1
table += ","
if q[len(q)-1] == "0":
table += "6"
if q[len(q)-1] == "1":
table += "5"
table += "\n"
for i in lines1:
q = i.split("#")[0].replace(" ", "").split(",")
if len(q[0]) > 1:
if len(q) == 1 and (q[0][1] == "0" or q[0][1] == "1"):
q = list(q[0])
if len(q) > 1:
vn = 0
ovn = 0
dvn = 0
qvn = 0
if q[0] == "0":
table += "0,"
for j in q[1:-1]:
if j == "0":
table += dvars[dvn]
dvn += 1
elif j == "1":
table += ovars[ovn]
ovn += 1
elif j != "#":
table += vars[vn]
vn += 1
table += ","
if q[len(q)-1] == "1":
table += "1"
else:
table += "0"
table += "\n"
for i in lines1:
q = i.split("#")[0].replace(" ", "").split(",")
if len(q[0]) > 1:
if len(q) == 1 and (q[0][1] == "0" or q[0][1] == "1"):
q = list(q[0])
if len(q) > 1:
vn = 0
ovn = 0
dvn = 0
qvn = 0
if q[0] == "0":
table += "0,"
for j in q[1:-1]:
if j == "0":
table += qvars[qvn]
qvn += 1
elif j == "1":
table += "3"
elif j != "#":
table += vars[vn]
vn += 1
table += ","
if q[len(q)-1] == "1":
table += "4"
else:
table += "0"
table += "\n"
colours = """
0 0 0 0
1 0 0 255
2 0 0 127
3 255 0 0
4 127 0 0
5 0 255 0
6 0 127 0
7 255 0 255
8 127 0 127
"""
self.saverule("APG_ContagiousLife_"+self.alphanumeric, comments, table, colours)
class Soup:
# The rule generator:
rg = RuleGenerator()
# Should we skip error-correction:
skipErrorCorrection = False
# A dict mapping binary representations of small possibly-pseudo-objects
# to their equivalent canonised representation.
#
# This is many-to-one, as (for example) all of these will map to
# the same pseudo-object (namely the beacon on block):
#
# ..**.** ..**.** **..... **.....
# ..**.** ...*.** **..... *......
# **..... *...... ..**... ...*.**
# **..... **..... ..**... [...12 others omitted...] ..**.**
# ....... ....... ....... .......
# ....... ....... ..**... .......
# ....... ....... ..**... .......
#
# The first few soups are much slower to process, as objects are being
# entered into the cache.
cache = {}
# A dict to store memoized decompositions of possibly-pseudo-objects
# into constituent parts. This is initialised with the unique minimal
# pseudo-still-life (two blocks on lock) that cannot be automatically
# separated by the routine pseudo_bangbang(). Any larger objects are
# ambiguous, such as this one:
#
# *
# * * **
# ** **
#
# * *** *
# ** * **
#
# Is it a (block on (lock on boat)) or ((block on lock) on boat)?
# Ahh, the joys of non-associativity.
#
# See http://paradise.caltech.edu/~cook/Workshop/CAs/2DOutTot/Life/StillLife/StillLifeTheory.html
decompositions = {"xs18_3pq3qp3": ["xs14_3123qp3", "xs4_33"]}
# A dict of objects in the form {"identifier": ("common name", points)}
#
# As a rough heuristic, an object is worth 15 + log2(n) points if it
# is n times rarer than the pentadecathlon.
#
# Still-lifes are limited to 10 points.
# p2 oscillators are limited to 20 points.
# p3 and p4 oscillators are limited to 30 points.
commonnames = {"xp2_7": ("blinker", 0),
"xp2_7e": ("toad", 0),
"xp2_318c": ("beacon", 0),
"xp2_xrhewehrz253y4352": ("spark coil on distant-boats", 10),
"xp2_o8031u0u1308ozol555o0o555lozx6430346": ("[4-fold diags]", 10),
"xp2_rhewehr": ("spark coil", 15),
"xp2_c8b8acz31d153" :("symmetric griddles", 15),
"xp2_2a54": ("clock", 16),
"xp2_31ago": ("bipole", 17),
"xp2_0g0k053z32": ("quadpole", 18),
"xp2_g8gid1e8z1226": ("great on-off", 19),
"xp2_jlg4glj": ("[2-fold odd] pole variant", 20),
"xp2_318c0f9": ("cis-beacon and table", 20),
"xp2_wbq23z32": ("trans-beacon and table", 20),
"xp2_31egge13": ("test tubebaby", 20),
"xp2_31ago0ui": ("cis-bipole and table", 20),
"xp2_318c0fho": ("cis-beacon-down and long hook", 20),
"xp2_318c0f96": ("cis-beacon and cap", 20),
"xp2_3hu0og26": ("cis-beacon-up and long hook", 20),
"xp2_jlg44glj": ("[2-fold even] pole variant", 20),
"xp2_wbq2sgz32": ("beacon and two tails", 20),
"xp2_g0k053z11": ("tripole", 20),
"xp2_g45h1sz03": ("fox", 20),
"xp2_33gv1og26": ("Z_and_cis-beacon_and_block", 20),
"xp2_4alhewehr": ("030c029200f50292030c", 20),
"xp2_31egge1da": ("01860149007a004b0030", 20),
"xp2_253gv1og26": ("?-Z-and_beacon_and_boat", 20),
"xp2_xoga13z253": ("bipole on boat", 20),
"xp2_xoga13z653": ("ship on bipole", 20),
"xp2_4k1u1k4zx1": ("by flops", 20),
"xp2_c8b8aczw33": ("griddle and block", 20),
"xp2_w8o0uh3z32": ("trans-beacon-down and long hook", 20),
"xp2_mlhewehrz1": ("01860149007b014801870001", 20),
"xp2_4aarhewehr": ("spark_coil siamese hat", 20),
"xp2_318czw3553": ("trans-beacon and cap", 20),
"xp2_c813z31178c": ("?-eater_siamese_table and ?-beacon", 20),
"xp2_gbhewehrz11": ("0186014a0078014a01850003", 20),
"xp2_25acz31d153": ("griddle and long_boat", 20),
"xp2_wgbq2sgz642": ("bipole and 2_tails", 20),
"xp2_gbhewehrz01": ("?21?004", 20),
"xp2_j1u062goz11": ("cis-beacon and dock", 20),
"xp2_c88b9iczw32": ("001800280041005f0030000c000c", 20),
"xp2_318cz311dic": ("beehive_with_long_leg and cis-beacon", 20),
"xp2_35acz31d153": ("griddle and long_ship", 20),
"xp2_c8b8acz3553": ("griddle and cap", 20),
"xp2_318cz315b8o": ("0318031000d400da00020003", 20),
"xp2_c813z311dio": ("0300024001a30023002c006c", 20),
"xp2_c813z311dic": ("[[cis-beacon-up and beehive_fuse_longhook]]", 20),
"xp2_318cz3115a4": ("tub_with_long_leg and cis-beacon", 20),
"xp2_4k1v0f9zw11": ("griddle and table", 20),
"xp2_ca9baiczw32": ("1 beacon", 20),
"xp2_318cz311da4": ("cis-boat_with_long_leg and ?-beacon", 20),
"xp2_c813z3115a4": ("tub_with_long_leg and ?-beacon", 20),
"xp2_8e1t2gozw23": ("21P2", 20),
"xp2_31egge132ac": ("?-test_tubebaby siamese eater", 20),
"xp2_c813z311da4": ("cis-boat_with_long_leg and ?-beacon", 20),
"xp2_03lk46z6401": ("trans-beacon-up and long hook", 20),
"xp2_gk2t2sgzw23": ("00180028000b006a000a00140008", 20),
"xp2_rb88czx318c": ("?19?036", 20),
"xp2_c8b8acz0253": ("griddle and boat", 20),
"xp2_rb88brz0103": ("griddle and blocks", 20),
"xp2_c813z319lic": ("cis-loaf_with_long_leg and ?-beacon", 20),
"xp2_caabaiczw32": ("18P2.471", 20),
"xp2_wg0k053z642": ("pentapole", 20),
"xp2_35453z255dic": ("0308029400f4029603090006", 20),
"xp2_xg8o652zca02": ("tripole on boat", 20),
"xp2_0mlhewehrz32": ("030c029200f60290030e00010003", 20),
"xp2_03544oz4a511": ("odd test tubebaby", 20),
"xp2_0318cz69d596": ("00c0012c00ac01a3012300c0", 20),
"xp2_03544ozca511": ("trans-boat test tubebaby", 20),
"xp2_318cz39d1d96": ("018c0189006b0068000b00090006", 20),
"xp2_0318cz255d96": ("racetrack and ?-beacon", 20),
"xp2_03544ozcid11": ("beehive test tubebaby", 20),
"xp2_og2t1egozx32": ("00180024003400d500d300100018", 20),
"xp2_318czw3115a4": ("tub_with_long_leg and ?-beacon", 20),
"xp2_318czw3115ac": ("trans-boat_with_long_leg and trans-beacon", 20),
"xp2_04a96z31d153": ("[[loaf on griddle]]", 20),
"xp2_03lk453z6401": ("trans-beacon and dock", 20),
"xp2_6a88baiczx32": ("24P2", 20),
"xp2_069b8b5zc813": ("018001860069006b0008000b0005", 20),
"xp2_39u0og26z023": ("long_and_cis-beacon", 20),
"xp2_69b8baiczx32": ("22P2", 20),
"xp2_c813z311dik8": ("trans-loaf_with_long_leg and ?-beacon", 20),
"xp2_c8b8q4oz33x1": ("006c006a0008000b001800240018", 20),
"xp2_039c8a6zc813": ("?-hook_siamese_carrier and beacon", 20),
"xp2_69e0ehhe0e96": ("04620a950af506960060", 20),
"xp2_03544oz4ad11": ("cis-boat test tubebaby", 20),
"xp2_69b8b9iczx32": ("26P2", 20),
"xp2_ciab8brzw103": ("006c006a0008006b002800240018", 20),
"xp2_04aab8ozc813": ("01800184006a006a000b00080018", 20),
"xp2_og2t1e8z6221": ("00d800580046003d0001000e0008", 20),
"xp2_31agozca22ac": ("cis-bipole and dock", 20),
"xp2_318czw31178c": ("?-eater_siamese_table and ?-beacon", 20),
"xp2_g318cz1pl552": ("004000ac00ac02a303230030", 20),
"xp2_xrhewehrz253": ("boat on spark coil", 20),
"xp2_0318cz69d552": ("00c0012301a300ac00ac0040", 20),
"xp2_4k1v0ciczw11": ("griddle and beehive", 20),
"xp2_06996z31d153": ("griddle and pond", 20),
"xp2_318czcid1d96": ("0306030900d600d000160012000c", 20),
"xp2_04aab96zc813": ("racetrack and ortho-beacon", 20),
"xp2_wo44871z6221": ("eater plug", 20),
"xp2_xg8o653zca02": ("tripole on ship", 20),
"xp2_ci9b8brzw103": ("006c006a0008006b004800240018", 20),
"xp2_318cz311dik8": ("loaf_with_long_leg and cis-beacon-down", 20),
"xp2_og2t1acz6221": ("00d800580046003d0001000a000c", 20),
"xp2_09v0c813z321": ("23P2", 20),
"xp2_4k1u1k4zw121": ("why not", 20),
"xp2_rhewe44ewehr": ("long piston", 20),
"xp2_31agozwca22ac": ("trans-bipole and dock", 20),
"xp2_06a88a52zc813": ("tub_with_long_nine and ?-beacon", 20),
"xp2_8kaabaiczw103": ("[[?19?037]]", 20),
"xp2_0g04q23z345lo": ("03000100016300d5003400240018", 20),
"xp2_0c813z69d1dic": ("0180024001a3002301ac012c00c0", 20),
"xp2_0cimgm96zog26": ("0300030c00d200d60010001600090006", 20),
"xp2_04aab871zc813": ("anvil and ?-beacon", 20),
"xp2_w8o0uh32acz32": ("long_hook_siamese_eater and trans-beacon-down", 20),
"xp2_0ggm952z344ko": ("loaf test tubebaby", 20),
"xp2_0c813z255d1e8": ("cis-beacon and anvil", 20),
"xp2_0c813z65115a4": ("tub_with_long_nine and ?-beacon", 20),
"xp2_318c0s26zw311": ("00c000c300350034000400380020", 20),
"xp2_xohf0318cz253": ("long_hook_on_boat and cis-beacon-up", 20),
"xp2_651u8z318nge2": ("01980298020601fd0041000e0008", 20),
"xp2_03lkmzojc0cjo": ("lightbulb_and_cis-hook", 20),
"xp2_0318cz65115a4": ("tub_with_long_nine and ?-beacon", 20),
"xp2_25a88gz8ka221": ("tubs test tubebaby", 20),
"xp2_8ki1688gzx3421": ("4 boats", 20),
"xp2_wca9e0eiczc813": ("?-loaf_siamese_table_and_R-bee_and_beacon", 20),
"xp2_31a08zy0123cko": ("quadpole on ship", 20),
"xp2_356o8gz318nge3": ("031802980186007d0041002e0018", 20),
"xp2_0318cz8kid1d96": ("01000283024301ac002c01a0012000c0", 20),
"xp2_256o8gzy120ago": ("boat on quadpole", 20),
"xp2_ca1n0brz330321": ("fore and back", 20),
"xp2_c88b96z330318c": ("worm and cis-beacon_and_block", 20),
"xp2_c8b8acz3303552": ("?-griddle and R-bee_and_block", 20),
"xp2_0318cz259d1d96": ("?22?047", 20),
"xp2_0c8b8acz651156": ("griddle and dock", 20),
"xp2_3hu062goz01226": ("?-long_hook_siamese_eater and beacon", 20),
"xp2_03lkl3zojc0cjo": ("lightbulb_and_cis-C", 20),
"xp2_3p6gmkkozw1046": ("00c000a8002e0041005f0030000c000c", 20),
"xp2_62go0u1u066zy21": ("very_long_beehive and ?-beacon and block", 20),
"xp2_4a960u2kgzx6952": ("004000ac012a00c8000b0018002400280010", 20),
"xp2_c813z35t1eozx11": ("030005c0042303a300ac006c", 20),
"xp2_6t1ege11egozw11": ("031804a406bc02a502430180", 20),
"xp2_xrhewehrz653y4356": ("spark coil on distant-ships", 20),
"xp2_4a4owo4a96zx1221": ("loaf tub test tubebaby", 20),
"xp2_wgj1u0og26z25421": ("20P2", 20),
"xp2_318c0s48gzw359611": ("R-loaf_at_R-loaf and beacon", 20),
"xp2_g8o6hewehrz121011": ("0610052801e80526061500350002", 20),
"xp2_bh2882hbzd841148d": ("[4-fold] pole variant", 20),
"xp2_318czw35t1eozy111": ("0600060001b001a8002e0021001d0006", 20),
"xp2_3p606p3z0o555oz6430346": ("lightbulbs", 20),
"xp2_25a4owo4a52z4a521w125a4": ("04020a05050a029400f00294050a0a050402", 20),
"xp2_256o808ozxg999gzx110116a4": ("spark coil on diagnal_boats", 20),
"xp2_25ic0ci52zwhaaahz8k96069k8": ("tub_lightbulbs", 20),
"xp3_co9nas0san9oczgoldlo0oldlogz1047210127401": ("pulsar", 8),
"xp3_025qzraaahz08kb": ("[[long_hooks eating tubs]]", 10),
"xp3_03p606p3z252x252": ("[[short_hooks eating tubs]]", 10),
"xp3_0gs46364sgz13e8ogo8e31zy11": ("star", 15),
"xp3_4s3ia4oggzw12e": ("pulsar quadrant", 20),
"xp3_695qc8zx33": ("jam", 24),
"xp3_025qzrq221": ("trans-block tub eater", 28),
"xp3_4hh186z07": ("caterer", 29),
"xp3_025qz32qq1": ("cis-block tub eater", 30),
"xp3_3560uh224a4": ("longhook_eating_tub and trans-ship-up", 30),
"xp3_4a422hu06a4": ("cis-boat and long_hook_eating_tub", 30),
"xp3_2530fh884a4": ("longhook_eating_tub and cis-boat-up", 30),
"xp3_4a422hu0696": ("cis-beehive tub eater", 30),
"xp3_2560uh224a4": ("longhook_eating_tub and trans-boat-up", 30),
"xp3_2524sws4252": ("bent keys", 30),
"xp3_2524sws44a4": ("odd keys", 30),
"xp3_2522ewe2252": ("short keys", 30),
"xp3_4a422hu069a4": ("longhook_eating_tub and ortho-loaf-up", 30),
"xp3_4a422hu06996": ("longhook_eating_tub and cis-pond", 30),
"xp3_31ecsggsce13": ("keys variant", 30),
"xp3_6a889b8ozx33": ("00300033000900fe008000180018", 30),
"xp3_04qi3s4z33w1": ("trice tongs", 30),
"xp3_0g31u0ooz345d": ("0060009000b301a9001e000000180018", 30),
"xp3_6a889b8ozx356": ("0060005000100056009500d300100018", 30),
"xp3_co9na4oggzw12e": ("2 pulsar quadrants", 30),
"xp3_g8o0uh224a4z01": ("longhook_eating_tub and trans-boat-down", 30),
"xp3_g8o0uh224a4z11": ("longhook_eating_tub and trans-boat-down", 30),
"xp3_j1u0uh224a4z11": ("dock tub eater", 30),
"xp3_g31u0ooz11078c": ("00300033000900fe010001980018", 30),
"xp3_o4o0uh224a4z01": ("longhook_eating_tub and trans-beehive", 30),
"xp3_g88rb88gz11w8kb": ("longhook siamese hook_eating_tub and ?-block", 30),
"xp3_8k4o0uh224a4zw1": ("longhook_eating_tub and ortho-loaf-down", 30),
"xp3_g31u0o8zd5430fio": ("0300024801f80000007e008900b301b0", 30),
"xp3_0ggmmggs252z32w46": ("020005180298008000fe000900330030", 30),
"xp3_s471174sz11744711": ("cross", 30),
"xp3_g31u0u13gz1pgf0fgp1": ("00c600aa002800aa012901ab0028002800aa00c6", 30),
"xp3_ca66311366acz3566c88c6653": ("sym boat keys", 30),
"xp3_3js46364sj3zhje8ogo8ejhz11x1x11": ("4 blocks on star", 30),
"xp3_69ba99b606b99ab96z69d599d606d995d96": ("[4-fold]?p3", 30),
"xp3_s47117471174szt57y375tz1174471744711": ("cross 2", 30),
"xp3_3jc42s0s24cj3z08l55o0o55l8z6611210121166": ("[4-fold] trice tongs", 30),
"xp3_3jc42s0s24cj3z08l55t0t55l8z6611210121166": ("[4-fold] trice tongs variant", 30),
"xp3_co9na4o0o4an9oczwhaaah0haaahz63ita43034ati36": ("[4-fold] pulsar quadrants", 30),
"xp3_y43p606p3zo80gg8ka52x25ak8gg08ozol588g8gy3g8g885lozy2125qxq521zy46430346": ("[4-fold] short_hooks eating tubs", 30),
"xp4_y1g8bb8gzcc0u1y21u0cczy0124kk421zy311": ("octagon IV", 20),
"xp4_y1g8bb8gzcc0u1wccw1u0cczy0124kk421zy311": ("octagon IV w block", 20),
"xp4_37bkic": ("mold", 21),
"xp4_ssj3744zw3": ("mazing", 23),
"xp4_8eh5e0e5he8z178a707a871": ("cloverleaf", 25),
"xp4_hv4a4vh": ("monogram", 30),
"xp4_3lk453z34ats": ("cis-mold and dock", 30),
"xp4_199aaooaa991": ("?p4", 30),
"xp4_ciq3k3qiczcimgagmiczx343": ("cloverleaf siamese hat", 30),
"xp4_g8eh5e0e5he8gz0178a707a871": ("cloverleaf siamese beehives", 30),
"xp4_17f8a606a8f71z8ef1560651fe8": ("4 tails", 30),
"xp4_y38eh5e0e5he8zg88cgc89n8a707a8n98cgc88gzhara0arahy3hara0arahz1226162it2as0sa2ti2616221zy32ehke0ekhe2": ("[4-fold] cloverleafs", 30),
"xp5_idiidiz01w1": ("octagon II", 26),
"xp5_y131u0u13z3lkkk21x12kkkl3z32x12s0s21x23zy13210123": ("harbor", 30),
"xp5_3pmwmp3zx11": ("fumarole", 33),
"xp5_4aarahrrharaa4": ("hats ?p5", 40),
"xp5_25a84cgz8ka2461": ("tubs-fumarole", 40),
"xp5_35a84cgzoka2461": ("boats-fumarole", 40),
"xp5_39u0u93z0fm0mfzc97079c": ("bookends_pair ?p5", 40),
"xp5_356o8gy0g8o653zy09cvwvc9": ("ship-tie-boats fumarole", 40),
"xp5_oca71v0v17acoztll55o0o55lltz012747074721": ("[4-fold] hearts", 40),
"xp6_ccb7w66z066": ("unix", 20),
"xp6_3jc42sws24cj3z0g999gwg999gzcc3243w3423cc": ("[4-fold] pseudo trice tongs", 25),
"xp6_co9na4owo4an9oczw1iii1w1iii1zc65qk87w78kq56czw11y411": ("[4-fold] pseudo pulsar quadrants", 25),
"xp6_g80e55e08gz120ekke021": ("A for all", 45),
"xp8_gk2gb3z11": ("figure-8", 20),
"xp8_g3jgz1ut": ("blocker", 24),
"xp8_wgovnz234z33": ("Tim Coe's p8", 31),
"xp8_2erore2z07x7": ("smiley", 45),
"xp14_j9d0d9j": ("tumbler", 25),
"xp15_4r4z4r4": ("pentadecathlon", 15),
"xp15_sksy0ohf0fhoy0skszvnvy0c4o0o4cy0vnvzy423032": ("bi-pentadecathlon w sym-hooks_pair", 25),
"xp30_w33z8kqrqk8zzzw33": ("cis-queen-bee-shuttle", 24),
"xp30_w33z8kqrqk8zzzx33": ("trans-queen-bee-shuttle", 24),
"xp30_w33z8kqrqk8zzzz25brb52zwoo": ("symmetric queen-bee-shuttle 1", 24),
"xp46_330279cx1aad3y833zx4e93x855bc": ("cis-twin-bees-shuttle", 35),
"xp46_330279cx1aad3zx4e93x855bcy8cc": ("trans-twin-bees-shuttle", 35),
"xq3_16614e8o4aa82cc2": ("edge-repair spaceship 1", 75),
"xq3_km92z1d9czxor33zy11": ("edge-repair spaceship 2", 75),
"xq3_mhqkzarahh0heezdhb5": ("dart", 75),
"xq3_3u0228mc53bgzof0882d6koq1": ("turtle", 75),
"xq4_153": ("glider", 0),
"xq4_6frc": ("lightweight spaceship", 6),
"xq4_27dee6": ("middleweight spaceship", 8),
"xq4_27deee6": ("heavyweight spaceship", 12),
"xq4_27dee6z4eb776": ("MWSS on MWSS 1", 20),
"xq4_27deee6z4eb7776": ("HWSS on HWSS 1", 20),
"xq4_27de6z4eb776": ("LWSS on MWSS 1", 40),
"xq4_27du6ze98885": ("LWSS on MWSS 3", 40),
"xq4_0791h1az8smec": ("LWSS on MWSS 2", 40),
"xq4_27de6z4eb7776": ("LWSS on HWSS 1", 40),
"xq4_6ed72z6777be4": ("LWSS on HWSS 2", 40),
"xq4_27de6z8smeeec": ("LWSS on HWSS 5", 40),
"xq4_27dumze988885": ("LWSS on HWSS 7", 40),
"xq4_27due6ze98885": ("MWSS on MWSS 5", 40),
"xq4_27de6zw8smeeec": ("LWSS on HWSS 3", 40),
"xq4_027deee6z8smec": ("LWSS on HWSS 4", 40),
"xq4_06eeed72zcems8": ("LWSS on HWSS 6", 40),
"xq4_27dee6zw4eb776": ("MWSS on MWSS 2", 40),
"xq4_677be4zx48889e": ("MWSS on MWSS 3", 40),
"xq4_06eed72zaghgis": ("MWSS on MWSS 4", 40),
"xq4_27dee6z4eb7776": ("MWSS on HWSS 2", 40),
"xq4_6eeed72zaghgis": ("MWSS on HWSS 10", 40),
"xq4_27duee6ze98885": ("MWSS on HWSS 11", 40),
"xq4_27duu6ze988885": ("MWSS on HWSS 13", 40),
"xq4_6eed72zaghhgis": ("MWSS on HWSS 14", 40),
"xq4_27dee6zw4eb7776": ("MWSS on HWSS 1", 40),
"xq4_06eeed72z677be4": ("MWSS on HWSS 3", 40),
"xq4_27dee6zx8smeeec": ("MWSS on HWSS 4", 40),
"xq4_aghgiszza1hh197": ("MWSS on HWSS 7", 40),
"xq4_02111197zcssqe4": ("MWSS on HWSS 9", 40),
"xq4_0791hh1az8smeec": ("MWSS on HWSS 12", 40),
"xq4_06eeed72zaghgis": ("MWSS on HWSS 15", 40),
"xq4_27duue6ze988885": ("HWSS on HWSS 7", 40),
"xq4_0sighhgazz791h1a": ("MWSS on HWSS 5", 40),
"xq4_sighhgazz791hh1a": ("MWSS on HWSS 6", 40),
"xq4_0aghhgiszza1h197": ("MWSS on HWSS 8", 40),
"xq4_27deee6zw4eb7776": ("HWSS on HWSS 2", 40),
"xq4_027deee6zsighhga": ("HWSS on HWSS 6", 40),
"xq4_06eeed72zaghhgis": ("HWSS on HWSS 8", 40),
"xq4_a1hh197zx6777be4": ("HWSS on HWSS 9", 40),
"xq4_27deee6zwsighhga": ("HWSS on HWSS 10", 40),
"xq4_0aghhgiszza1hh197": ("HWSS on HWSS 4", 40),
"xq4_aghhgiszzwa1hh197": ("HWSS on HWSS 5", 40),
"xq4_027deee6z4eqscc6": ("sidecar", 45),
"xq4_a1hhpnzy03mvo2ms8zy211": ("MWSS on pushalong", 55),
"xq4_82gfxossz34d72w37d6": ("HWSS on semi-X66", 60),
"xq4_0oorxroozdjio404oijd": ("X66", 66),
"xq7_3nw17862z6952": ("loafer", 70),
"xq7_7dfxg88gxfd7zw123u2222u321": ("weekender", 70),
"xq12_fh1i0i1hfzw8sms8zxfjf": ("lightweight schick engine", 50),
"xq12_xupuzw27d72zu10109ghuz3221": ("asymmetric schick engine", 50),
"xq12_v1120211vz01gpcpg1zxv7vzy01": ("middleweight schick engine", 50),
"xq12_v1120211vz120ioi021zw1vev1zx121": ("heavyweight schick engine", 50),
"xq16_gcbgzvgg826frc": ("Coe ship", 50),
"xs4_33": ("block", 0),
"xs4_252": ("tub", 0),
"xs5_253": ("boat", 0),
"xs6_bd": ("snake", 0),
"xs6_696": ("beehive", 0),
"xs6_356": ("ship", 0),
"xs6_39c": ("carrier", 0),
"xs6_25a4": ("barge", 0),
"xs7_25ac": ("long boat", 0),
"xs7_2596": ("loaf", 0),
"xs7_178c": ("eater", 0),
"xs7_3lo": ("long_snake", 2),
"xs8_3pm": ("shillelagh", 0),
"xs8_69ic": ("mango", 0),
"xs8_6996": ("pond", 0),
"xs8_35ac": ("long ship", 0),
"xs8_178k8": ("twit", 0),
"xs8_25ak8": ("long barge", 0),
"xs8_312ko": ("canoe", 0),
"xs8_31248c": ("very long snake", 3),
"xs8_32qk": ("hook with tail", 4),
"xs9_4aar": ("hat", 0),
"xs9_31ego": ("integral sign", 0),
"xs9_25ako": ("very long boat", 0),
"xs9_178ko": ("trans boat with tail", 0),
"xs9_178kc": ("cis boat with tail", 2),
"xs9_312453": ("long shillelagh", 4),
"xs9_25a84c": ("tub with long tail", 4),
"xs9_g0g853z11": ("long canoe", 4),
"xs9_178426": ("long_hook_with_tail", 6),
"xs9_31248go": ("very very long Snake", 8),
"xs10_35ako": ("very long ship", 0),
"xs10_g8o652z01": ("boat-tie", 0),
"xs10_32qr": ("block on table", 1),
"xs10_178kk8": ("beehive with tail", 1),
"xs10_69ar": ("loop", 2),
"xs10_358gkc": ("cis-shillelagh", 3),
"xs10_0drz32": ("broken snake", 3),
"xs10_g0s252z11": ("integral with tub", 3),
"xs10_3542ac": ("long integral", 4),
"xs10_1784ko": ("claw with tail", 4),
"xs10_3215ac": ("boat with long tail", 4),
"xs10_ggka52z1": ("trans barge with tail", 5),
"xs10_g8ka52z01": ("very long barge", 5),
"xs10_0j96z32": ("?10?006", 6),
"xs10_0cp3z32": ("?10?007", 6),
"xs10_4al96": ("barge siamese loaf", 7),
"xs10_178ka4": ("cis-barge with tail", 7),
"xs10_31eg8o": ("?10?010", 7),
"xs10_xg853z321": ("very long canoe", 8),
"xs10_drz32": ("?10?004", 9),
"xs10_25a8426": ("?10?009", 10),
"xs10_ggka23z1": ("?10?008", 10),
"xs10_2eg853": ("?10?011", 11),
"xs10_1784213": ("?10?012", 11),
"xs10_wg853z65": ("very^3 long Snake", 14),
"xs11_g8o652z11": ("boat tie ship", 0),
"xs11_g0s453z11": ("elevener", 2),
"xs11_ggm952z1": ("trans loaf with tail", 3),
"xs11_69lic": ("loaf siamese loaf", 4),
"xs11_178jd": ("11-loop", 4),
"xs11_178kic": ("cis loaf with tail", 4),
"xs11_2530f9": ("cis boat and table", 5),
"xs11_ggka53z1": ("trans-longboat with tail", 5),
"xs11_g0s253z11": ("trans boat with nine", 5),
"xs11_2ege13": ("?11?011", 6),
"xs11_2560ui": ("trans-boat and table", 6),
"xs11_178b52": ("?11-boat bend tail", 6),
"xs11_3586246": ("[11-snake]", 6),
"xs11_g0s256z11": ("cis-boat with nine", 6),
"xs11_31e853": ("?11?015", 7),
"xs11_31461ac": ("?11?004", 7),
"xs11_g8ka52z11": ("very very long boat", 7),
"xs11_25icz65": ("?11?013", 8),
"xs11_178c4go": ("?11?002", 8),
"xs11_32132ac": ("?11?005", 8),
"xs11_256o8go": ("boat on snake", 8),
"xs11_354c826": ("?11?010", 8),
"xs11_69jzx56": ("?11?008", 8),
"xs11_08o652z32": ("boat on aircraft", 8),
"xs11_178ka6": ("cis-longboat with tail", 9),
"xs11_25a84ko": ("?11?014", 9),
"xs11_3542156": ("?11?018", 9),
"xs11_69jzx123": ("?11?017", 9),
"xs11_25akg8o": ("?11?016", 10),
"xs11_178c48c": ("?11?022", 10),
"xs11_0cp3z65": ("?11?029", 10),
"xs11_31eg84c": ("?11?007", 10),
"xs11_358gka4": ("?11?006", 10),
"xs11_4ai3zx123": ("?11?021", 10),
"xs11_17842ac": ("?11?020", 11),
"xs11_35a8426": ("?11?019", 11),
"xs11_0drz65": ("?11?027", 12),
"xs11_25iczx113": ("?11?025", 12),
"xs11_wg84213z65": ("very very long canoe", 12),
"xs11_0g0s252z121": ("?11?028", 12),
"xs11_g88a52z23": ("?11?030", 13),
"xs11_3215a8o": ("?11?031", 14),
"xs11_17842sg": ("?11?023", 15),
"xs11_03ia4z65": ("very_long_hook_with_tail", 15),
"xs11_321eg8o": ("005000680008000b0005", 17),
"xs11_xg853zca1": ("very^4 long Snake", 18),
"yl12_1_8_c7310da81295b6611e6e4e34a80a5523": ("pufferfish", 75),
"yl144_1_16_afb5f3db909e60548f086e22ee3353ac": ("block-laying switch engine", 16),
"yl384_1_59_7aeb1999980c43b4945fb7fcdb023326": ("glider-producing switch engine", 17),
"xp10_9hr": ("[HighLife] p10", 6),
"xp7_13090c8": ("[HighLife] p7", 9),
"xq48_07z8ca7zy1e531": ("[HighLife] bomber", 9),
"xq8_2je4": ("[2x2] crawler", 0),
"yl8_1_1_aae0a4678d7caeb6b463f7c082d8bd1a": ("crawler wick", 20),
"yl4_1_1_38bc1dca7a1fb43eaade7bc292acedb5": ("crawler double wick", 50),
"xs8_33cc": ("block-tie", 0),
"xs12_33cc33": ("tri-block I", 3),
"xs12_ggcc33z11": ("tri-block II", 5),
"xs16_ccjjcczw11": ("quad-block", 8),
"xs16_ciddiczw11": ("16_big_honeycomb", 1),
"xs18_04ak8a53z6521": ("?-backwards-unix-boats-18", 3),
"xs9_256oo": ("block-tie-boat", 2),
"xs10_33cko": ("block-tie-ship", 0),
"xs11_g8kc33z01": ("block-tie-long-boat", 4),
"xs12_g8kc33z11": ("block-tie-long-ship", 5),
"xs10_3146oo": ("block-tie-carrier", 7),
"xs11_33c453": ("block-tie-eater I", 8),
"xs11_ggkc33z1": ("block-tie-eater II", 10),
"xs10_3123cc": ("block-tie-snake", 8),
"xs13_33cj96": ("t-eater", 10),
"xs14_o4o7poz01": ("t-eater", 10),
"xs14_3p6o66zw1": ("t-eater", 10),
"xs15_32134bjo": ("t-eater", 10),
"xs15_3pq4og4c": ("t-eater", 10),
"xs15_4ap6ooz32": ("t-eater", 10),
"xs15_8k4o7pozw1": ("t-eater", 10),
"xs15_gbbk46z121": ("t-eater", 10),
"xs15_i5p6ooz11": ("t-eater", 10),
"xs18_rr44rr": ("t-eater", 10),
"xs15_32qjc33": ("t-eater+.1", 12),
"xs16_8ehla4z33": ("t-eater+.1", 12),
"xs16_3js3pm": ("t-eater+.2", 12),
"xs17_3js3qic": ("t-eater++", 14),
"xq5_27": ("t", 0),
"xq4_1ba4": ("ant", 0),
"xq3_4aar": ("hat ship", 6),
"xq2_4ear": ("hat ship", 6),
"xq4_36bsk": ("double glider", 5),
"xq4_xkgp47z247": ("triple glider", 16),
"xq6_2ju0uj2": ("mirrored 2c/6", 14),
"xq2_1fcgcf1": ("c/2 #1", 13),
"xq2_0ki313ikz12": ("c/2 #2", 26),
"xq2_2l4x4l2zw13231": ("c/2 #3", 26),
"xq2_1feice0ecief1": ("c/2 #4", 36),
"xq2_523a6m8zxl5dgz526233": ("c/2 #5", 42),
"xq4_v91e0e19v": ("symmetric 2c/4", 33),
"xq4_1b997zw8d99e": ("skew 2c/4", 39),
"xq5_227x4ee": ("double t", 13),
"xq5_72202a0ew8s": ("trans triple-t", 26),
"xq5_72202a0ew27": ("cis triple-t", 26),
"xq6_4rrwe465": ("2c/6 #1", 22),
"xq4_0g8e71f6cz707": ("c/4 #1", 30),
"xp4_69f": ("cap", 0),
"xp4_0diczpi62ac": ("cap variant I", 34),
"xp4_39c8a6z0f96zc93156": ("cap variant II", 34),
"xp4_9f0ciu": ("oscillating cap on table", 23),
"xp4_69f0ciu": ("oscillating cap on cap", 23),
"xp4_08o69fz321": ("cap on bookend", 22),
"xp4_g8o69fz121": ("cap on bun", 22),
"xp4_0ggca96z7w7": ("cap on R-loaf", 23),
"xp4_8g0si96zb43": ("cap on R-mango", 22),
"xp6_1e4278": ("[[tlife]] p6", 7),
"xp8_44hrrhrrh44": ("semi-octagon", 25),
"xp160_3v": ("[[tlife]] p160", 0),
"xs8_rr": ("bi-block", 0),
"xs10_660696": ("block-on-beehive", 0),
"xs9_253033": ("block-on-boat", 0),
"xs12_6960696": ("beehive-on-beehive", 0),
"xs11_2596066": ("block-on-loaf", 0),
"xp2_rbzw23": ("block-on-beacon", 5),
"xs12_3560653": ("cis-ship-on-ship", 0),
"xs10_2530352": ("cis-boat-on-boat", 0),
"xs12_6606996": ("block-on-pond", 0),
"xs11_2560696": ("boat-on-beehive", 0),
"xs13_25960696": ("loaf-on-beehive", 0),
"xs14_259606952": ("cis-loaf-on-loaf", 0),
"xs10_330356": ("block-on-ship", 0),
"xs14_69606996": ("beehive-on-pond", 0),
"xs11_25ac0cc": ("block-on-long-boat", 0),
"xs10_25606a4": ("trans-boat-on-boat", 0),
"xs16_699606996": ("pond-on-pond", 0),
"xs13_25ac0cic": ("beehive-on-longboat", 0),
"xs12_3560696": ("beehive-on-ship", 0),
"xs11_33032ac": ("tail block-on-eater", 0),
"xp2_2530318c": ("cis-beacon-on-boat", 8),
"xs12_25606952": ("cis-boat-on-loaf", 1),
"xs12_256069a4": ("trans-boat-on-loaf", 1),
"xp2_rbzw23033": ("cis-block-on-beacon-on-block", 9),
"xs13_25606996": ("boat-on-pond", 2),
"xs16_rr0rr": ("tetra-block I", 2),
"xs16_rrzmmz11": ("tetra-block II", 2),
"xs15_259606996": ("loaf-on-pond", 3),
"xs12_3303pm": ("head block-on-shillelagh", 3),
"xs14_25ac0ca52": ("cis-longboat-on-longboat", 3),
"xs12_rrz66": ("[[pseudo]] tri-block", 3),
"xp2_318c0cic": ("beehive-on-beacon", 11),
"xs14_2596069a4": ("trans-loaf-on-loaf", 3),
"xs12_66069ic": ("block-on-mango", 3),
"xs11_2530356": ("cis-boat-on-ship", 3),
"xs12_33035ac": ("block-on-long-ship", 3),
"xs11_25606ac": ("trans-boat-on-ship", 3),
#added on 20200820 when apgsearching B23n3/S23-q(leaplife)
"xp3_334oo":("[[leaplife]] p3",0),
"xp6_ehhhe":("[[leaplife]] p6",0)}
# First soup to contain a particular object:
alloccur = {}
# A tally of objects that have occurred during this run of apgsearch:
objectcounts = {}
# Any soups with positive scores, and the number of points.
soupscores = {}
# Temporary list of unidentified objects:
unids = []
# Things like glider guns and large oscillators belong here:
superunids = []
gridsize = 0
resets = 0
# For profiling purposes:
qlifetime = 0.0
ruletime = 0.0
gridtime = 0.0
# Increment object count by given value:
def incobject(self, obj, incval):
if (incval > 0):
if obj in self.objectcounts:
self.objectcounts[obj] = self.objectcounts[obj] + incval
else:
self.objectcounts[obj] = incval
# Increment soup score by given value:
def awardpoints(self, soupid, incval):
if (incval > 0):
if soupid in self.soupscores:
self.soupscores[soupid] = self.soupscores[soupid] + incval
else:
self.soupscores[soupid] = incval
# Increment soup score by appropriate value:
def awardpoints2(self, soupid, obj):
if obj in self.commonnames:
self.awardpoints(soupid, self.commonnames[obj][1])
if not (obj in self.alloccur):
self.alloccur[obj] = [soupid]
elif (obj[0] == 'y'):
# Save all soupids for growing patterns
self.alloccur[obj] += [soupid]
else:
if not (obj in self.alloccur):
self.alloccur[obj] = [soupid]
else:
self.alloccur[obj] += [soupid]
if (obj[0] == 'x'):
prefix = obj.split('_')[0]
prenum = int(prefix[2:])
if (obj[1] == 's'):
self.awardpoints(soupid, 10) # rare still-lifes are limited to 10 points
elif (obj[1] == 'p'):
if (prenum == 2):
self.awardpoints(soupid, 20) # p2 oscillators are limited to 20 points
elif ((prenum == 3) | (prenum == 4)):
self.awardpoints(soupid, 30) # p3 and p4 oscillators are limited to 30 points
else:
self.awardpoints(soupid, 40)
else:
self.awardpoints(soupid, 50)
else:
self.awardpoints(soupid, 50)
# Assuming the pattern has stabilised, perform a census:
def census(self, stepsize):
g.setrule("APG_CoalesceObjects_" + self.rg.alphanumeric)
g.setbase(2)
g.setstep(stepsize)
g.step()
# apgsearch theoretically supports up to 2^14 rules, whereas the Guy
# glider is only stable in 2^8 rules. Ensure that this is one of these
# rules by doing some basic Boolean arithmetic.
#
# This should be parsed as `gliders exist', not `glider sexist':
glidersexist = self.rg.ess[2] & self.rg.ess[3] & (not self.rg.ess[1]) & (not self.rg.ess[4])
glidersexist = glidersexist & (not (self.rg.bee[4] | self.rg.bee[5]))
if (glidersexist):
g.setrule("APG_IdentifyGliders")
g.setbase(2)
g.setstep(2)
g.step()
g.setrule("APG_ClassifyObjects_" + self.rg.alphanumeric)
g.setbase(2)
g.setstep(max(8, stepsize))
g.step()
# Only do this if we have an infinite-growth pattern:
if (stepsize > 8):
g.setrule("APG_HandlePlumes")
g.setbase(2)
g.setstep(1)
g.step()
g.setrule("APG_ClassifyObjects_" + self.rg.alphanumeric)
g.setstep(stepsize)
g.step()
# Remove any gliders:
if (glidersexist):
g.setrule("APG_ExpungeGliders")
g.run(1)
pop5 = int(g.getpop())
g.run(1)
pop6 = int(g.getpop())
self.incobject("xq4_153", (pop5 - pop6)/5)
# Remove any blocks, blinkers and beehives:
g.setrule("APG_ExpungeObjects")
pop0 = int(g.getpop())
g.run(1)
pop1 = int(g.getpop())
g.run(1)
pop2 = int(g.getpop())
g.run(1)
pop3 = int(g.getpop())
g.run(1)
pop4 = int(g.getpop())
# Blocks, blinkers and beehives removed by ExpungeObjects:
self.incobject("xs1_1", (pop0-pop1))
self.incobject("xs4_33", (pop1-pop2)/4)
self.incobject("xp2_7", (pop2-pop3)/5)
self.incobject("xs6_696", (pop3-pop4)/8)
# Removes an object incident with (ix, iy) and returns the cell list:
def grabobj(self, ix, iy):
allcells = [ix, iy, g.getcell(ix, iy)]
g.setcell(ix, iy, 0)
livecells = []
deadcells = []
marker = 0
ll = 3
while (marker < ll):
x = allcells[marker]
y = allcells[marker+1]
z = allcells[marker+2]
marker += 3
if ((z % 2) == 1):
livecells.append(x)
livecells.append(y)
else:
deadcells.append(x)
deadcells.append(y)
for nx in xrange(x - 1, x + 2):
for ny in xrange(y - 1, y + 2):
nz = g.getcell(nx, ny)
if (nz > 0):
allcells.append(nx)
allcells.append(ny)
allcells.append(nz)
g.setcell(nx, ny, 0)
ll += 3
return livecells
# Command to Grab, Remove and IDentify an OBJect:
def gridobj(self, ix, iy, gsize, gspacing, pos):
allcells = [ix, iy, g.getcell(ix, iy)]
g.setcell(ix, iy, 0)
livecells = []
deadcells = []
# This tacitly assumes the object is smaller than 1000-by-1000.
# But this is okay, since it is only used by the routing logic.
dleft = ix + 1000
dright = ix - 1000
dtop = iy + 1000
dbottom = iy - 1000
lleft = ix + 1000
lright = ix - 1000
ltop = iy + 1000
lbottom = iy - 1000
lpop = 0
dpop = 0
marker = 0
ll = 3
while (marker < ll):
x = allcells[marker]
y = allcells[marker+1]
z = allcells[marker+2]
marker += 3
if ((z % 2) == 1):
livecells.append(x)
livecells.append(y)
lleft = min(lleft, x)
lright = max(lright, x)
ltop = min(ltop, y)
lbottom = max(lbottom, y)
lpop += 1
else:
deadcells.append(x)
deadcells.append(y)
dleft = min(dleft, x)
dright = max(dright, x)
dtop = min(dtop, y)
dbottom = max(dbottom, y)
dpop += 1
for nx in xrange(x - 1, x + 2):
for ny in xrange(y - 1, y + 2):
nz = g.getcell(nx, ny)
if (nz > 0):
allcells.append(nx)
allcells.append(ny)
allcells.append(nz)
g.setcell(nx, ny, 0)
ll += 3
lwidth = max(0, 1 + lright - lleft)
lheight = max(0, 1 + lbottom - ltop)
dwidth = max(0, 1 + dright - dleft)
dheight = max(0, 1 + dbottom - dtop)
llength = max(lwidth, lheight)
lbreadth = min(lwidth, lheight)
dlength = max(dwidth, dheight)
dbreadth = min(dwidth, dheight)
self.gridsize = max(self.gridsize, llength)
objid = "unidentified"
bitstring = 0
if (lpop == 0):
objid = "nothing"
else:
if ((lwidth <= 7) & (lheight <= 7)):
for i in xrange(0, lpop*2, 2):
bitstring += (1 << ((livecells[i] - lleft) + 7*(livecells[i + 1] - ltop)))
if bitstring in self.cache:
objid = self.cache[bitstring]
if (objid == "unidentified"):
# This has passed through the routing logic without being identified,
# so save it in a temporary list for later identification:
self.unids.append(bitstring)
self.unids.append(livecells)
self.unids.append(lleft)
self.unids.append(ltop)
elif (objid != "nothing"):
# The object is non-empty, so add it to the census:
ux = int(0.5 + float(lleft)/float(gspacing))
uy = int(0.5 + float(ltop)/float(gspacing))
soupid = ux + (uy * gsize) + pos
for comp in self.decompositions[objid]:
self.incobject(comp, 1)
self.awardpoints2(soupid, comp)
# Tests for population periodicity:
def naivestab(self, period, security, length):
depth = 0
prevpop = 0
for i in xrange(length):
g.run(period)
currpop = int(g.getpop())
if (currpop == prevpop):
depth += 1
else:
depth = 0
prevpop = currpop
if (depth == security):
# Population is periodic.
return True
return False
# This should catch most short-lived soups with few gliders produced:
def naivestab2(self, period, length):
for i in xrange(length):
r = g.getrect()
if (len(r) == 0):
return True
pop0 = int(g.getpop())
g.run(period)
hash1 = g.hash(r)
pop1 = int(g.getpop())
g.run(period)
hash2 = g.hash(r)
pop2 = int(g.getpop())
if ((hash1 == hash2) & (pop0 == pop1) & (pop1 == pop2)):
if (g.getrect() == r):
return True
g.run((2*int(max(r[2], r[3])/period)+1)*period)
hash3 = g.hash(r)
pop3 = int(g.getpop())
if ((hash2 == hash3) & (pop2 == pop3)):
return True
return False
# Runs a pattern until stabilisation with a 99.99996% success rate.
# False positives are handled by a later error-correction stage.
def stabilise3(self):
# Phase I of stabilisation detection, designed to weed out patterns
# that stabilise into a cluster of low-period oscillators within
# about 6000 generations.
if (self.naivestab2(12, 10)):
return 4;
if (self.naivestab(12, 50, 200)):
return 4;
if (self.naivestab(60, 30, 200)):
return 6;
# Phase II of stabilisation detection, which is much more rigorous
# and based on oscar.py.
# Should be sufficient:
prect = [-2000, -2000, 4000, 4000]
# initialize lists
hashlist = [] # for pattern hash values
genlist = [] # corresponding generation counts
for j in xrange(4000):
g.run(30)
h = g.hash(prect)
# determine where to insert h into hashlist
pos = 0
listlen = len(hashlist)
while pos < listlen:
if h > hashlist[pos]:
pos += 1
elif h < hashlist[pos]:
# shorten lists and append info below
del hashlist[pos : listlen]
del genlist[pos : listlen]
break
else:
period = (int(g.getgen()) - genlist[pos])
prevpop = g.getpop()
for i in xrange(20):
g.run(period)
currpop = g.getpop()
if (currpop != prevpop):
period = max(period, 4000)
break
prevpop = currpop
return max(1 + int(math.log(period, 2)),3)
hashlist.insert(pos, h)
genlist.insert(pos, int(g.getgen()))
g.setalgo("HashLife")
g.setrule(self.rg.slashed)
g.setbase(2)
g.setstep(16)
g.step()
stepsize = 12
g.setalgo("QuickLife")
g.setrule(self.rg.slashed)
return 12
# Differs from oscar.py in that it detects absolute cycles, not eventual cycles.
def bijoscar(self, maxsteps):
initpop = int(g.getpop())
initrect = g.getrect()
if (len(initrect) == 0):
return 0
inithash = g.hash(initrect)
for i in xrange(maxsteps):
g.run(1)
if (int(g.getpop()) == initpop):
prect = g.getrect()
phash = g.hash(prect)
if (phash == inithash):
period = i + 1
if (prect == initrect):
return period
else:
return -period
return -1
# For a non-moving unidentified object, we check the dictionary of
# memoized decompositions of possibly-pseudo-objects. If the object is
# not already in the dictionary, it will be memoized.
#
# Low-period spaceships are also separated by this routine, although
# this is less important now that there is a more bespoke prodecure
# to handle disjoint unions of standard spaceships.
#
# @param moving a bool which specifies whether the object is moving
def enter_unid(self, unidname, soupid, moving):
if not(unidname in self.decompositions):
# Separate into pure components:
if (moving):
g.setrule("APG_CoalesceObjects_" + self.rg.alphanumeric)
g.setbase(2)
g.setstep(3)
g.step()
else:
pseudo_bangbang(self.rg.alphanumeric)
listoflists = [] # which incidentally don't contain themselves.
# Someone who plays the celllo:
celllist = g.join(g.getcells(g.getrect()), [0])
for i in xrange(0, len(celllist)-1, 3):
if (g.getcell(celllist[i], celllist[i+1]) != 0):
livecells = self.grabobj(celllist[i], celllist[i+1])
if (len(livecells) > 0):
listoflists.append(livecells)
listofobjs = []
for livecells in listoflists:
g.new("Subcomponent")
g.setalgo("QuickLife")
g.setrule(self.rg.slashed)
g.putcells(livecells)
period = self.bijoscar(1000)
canonised = canonise(abs(period))
if (period < 0):
listofobjs.append("xq"+str(0-period)+"_"+canonised)
elif (period == 1):
listofobjs.append("xs"+str(len(livecells)/2)+"_"+canonised)
else:
listofobjs.append("xp"+str(period)+"_"+canonised)
self.decompositions[unidname] = listofobjs
# Actually add to the census:
for comp in self.decompositions[unidname]:
self.incobject(comp, 1)
self.awardpoints2(soupid, comp)
# This function has lots of arguments (hence the name):
#
# @param gsize the square-root of the number of soups per page
# @param gspacing the minimum distance between centres of soups
# @param ashes a list of cell lists
# @param stepsize binary logarithm of amount of time to coalesce objects
# @param intergen binary logarithm of amount of time to run HashLife
# @param pos the index of the first soup on the page
def teenager(self, gsize, gspacing, ashes, stepsize, intergen, pos):
# For error-correction:
if (intergen > 0):
g.setalgo("HashLife")
g.setrule(self.rg.slashed)
# If this gets incremented, we panic and perform error-correction:
pathological = 0
# Draw the soups:
for i in xrange(gsize * gsize):
x = int(i % gsize)
y = int(i / gsize)
g.putcells(ashes[3*i], gspacing * x, gspacing * y)
# Because why not?
g.fit()
g.update()
# For error-correction:
if (intergen > 0):
g.setbase(2)
g.setstep(intergen)
g.step()
# Apply rules to coalesce objects and expunge annoyances such as
# blocks, blinkers, beehives and gliders:
start_time = time.clock()
self.census(stepsize)
end_time = time.clock()
self.ruletime += (end_time - start_time)
# Now begin identifying objects:
start_time = time.clock()
celllist = g.join(g.getcells(g.getrect()), [0])
if (len(celllist) > 2):
for i in xrange(0, len(celllist)-1, 3):
if (g.getcell(celllist[i], celllist[i+1]) != 0):
self.gridobj(celllist[i], celllist[i+1], gsize, gspacing, pos)
# If we have leftover unidentified objects, attempt to canonise them:
while (len(self.unids) > 0):
ux = int(0.5 + float(self.unids[-2])/float(gspacing))
uy = int(0.5 + float(self.unids[-1])/float(gspacing))
soupid = ux + (uy * gsize) + pos
unidname = self.process_unid()
if (unidname == "PATHOLOGICAL"):
pathological += 1
if (unidname != "nothing"):
if ((unidname[0] == 'U') & (unidname[1] == 'S') & (unidname[2] == 'S')):
# Union of standard spaceships:
countlist = unidname.split('_')
self.incobject("xq4_6frc", int(countlist[1]))
for i in xrange(int(countlist[1])):
self.awardpoints2(soupid, "xq4_6frc")
self.incobject("xq4_27dee6", int(countlist[2]))
for i in xrange(int(countlist[2])):
self.awardpoints2(soupid, "xq4_27dee6")
self.incobject("xq4_27deee6", int(countlist[3]))
for i in xrange(int(countlist[3])):
self.awardpoints2(soupid, "xq4_27deee6")
elif ((unidname[0] == 'x') & ((unidname[1] == 's') | (unidname[1] == 'p'))):
self.enter_unid(unidname, soupid, False)
else:
if ((unidname[0] == 'x') & (unidname[1] == 'q') & (unidname[3] == '_')):
# Separates low-period (<= 9) non-standard spaceships in medium proximity:
self.enter_unid(unidname, soupid, True)
else:
self.incobject(unidname, 1)
self.awardpoints2(soupid, unidname)
end_time = time.clock()
self.gridtime += (end_time - start_time)
return pathological
# This basically orchestrates everything:
def stabilise_soups_parallel(self, root, pos, gsize):
ashes = []
stepsize = 3
g.new("Random soups")
g.setalgo("QuickLife")
g.setrule(self.rg.slashed)
gspacing = 0
# Generate and run the soups until stabilisation:
for i in xrange(gsize * gsize):
# Generate the soup from the SHA-256 of the concatenation of the
# seed with the index:
blonksoup(root + str(pos + i))
# thesoup.put()
start_time = time.clock()
# Ensure glider hits soup or has passed through
g.run(1000)
# Run the soup until stabilisation:
stepsize = max(stepsize, self.stabilise3())
end_time = time.clock()
self.qlifetime += (end_time - start_time)
# Ironically, the spelling of this variable is incurrrect:
currrect = g.getrect()
ashes.append(g.getcells(currrect))
if (len(currrect) == 4):
ashes.append(currrect[0])
ashes.append(currrect[1])
# Choose the grid spacing based on the size of the ash:
gspacing = max(gspacing, 2*currrect[2])
gspacing = max(gspacing, 2*currrect[3])
g.select(currrect)
g.clear(0)
else:
# Stabilized soup is empty
ashes.append(0)
ashes.append(0)
self.incobject("xs1_0_diehard",1)
self.awardpoints2(pos + i, "xs1_0_diehard")
g.select([])
# Account for any extra enlargement caused by running CoalesceObjects:
gspacing += 2 ** (stepsize + 1) + 1000
# Remember the dictionary, just in case we have a pathological object:
prevdict = self.objectcounts.copy()
prevscores = self.soupscores.copy()
prevunids = self.superunids[:]
if (self.teenager(gsize, gspacing, ashes, stepsize, 0, pos) > 0):
if (self.skipErrorCorrection == False):
# Arrrggghhhh, there's a pathological object! Usually this means
# that naive stabilisation detection returned a false positive.
self.resets += 1
# Reset the object counts:
self.objectcounts = prevdict
self.soupscores = prevscores
self.superunids = prevunids
# 2^18 generations should suffice. This takes about 30 seconds in
# HashLife, but error-correction only occurs very infrequently, so
# this has a negligible impact on mean performance:
gspacing += 2 ** 19
stepsize = max(stepsize, 12)
# Clear the universe:
g.new("Error-correcting phase")
self.teenager(gsize, gspacing, ashes, stepsize, 18, pos)
# Erase any ashes. Not least because England usually loses...
ashes = []
# Pop the last unidentified object from the stack, and attempt to
# ascertain its period and classify it.
def process_unid(self):
g.new("Unidentified object")
g.setalgo("QuickLife")
g.setrule(self.rg.slashed)
y = self.unids.pop()
x = self.unids.pop()
livecells = self.unids.pop()
bitstring = self.unids.pop()
g.putcells(livecells, -x, -y, 1, 0, 0, 1, "or")
period = self.bijoscar(1000)
if (period == -1):
# Infinite growth pattern, probably. Most infinite-growth
# patterns are linear-growth (such as puffers, wickstretchers,
# guns etc.) so we analyse to see whether we have a linear-
# growth pattern:
descriptor = linearlyse(1000)
if (descriptor[0] == "y"):
return descriptor
# Okay, so it's not linear growth. It may be an unstabilised
# ember that slipped through the net, but this will be handled
# by error-correction (unless it persists another 2^18 gens,
# which is so unbelievably improbable that you are more likely
# to be picked up by a passing ship in the vacuum of space).
self.superunids.append(livecells)
self.superunids.append(x)
self.superunids.append(y)
return "PATHOLOGICAL"
elif (period == 0):
return "nothing"
else:
if (period == -4):
triple = countxwsses()
if (triple != (-1, -1, -1)):
# Union of Standard Spaceships:
return ("USS_" + str(triple[0]) + "_" + str(triple[1]) + "_" + str(triple[2]))
canonised = canonise(abs(period))
if (canonised == "#"):
# Okay, we know that it's an oscillator or spaceship with
# a non-astronomical period. But it's too large to canonise
# in any of its phases (i.e. transcends a 40-by-40 box).
self.superunids.append(livecells)
self.superunids.append(x)
self.superunids.append(y)
return "OVERSIZED"
else:
# Prepend a prefix according to whether it is a still-life,
# oscillator or moving object:
if (period == 1):
descriptor = ("xs"+str(len(livecells)/2)+"_"+canonised)
elif (period > 0):
descriptor = ("xp"+str(period)+"_"+canonised)
else:
descriptor = ("xq"+str(0-period)+"_"+canonised)
if (bitstring > 0):
self.cache[bitstring] = descriptor
return descriptor
# This doesn't really do much, since unids should be empty and
# actual pathological/oversized objects will rarely arise naturally.
def display_unids(self):
g.new("Unidentified objects")
g.setalgo("QuickLife")
g.setrule(self.rg.slashed)
rowlength = 1 + int(math.sqrt(len(self.superunids)/3))
for i in xrange(len(self.superunids)/3):
xpos = i % rowlength
ypos = int(i / rowlength)
g.putcells(self.superunids[3*i], xpos * (self.gridsize + 8) - self.superunids[3*i + 1], ypos * (self.gridsize + 8) - self.superunids[3*i + 2], 1, 0, 0, 1, "or")
g.fit()
g.update()
# Saves a machine-readable textual file containing the census:
def save_progress(self, numsoups, root):
g.show("Saving progress...")
# Count the total number of objects:
totobjs = 0
censustable = "@CENSUS TABLE\n"
tlist = sorted(self.objectcounts.iteritems(), key=operator.itemgetter(1), reverse=True)
for objname, count in tlist:
totobjs += count
censustable += objname + " " + str(count) +"\n"
g.show("Writing header information...")
# The MD5 hash of the root string:
md5root = hashlib.md5(root).hexdigest()
# Header information:
results = "@SEARCH blonksoup\n"
results += "@MD5 "+md5root+"\n"
results += "@ROOT "+root+"\n"
results += "@NUM_SOUPS "+str(numsoups)+"\n"
results += "@NUM_OBJECTS "+str(totobjs)+"\n"
results += "@RULE "+self.rg.slashed+"\n"
results += "\n"
# Census table:
results += censustable
g.show("Compactifying score table...")
results += "\n"
# Number of soups to record:
highscores = 100
results += "@TOP "+str(highscores)+"\n"
ilist = sorted(self.soupscores.iteritems(), key=operator.itemgetter(1), reverse=True)
# Empty the high score table:
self.soupscores = {}
for soupnum, score in ilist[:highscores]:
self.soupscores[soupnum] = score
results += str(soupnum) + " " + str(score) + "\n"
# all soupids for rare objects
results += "\n@SAMPLE_SOUPIDS\n"
rareobj = ""
for objname, count in tlist:
# blinkers and gliders have no alloccur[] entry for some reason,
# so the line below avoids errors in B3/S23, maybe other rules too?
if objname in self.alloccur:
if objname in self.commonnames:
results += objname + " "
for soup in self.alloccur[objname]: results+=root+str(soup)+" "
results=results[:-1]+"\n"
else:
rareobj += objname+" "+root+str(self.alloccur[objname][0])+"\n"
results += "\n@RARE_OBJ_SOUPIDS\n" + rareobj
g.show("Writing progress file...")
dirname = g.getdir("data")
separator = dirname[-1]
progresspath = dirname + "apgsearch" + separator + "progress" + separator
if not os.path.exists(progresspath):
os.makedirs(progresspath)
filename = progresspath + "search_" + md5root + ".txt"
try:
f = open(filename, 'w')
f.write(results)
f.close()
except:
g.warn("Unable to create progress file:\n" + filename)
# Save soup RLE:
def save_soup(self, root, soupnum):
# Soup pattern will be stored in a temporary directory:
souphash = hashlib.sha256(root + str(soupnum))
rlepath = souphash.hexdigest()
rlepath = g.getdir("temp") + rlepath + ".rle"
results = "<a href=\"open:" + rlepath + "\">"
results += root + str(soupnum)
results += "</a>"
prect = g.getrect()
if prect:
g.select(prect)
g.clear(0)
g.select([])
blonksoup(root + str(soupnum))
try:
g.save(rlepath, "rle")
except:
g.warn("Unable to create soup pattern:\n" + rlepath)
return results
# Display results in Help window:
def display_census(self, numsoups, root):
dirname = g.getdir("data")
separator = dirname[-1]
apgpath = dirname + "apgsearch" + separator
objectspath = apgpath + "objects" + separator + self.rg.alphanumeric + separator
if not os.path.exists(objectspath):
os.makedirs(objectspath)
# save_soup() builds the pattern in the current layer
g.new("Soup regeneration")
g.setrule(self.rg.slashed)
results = "<html>\n<title>Census results</title>\n<body bgcolor=\"#FFFFCE\">\n"
results += "<p>Soup search conducted in " + self.rg.slashed + " with blonksoup() soup generation.\n"
results += "<p>Census results after processing " + str(numsoups) + " soups:\n"
tlist = sorted(self.objectcounts.iteritems(), key=operator.itemgetter(1), reverse=True)
results += "<p><center>\n"
results += "<table cellspacing=1 border=2 cols=2>\n"
results += "<tr><td> Object </td><td align=center> Common name </td>\n"
results += "<td align=right> Count </td><td> First occurrence </td></tr>\n"
for objname, count in tlist:
if (objname[0] == 'x'):
if (objname[1] == 'p'):
results += "<tr bgcolor=\"#CECECF\">"
elif (objname[1] == 'q'):
results += "<tr bgcolor=\"#CEFFCE\">"
else:
results += "<tr>"
else:
results += "<tr bgcolor=\"#FFCECE\">"
results += "<td>"
results += " "
# Using "open:" link enables one to click on the object name to open the pattern in Golly:
rlepath = objectspath + objname + ".rle"
if (objname[0] == 'x'):
results += "<a href=\"open:" + rlepath + "\">"
# If the name is longer than that of the block-laying switch engine:
if len(objname) > 51:
# Contract name and include ellipsis:
results += objname[:40] + "…" + objname[-10:]
else:
results += objname
if (objname[0] == 'x'):
results += "</a>"
results += " "
if (objname[0] == 'x'):
# save object in rlepath if it doesn't exist
if not os.path.exists(rlepath):
# Canonised objects are at most 40-by-40:
rledata = "x = 40, y = 40, rule = " + self.rg.slashed + "\n"
# http://ferkeltongs.livejournal.com/15837.html
compact = objname.split('_')[1] + "z"
i = 0
strip = []
while (i < len(compact)):
c = ord2(compact[i])
if (c >= 0):
if (c < 32):
# Conventional character:
strip.append(c)
else:
if (c == 35):
# End of line:
if (len(strip) == 0):
strip.append(0)
for j in xrange(5):
for d in strip:
if ((d & (1 << j)) > 0):
rledata += "o"
else:
rledata += "b"
rledata += "$\n"
strip = []
else:
# Multispace character:
strip.append(0)
strip.append(0)
if (c >= 33):
strip.append(0)
if (c == 34):
strip.append(0)
i += 1
d = ord2(compact[i])
for j in xrange(d):
strip.append(0)
i += 1
# End of pattern representation:
rledata += "!\n"
try:
f = open(rlepath, 'w')
f.write(rledata)
f.close()
except:
g.warn("Unable to create object pattern:\n" + rlepath)
results += "</td><td align=center> "
if (objname in self.commonnames):
results += self.commonnames[objname][0]
results += " </td><td align=right> " + str(count) + " "
results += "</td><td>"
if objname in self.alloccur:
results += " "
for soup in self.alloccur[objname]:
results += self.save_soup(root, soup) + " "
results += "</td></tr>\n"
results += "</table>\n</center>\n"
ilist = sorted(self.soupscores.iteritems(), key=operator.itemgetter(1), reverse=True)
results += "<p><center>\n"
results += "<table cellspacing=1 border=2 cols=2>\n"
results += "<tr><td> Soup number </td><td align=right> Score </td></tr>\n"
for soupnum, score in ilist[:50]:
results += "<tr><td> "
results += self.save_soup(root, soupnum)
results += " </td><td align=right> " + str(score) + " </td></tr>\n"
results += "</table>\n</center>\n"
results += "</body>\n</html>\n"
htmlname = apgpath + "latest_census.html"
try:
f = open(htmlname, 'w')
f.write(results)
f.close()
g.open(htmlname)
except:
g.warn("Unable to create html file:\n" + htmlname)
# Converts a base-36 case-insensitive alphanumeric character into a
# numerical value.
def ord2(char):
x = ord(char)
if ((x >= 48) & (x < 58)):
return x - 48
if ((x >= 65) & (x < 91)):
return x - 55
if ((x >= 97) & (x < 123)):
return x - 87
return -1
# Obtain the parameters to conduct the search:
number = int(g.getstring("How many soups to search?", "1000000"))
rootstring = g.getstring("What seed to use for this search (make this unique)?", datetime.datetime.now().replace(microsecond=0).isoformat()+"_")
rulestring = g.getstring("Which rule to use?", "B3/S23")
blocknumber = int(g.getstring("How many blocks in a soup? (2-25)", "24"))-1
# initpos = int(g.getstring("Initial position: ", "0"))
initpos = 0
start_time = time.clock()
soup = Soup()
soup.rg.setrule(rulestring)
soup.rg.saveAllRules()
if (soup.rg.slashed == "B36/S23"):
soup.skipErrorCorrection = True
scount = 0
# We have a lot of soups per page, instead of one. This parallel approach
# was suggested by Tomas Rokicki, and results in approximately a
# fourfold increase in soup-searching speed!
sqrtspp = 10
spp = sqrtspp ** 2
# Do stuff repeatedly:
for i in xrange(int((number-1)/spp)+1):
soup.stabilise_soups_parallel(rootstring, scount + initpos, sqrtspp)
scount = spp*(i+1)
g.show(str(scount) + " soups processed ("+str(int(scount/(time.clock() - start_time)))+" per second) : (type 's' to see latest census or 'q' to quit).")
# Automatically save progress every 5000 soups:
if ((scount % 5000) == 0):
soup.save_progress(scount, rootstring)
event = g.getevent()
if event.startswith("key"):
evt, ch, mods = event.split()
if ch == "s":
g.setrule(soup.rg.slashed)
soup.save_progress(scount, rootstring)
soup.display_census(scount, rootstring)
elif ch == "q":
break
end_time = time.clock()
g.setrule(soup.rg.slashed)
soup.save_progress(scount, rootstring)
# Give the number of soups processed together with the amount of time
# elapsed (and indications as to which parts of the script are taking
# the longest).
g.show(str(scount) + " soups processed in " + str(end_time - start_time) +
"(" + str(soup.qlifetime) + ", " + str(soup.ruletime) + ", " + str(soup.gridtime) + ") secs.")
soup.display_census(scount, rootstring)
soup.display_unids()