The script itself is not very long but you will probably also want the gencols files that are in the gencols subdirectory.
I will try to explain how it works with a couple of examples from how I reduced the Crab synthesis recently.
First thing to do is supply a working example of the component you are interested. For the Crab the component in question came from A for Awesome. This is the input I give to the search script:
Code: Select all
x = 50, y = 9, rule = LifeHistory
2.2C2.C$2.3C.C$5.C2$3A3.2A36.6B$5.A2.A34.2BAB2AB$4.A2.A35.BA2BA2B$5.
2A36.2B2A3B$43.6B!
OK. Run the script with the above in your Golly window.
First of all direction can be S or SE. For this we want the reaction to approach the mango straight down and so we choose S. SE is the only other direction available. You must transform the input accordingly.
Next is to enter the delay. The delay is 4 in this case meaning that 4 ticks from the pattern on the left we get the pattern on the right. The script will report a result if all of the cells in green are on after 4 ticks and all of the cells in blue are off. I have chosen quite a modest blue area in this example because, well, I know there won't be any results otherwise ;). If the example component you have specified is not valid according to this criteria then you will be told so.
Next question is the mysterious "cell increase at penultimate step". For this I need to explain how cells are numbered by the script. If direction is S then moving one cell to the right increases the count by 1 and moving downwards by 1 cells increases the count by 1000. Thus the "maximum" cell in the "component" part of the reaction is the on cell that is closest to the mango. This cell serves as the "origin" and every pattern that perturbs the mango will be tested with its "maximum" cell in precisely the same location.
So for this example we set "cell increase at penultimate step" to 1. This means it is fine for any reaction cells to be one step to the left of the origin on the generation before the reaction cells reach the origin.
This is slightly on the generous side because clearly there is no guarantee that the reaction will avoid the blinker on previous generations. If you wanted to be more strict you could set "cell increase" to something like 100 which pretty much guarantees that the reaction cells will never have the same y-coordinate as the origin cell until the final step.
But running with cell increase = 1 works fine so we just go with that.
Next question is "Horizontal offset between before/after". The default is 40 which means that the output is 40 cells translated to the right from the input. I think 40 should nearly always be sufficient but you can increase this if you need to.
Next option is "final population". If you are lucky enough to be getting a tonne of results and only want to see results that have a certain population after 100 generations then you can enter that here. Presumably you are being greedy and are looking for a reaction that will accomplish the transformation and require no clean up :)
Next option is to enter the gencols filename that actually specifies where the new reactions will come from. In the gencols directory I have 4 files:
2g.col --- this is just SE glider meeets SW glider.
3g.col --- 2 SE gliders meet one SW glider. From memory the 2nd SE glider must hit the other two within 15 generations of the first collision and so this is in no way exhaustive.
3gb.col --- collisions between SE, SW and NE gliders. This file is fairly useless when direction is "S". I use this only when direction is "SE" and generally only as a last resort.
s2g.col --- a SE and SW glider hit a common object. I think the common object can be block, both orientations of beehive, both phases of blinker and pond.
We go for "3g" in this example. You shouldn't enter the ".col" suffix.
The final option is the symmetry to apply to the input reactions. This can be blank, x, d or xd. Or it can be default. If default is entered and you are using one of the standard 4 files mentioned above then it will automatically choose some sensible symmetries to apply. If x is selected then all input patterns are also tried under the transformation x <-> -x. If d is selected then all input patterns are also tried under the transformation (x,y) <-> (y,x).
That is all. Sadly it is not very fast. You press 'x' at any time to copy the latest results to the clipboard. The original layer remains untouched. Two new layers are created: a results layer and a work layer. If the search gets to the end then the work layer should be deleted.
Nothing that immediately works for a Crab synthesis is apparent from this run of the script. The result that inspired further tries was this one:
Code: Select all
x = 13, y = 23, rule = B3/S23
4bo$2bobo$3b2o2$11bo$10bo$10b3o$bo$2b2o$b2o10$3o3b2o$5bo2bo$4bo2bo$5b
2o!
EDIT by dvgrn: Here's a Python3-compatible version of spark_search.py, courtesy of Extrementhusiast. You'll still need to go get the gencols files from the repository linked above.
spark-search-Python3.py:
Code: Select all
import golly as g
from collections import deque
direction = g.getstring("Enter direction (S or SE):", "S").upper()
if direction not in ["S", "SE"]:
g.exit("Invalid direction")
delay = int(g.getstring("Enter delay:", "0"))
increase = int(g.getstring("Enter cell increase at penultimate step:", "1"))
offset = int(g.getstring("Enter horizontal offset between before/after:", "40"))
finalpop = int(g.getstring("Enter final population (-1 == don't care):", "-1"))
filename = g.getstring("Enter gencols filename:", "3g")
symm = g.getstring("Enter symmetries (x, d or xd):", "default")
if symm == "default":
symm = ""
if filename == "3g":
symm += "x"
if direction == "SE" and filename in ["2g", "3g", "s2g"]:
symm += "d"
status = "dir: %s; delay %d; inc %d; symm %s; " % (direction, delay, increase, symm)
if finalpop >= 0: status += "finalpop %d; " % finalpop
status += "cols %s; " % filename
g.getevent()
[a, b] = [1, 1000] if direction == "S" else [1000, 1001]
cells = g.getcells(g.getrect())
cells = list(zip(cells[::3], cells[1::3], cells[2::3]))
mx = -9999999
ox = oy = 0
for x, y, z in cells:
if z != 3:
continue
idx = a * x + b * y
if idx > mx:
mx = idx
ox = x
oy = y
on_cells = []
off_cells = []
example_cells = []
start_cells = []
for x, y, z in cells:
if x <= ox + offset // 2:
if z == 3:
example_cells.append(x - ox)
example_cells.append(y - oy)
elif z == 1:
start_cells.append(x - ox)
start_cells.append(y - oy)
else:
if z == 1:
on_cells.append((x - ox - offset, y - oy))
elif z == 2:
off_cells.append((x - ox - offset, y - oy))
results_layer = g.addlayer()
g.setname("Results")
g.setrule("Life")
g.setalgo("QuickLife")
work_layer = g.addlayer()
g.setname("Work area")
g.putcells(start_cells)
g.putcells(example_cells)
g.run(delay)
if not all(g.getcell(x, y) for x, y in on_cells) or any(g.getcell(x, y) for x, y in off_cells):
g.warn("Warning: the example pattern is not a solution")
results = 0
spacing = 100
def apply_sym(pat, symm):
yield pat
if "x" in symm: yield g.transform(pat, 0, 0, -1, 0, 0, 1)
if "d" in symm: yield g.transform(pat, 0, 0, 0, 1, 1, 0)
if "x" in symm and "d" in symm:
yield g.transform(pat, 0, 0, 1, -1, 0)
def testkey():
if results and g.getevent().startswith("key x"):
g.setlayer(results_layer)
g.select(g.getrect())
g.copy()
g.select([])
g.setlayer(work_layer)
def test(pat, gen, loc, x, y):
global results, spacing
if not all(g.getcell(x+loc, y) for x, y in on_cells):
return
if any(g.getcell(x+loc, y) for x, y in off_cells):
return
begin = start_cells + g.evolve(g.transform(pat, -x, -y), gen)
if finalpop >= 0 and len(g.evolve(begin, 100)) != 2 * finalpop:
return
results += 1
g.setlayer(results_layer)
g.putcells(g.evolve(pat, gen % 8), spacing * results-x, -y)
g.putcells(begin, spacing * results, spacing)
g.putcells(g.evolve(begin, delay), spacing * results, 2*spacing)
g.setlayer(work_layer)
count = 0
for s in open("gencols/" + filename + ".col"):
count += 1
pat = g.parse(s.split(" ")[0].replace('!','$').replace('.','b').replace('*','o')+'!')
if count % 100 == 0:
g.show(status + "results %d; <x> to copy to clipboard. count %d" % (results, count))
testkey()
for pat in apply_sym(pat, symm):
g.new('')
g.putcells(pat)
q = deque()
orect = [-128, -128, 256, 256]
all_max = -9999999
locs = set()
for gen in range(80 + delay):
cells = g.getcells(orect)
current_max = -9999999
for i in range(0, len(cells), 2):
idx = a * cells[i] + b * cells[i+1]
if idx > current_max:
current_max = idx
ox = cells[i]
oy = cells[i+1]
if gen > 0 and current_max >= all_max + increase:
loc = 256
while loc in locs:
loc += 256
locs.add(loc)
q.append((gen, loc, ox, oy))
g.putcells(cells, loc - ox, -oy)
g.putcells(start_cells, loc)
if q and q[0][0] + delay == gen:
test(pat, *q[0])
g.select([-128 + q[0][1], -128, 256, 256])
g.clear(0)
locs.remove(q[0][1])
q.popleft()
all_max = max(all_max, current_max)
g.run(1)
testkey()
g.dellayer()
g.setlayer(results_layer)
g.fit()