Miscellaneous topics in Conway's Game of Life -- unfinished projects of all kinds and conditions

02 November 2006

Xlife #I format for Golly? Sort of...

I've added a couple of Python scripts, breeder.py and breeder-display.py, to a subsidiary folder in the Golly CVS system. These are by way of being translations of the original Xlife breeder.life -- one is short and fast, and the other is a slower on-screen walkthrough showing the breeder being incrementally constructed from smaller pieces.

I was hoping to be able to use breeder.life as a test case for code that would take *.life as input and produce one of these two types of .py script as output... but it's only _almost_ that simple.

The breeder.py script is a direct translation -- the numbers after each #I line show up pretty much unchanged in the Python script, and there's an easy glife conversion for the rotation and reflection specs:

0, 1 -> identity
1, 1 -> rcw
2, 1 -> flip
3, 1 -> rccw
0,-1 -> flip_y
1,-1 -> swap_xy
2,-1 -> flip_x
3,-1 -> swap_xy_flip

The only problem is that four subpatterns in breeder.life are defined with an 18-step time offset, like this:

#B ./breederrake.11
#I :breederpuffer 71 37 0 1 0
#I :ss.s 75 14 0 1 18
#I :ss.m 60 6 0 1 18
#I :ss.m 60 -8 0 -1 18
#E

Translated into English: "To make a breederrake11, put a breederpuffer at (71,37), run it for 18 generations, and then drop an LWSS at (75,14), an MWSS at (60,6), and a reflected MWSS at (60,-8)" [where breederpuffer, LWSS (ss.s), and MWSS (ss.m) are defined later in the file.]

This would all be very well and good, but here's the rub: the resulting breederrake11 subpattern is _not_ the 'flat' pattern resulting from running breederpuffer for 18 generations and adding the *WSSs. It's a true recursive definition -- which means that to get the right results in the final pattern, you have to rebuild each and every instance of breederrake11 by dropping a breederpuffer at the right relative location, waiting 18 ticks, and then adding the spaceships!

The glife system, on the other hand, generally deals with 'flat' subpatterns: if you give the above recipe for a breederrake11 and drop a copy of it into the Golly universe, what you'll see at t=0 in your final pattern is *generation 18* of breederpuffer!

The only safe way around this appears to be to "unpack" every definition that has a nonzero time value -- flatten out the nested definitions... and end up with a significantly longer recipe.

So in the end I cheated. In this specific case, it was easy enough to move the spaceships forward 9 cells to produce an equivalent breederrake11 that was all defined at t=0 -- no 18-tick time offset. The revised subpatterns were safe to use for the next level of definitions (this is not necessarily true, but it happened to be true in this case.)

The only remaining ugliness was the way that Xlife drops subpatterns into the universe, then runs the universe until it's time to drop in the next pattern -- instead of running each subpattern in isolation, then dropping it in when it has reached the right phase. Doing this in Python and storing the subpatterns in variables can require a lot of nested parentheses [or a long series of assignments to intermediate variables]:

flotilla = (((((((breederrake11(314,242)[4]
+ breederrake11(312,201,flip_y))[101]
+ breederrake00(368,286))[33]
+ breederrake10(359,158,flip_y))[306]
+ breederrake10(406,137,flip_y))[129]
+ breederrake01(349,273))[141]
+ breederrake10(428,336) + breederrake10(426,107,flip_y))[223]
+ breederrake00(473,371))[1]
+ breederrake00(472,71,flip_y)

The final result is a Python variable containing a complete breeder pattern.

--------------------------------------

The other way to build a breeder using the Xlife recipe is to use Golly's visible universe -- actually drop subpatterns in and run them. The second Python script, breeder-display.py, does this, in a way that lets you see the breeder pattern coming together:

breederrake11.put(314,242)
run(4)
breederrake11.put(312,201,flip_y)
run(101)
breederrake00.put(368,286)
run(33)
breederrake10.put(359,158,flip_y)
run(306)
breederrake10.put(406,137,flip_y)
run(129)
breederrake01.put(349,273)
run(141)
breederrake10.put(428,336)
breederrake10.put(426,107,flip_y)
run(223)
breederrake00.put(473,371)
run(1)
breederrake00.put(472,71,flip_y)

[if you look at what the script is actually doing, it's actually a bit more complicated than that, because it highlights the location of the next paste operation ahead of time -- but the above is the basic idea.]

--------------------------------------

In either case, the Xlife model doesn't seem to be the most efficient way of describing patterns compactly _or_ building them quickly -- my final target here is still a structured-format Caterpillar. The Xlife system essentially requires rebuilding every single copy of every single component pattern! It's possible to take shortcuts some of the time, but they're dangerous -- a pattern that Xlife can successfully load may blow up catastrophically if shortcuts are used.

Golly's normal method of defining new patterns in terms of transformed and rephased subpatterns, and then re-using the 'flat' results, seems to be more generally useful for recursive pattern definitions.