I ended up rewriting my old code almost completely, to make it easier to add new objects to the library. Not that it's really easy yet. Eventually the library will move to an editable INI file, probably. It also seems like a good idea to add keyboard shortcuts to rewind and fast-forward all target objects by increments other than just one tick backwards.
For example, maybe I should just keep the script running until [ESC] is pressed, and allow say an 'N' keypress to pop up a dialog box to ask for a number of ticks to move backward or forward... plus standard shortcuts for +1, -1, +16, -16, +256, -256, or something along those lines. Any size jump or series of jumps like this could be done relatively quickly once the recognition and registration step is completed.
EDIT: updated to v0.81 to fix recognition bug reported by Andrew --
Code: Select all
# glider-rewinder.py
# Version 0.81 -- alpha release
# Dave Greene, 29 May 2013 9pm
# - fixed a bug in getenvelope() that prevented recognition of closely-spaced gliders or *WSSes
import golly as g
import os
import itertools
from glife import *
RewinderINI = os.path.join(g.appdir() + "Scripts","glider-rewinder.ini")
lib=[["NW","bo$2o$obo!"],["NE","2o$b2o$o!"],["SE","obo$b2o$bo!"],["SW","2bo$2o$b2o!"],
["NL","bo$3o$ob2o$b3o$b2o!"],["EL","2b2o$2ob2o$4o$b2o!"],["SL","b2o$3o$2obo$b3o$2bo!"],["WL","2b2o$b4o$2ob2o$b2o!"],
["NM","bo$3o$ob2o$b3o$b3o$b2o!"],["EM","3b2o$3ob2o$5o$b3o!"],["SM","b2o$3o$3o$2obo$b3o$2bo!"],["WM","2b3o$b5o$2ob3o$b2o!"],
["NH","bo$3o$ob2o$b3o$b3o$b3o$b2o!"],["EH","4b2o$4ob2o$6o$b4o!"],["SH","b2o$3o$3o$3o$2obo$b3o$2bo!"],["WH","2b4o$b6o$2ob4o$b2o!"],
["B3","3o!"],["B8a","2o$o$3bo$2b2o!"],["B8b","2b2o$3bo$o$2o!"],["loaferWa","b2o4b2o$o2b2ob3o$bobo$2bo$8bo$6b3o$5bo$6b2o$7bo!"]]
def getenvelope(pat):
env=[]
if len(pat)%2:
g.exit("Must be a 2-state list.")
for i in range(0,len(pat)-1,2):
for x in range(-1,2):
for y in range(-1,2):
if abs(x)!=0 or abs(y)!=0: # add eight neighbors of each ON cell to the mask
if [pat[i]+x,pat[i+1]+y] not in env:
env.append([pat[i]+x,pat[i+1]+y])
for i in range(0,len(pat),2):
if [pat[i],pat[i+1]] in env:
env.remove([pat[i],pat[i+1]]) # take original pattern back out of mask
# else: # with the reduced envelope, this will now happen, e.g. with *WSS singleton sparks
# g.note("Technical error: " + str([pat[i],pat[i+1]])+ " not in envelope:" + str(env) + " \n\n" + str(pat))
return list(itertools.chain.from_iterable(env))
r=g.getselrect()
if len(r)==0:
r=g.getrect()
if len(r)==0: g.exit("No pattern, nothing to do.")
sel = g.getcells(r)
if len(sel)==0: g.exit("Nothing in selection.")
if len(sel)%2: g.exit("Can't do the rewinding trick on multistate rules.")
all = g.getcells(g.getrect())
allcoords=[]
for i in range(0,len(all),2):
allcoords.append([all[i],all[i+1]])
# g.show("Processing object library...")
odict=dict()
for i in range(len(lib)):
# g.show("Processing object " + lib[i][0])
# run and normalize each library object until a duplicate of the original pattern appears
# The number of ticks to duplication, and the offset, give all the information needed to rewind...
obj = g.parse(lib[i][1])
basex, basey,ticks,newobj=obj[0],obj[1],0,[]
baseobj = g.transform(obj,-basex,-basey)
basepat = pattern(baseobj) # use glife to avoid having to add a layer in Golly
while cmp(baseobj, newobj)!=0:
ticks+=1
newpat=basepat[ticks]
newlist = list(newpat)
newobj=g.transform(newpat,-newlist[0],-newlist[1])
if ticks>999:
g.exit(obj[0] + " in library has no reasonable repeat time.")
stridex,stridey=newlist[0],newlist[1]
# odict key is name+phase, and there's an entry for each phase of each object
# Contains list of repeat, stridex, stridey, dx, dy, clist, envclist.
# By convention, the first ON cell in phase 0 is at (0,0) and dx and dy are also 0,0.
# The first ON cell in other phases is also 0,0 but dx and dy will be altered appropriately.
odict[lib[i][0]+"_0"]=[ticks, stridex, stridey, 0, 0, baseobj, getenvelope(baseobj)]
for t in range(1,ticks):
newlist=list(basepat[t])
normalized=g.transform(newlist,-newlist[0],-newlist[1])
odict[lib[i][0]+"_"+str(t)]=[ticks,stridex,stridey,newlist[0],newlist[1],normalized,getenvelope(normalized)]
g.show("")
# make a list of coordinate pairs that might be Objects of Interest (gliders or LWSSes)
coords=[]
for i in range(0,len(sel),2):
coords.append([sel[i],sel[i+1]])
rewindable=[]
# now go through the selection and find all the recognizable patterns
i=0
while i<len(coords):
x0,y0=coords[i][0], coords[i][1]
for k, v in odict.items():
clist = v[5] # object cell list
match=1
for j in range(0,len(clist),2):
if [x0+clist[j],y0+clist[j+1]] not in coords:
match=0
break # a cell in this object is not in the selected pattern
if match:
envclist=v[6] # object envelope cell list
for j in range(0,len(envclist),2):
if [x0+envclist[j],y0+envclist[j+1]] in allcoords: # could use coords instead, but selection should be increased 2 cells all around
match=0
break # a cell in the surrounding must-be-OFF area is ON
if match:
# remove all recognized cells from coords
for j in range(0,len(clist),2):
crd=[x0+clist[j],y0+clist[j+1]]
if crd in coords:
coords.remove(crd)
else:
g.exit("A coordinate that should have been there wasn't: " + str(crd) + "\n\n" + str(coords))
rewindable.append([k,x0-v[3],y0-v[4]])
break # no need to go through rest of items in dictionary
if not match:
i+=1 # only increment index if current coord has not been removed due to a match
# at the end of this process, coords should be flattenable to a cell list containing all unrecognized stuff
g.setgen("-1")
# remove all rewindable items
for item in rewindable:
lookup=odict[item[0]]
# odict[name+phase]=[ticks, stridex, stridey, dx, dy, clist, getenvelope(clist)]
clist = lookup[5]
for i in range(0,len(clist),2):
g.setcell(clist[i]+item[1]+lookup[3],clist[i+1]+item[2]+lookup[4],0)
# now put them back one tick earlier...
for item in rewindable:
namephase=item[0]
ind=namephase.find("_")
name=namephase[:ind]
dx, dy=0,0
phase=int(namephase[ind+1:])
if phase==0:
temp=odict[namephase]
phase, dx, dy = temp[0], temp[1], temp[2] # number of ticks until repeat
newobj=odict[name+"_"+str(phase-1)]
offsetx,offsety,clist=newobj[3],newobj[4],newobj[5]
for i in range(0,len(clist),2):
# recorded location plus relative cell location plus offset minus stride-if-phase-has-cycled:
g.setcell(item[1]+clist[i]+offsetx-dx,item[2]+clist[i+1]+offsety-dy,1)
# could easily use the envelope, newobj[6], here to check for conflicts...
# but what exactly should I do if there's a conflict?
Code: Select all
#C sample pattern containing all object phases
#C that version 0.8 of glider-rewinder.py can recognize.
x = 217, y = 217, rule = B3/S23
107bobo$106bo$106bo3bo$106bo3bo$106bo$106bo2bo$106b3o5$107b2o$107b3o$
107b3o$107b3o$106bob2o$106b3o$107bo3$106bobo$109bo$105bo3bo$105bo3bo$
109bo$106bo2bo$107b3o5$107b2o$106b3o$106b3o$106b3o$106b2obo$107b3o$
108bo3$108bobo$107bo$107bo3bo$107bo$107bo2bo$107b3o5$108b2o$108b3o$
108b3o$107bob2o$107b3o$108bo3$106bobo$109bo$105bo3bo$109bo$106bo2bo$
107b3o5$107b2o$106b3o$106b3o$106b2obo$107b3o$108bo3$108bobo$107bo$107b
o$107bo2bo$107b3o5$108b2o$108b3o$107bob2o$107b3o$108bo$91bo$92b2o30bob
o$91b2o13bobo6bo8b2o$101b3o5bo5bo9bo$96bo12bo5bo5bo$94bobo9bo2bo10bo$
95b2o10b3o10b3o$89b2o$89b2o7bobo17bo9b2o$91b2o6b2o15b2o11bo$91b2o6bo
17b2o7bo$103bo3b2o17b2o$104bob3o4bo$102b3ob2obo3bobo$107b3o3b2o$42bo
65bo47bo36b2o$2b2o36bo3bo6b3o22bo2bo6b2o25b2o6bo2bo20b3o6bo3bo22b4o6bo
4bo3b2o8b6o$o4bo6b4o5b6o8b2o8bo4b5o4b5o7b2o7bo4b4o4b4o6b2o7b4o4bo7b2o
6b4o4b5o4bo8b2o7b5o4b6o4bo8b2ob4o4bo5bo$6bo4b6o3bo5bo4b4ob2o2bo4bo4b3o
b2o2bo4bo4b3ob2o2bo3bo4b2ob2o2bo3bo4b2ob2o5b2ob2o4bo3bo2b2ob2o4bo3bo2b
2ob3o4bo4bo2b2ob3o4bo4bo2b2ob4o4bo5bo3b6o4bo$o5bo4b4ob2o8bo4b6o4b5o7b
2o8bo4b5o4b4o6b2o7bo4b4o7b2o6b4o4b4o4bo7b2o7b5o4b5o4bo8b2o8b6o5b4o6bo
4bo$b6o8b2o3bo4bo6b4o22bo3bo6b3o20bo2bo6b2o25b2o6bo2bo22b3o6bo3bo36b2o
$22b2o36bo47bo65bo$102b2o3b3o$101bobo3bob2ob3o$58b2obo29b2o10bo4b3obo$
58b5o28b2o15b2o3bo$57bo5b2o24b2o7b2o17bo8b2o$58b7o24b2o8b2o15b2o8bo$
59b3o36bo17bobo10bo$64b3o61b2o$65b2o27b3o10b3o10b2o$62b2obo30bo10bo2bo
9bobo$62bo2bo29bo11bo12bo$63b2o26bo15bo$91b2o15bobo4bo8b2o$90bobo6b3o
13bo7b2o$115bo9bo$108bo$107b3o$106b2obo$106b3o$107b2o5$107b3o$106bo2bo
$109bo$109bo$106bobo3$108bo$107b3o$107bob2o$108b3o$108b3o$108b2o5$107b
3o$107bo2bo$107bo$107bo3bo$107bo$108bobo3$108bo$107b3o$106b2obo$106b3o
$106b3o$107b2o5$107b3o$106bo2bo$109bo$105bo3bo$109bo$106bobo3$108bo$
107b3o$107bob2o$108b3o$108b3o$108b3o$108b2o5$107b3o$107bo2bo$107bo$
107bo3bo$107bo3bo$107bo$108bobo3$109bo$108b3o$107b2obo$107b3o$107b3o$
107b3o$108b2o5$108b3o$107bo2bo$110bo$106bo3bo$106bo3bo$110bo$107bobo!
A similar thing happens, for example, with the loafer in the sample pattern. Eventually the loafer backs up against one of the beacons and stops. Notice that it continues to get rewound well beyond the last "safe" tick -- as long as it's recognizable and surrounded by a ring of OFF cells, it will continue to get removed and replaced with its predecessor.
--------------------------------
This is definitely an alpha release. The code is probably a bit painful to read, and the script is fairly slow -- partly because it's looking for lots of possible rewindable objects, partly because it re-processes the whole pattern after every -1 step backwards, and partly because it re-analyzes all the library objects on every run -- figures out the period, speed, direction, and safe zones all over again each time. Should run a bit faster if I cache those results somewhere.
I haven't tried this, but the script should work to rewind moving objects in other rules as well -- usually with a different library list, of course, and perhaps in some cases a few changes to getenvelope().
Suggestions and feature requests are welcome. I think the next release will be a beta, but it might not happen for a while...