ConwayLife.com - A community for Conway's Game of Life and related cellular automata
Home  •  LifeWiki  •  Forums  •  Download Golly

Golly scripts

For scripts to aid with computation or simulation in cellular automata.

Re: Golly scripts

Postby Andrew » October 10th, 2009, 8:17 pm

Eylrid wrote:I can't seem to get it to work for some reason.

PM 2Ring is using a very old version of Golly so some of his scripts don't work with newer versions (2.0 or later). Here is a fixed version:
# Golly Python script. Written by PM 2Ring, March 2009. Updated August 2009.
# Modified for Golly 2.0+ by Andrew Trevorrow.

''' Create a 'bitmap printer'.

    Print a bitmap in LWSSs, using the current selection as the bitmap source.
'''

import golly
from glife import *

def get_bitmap():
    ''' Get current selection & turn it into a bitmap. '''
    selrect = golly.getselrect()
    if len(selrect) == 0: golly.exit("There is no selection, aborting.")

    #Get bitmap size
    w, h = selrect[2:]
    #Adjust width, so it's in the form of 4m, m>1
    w = (w + 3) // 4 * 4
    w = max(8, w)

    #Initialize empty bitmap
    row = w * [0]
    bm = [row[:] for i in xrange(h)]

    #Populate bitmap with cell data
    u, v = selrect[:2]
    cells = golly.getcells(selrect)
    cellsxy = [(cells[i] - u, cells[i+1] - v) for i in xrange(0, len(cells), 2)]
    for x, y in cellsxy:
        bm[y][x] = 1
    return bm

def linemaker(loopm):
    ''' Create an empty LineMaker '''
    LM = pattern('''
34b2o$34bo$25b2o5bobo$24b3o5b2o$12bo8bob2o$12bobo6bo2bo$2o11bobo5bob2o
$2o11bo2bo7b3o21b2o$13bobo9b2o21b2o$12bobo$12bo9bo$23b2o$22b2o6$47b3o$
24b2o20bo3bo$24b2o19bo5bo$46bo3bo$47b3o$47b3o5$49b3o$44bo3b2ob2o$42b2o
4b2ob2o$35b2o6b2o3b5o$35b2o10b2o3b2o7$45b2o5b2o$45bo6bo$46b3o4b3o$48bo
6bo!''')
    LMTop = LM + pattern('2b2o$2bo$obo$2o!', 32, 7)
    LMBase = pattern('''
11bo$10bo$10b3o12$23b2o$23bobo$10bobo13bo$9bo2bo2b2o6bo2bo12b2o$8b2o5b
obo8bo11b3o$2o4b2o3bo3bo3bo3bobo9bob2o15bo$2o6b2o5b3ob2o2b2o10bo2bo8b
3o4bobo$9bo2bo3b2o17bob2o16bobo$10bobo25b3o2b2o2bo7bo2bo3b2o$39b2o2bo
3bo7bobo4b2o$44bo2bo6bobo$26bo17b2o8bo$24bobo$25b2o2$36bo$36bobo$36b2o
7$33bo7bo$32b4o5bobo$27b2o2bo2b2o8b2o4b2o$27b2o2b2o11b2o4b2o$16b2o6b2o
10bo7b2o$16b2o5b3o10bo4bobo$24b2o10bo4bo$27b2o$27b2o!''', 14, 29)
    LMBase += LM(55, 42, flip)
    return LMTop(8 + loopm, -21 - loopm) + LMBase

