Related to the optimization problems that have been coming up in the Construction practice thread recently --
The script that chris_c wrote to automatically produce "well-separated" synchronized salvos from a single input glider, needs a total of nine different mechanisms -- 180-degree (0|+4) color changers and color preservers (the "A" and "B" components, and 180-degree (0|1|2|3) adjusters (the "C" components) -- plus a single type of glider splitter.
This produces very workable but somewhat unwieldy "ladder" structures. The choice of H-to-2G splitter means that the ladder narrows toward the far end, and usually the limited space there constrains the width of the entire ladder. When the output gliders are feeding multiple edge shooters in a shotgun, you have to move the 180-degree pieces to their widest adjustment at the narrow end of the ladder, to allow the first constructed gliders enough time to catch up. That leaves a lot of space at the upper end.
It might work a lot better to switch to a different H-to-2G, choosing maybe the fastest possible 0-degree output that stays on the same diagonal as the input, more or less. Could be worth investigating that.
What I've been working on is a different idea. The two 180-degree reflectors are overkill -- you end up with two different places where you can move a reflector to adjust by +/-8N, where you really only need one. I think that a significantly more compact toolkit can be designed, using a variety of H-to-2G splitters, and making connections to the outputs with Snark chains and nothing else.
EDIT: Improved classifier script, works with two gliders in any orientation:
Code: Select all
# Hto2Gclassifier_v1.1.py
# -- given any two selected gliders,
# display the class of Herschel-to-2-glider converter
# that produces those two synchronized gliders,
# with the help of some number of Snark reflectors
import golly as g
equivdict={"NW":[0,0,0],"NE":[2,0,1],"SE":[0,1,1],"SW":[2,1,0]}
gliders=["NW0:3o$o$bo!","NW1:bo$2o$obo!","NW2:2o$obo$o!","NW3:b2o$2o$2bo!",
"NE0:b2o$obo$2bo!","NE1:2o$b2o$o!","NE2:3o$2bo$bo!","NE3:bo$b2o$obo!",
"SE0:bo$2bo$3o!","SE1:obo$b2o$bo!","SE2:2bo$obo$b2o!","SE3:o$b2o$2o!",
"SW0:o$obo$2o!","SW1:2bo$2o$b2o!","SW2:bo$o$3o!","SW3:obo$2o$bo!"]
gdict={}
for item in gliders:
name,rle = item.split(":")
gdict[name]=g.parse(rle)
def findglider(clist):
canonical = g.transform(clist,-clist[0],-clist[1])
canoncoords = zip(canonical[0::2],canonical[1::2])
for key in gdict:
glist = g.transform(gdict[key],-gdict[key][0],-gdict[key][1])
gcoords = zip(glist[0::2],glist[1::2])
match=1
for coord in gcoords:
if coord not in canoncoords:
match=0
break
if match==1:
for coord in gcoords: # remove the matching glider
canoncoords.remove(coord)
# return UL bounding box corner of matching glider
x, y = min(glist[0::2])+clist[0],min(glist[1::2])+clist[1]
dir,phase = key[:2],int(key[2])
outlist=[]
for a,b in canoncoords: outlist+=[a,b]
return [dir,phase,x,y],g.transform(outlist,clist[0],clist[1])
def findgliders(clist):
# check the top left cell in clist against the first cell in each gdict[key]
stats1,remainder=findglider(clist)
stats2,empty=findglider(remainder)
if empty!=[]: g.exit("Could not recognize gliders in selection.")
return stats1, stats2
# find an {outdir}-traveling glider equivalent to one with the statistics in statlist
def findcanonical(statslist,outdir):
dir,phase,x,y = statslist
dphase, dx, dy = equivdict[dir]
dphaseout, dxout, dyout = equivdict[outdir]
# this is really all two-state arithmetic, so + vs. - below doesn't actually matter
return [outdir, (phase+dphase-dphaseout)%4, x+dx-dxout, y+dy-dyout]
if g.numstates()!=2: g.exit("Please convert to standard two-state Life before running this script.")
clist = g.getcells(g.getselrect())
if len(clist)!=20: g.exit("Please select a rectangle containing the two gliders to be synchronized, and nothing else." \
+ "\nThere should be at least a four-cell gap between the gliders horizontally and vertically.")
while 1: # iterate until we find NW0
stats1, stats2 = findgliders(clist)
canonical1 = findcanonical(stats1,"NW")
canonical2 = findcanonical(stats2,"SE")
if canonical1[1]==0: break
clist = g.evolve(clist,1)
dir1,phase1,x1,y1 = canonical1
dir2,phase2,x2,y2 = canonical2
charx = "E" if (x1-x2)%2 else "O" # looks backwards -- the usual fencepost issue
chary = "e" if (y1-y2)%2 else "o" # (bounding box coords vs. # of cells in bounding box width)
rating = charx+chary+str(phase2)
clist = g.getcells(g.getselrect())
while 1: # iterate until we find NW0
stats1, stats2 = findgliders(clist)
stats2mod = stats2[:]
stats2mod[2]-=1024
stats2mod[3]-=1024 # shouldn't matter, but it seems to!
reversed1 = findcanonical(stats1,"SE")
reversed2 = findcanonical(stats2mod,"NW")
if reversed2[1]==0: break
clist = g.evolve(clist,1)
dirr1,phaser1,xr1,yr1 = reversed1
dirr2,phaser2,xr2,yr2 = reversed2
charrx = "E" if (xr1-xr2)%2 else "O"
charry = "e" if (yr1-yr2)%2 else "o"
ratingr = charrx+charry+str(phaser1)
g.note(rating + " if the first glider in the selection (reading from top down and left to right) is the reference glider\n" \
+ ratingr + " if the second glider is the reference glider (i.e., it will be connected to the NW output in the stamp collection)")
Code: Select all
# Hto2Gclassifier_v1.2.py
# -- given any two selected gliders,
# display the class of Herschel-to-2-glider converter
# that produces those two synchronized gliders,
# with the help of some number of Snark reflectors
# v1.2: Updated to work with Python 3.x, 20 October 2020
import golly as g
equivdict={"NW":[0,0,0],"NE":[2,0,1],"SE":[0,1,1],"SW":[2,1,0]}
gliders=["NW0:3o$o$bo!","NW1:bo$2o$obo!","NW2:2o$obo$o!","NW3:b2o$2o$2bo!",
"NE0:b2o$obo$2bo!","NE1:2o$b2o$o!","NE2:3o$2bo$bo!","NE3:bo$b2o$obo!",
"SE0:bo$2bo$3o!","SE1:obo$b2o$bo!","SE2:2bo$obo$b2o!","SE3:o$b2o$2o!",
"SW0:o$obo$2o!","SW1:2bo$2o$b2o!","SW2:bo$o$3o!","SW3:obo$2o$bo!"]
gdict={}
for item in gliders:
name,rle = item.split(":")
gdict[name]=g.parse(rle)
def findglider(clist):
canonical = g.transform(clist,-clist[0],-clist[1])
canoncoords = list(zip(canonical[0::2],canonical[1::2]))
for key in gdict:
glist = g.transform(gdict[key],-gdict[key][0],-gdict[key][1])
gcoords = list(zip(glist[0::2],glist[1::2]))
match=1
for coord in gcoords:
if coord not in canoncoords:
match=0
break
if match==1:
for coord in gcoords: # remove the matching glider
canoncoords.remove(coord)
# return UL bounding box corner of matching glider
x, y = min(glist[0::2])+clist[0],min(glist[1::2])+clist[1]
dir,phase = key[:2],int(key[2])
outlist=[]
for a,b in canoncoords: outlist+=[a,b]
return [dir,phase,x,y],g.transform(outlist,clist[0],clist[1])
def findgliders(clist):
# check the top left cell in clist against the first cell in each gdict[key]
stats1,remainder=findglider(clist)
stats2,empty=findglider(remainder)
if empty!=[]: g.exit("Could not recognize gliders in selection.")
return stats1, stats2
# find an {outdir}-traveling glider equivalent to one with the statistics in statlist
def findcanonical(statslist,outdir):
dir,phase,x,y = statslist
dphase, dx, dy = equivdict[dir]
dphaseout, dxout, dyout = equivdict[outdir]
# this is really all two-state arithmetic, so + vs. - below doesn't actually matter
return [outdir, (phase+dphase-dphaseout)%4, x+dx-dxout, y+dy-dyout]
if g.numstates()!=2: g.exit("Please convert to standard two-state Life before running this script.")
clist = g.getcells(g.getselrect())
if len(clist)!=20: g.exit("Please select a rectangle containing the two gliders to be synchronized, and nothing else." \
+ "\nThere should be at least a four-cell gap between the gliders horizontally and vertically.")
while 1: # iterate until we find NW0
stats1, stats2 = findgliders(clist)
canonical1 = findcanonical(stats1,"NW")
canonical2 = findcanonical(stats2,"SE")
if canonical1[1]==0: break
clist = g.evolve(clist,1)
dir1,phase1,x1,y1 = canonical1
dir2,phase2,x2,y2 = canonical2
charx = "E" if (x1-x2)%2 else "O" # looks backwards -- the usual fencepost issue
chary = "e" if (y1-y2)%2 else "o" # (bounding box coords vs. # of cells in bounding box width)
rating = charx+chary+str(phase2)
clist = g.getcells(g.getselrect())
while 1: # iterate until we find NW0
stats1, stats2 = findgliders(clist)
stats2mod = stats2[:]
stats2mod[2]-=1024
stats2mod[3]-=1024 # shouldn't matter, but it seems to!
reversed1 = findcanonical(stats1,"SE")
reversed2 = findcanonical(stats2mod,"NW")
if reversed2[1]==0: break
clist = g.evolve(clist,1)
dirr1,phaser1,xr1,yr1 = reversed1
dirr2,phaser2,xr2,yr2 = reversed2
charrx = "E" if (xr1-xr2)%2 else "O"
charry = "e" if (yr1-yr2)%2 else "o"
ratingr = charrx+charry+str(phaser1)
g.note(rating + " if the first glider in the selection (reading from top down and left to right) is the reference glider\n" \
+ ratingr + " if the second glider is the reference glider (i.e., it will be connected to the NW output in the stamp collection)")
Code: Select all
import golly as g
r=g.getrect()
if len(r)==0: g.exit("No H-to-2G pattern to classify.")
clist = g.getcells(r)
if len(clist)%2: g.exit("Use two-state rule B3/S23 to avoid voiding the warranty..")
g.run(4096)
r=g.getrect()
xmin, ymin, xmax, ymax = r[0], r[1], r[2]+r[0]-1,r[3]+r[1]-1
# the four cells below are always on in any phase of NW and SE gliders,
# if those gliders have reached the corners of the bounding box
# -- so this is only guaranteed to work if there are no other gliders in the pattern
# Or other spaceships, puffers, etc. Works as long as it's a H-to-2G NW and SE.
if g.getcell(xmin,ymin+1)==0 or g.getcell(xmin+1,ymin)==0 or g.getcell(xmax,ymax-1)==0 or g.getcell(xmax-1,ymax)==0:
g.exit("Please start with an H-to-2G, gliders pointing NW and SE.")
count = 0
while 1:
if count==4: g.exit("There's something weird in the NW corner.")
if g.getcell(xmin, ymin)==1 and g.getcell(xmin+1, ymin)==1 and g.getcell(xmin+2, ymin)==1 \
and g.getcell(xmin, ymin+1)==1 and g.getcell(xmin+1, ymin+2)==1: break
count+=1
g.run(1)
r=g.getrect()
xmin, ymin, xmax, ymax = r[0], r[1], r[2]+r[0]-1,r[3]+r[1]-1
idstring = "E" if r[2]%2==0 else "O"
idstring+="e" if r[3]%2==0 else "o"
# EXEY, EXOY, OXEY, OXOY
if g.getcell(xmax-1, ymax-2)==1: idstring+="0"
elif g.getcell(xmax-1, ymax-1)==1 and g.getcell(xmax, ymax-2)==1: idstring+="1"
elif g.getcell(xmax-2, ymax-1)==1: idstring+="2"
else: idstring+="3"
# could not be so lazy, and check all five cells in each case
# -- but what kind of weirdness would cause false positives here, really?
# g.setclipstr(idstring+" (count = "+str(count)+")")
g.show(idstring+" (count = "+str(count)+")")
There are sixteen distinct ways for output gliders to be produced in relation to each other, such that you can't convert from one class to another by adding Snarks. The script isn't set up to care how long it takes to produce the gliders from an input signal -- only the relative placement of the outputs matters.
I labeled the sixteen types [E|O][e|o][0|1|2|3], where the output gliders are reflected so they're traveling away from each other, one heading northwest and one southeast, and the NW glider is in 3o$o$bo! orientation.
- E = bounding box around the two gliders has even width
- O = bounding box around the two gliders has odd width
- e = bounding box around the two gliders has even height
- o = bounding box around the two gliders has odd height
- 0 = SE glider is bo$2bo$3o!
- 1 = SE glider is obo$b2o$bo!
- 2 = SE glider is 2bo$obo$b2o$
- 3 = SE glider is o$b2o$2o!
I chose this classification because it seemed easier to distinguish the classes with a just few manual steps in Golly:
- select two gliders that are moving away from each other, with one glider in the NW corner;
- hit 's' to shrink the bounding box;
- check width and height to get the E/O and e/o part (even vs. odd width and height respectively);
- look at the SE glider to decide the 0, 1, 2, or 3 part. 0 would be a 180-degree rotated copy of the NW glider.
Which glider you put in the NW corner depends on which side of the gliders you want your G-to-2G to be on. The script assumes that the source signal is coming from the W, SW, or S. If your Herschel is coming in from some other direction, either the output signals may have to cross output signals, or the output signals may have to cross each other. This is perfectly possible but often causes minor trouble with the circuit's repeat time.
An odd artifact of the classification scheme is that some H-to-2Gs are in the same class with their mirror images, but some change classes if you reflect them. Specifically:
Oe2 <--> Eo2
Ee1 <--> Eo3
Eo1 <--> Ee3
Oe1 <--> Oo3
Oo1 <--> Oe3
Even more complicated things happen with rotations. In some cases, a clockwise rotation of the H-to-2G doesn't change the class, but a counterclockwise rotation does. Other equivalence classes are invariant under both rotation and reflection. If this seems weird, just think about how weird gliders are -- it will all make sense eventually.
Here are the equivalence classes that are related to each other via rotation and reflection:
Code: Select all
Oo0 (never changes)
Oo1/Ee1/Oe3/Eo3
Oo2/Ee2
Oo3/Oe1
Oe0/Eo0
Oe1: see Oo3
Oe2/Eo2
Oe3: see Oo1
Eo0: see Oe0
Eo1/Ee3
Eo2: see Oe2
Eo3: see Oo1
Ee0 (never changes)
Ee1: see Oo1
Ee2: see Oo2
Ee3: see Eo1