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]) # 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]) basex, basey,ticks,newobj=obj,obj,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,-newlist) if ticks>999: g.exit(obj + " in library has no reasonable repeat time.") stridex,stridey=newlist,newlist # 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"]=[ticks, stridex, stridey, 0, 0, baseobj, getenvelope(baseobj)] for t in range(1,ticks): newlist=list(basepat[t]) normalized=g.transform(newlist,-newlist,-newlist) odict[lib[i]+"_"+str(t)]=[ticks,stridex,stridey,newlist,newlist,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], coords[i] for k, v in odict.items(): clist = v # 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 # 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,y0-v]) 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] # odict[name+phase]=[ticks, stridex, stridey, dx, dy, clist, getenvelope(clist)] clist = lookup for i in range(0,len(clist),2): g.setcell(clist[i]+item+lookup,clist[i+1]+item+lookup,0) # now put them back one tick earlier... for item in rewindable: namephase=item 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, temp, temp # number of ticks until repeat newobj=odict[name+"_"+str(phase-1)] offsetx,offsety,clist=newobj,newobj,newobj 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+clist[i]+offsetx-dx,item+clist[i+1]+offsety-dy,1) # could easily use the envelope, newobj, 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...