def dmprinter(pdy, copies=1):
    ''' Generate & display a dot matrix printer for named bitmap pattern '''
    #Horizontal pixel separation between LineMakers. minimum = 5
    LMsx = 5

    #Horizontal distance between bitmap pixels. Constant, due to LineMaker period
    pdx = 15

    #Distance between LineMakers
    LMdx, LMdy = -LMsx * pdx, pdy

    #Get bitmap pattern from current selection.
    bm = get_bitmap()

    #Make printer in a new layer
    golly.duplicate()
    golly.setoption("syncviews", False)

    #Build new window title from old
    title = golly.getname().split('.')[0]
    title = '%s_Printer [%d] v0.10' % (title, pdy)
    golly.new(title)

    #Make sure we're using the standard Life generation rule
    golly.setrule("B3/S23")

    #Bitmap dimensions. Width MUST be of form 4m, for m >=1
    bmheight = len(bm)
    bmwidth = len(bm[0])
    mid = (bmheight + 1) // 2

    loopw = (bmwidth - 8) // 4
    loopm = pdx * loopw

    #Gliders, heading north-east
    g0 = pattern('2bo$2o$b2o!')
    g1 = pattern('obo$2o$bo!')
    gliders = [g0, g1, g1(0, 2, rccw), g0(0, 2, rccw),
        g0(2, 2, flip), g1(2, 2, flip), g1(2, 0, rcw), g0(2, 0, rcw)]
       
    #Create full Glider loop.
    gg = []
    ox, oy = 35, 23
    for j in xrange(loopw + 1):
        dd = pdx * j
        gg += [gliders[0](ox + dd, oy - dd), gliders[1](ox + 8 + dd, oy - 7 - dd)]
    dd = loopm
    gg += [gliders[2](45 + dd, 4 - dd), gliders[3](37 + dd, -3 - dd)]

    ox, oy = 26 + loopm, -4 - loopm
    for j in xrange(loopw + 1):
        dd = pdx * j
        gg += [gliders[4](ox - dd, oy + dd), gliders[5](ox - 8 - dd, oy + 7 + dd)]
    dd = loopm
    gg += [gliders[6](16, 15), gliders[7](24, 22)]
    parity = 2*((loopw + 1)*(0, 0) + (1, 1))

    def buildLM():
        ''' Build a complete LineMaker '''
        #Populate glider loop. (jj, ii) are bitmap coords
        gloop = pattern()
        for j in xrange(bmwidth):
            jj = (j - delta + bmwidth - 1) % bmwidth

            #Is there a pixel at this location?
            if bm[ii][jj] == parity[j]:               
                gloop += gg[j]  #Add glider to the loop

        #Only put LineMaker if glider loop isn't empty
        if len(gloop) > 0:
            (LMBlank + gloop).put(Lx, Ly, trans)

    #Assemble blank LineMaker
    LMBlank = linemaker(loopm)

    #Do upper LineMakers
    trans = identity
    for i in xrange(mid):
        ii = mid - (i + 1)
        io = mid + (i + 1)
        Lx, Ly = io * LMdx, ii * LMdy
        delta = LMsx * io
        buildLM()

    #Do lower LineMakers
    trans = flip_y
    for i in xrange(mid, bmheight):
        ii = i
        io = i + 2
        Lx, Ly = io * LMdx + pdx, ii * LMdy + 128
        delta = LMsx * io - 1
        buildLM()

    #Eaters facing south-east & north-east
    eaterSE = pattern('2o$bo$bobo$2b2o!')
    eaterNE = eaterSE(0, 10, flip_y)
    eY = 59
    eaters = (eaterNE(0, eY), eaterSE(0, eY))
   
    #Eater horizontal displacement. Must be odd
    #bmwidth muliplier determines number of copies visible 'outside' printer.
    eX = (bmheight + 1) * LMdx - (copies * bmwidth - 1) * pdx   
    eX = 1 + eX // 2 * 2    #Adjust parity
    all = pattern()
    for i in xrange(bmheight):
        all += eaters[i % (1 + LMsx % 2)](eX, i * LMdy)
    all.put()   

    #Centre view over bitmap output area
    golly.setpos(str(-pdx * bmwidth // 2 + (bmheight - 1) * LMdx), str(Ly//2))
    #golly.setmag(-2)    #Aliasing effects happen at smaller scales than -2 :(
    golly.fit()

def main():
    #Vertical distance between pixels. Maybe get this from user. minimum = 16
    pdy = 16

    #Generate & display a dot matrix printer from current selection
    dmprinter(pdy, copies=1)

    golly.setcursor("Zoom In")
    golly.setalgo("HashLife")
    golly.setbase(2)
    golly.setstep(6)

main()
User avatar
Andrew
Moderator
 
Posts: 629
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: Golly scripts

Postby Nathaniel » October 10th, 2009, 10:46 pm

Eylrid wrote:I can't seem to get it to work for some reason.


When reporting difficulty with scripts, please let us know *what* doesn't work. For example: do you get an error message? If so, what's the message? If not, what happens? Does nothing at all happen? Etc.
User avatar
Nathaniel
Site Admin
 
Posts: 407
Joined: December 10th, 2008, 3:48 pm
Location: New Brunswick, Canada

Re: Golly scripts

Postby Eylrid » October 12th, 2009, 5:16 am

Andrew wrote:
Eylrid wrote:I can't seem to get it to work for some reason.

PM 2Ring is using a very old version of Golly so some of his scripts don't work with newer versions (2.0 or later). Here is a fixed version:
# Golly Python script. Written by PM 2Ring, March 2009. Updated August 2009.
# Modified for Golly 2.0+ by Andrew Trevorrow.

''' Create a 'bitmap printer'.

    Print a bitmap in LWSSs, using the current selection as the bitmap source.
'''

import golly
from glife import *

def get_bitmap():
    ''' Get current selection & turn it into a bitmap. '''
    selrect = golly.getselrect()
    if len(selrect) == 0: golly.exit("There is no selection, aborting.")

    #Get bitmap size
    w, h = selrect[2:]
    #Adjust width, so it's in the form of 4m, m>1
    w = (w + 3) // 4 * 4
    w = max(8, w)

    #Initialize empty bitmap
    row = w * [0]
    bm = [row[:] for i in xrange(h)]

    #Populate bitmap with cell data
    u, v = selrect[:2]
    cells = golly.getcells(selrect)
    cellsxy = [(cells[i] - u, cells[i+1] - v) for i in xrange(0, len(cells), 2)]
    for x, y in cellsxy:
        bm[y][x] = 1
    return bm

def linemaker(loopm):
    ''' Create an empty LineMaker '''
    LM = pattern('''
34b2o$34bo$25b2o5bobo$24b3o5b2o$12bo8bob2o$12bobo6bo2bo$2o11bobo5bob2o
$2o11bo2bo7b3o21b2o$13bobo9b2o21b2o$12bobo$12bo9bo$23b2o$22b2o6$47b3o$
24b2o20bo3bo$24b2o19bo5bo$46bo3bo$47b3o$47b3o5$49b3o$44bo3b2ob2o$42b2o
4b2ob2o$35b2o6b2o3b5o$35b2o10b2o3b2o7$45b2o5b2o$45bo6bo$46b3o4b3o$48bo
6bo!''')
    LMTop = LM + pattern('2b2o$2bo$obo$2o!', 32, 7)
    LMBase = pattern('''
11bo$10bo$10b3o12$23b2o$23bobo$10bobo13bo$9bo2bo2b2o6bo2bo12b2o$8b2o5b
obo8bo11b3o$2o4b2o3bo3bo3bo3bobo9bob2o15bo$2o6b2o5b3ob2o2b2o10bo2bo8b
3o4bobo$9bo2bo3b2o17bob2o16bobo$10bobo25b3o2b2o2bo7bo2bo3b2o$39b2o2bo
3bo7bobo4b2o$44bo2bo6bobo$26bo17b2o8bo$24bobo$25b2o2$36bo$36bobo$36b2o
7$33bo7bo$32b4o5bobo$27b2o2bo2b2o8b2o4b2o$27b2o2b2o11b2o4b2o$16b2o6b2o
10bo7b2o$16b2o5b3o10bo4bobo$24b2o10bo4bo$27b2o$27b2o!''', 14, 29)
    LMBase += LM(55, 42, flip)
    return LMTop(8 + loopm, -21 - loopm) + LMBase

def dmprinter(pdy, copies=1):
    ''' Generate & display a dot matrix printer for named bitmap pattern '''
    #Horizontal pixel separation between LineMakers. minimum = 5
    LMsx = 5

    #Horizontal distance between bitmap pixels. Constant, due to LineMaker period
    pdx = 15

    #Distance between LineMakers
    LMdx, LMdy = -LMsx * pdx, pdy

    #Get bitmap pattern from current selection.
    bm = get_bitmap()

    #Make printer in a new layer
    golly.duplicate()
    golly.setoption("syncviews", False)

    #Build new window title from old
    title = golly.getname().split('.')[0]
    title = '%s_Printer [%d] v0.10' % (title, pdy)
    golly.new(title)

    #Make sure we're using the standard Life generation rule
    golly.setrule("B3/S23")

    #Bitmap dimensions. Width MUST be of form 4m, for m >=1
    bmheight = len(bm)
    bmwidth = len(bm[0])
    mid = (bmheight + 1) // 2

    loopw = (bmwidth - 8) // 4
    loopm = pdx * loopw

    #Gliders, heading north-east
    g0 = pattern('2bo$2o$b2o!')
    g1 = pattern('obo$2o$bo!')
    gliders = [g0, g1, g1(0, 2, rccw), g0(0, 2, rccw),
        g0(2, 2, flip), g1(2, 2, flip), g1(2, 0, rcw), g0(2, 0, rcw)]
       
    #Create full Glider loop.
    gg = []
    ox, oy = 35, 23
    for j in xrange(loopw + 1):
        dd = pdx * j
        gg += [gliders[0](ox + dd, oy - dd), gliders[1](ox + 8 + dd, oy - 7 - dd)]
    dd = loopm
    gg += [gliders[2](45 + dd, 4 - dd), gliders[3](37 + dd, -3 - dd)]

    ox, oy = 26 + loopm, -4 - loopm
    for j in xrange(loopw + 1):
        dd = pdx * j
        gg += [gliders[4](ox - dd, oy + dd), gliders[5](ox - 8 - dd, oy + 7 + dd)]
    dd = loopm
    gg += [gliders[6](16, 15), gliders[7](24, 22)]
    parity = 2*((loopw + 1)*(0, 0) + (1, 1))

    def buildLM():
        ''' Build a complete LineMaker '''
        #Populate glider loop. (jj, ii) are bitmap coords
        gloop = pattern()
        for j in xrange(bmwidth):
            jj = (j - delta + bmwidth - 1) % bmwidth

            #Is there a pixel at this location?
            if bm[ii][jj] == parity[j]:               
                gloop += gg[j]  #Add glider to the loop

        #Only put LineMaker if glider loop isn't empty
        if len(gloop) > 0:
            (LMBlank + gloop).put(Lx, Ly, trans)

    #Assemble blank LineMaker
    LMBlank = linemaker(loopm)

    #Do upper LineMakers
    trans = identity
    for i in xrange(mid):
        ii = mid - (i + 1)
        io = mid + (i + 1)
        Lx, Ly = io * LMdx, ii * LMdy
        delta = LMsx * io
        buildLM()

    #Do lower LineMakers
    trans = flip_y
    for i in xrange(mid, bmheight):
        ii = i
        io = i + 2
        Lx, Ly = io * LMdx + pdx, ii * LMdy + 128
        delta = LMsx * io - 1
        buildLM()

    #Eaters facing south-east & north-east
    eaterSE = pattern('2o$bo$bobo$2b2o!')
    eaterNE = eaterSE(0, 10, flip_y)
    eY = 59
    eaters = (eaterNE(0, eY), eaterSE(0, eY))
   
    #Eater horizontal displacement. Must be odd
    #bmwidth muliplier determines number of copies visible 'outside' printer.
    eX = (bmheight + 1) * LMdx - (copies * bmwidth - 1) * pdx   
    eX = 1 + eX // 2 * 2    #Adjust parity
    all = pattern()
    for i in xrange(bmheight):
        all += eaters[i % (1 + LMsx % 2)](eX, i * LMdy)
    all.put()   

    #Centre view over bitmap output area
    golly.setpos(str(-pdx * bmwidth // 2 + (bmheight - 1) * LMdx), str(Ly//2))
    #golly.setmag(-2)    #Aliasing effects happen at smaller scales than -2 :(
    golly.fit()

def main():
    #Vertical distance between pixels. Maybe get this from user. minimum = 16
    pdy = 16

    #Generate & display a dot matrix printer from current selection
    dmprinter(pdy, copies=1)

    golly.setcursor("Zoom In")
    golly.setalgo("HashLife")
    golly.setbase(2)
    golly.setstep(6)

main()


Thankyou! That is really cool!

Nathaniel wrote:
Eylrid wrote:I can't seem to get it to work for some reason.


When reporting difficulty with scripts, please let us know *what* doesn't work. For example: do you get an error message? If so, what's the message? If not, what happens? Does nothing at all happen? Etc.


Will do. (That one wasn't doing anything at all.)
Eylrid
 
Posts: 30
Joined: October 8th, 2009, 10:28 pm

Re: Golly scripts

Postby PM 2Ring » October 17th, 2009, 6:25 am

Eylrid wrote:Thankyou! That is really cool!

I'm glad you like it, Eylrid.

Some of my scripts print error messages to the console, so it can be useful when running my scripts to launch Golly from a terminal, rather than via the icon.
User avatar
PM 2Ring
 
Posts: 152
Joined: March 26th, 2009, 11:18 am

Re: Golly scripts

Postby Andrew » October 19th, 2009, 6:26 pm

I've added some new Python scripts to the Golly Scripts Database:

fast-duplicate.py -- Duplicates the current pattern and settings in a new layer, but doesn't copy the undo/redo history. Much faster than the Duplicate Layer menu command, especially for very large patterns.

save-as-mc.py -- Golly's Save Pattern dialog always uses .rle as the default format, so this script brings up the same dialog but with .mc as the default format. Works with all algorithms except QuickLife.

save-image.py -- Saves the current selection or pattern in a specified image file (.png/.bmp/.gif/.tif/.jpg). Requires the Python Imaging Library.

The new scripts all require Golly 2.1, and you can download them via Help > Online Archives.
User avatar
Andrew
Moderator
 
Posts: 629
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: Golly scripts

Postby PM 2Ring » October 19th, 2009, 9:48 pm

Andrew,

Is there a Golly Python command to select the whole current pattern, like the Edit/Select All menu? Currently, I use
golly.select([-maxint//2, -maxint//2, maxint, maxint]); golly.shrink()
Is there a better way?
User avatar
PM 2Ring
 
Posts: 152
Joined: March 26th, 2009, 11:18 am

Re: Golly scripts

Postby Andrew » October 19th, 2009, 11:20 pm

PM 2Ring wrote:Is there a Golly Python command to select the whole current pattern, like the Edit/Select All menu?

Yes, just do golly.select(golly.getrect()).
User avatar
Andrew
Moderator
 
Posts: 629
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: Golly scripts

Postby PM 2Ring » October 20th, 2009, 9:45 pm

Andrew wrote:
PM 2Ring wrote:Is there a Golly Python command to select the whole current pattern, like the Edit/Select All menu?

Yes, just do golly.select(golly.getrect()).

Ah, thanks, Andrew! I must be going blind. :)
User avatar
PM 2Ring
 
Posts: 152
Joined: March 26th, 2009, 11:18 am

Re: Golly scripts

Postby PM 2Ring » October 25th, 2009, 6:27 am

LWSS & MWSS maker

# Golly python script.
# Written by PM 2Ring, October 2009

''' Create a LWSS or MWSS spaceship gun

The spaceship gun is created from the currently loaded pattern, which is assumed
to be some sort of glider gun. The pattern is built in the current layer.
The gun's glider must be about to leave the guns bounding box.
'''

import golly
from glife import *

def toxy(cells):
    ''' Convert cell list to a sorted list of (x, y) tuples '''
    xy = zip(cells[::2], cells[1::2])
    xy.sort()
    return xy
           
def MakeSSGun(sstype):
    ''' Make a LWSS or MWSS gun '''
   
    #Set up glider identification stuff
    #Glider heading SE _\
    glider = pattern('bo$2bo$3o!')
   
    #Glider directions & associated transforms
    xforms = {'SE':identity, 'SW':flip_x, 'NE':flip_y, 'NW':flip}
   
    #Glider transformed by j with phase i, as a tuple-ised cell list
    def gliderxy(j, i):
        g = toxy(list(glider[i].apply(j)))
        #Adjust origin to top left corner
        ox, oy = g[0][0], min([i[1] for i in g])
        return sum([(x-ox, y-oy) for x,y in g], ())
   
    #All 16 gliders, with direction string & phase
    glidict = dict([(gliderxy(j, i), (k, i))
        for k,j in xforms.iteritems() for i in xrange(4)])
   
    #Select entire pattern
    selrect = golly.getrect()
    if len(selrect) == 0: golly.exit("No pattern!")
    gun0 = pattern(golly.getcells(selrect))

    #Emit one glider, to act as a positioning key
    golly.run(16)
    golly.select(selrect)
    golly.clear(0)

    #Find the "key" glider
    selrect = golly.getrect()
    if len(selrect) == 0: golly.exit("No glider!")

    #Get the glider bounding box & cell data
    ox, oy = selrect[:2]
    cells = golly.getcells(selrect)
   
    #Convert cell list to a tuple & make top left cell the glider's origin   
    cells = sum([(x-ox, y-oy) for x,y in toxy(cells)], ())
   
    #Determine glider direction & current phase
    direction, phase = glidict[cells]
    xform = xforms[direction]
    dx, dy = xform[::3]
    golly.show('Direction=%s, Phase=%d' % (direction, phase))

    #Reorient gun if the key glider isn't heading SE   
    if direction != 'SE':
        ox += 1 - dx
        oy += 1 - dy     
        gun0 = gun0.apply(xform)
   
    #Move the gun's origin to the new origin of the key glider
    ox -= 4 * dx
    oy -= 4 * dy   
    gun0 = gun0(-ox * dx, -oy * dy)
    gun0bb = getminbox(gun0)

    #Determine minimum glider gun separation
    mx, my = gun0bb[0] + gun0bb[2], gun0bb[1] + gun0bb[3]   
    d = max(0, mx, my)
    #print gun0bb, mx, my, 'd =', d

    #Phase based adjustment for gun2 & eater   
    gx = (phase==0 or phase==3) and -2 or 0
           
    #An eater to catch the SS stream
    eater = pattern('2b2o$3bo$3o$o!', gx//2 - 3, 0)

    #Adjust the glider guns' orientation, phase & position     
    if sstype == 'L':
        #LWSS gun. Min period=23
        #Bottom left gun
        gun1 = gun0(-4, 4, flip_y)
   
        #Top right gun. Horizontal position depends on phase of initial glider
        gun2 = gun0[1](5 + gx, 4, flip_x)       
    else:   #sstype == 'M'
        #MWSS gun. Min period=44
        #Bottom left gun. Shifted SW by d1 units, to allow MWSS to pass safely
        d1 = 3   
        gun1 = gun0[4*d1 + 3](-1 - d1, 7 + d1, flip_y)
       
        #Top right gun. Horizontal position depends on phase of initial glider
        gun2 = gun0(5 + gx, -3, flip_x)       
        eater = eater(0, 1)

    #Clear the layer
    golly.new(sstype + 'WSS' + golly.getname())
     
    #Build the SS gun.
    all = gun0(-d, -d) + gun1(-d, d) + gun2(d, -d)
    all += eater(0, (2*d + gun0bb[3])//2*2)
    all.put()   
    golly.fit()

    #Select the "key" glider of gun0   
    #golly.select([-d, -d, 3, 3])   
       
def main():
    #Get input data
    prompt = 'Enter M or L (default) to select spaceship type:'
    sstype = getstring(prompt).upper() == 'M' and 'M' or 'L'
    MakeSSGun(sstype)

if __name__ == '__main__':
    main()
User avatar
PM 2Ring
 
Posts: 152
Joined: March 26th, 2009, 11:18 am

Re: Golly scripts

Postby Axaj » October 25th, 2009, 11:14 am

Can someone who knows how to script make one for lifecolor to convert all cells that are one color to another?
Image
Axaj
 
Posts: 232
Joined: September 26th, 2009, 12:23 am

Re: Golly scripts

Postby Andrew » October 26th, 2009, 1:06 am

Axaj wrote:Can someone who knows how to script make one for lifecolor to convert all cells that are one color to another?

The following script should do the job. I've also added it to the Golly Scripts Database.

change-state.py
# Change all cells from one given state to another given state.
# Works in any rule with more than 2 states.
# Author: Andrew Trevorrow (andrew@trevorrow.com), Oct 2009.

import golly as g
from glife import rect
from time import time

r = rect( g.getrect() )
if r.empty: g.exit("There is no pattern.")

maxstate = g.numstates() - 1
if maxstate == 1: g.exit("The current rule only has 2 states.")

result = g.getstring(
"Change all the cells in one state (the 1st number)\n" +
"to a different state (the 2nd number) where both\n" +
"numbers must be from 0 to " + str(maxstate) + ":",
"1 2", "Change state")

try:
   s1, s2 = result.split()
except:
   g.exit("Enter 2 numbers separated by a space.")
try:
   fromstate = int(s1)
except:
   g.exit("1st state is not a number: " + s1)
try:
   tostate = int(s2)
except:
   g.exit("2nd state is not a number: " + s2)
if fromstate < 0 or fromstate > maxstate:
   g.exit("1st state is out of range: " + s1)
if tostate < 0 or tostate > maxstate:
   g.exit("2nd state is out of range: " + s2)
if fromstate == tostate:
   g.exit("The given states are the same.")

changed = 0
total = float(r.wd) * float(r.ht)
oldsecs = time()
for row in xrange(r.top, r.top + r.height):
   # might be large pattern so show percentage done each second
   newsecs = time()
   if newsecs - oldsecs >= 1.0:
      oldsecs = newsecs
      g.show("Changing cell states: " +
             str(int(100 * (row - r.top) * r.wd / total)) + "%")
      g.update()

   # allow keyboard interaction
   g.dokey( g.getkey() )

   for col in xrange(r.left, r.left + r.width):
      if g.getcell(col, row) == fromstate:
         g.setcell(col, row, tostate)
         changed += 1

if changed == 0:
   # better to beep in this case
   g.exit("There are no cells in state %d." % fromstate)
else:
   g.show("%d cells were changed from state %d to state %d."
          % (changed, fromstate, tostate))
User avatar
Andrew
Moderator
 
Posts: 629
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: Golly scripts

Postby PM 2Ring » October 26th, 2009, 3:29 am

Andrew wrote:PM 2Ring is using a very old version of Golly so some of his scripts don't work with newer versions (2.0 or later). Here is a fixed version:

Thanks very much for fixing that, Andrew. I really would like to run the latest version, and take advantage of undo/redo (and the new scripting features) but my distro's too old. I just tried running Golly 2.1, but it complains that I'm missing libgio. I guess I really need to get a new distro. (apt-get won't update or upgrade my system - it says I don't have enough memory.)
User avatar
PM 2Ring
 
Posts: 152
Joined: March 26th, 2009, 11:18 am

Re: Golly scripts

Postby Axaj » October 26th, 2009, 6:23 pm

Andrew wrote:
Axaj wrote:Can someone who knows how to script make one for lifecolor to convert all cells that are one color to another?

The following script should do the job. I've also added it to the Golly Scripts Database.

change-state.py
# Change all cells from one given state to another given state.
# Works in any rule with more than 2 states.
# Author: Andrew Trevorrow (andrew@trevorrow.com), Oct 2009.

import golly as g
from glife import rect
from time import time

r = rect( g.getrect() )
if r.empty: g.exit("There is no pattern.")

maxstate = g.numstates() - 1
if maxstate == 1: g.exit("The current rule only has 2 states.")

result = g.getstring(
"Change all the cells in one state (the 1st number)\n" +
"to a different state (the 2nd number) where both\n" +
"numbers must be from 0 to " + str(maxstate) + ":",
"1 2", "Change state")

try:
   s1, s2 = result.split()
except:
   g.exit("Enter 2 numbers separated by a space.")
try:
   fromstate = int(s1)
except:
   g.exit("1st state is not a number: " + s1)
try:
   tostate = int(s2)
except:
   g.exit("2nd state is not a number: " + s2)
if fromstate < 0 or fromstate > maxstate:
   g.exit("1st state is out of range: " + s1)
if tostate < 0 or tostate > maxstate:
   g.exit("2nd state is out of range: " + s2)
if fromstate == tostate:
   g.exit("The given states are the same.")

changed = 0
total = float(r.wd) * float(r.ht)
oldsecs = time()
for row in xrange(r.top, r.top + r.height):
   # might be large pattern so show percentage done each second
   newsecs = time()
   if newsecs - oldsecs >= 1.0:
      oldsecs = newsecs
      g.show("Changing cell states: " +
             str(int(100 * (row - r.top) * r.wd / total)) + "%")
      g.update()

   # allow keyboard interaction
   g.dokey( g.getkey() )

   for col in xrange(r.left, r.left + r.width):
      if g.getcell(col, row) == fromstate:
         g.setcell(col, row, tostate)
         changed += 1

if changed == 0:
   # better to beep in this case
   g.exit("There are no cells in state %d." % fromstate)
else:
   g.show("%d cells were changed from state %d to state %d."
          % (changed, fromstate, tostate))


Thanks a ton! Do you think you could change it to do only the current selection?
Image
Axaj
 
Posts: 232
Joined: September 26th, 2009, 12:23 am

Re: Golly scripts

Postby Andrew » October 26th, 2009, 6:32 pm

Axaj wrote:Thanks a ton! Do you think you could change it to do only the current selection?

Just change getrect() to getselrect(), and the error message on the next line to "There is no selection.".
User avatar
Andrew
Moderator
 
Posts: 629
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: Golly scripts

Postby Axaj » October 26th, 2009, 8:45 pm

Andrew wrote:
Axaj wrote:Thanks a ton! Do you think you could change it to do only the current selection?

Just change getrect() to getselrect(), and the error message on the next line to "There is no selection.".


Great, it works! Thanks again!
Image
Axaj
 
Posts: 232
Joined: September 26th, 2009, 12:23 am

Re: Golly scripts

Postby PM 2Ring » November 3rd, 2009, 7:38 am

Fill current selection with a random glider field. Density must be a random floating point number between 0 & 1. Separation & seed are integers >= 0

# Golly python script.
# Written by PM 2Ring, November 2009

''' Fill current selection with a random glider field '''

import golly
from glife import *
from random import seed, random, choice

def getargs():
    ''' Get input data '''
    prompt = 'Enter density, separation and seed'
    usage = 'Create a random grid of gliders.\n' + prompt + ', separated by spaces.'

    #Loop until we get correct args, or are aborted by ESC
    while True:
        try:
            args = getstring(prompt + ' :').split()
            if len(args) < 3:
                raise ValueError, 'Not enough data.'
           
            args = float(args[0]), int(args[1]), int(args[2])
           
        except ValueError, s:
            golly.warn('%s\n\n%s' % (s, usage))
        else:
            break
    return args

def toxy(cells):
    ''' Convert cell list to a sorted list of (x, y) tuples '''
    xy = zip(cells[::2], cells[1::2])
    xy.sort()
    return xy

def MakeGliders():
    ''' Create a list of all gliders '''
    #Glider heading SE _\
    glider = pattern('bo$2bo$3o!')

    #Glider transforms
    xforms = [identity, flip_x, flip_y, flip]

    #Glider transformed by j with phase i, as a cell list
    def gliderxy(j, i):
        g = toxy(list(glider[i].apply(j)))
        #Adjust origin to top left corner
        ox, oy = g[0][0], min([i[1] for i in g])
        return sum([[x-ox, y-oy] for x,y in g], [])

    #All 16 gliders
    return [pattern(gliderxy(j, i)) for j in xforms for i in xrange(4)]

def RandGliders():
    #Get density, separation & randomizer seed from user
    density, dd, rseed = getargs()
    dd += 3
    seed(rseed)

    selrect = golly.getselrect()
    if len(selrect) == 0: golly.exit("No area selected")
    ox, oy, wx, wy = selrect
    wx -= 2; wy -= 2
   
    #A list of all 16 gliders
    gliders = MakeGliders()

    #Fill grid
    all = pattern()
    for y in xrange(0, wy, dd):
        for x in xrange(0, wx, dd):
            if random() < density:
                g = choice(gliders)
                all += g(ox + x, oy + y)

    all.put()
    #golly.fit()

def main():
    RandGliders()

if __name__ == '__main__':
    main()
User avatar
PM 2Ring
 
Posts: 152
Joined: March 26th, 2009, 11:18 am

Re: Golly scripts

Postby PM 2Ring » April 2nd, 2010, 1:16 am

This script creates a numbered grid of multiple generations of the currently selected pattern. Each grid entry is numbered using the Snakial font. It was written to display all phases of an oscillator (or guns) in a single pattern.

PatGrid0.py
# Golly Python script
# Display a numbered grid of generations of currently selected pattern
# Written by PM 2Ring, 2010.3.22

import golly
from glife import *

def snakenum(n): return make_text(str(n))

def makegrid(patA, count, dx, dy, cols):
    ''' Build a numbered grid of pattern states '''
   
    def slots():
        ''' Return co-ords of next grid slot '''
        xr = xrange(0, dx*cols, dx)
        y = 0
        while 1:
            for x in xr:
                yield x, y
            y += dy
           
    #Adjust height & width to allow for Snakial font         
    dy += 20   
    dx = max(30, dx)   

    slot = slots()
    for i in xrange(count):
        num = snakenum(i)
        pat = patA[i] + num(0, -5)       
        pat.put(*slot.next())
        if i % cols == 0:
            #golly.fit()
            golly.update()
    golly.fit()

def getargs():
    ''' Get input data: generation count and the number of columns in the grid '''

    prompt = 'Enter number of gens and columns'
    usage = prompt + ' , separated by spaces.'

    #Loop until we get correct args, or are aborted by ESC
    while True:
        try:
            args = getstring(prompt + ' :').split()
            if args == []:
                raise ArgError

            #Check that all args are valid integers
            for s in args:
                if not validint(s):
                    raise ArgError, '[%s] is not a valid integer.' % s

            if len(args) < 2:
                raise ArgError, 'Not enough data.'

            #Convert arguments to integer
            args = [int(s) for s in args]
            for i in args:
                if i<0:
                    raise ArgError, 'Data must be >= 0, not %d!' % i

        except ArgError, s:
            g.warn('%s\n\n%s' % (s, usage))
        else:
            break
    return args

def main():
    #Get pattern to evolve
    selrect = golly.getselrect()
    if len(selrect) == 0: golly.exit('No pattern selected.')

    #Get generation count & number of columns
    count, cols = getargs()   
    #count, cols = (12, 4)   
       
    #Get pattern data from selection
    ox, oy = selrect[:2]    #Pattern origin
    dx, dy = selrect[2:]    #Pattern size
    pat = pattern(golly.getcells(selrect))(-ox, -oy)

    #Initialize universe
    #rule()
    title = golly.getname().split('.')[0] + 'Grid'   
    golly.addlayer()
    golly.new(title)
    golly.setcursor(zoomin)

    #Set viewport
    golly.select([0, 0, dx*cols, dy])
    golly.fitsel()
    golly.update()
    golly.select([])
   
    makegrid(pat, count, dx, dy, cols)

if __name__ == '__main__':
    main()
User avatar
PM 2Ring
 
Posts: 152
Joined: March 26th, 2009, 11:18 am

Re: Golly scripts

Postby Awesomeness » July 9th, 2010, 6:01 pm

Nathaniel wrote:If you have any custom Golly python or perl scripts, feel free to post them here. The first one is a blazingly-fast version of the "goto.py" script that comes with Golly:

gofast.py
#Golly Python script
#Written by PM 2Ring 2009.3.24

''' Advance generation count using binary hashing '''

from glife import getstring, validint
import golly as g

def intbits(n):
  ''' Convert integer n to a bit list '''
  bits = []
  while n:
    bits += [n & 1]
    n >>= 1
  return bits   
   
def gofast(num):
  ''' Fast goto '''
  #Save current settings 
  oldbase = g.getbase()
  oldstep = g.getstep()
  oldhash = g.setoption("hashing", True)
 
  g.show('Hit ESC to abort')

  #Advance by binary powers, to maximize advantage of hashing 
  g.setbase(2)
  for i, b in enumerate(intbits(num)):
    if b:
      g.setstep(i)
      g.step()
      g.dokey(g.getkey())
      g.update()
      if golly.empty():
        break
       
  g.show('')
 
  #Restore settings 
  g.setoption("hashing", oldhash)
  g.setbase(oldbase)
  g.setstep(oldstep)
     
def main():
  numstr = getstring('How many gens to advance? :')
  if not validint(numstr):
    g.exit('Bad number: %s' % numstr)
  num = int(numstr)
  if num<1:
    g.exit('Number must be positive!')
  gofast(num)

main()

I decided to try out this, however there were problems. At line 4, you referenced golly as golly instead of g, giving me an error. If you fix that, (On my mac, I don't know about PCs) it stops giving errors but is still unusable because when I try to type into the little input thing at the top infobar, it triggers hotkeys on the menu which change the scale. Since the scale goes on powers of two, numbers such as 3, 5, 6, 7, and 9 are not affected, but it doesn't type anyway for those numbers, suggesting a bug in Golly. But anyway, the result is you cannot type anything, instead it just zooms in and out like the magnifying glass is having a seizure. Using the same input system as goto.py, (which makes a text box in a new window instead) I fixed it. After all the problems were fixed, I realized just how fast gofast.py was... Oh my gosh! I never expected it to do hundreds of thousands of generations of a universal constructor in five seconds! That's ridiculous!

Anyway, this is the code that worked for me:
#Golly Python script
#Written by PM 2Ring 2009.3.24
#Improved (at least in his opinion) by Awesomeness 2010.7.9

''' Advance generation count using binary hashing '''

from glife import validint
import golly as g
import os

def intbits(n):
   ''' Convert integer n to a bit list '''
   bits = []
   while n:
      bits += [n & 1]
      n >>= 1
   return bits   
   
def gofast(num):
   ''' Fast goto '''
   #Save current settings   
   oldbase = g.getbase()
   oldstep = g.getstep()
   oldhash = g.setoption("hashing", True)
   
   g.show('Hit ESC to abort')

   #Advance by binary powers, to maximize advantage of hashing   
   g.setbase(2)
   for i, b in enumerate(intbits(num)):
      if b:
         g.setstep(i)
         g.step()
         g.dokey(g.getkey())
         g.update()
         if g.empty():
            break
         
   g.show('')
   
   #Restore settings   
   g.setoption("hashing", oldhash)
   g.setbase(oldbase)
   g.setstep(oldstep)
      
def main():
   GotoINIFileName = g.getdir("data") + "goto.ini"
   previousgen = ""
   if os.access(GotoINIFileName, os.R_OK):
      f = open(GotoINIFileName, 'r')
      previousgen = f.readline()
      f.close()
      if not validint(previousgen):
         previousgen = ""
      numstr = g.getstring("Enter the desired generation number.", previousgen, "Go to generation")
      if not validint(numstr):
         g.exit('Bad number: %s' % numstr)
      num = int(numstr)
      if num<1:
         g.exit('Number must be positive!')
      gofast(num)
      previousgen = numstr
   if os.access(GotoINIFileName, os.W_OK) or not os.access(GotoINIFileName, os.F_OK):
      try:
         f = open(GotoINIFileName, 'w')
         f.write(previousgen)
         f.close()
      except:
         g.show("Unable to save defaults to " + GotoINIFileName)
   
main()
Awesomeness
 
Posts: 126
Joined: April 5th, 2009, 7:30 am

Re: Golly scripts

Postby PM 2Ring » July 10th, 2010, 12:33 pm

Thanks for fixing that, Awesomeness.

I'm still using Golly 1.2 on this ancient machine. I need to do major "renovations" before I can run later versions. So there are a few differences in the way scripts behave on my system.

I don't know if this has been changed in later versions, but if you do a Python import in a Golly session, then the module stays imported for subsequent scripts until you quit from Golly. This can be handy, but it can also mask bugs like the one you discovered (and the rest of us missed :) ).
User avatar
PM 2Ring
 
Posts: 152
Joined: March 26th, 2009, 11:18 am

Re: Golly scripts

Postby Awesomeness » September 26th, 2010, 8:03 pm

I'd like to get started with Python (Gosh, I haven't used it in years!) in Golly, and I was wondering:

How should I get started? Is there anything special I need to know? (Like what weird coordinate system Golly must use since it's an infinite plane?)

And just as an example, could someone create a script for me that turns the four corner cells of the current selection on? That would help so much and teach me not only how to use a selection but how to change cells as well.
Awesomeness
 
Posts: 126
Joined: April 5th, 2009, 7:30 am

Re: Golly scripts

Postby dvgrn » September 26th, 2010, 10:46 pm

Awesomeness wrote:How should I get started? Is there anything special I need to know? (Like what weird coordinate system Golly must use since it's an infinite plane?)

Read through Help > Python Scripting carefully before you start writing your scripts -- especially the Cell Lists section toward the bottom, explaining how coordinate lists are handled. The coordinate system is straightforward, but cell lists are a little tricky... most of my weird bugs recently have been caused by a script that builds a multi-state list and passes it to a Golly function that treats it as a one-state list (because there happen to be an even number of cells in the list, and therefore the list has an even number of elements, and I forgot to add a [0] at the end to indicate it's a multi-state list.)

Of course, if you aren't writing scripts that use LifeHistory or some other multi-state rule, you may never run into this particular problem -- but it's good to understand how cell lists work, anyway.

And just as an example, could someone create a script for me that turns the four corner cells of the current selection on? That would help so much and teach me not only how to use a selection but how to change cells as well.

import golly as g
r = g.getselrect()
if len(r)==0: g.exit("There is no selection.")
for x in [r[0],r[0]+r[2]-1]:
   for y in [r[1],r[1]+r[3]-1]:
      g.setcell(x,y,1)

I love how Python scripts so often work the first time I try them... The quickest way to run the above is to copy it and choose File > Run From Clipboard.
dvgrn
Moderator
 
Posts: 3996
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI

Re: Golly scripts

Postby Awesomeness » September 27th, 2010, 8:05 am

Yay! I've already made my first script!
Draws a box around the current selection, which has to have odd x and y values. (Is there a way I could fix that? I could up the spacing to two for certain directions when the width and height are odd or even, but that'd be inconsistent.)
################
#### box.py ####
################

# A script by Awesomeness 9.27.10
# conwaylife.com

import golly as g # Use golly
r = g.getselrect() # Get our selection as a list of four numbers
# If r has no items in it, that means there's no selection, which we need.
if len(r) == 0:
   g.exit("There is no selection.")
# If the numbers are even.  %2 means the remainder when divided by 2.
if r[2]%2 == 0 or r[3]%2 == 0:
   g.exit("Length and width must be odd!")
   
# r[0] = x of top left corner of selection
# r[1] = y of top left corner of selection
# r[2] = width of selection
# r[3] = height of selection

# Draw top line
x = r[0]
y = r[1]
while x <= r[0] + r[2] - 1:
   g.setcell(x,y,1)
   x = x + 2

# Draw bottom line
x = r[0]
y = r[1] + r[3] - 1
while x <= r[0] + r[2] - 1:
   g.setcell(x,y,1)
   x = x + 2
   
# Draw left line
x = r[0]
y = r[1]
while y <= r[1] + r[3] - 1:
   g.setcell(x,y,1)
   y = y + 2

# Draw right line
x = r[0] + r[2]-1
y = r[1]
while y <= r[1] + r[3 ]- 1:
   g.setcell(x,y,1)
   y = y + 2

Yeah, I always comment my code heavily, especially when other people are going to see it.
Awesomeness
 
Posts: 126
Joined: April 5th, 2009, 7:30 am

Utility Scripts for Hexagonal Neighborhoods

Postby Karatorian » October 3rd, 2010, 7:11 pm

I was playing around with the Patterson's Worms examples in Golly and started thinking about rules with hexagonal neighborhoods and how to implement them in Golly. I had two rather different ideas, but in the end I decided the best way was to use a Moore neighborhood in which two of the cells where ignored.

There are two basic options for such a rule, which are:

..... .....
.oo.. .oo..
.oOo. .oOo.
..oo. .oo..
..... .....

I decided to go with the first option, because it's symmetry leads to patterns that look more hexagonal. The second option is more common in programs with true hexagonal displays for technical reasons.

I implemented a rule table using the standard Life rule (B3/S23) and found that it worked, but that the rule wasn't very satisfying. After playing with a few other rules, I decided to write a script to generate rule tables as it was a little tedious to hand roll them. The script is currently a command line utility, but I plan to add an (optional) interface to allow it to be used from within Golly as well.

It's slightly optimized in that birth and survival rules share the same transitions where possible. I've thought about a further optimization, but I haven't implemented it yet. Currently, the resulting rules default to having all live cells die and the rules are implemented as exceptions to this. However, for some rules, it may be possible to have a smaller set of transitions if all cells are assumed to come to life unless otherwise specified. Furthermore, for some rules, the smallest ruleset might be result from having differing default behaviors for live and dead cells.

While playing around with various rules, I grew decided to write a Golly script to fake a hexagonal display. It works by grabbing the cells from the current layer and expanding them into two by two blocks in a second layer. By offsetting the rows, the result looks approximately hexagonal. For example, something like this:

oo.
o.o
.oo

Becomes this:

.oooo.
.oooo.
oo..oo
oo..oo
.oooo.
.oooo.

With a little extra programming, I added functionality to step and run patterns while displaying the results like this. The speed isn't the greatest when the number of cells gets large, but it's fairly functional.

With a four state dummy rule and some properly designed icons, this could be made to look even better, but I'm not sure that it would work very well for interactive use. As Golly doesn't allow each layer to have it's own rules, the script would have to switch rules every update, which I'm sure would be abysmally slow. However, I plan on making a non-interactive version, for grabbing screen shots and whatnot.

It would probably be possible to work around this by adding four dummy states to the hexagonal rule tables, but I'm afraid that that would slow things down. How badly is the speed of the RuleTable algorithm affected by additional states that have no transition rules?

I've also got some ideas for scripts to assist in editing hexagonal patterns. Basically, they'd perform the opposite function of the orthogonal to hexagon transformation that powers the hex-view script.

P.S. The other idea I for implementing hexagonal neighborhoods was to use a multi-state rule in which groups of four cells (laid out in offset rows) coordinate their behavior and act as meta-cells for a hexagonal rule. While possible, the number of states and rules required quickly got rather large. I abandoned this idea, but might produce a proof of concept example at some point.
Attachments
hex-tools-1.0.zip
(12.74 KiB) Downloaded 180 times
Karatorian
 
Posts: 21
Joined: September 18th, 2010, 10:56 am
Location: Rindge, NH, USA

Re: Utility Scripts for Hexagonal Neighborhoods

Postby Andrew » October 3rd, 2010, 8:38 pm

Karatorian wrote:I was playing around with the Patterson's Worms examples in Golly and started thinking about rules with hexagonal neighborhoods and how to implement them in Golly.

Quite a lot of work on this has already been done -- see http://code.google.com/p/ruletablerepository/wiki/TheRules#Hexagonal_neighborhood.

Tim Hutton (author of the RuleTable algo) has also done some work on suitable icons -- see his blog http://ferkeltongs.livejournal.com/31455.html. He's written some scripts that can create suitable .table and .icons files for hexagonal neighborhood rules. These scripts will be included in Golly 2.2 but if you want to play with them now you could probably just download all the stuff in the Scripts/Python directory from CVS (http://golly.cvs.sourceforge.net/viewvc/golly/golly/src/Scripts/Python/). From memory most of Tim's new stuff should work with Golly 2.1. In particular, see the AbsoluteHexTurmite-gen.py script and the glife module it relies on called EmulateHexagonal.py.

Golly doesn't allow each layer to have it's own rules...

Sure it does. If there is some situation where that isn't working then that's a bug, so let me know. (Cloned layers can't have different rules of course.)

Apologies to all who have been waiting patiently for Golly 2.2. Hopefully I can finish off support for bounded grids by the end of October (it was a lot more complicated than I thought!) and so 2.2 will be out soon after that.
User avatar
Andrew
Moderator
 
Posts: 629
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: Utility Scripts for Hexagonal Neighborhoods

Postby Karatorian » October 5th, 2010, 1:55 pm

Andrew wrote:
Karatorian wrote:Golly doesn't allow each layer to have it's own rules...

Sure it does. If there is some situation where that isn't working then that's a bug, so let me know. (Cloned layers can't have different rules of course.)

Could they in 1.4? I recently upgraded and am not quite sure where I got that mistaken impression. Anyway, that makes my someof my ideas more viable. Although, I suppose I should look into what's already been done first.
Karatorian
 
Posts: 21
Joined: September 18th, 2010, 10:56 am
Location: Rindge, NH, USA

PreviousNext

Return to Scripts

Who is online

Users browsing this forum: Yahoo [Bot] and 1 guest