Golly scripts

For scripts to aid with computation or simulation in cellular automata.
User avatar
SuperSupermario24
Posts: 120
Joined: July 22nd, 2014, 12:59 pm
Location: Within the infinite expanses of the Life universe

Re: Golly scripts

Post by SuperSupermario24 » March 21st, 2018, 12:35 pm

Andrew wrote:Actually, it should be. Like g.new and g.open, g.save also tells Golly to stop remembering changes. This is the case for both Lua and Python. I'd completely forgotten doing that, so thanks for prodding me to check. I'll update the docs for the next release.
That does make me wonder, though; why is it necessary that Golly remember every change that the script makes as it happens? Would it not be feasible to just save the current state of the universe/layers/etc. right before the script runs, so that you could just revert back to that after the script exits if so desired?

Code: Select all

bobo2b3o2b2o2bo3bobo$obobobo3bo2bobo3bobo$obobob2o2bo2bobo3bobo$o3bobo3bo2bobobobo$o3bob3o2b2o3bobo2bo!

User avatar
Andrew
Moderator
Posts: 808
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia
Contact:

Re: Golly scripts

Post by Andrew » March 21st, 2018, 6:31 pm

SuperSupermario24 wrote:Would it not be feasible to just save the current state of the universe/layers/etc. right before the script runs, so that you could just revert back to that after the script exits if so desired?
If only Life was that simple. Unfortunately (for me) we're dealing with an app that can create/edit patterns with billions/trillions of cells. If the current state has a monstrously large pattern and you run a script that simply changes a few cells then there would be a very long delay while the state is saved before your script even runs.

shouldsee
Posts: 406
Joined: April 8th, 2016, 8:29 am

Re: Golly scripts

Post by shouldsee » March 25th, 2018, 11:05 pm

The fixed "mutate.py" takes the current rulestring and flips a random hensel configuration. It depends on the messy collection "KBs.py" and please place them in the same folder.

mutate.py

Code: Select all

## Written by Feng (shouldsee.gem@gmail.com) March 2018.
import golly
import KBs

import random


# rulestr=golly.getstring('NTCA number',golly.getclipstr()).split('_')[-1];
kb = KBs.kb_2dntca()
# alias = golly.getrule()
curr=golly.getrule().split(':')
if len(curr)==1:
	curr+=['']
alias,suffix = curr

rulestr = kb.alias2rulestr(alias)
bitstr = KBs.hex2bin(rulestr,102)
# assert KBs.bin2hex(bitstr)==rulestr
bitlst = list(bitstr)

idx =  random.randint(0,102)
flip = {'0':'1','1':'0'}
bitlst[idx] = flip[bitlst[idx]]
rulestr = KBs.bin2hex(''.join(bitlst))
alias = kb.rulestr2alias(rulestr)


golly.note(alias)
golly.setclipstr(alias)

golly.setrule('%s:%s'%(alias,suffix))
KBs.py

Code: Select all

import numpy as np
import pickle
import itertools, collections, re
import copy
import random
import collections 
count = collections.Counter
identity = lambda x:x
# import collections
base2bin=lambda data,scale,num_of_bits: bin(int(data, scale))[2:].zfill(num_of_bits);
hex2bin=lambda hexdata,num_of_bits: base2bin(hexdata,16,num_of_bits);
bin2hex = lambda bitstr:hex(int(bitstr,2)).lstrip('0x').rstrip('L')

# from astropy.convolution import convolve
# convolve_int=lambda a,fir,method:np.around(convolve(a,fir,method)).astype(np.int);
import scipy.ndimage
convolve_int=lambda a,fir,method,**kwargs:scipy.ndimage.filters.convolve(a,fir,mode = method,**kwargs)

hensellist=['b0_','b1c','b1e','b2a','b2c','b3i','b2e','b3a','b2k','b3n','b3j','b4a','s0_','s1c','s1e','s2a','s2c','s3i','s2e','s3a','s2k','s3n','s3j','s4a','b2i','b3r','b3e','b4r','b4i','b5i','s2i','s3r','s3e','s4r','s4i','s5i','b2n','b3c','b3q','b4n','b4w','b5a','s2n','s3c','s3q','s4n','s4w','s5a','b3y','b3k','b4k','b4y','b4q','b5j','b4t','b4j','b5n','b4z','b5r','b5q','b6a','s3y','s3k','s4k','s4y','s4q','s5j','s4t','s4j','s5n','s4z','s5r','s5q','s6a','b4e','b5c','b5y','b6c','s4e','s5c','s5y','s6c','b5k','b6k','b6n','b7c','s5k','s6k','s6n','s7c','b4c','b5e','b6e','s4c','s5e','s6e','b6i','b7e','s6i','s7e','b8_','s8_',];
rca2ntca=[0, 1, 2, 3, 1, 4, 3, 5, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 13, 16, 15, 17, 14, 15, 18, 19, 20, 21, 22, 23, 2, 8, 6, 10, 3, 9, 7, 11, 24, 25, 26, 27, 25, 28, 27, 29, 14, 20, 18, 22, 15, 21, 19, 23, 30, 31, 32, 33, 31, 34, 33, 35, 1, 4, 8, 9, 36, 37, 38, 39, 3, 5, 10, 11, 38, 39, 40, 41, 13, 16, 20, 21, 42, 43, 44, 45, 15, 17, 22, 23, 44, 45, 46, 47, 8, 48, 49, 50, 38, 51, 52, 53, 25, 54, 55, 56, 57, 58, 59, 60, 20, 61, 62, 63, 44, 64, 65, 66, 31, 67, 68, 69, 70, 71, 72, 73, 2, 8, 24, 25, 8, 48, 25, 54, 6, 10, 26, 27, 49, 50, 55, 56, 14, 20, 30, 31, 20, 61, 31, 67, 18, 22, 32, 33, 62, 63, 68, 69, 6, 49, 26, 55, 10, 50, 27, 56, 26, 55, 74, 75, 55, 76, 75, 77, 18, 62, 32, 68, 22, 63, 33, 69, 32, 68, 78, 79, 68, 80, 79, 81, 3, 9, 25, 28, 38, 51, 57, 58, 7, 11, 27, 29, 52, 53, 59, 60, 15, 21, 31, 34, 44, 64, 70, 71, 19, 23, 33, 35, 65, 66, 72, 73, 10, 50, 55, 76, 40, 82, 59, 83, 27, 56, 75, 77, 59, 83, 84, 85, 22, 63, 68, 80, 46, 86, 72, 87, 33, 69, 79, 81, 72, 87, 88, 89, 1, 36, 8, 38, 4, 37, 9, 39, 8, 38, 49, 52, 48, 51, 50, 53, 13, 42, 20, 44, 16, 43, 21, 45, 20, 44, 62, 65, 61, 64, 63, 66, 3, 38, 10, 40, 5, 39, 11, 41, 25, 57, 55, 59, 54, 58, 56, 60, 15, 44, 22, 46, 17, 45, 23, 47, 31, 70, 68, 72, 67, 71, 69, 73, 4, 37, 48, 51, 37, 90, 51, 91, 9, 39, 50, 53, 51, 91, 82, 92, 16, 43, 61, 64, 43, 93, 64, 94, 21, 45, 63, 66, 64, 94, 86, 95, 9, 51, 50, 82, 39, 91, 53, 92, 28, 58, 76, 83, 58, 96, 83, 97, 21, 64, 63, 86, 45, 94, 66, 95, 34, 71, 80, 87, 71, 98, 87, 99, 3, 38, 25, 57, 9, 51, 28, 58, 10, 40, 55, 59, 50, 82, 76, 83, 15, 44, 31, 70, 21, 64, 34, 71, 22, 46, 68, 72, 63, 86, 80, 87, 7, 52, 27, 59, 11, 53, 29, 60, 27, 59, 75, 84, 56, 83, 77, 85, 19, 65, 33, 72, 23, 66, 35, 73, 33, 72, 79, 88, 69, 87, 81, 89, 5, 39, 54, 58, 39, 91, 58, 96, 11, 41, 56, 60, 53, 92, 83, 97, 17, 45, 67, 71, 45, 94, 71, 98, 23, 47, 69, 73, 66, 95, 87, 99, 11, 53, 56, 83, 41, 92, 60, 97, 29, 60, 77, 85, 60, 97, 85, 100, 23, 66, 69, 87, 47, 95, 73, 99, 35, 73, 81, 89, 73, 99, 89, 101];
henseldict={'0': ['_'],
 '1': ['c', 'e'],
 '2': ['a', 'c', 'e', 'k', 'i', 'n'],
 '3': ['i', 'a', 'n', 'j', 'r', 'e', 'c', 'q', 'y', 'k'],
 '4': ['a', 'r', 'i', 'n', 'w', 'k', 'y', 'q', 't', 'j', 'z', 'e', 'c'],
 '5': ['i', 'a', 'j', 'n', 'r', 'q', 'c', 'y', 'k', 'e'],
 '6': ['a', 'c', 'k', 'n', 'e', 'i'],
 '7': ['c', 'e'],
 '8': ['_']}


rca2ntca=np.array(rca2ntca,np.int);
henselidx={k: v for v, k in enumerate(hensellist)};
subconf='_cekainyqjrtwz';
p_NOTnumletter = re.compile(r'[^\da-zA-Z\-]')    

try:
    from data import *
    with open('tp','rb') as f:  # Python 3: open(..., 'rb')
       hdist, tst_data = pickle.load(f)
       hdist = np.array(hdist).reshape([512,512]);
except:
    print('[WARN]Not finding data.py')




def invert(s):
    num = s[0]
    conf = henseldict[num]
    return ''.join([num]+[x for x in conf if x not in s])

def ntuple(lst,n):
    """ntuple([0,3,4,10,2,3], 2) => [(0,3), (4,10), (2,3)]
    
    Group a list into consecutive n-tuples. Incomplete tuples are
    discarded e.g.
    
    >>> group(range(10), 3)
    [(0, 1, 2), (3, 4, 5), (6, 7, 8)]
    """    
    return zip(*[lst[i::n] for i in range(n)])

def add_all(s,prime,sold,neg=0):
    for c in subconf:
        conf=prime+sold+c;
        try:
            s[henselidx[conf]]=str(1-neg);
        except KeyError:
            pass
class kb_2dntca():
    def __init__(self):
        self.familyname='2dntca'
        pass
    def rulestr2alias(self, rulestr):
        OUT = ''
        # rulestr =  '000000000060031c61c67f86a0'
        r=hex2bin(rulestr,102);
        r=r[::-1];
        rule=[i for i,x in enumerate(r) if x=='1'];
#         print r
        lst = [hensellist[i] for i in rule]
        lst.sort()
        
        #### group by B/S
        d = collections.OrderedDict((('b',{}),('s',{}))) ### set default
#         d = {'b':{},'s':{}}   ### set default
        d.update(
            {k:list(gp) for k,gp in itertools.groupby(lst, lambda x:x[0])}        
        )
        for k,lst in d.items():
            d[k] = {k:list(gp) for k,gp in itertools.groupby(lst, lambda x:x[1])}
            
        for bs, dd in d.items():
            OUT += bs
            for k,lst in dd.items():
                OUT += k + ''.join( conf[-1] for conf in lst)
        OUT = OUT.replace('_','')
        alias = OUT
        return alias


    def alias2rulestr(self,alias): 
    # alias.replace('-','')
        alias = re.sub('(\d-[a-zA-Z]+)',lambda o:invert(o.group()),alias)
        alias = p_NOTnumletter.sub( '', alias).lower()
        OUT = ['0']*102
        d = collections.OrderedDict((('b',{}),('s',{}))) ### set default
        # d.update()
        # alias.split('s')
        s = alias
        lst = [x for x  in re.split("([bs])", s) if x]
        if len(lst) % 2: #### Padding to even length
            lst += ['']
        d  = dict(ntuple(lst,2))
        idxs = []
        for k, v in d.items():
            s = v
            lst = [x for x in re.split("(\d)", s) if x]
            L  = len(lst)
            v_old = ''
            for i,v in enumerate(lst):
                if v.isdigit():
                    if v_old.isdigit():
                        idx = [henselidx.get( k + v_old + c,None) for c in subconf]
                        idxs.extend(idx)
                    if i + 1 == L:
                        idx = [henselidx.get( k + v + c,None) for c in subconf]
                        idxs.extend(idx)
                    num = v
                else:
                    idx = [henselidx[ k + num + v_i]  for v_i in v ]
                    idxs.extend(idx)
                v_old = v
        idxs = [ x for x in idxs if x is not None] 
        for i in idxs:
            if not i is None:
                OUT[i] = '1'
        bitstr=''.join(OUT[::-1]);
        hexstr=hex(int(bitstr,2)).lstrip('0x').rstrip('L').zfill(26)
        return hexstr
#     def alias2rulestr(self, alias):
#         alias = p_NOTnumletter.sub( '', alias).lower()
#         OUT = ['0']*102
#         # print alias
#         d = collections.OrderedDict((('b',{}),('s',{}))) ### set default
#         # d.update()
#         # alias.split('s')
#         s = alias
#         lst = [x for x  in re.split("([bs])", s) if x]
#         if len(lst) % 2: #### Padding to even length
#             lst += ['']
#         d  = dict(ntuple(lst,2))
#         idxs = []
#         for k, v in d.items():
#             s = v
#             lst = [x for x in re.split("(\d)", s) if x]
#             L  = len(lst)
#             v_old = ''
#             for i,v in enumerate(lst):
#                 if v.isdigit():
#                     if v_old.isdigit():
#                         idx = [henselidx.get( k + v_old + c,None) for c in subconf]
#                         idxs.extend(idx)
#                     if i + 1 == L:
#                         idx = [henselidx.get( k + v + c,None) for c in subconf]
#                         idxs.extend(idx)
#                     num = v
#                 else:
#                     idx = [henselidx[ k + num + v_i]  for v_i in v ]
#                     idxs.extend(idx)
#                 v_old = v
#         idxs = [ x for x in idxs if x is not None] 
#         for i in idxs:
#             if not i is None:
#                 OUT[i] = '1'
#         bitstr=''.join(OUT[::-1]);
#         hexstr=hex(int(bitstr,2)).lstrip('0x').rstrip('L').zfill(26)
#         return hexstr
    def rulestr2adv(self,rulestr):
        ruleprj=np.array( 
            list(hex2bin(rulestr,102)[::-1]),
            np.int);
        adv = self.bin2adv(ruleprj)
#         ruleprj=np.array(list(hex2bin(rulestr,102)[::-1]));
#         fir=(2**np.arange(0,9)).reshape([1,3,3]);
#         pj=rca2ntca;
#         def adv(a,horizon):
#             return ruleprj[pj[convolve_int(a,fir,'wrap').astype(np.int)]]
#         # adv=lambda a, horizon: ruleprj[pj[convolve_int(a,fir,'wrap').astype(np.int)]]
        return adv 
    def conv(self,a):
        fir=(2**np.arange(0,9)).reshape([1,3,3]);
        pj=rca2ntca;
        return pj[convolve_int(a,fir,'wrap').astype(np.int)]
    def bin2adv(self, ruleprj):
        if isinstance(ruleprj,str):
            ruleprj = list(ruleprj)
        ruleprj = np.array(ruleprj,np.int)
        def adv(a,horizon=0):
            return ruleprj[self.conv(a)]
        return adv
    def rstr(self,callback=(lambda x:bin2hex(x).zfill(26)) ):
        r = '{:0102b}'.format(random.randrange(2**102))
        if callback is not None:
            r = callback(r)
        return r
    def randadv(self):
        return self.bin2adv(self.rstr(None))
    def bulk_rstr(self,seed = 0,bsize=2**18,**kwargs):
        random.seed(seed)
        lst = [{'family':self.familyname,
                'rulestr':self.rstr(**kwargs)} for x in range(bsize)]
        return lst    
    
    
class kb_2dtca():
#     def rulestr2alias(rulestr):
#         r=base2bin(int(rulestr),18,2);
#         r=r[:1:-1];
#         r+='0'*(18-len(r));
#         rule=[i for x,i in zip(r,range(len(r))) if x=='1'];
#         alias='b';
#         ps=1;
#         for a in rule:
#             if a>8 and ps:
#                 alias+='s';
#                 ps=0;
#             alias+=str((a)%9)
#         if ps==1:
#             alias+='s';
#         return alias        
    def alias2rulestr(self, ali):
        rule=['0']*18;
        ali=ali.replace('/','').lower().lstrip('b');
        (b,s)=ali.split('s');
        lst=list(str(int(i)+9) for i in s);
        bs=list(b)+(lst)
        for i in bs:
            rule[int(i)]='1';
        rnum=(''.join(rule[::-1]),2);
        return(rnum);
    def rulestr2adv(self,rulestr):
        #take an numpy array and convolution across the axis=[1,2];
        # project the convolved array back to value space according to the rule
        # 
        hex2bin(rulestr)
                                       
class CA_sys():
    def __init__(self,familyname = None,rulestr=None,dimsiz=None,adv=None,rdf=None):
#         siz=[600,100,400];
        if dimsiz is None:
            dimsiz = [128,128,32**2]
        if rulestr is None:
            rulestr = '0c83820e0060061941946a68f0'
        if familyname is None:
            familyname = '2dntca'
        self.familyname=familyname;
        self.rulestr=rulestr;
        self.adv=adv;
        self.dimsiz=dimsiz;
        self.change_size();
#         self.family = globals().get('familyname')
        self.family=eval('kb_%s()'%self.familyname);
        self.rulestr2alias()
#         self.
        if rdf==None:
            self.rdf=lambda:(np.random.random(self.siz)<=0.5).astype(np.int);

    def change_size(self,dimsiz=None):
        if dimsiz==None:
            dimsiz=self.dimsiz;
        N,hmax,ksq=dimsiz
        self.N=N;
        self.hmax=hmax;
        dd=int(ksq**0.5);
        self.siz = (N,dd,dd);
        
    def rulestr2alias(self):
#         kb=eval('kb_%s()'%self.familyname);
        # kb=eval('kb_%s'%self.familyname);
        self.alias=self.family.rulestr2alias(self.rulestr);
        self.adv=self.family.rulestr2adv(self.rulestr);
    def alias2rulestr(self):
#         kb=eval('kb_%s()'%self.familyname);
        # kb=eval('kb_%s'%self.familyname);
        self.rulestr=self.family.alias2rulestr(self.alias)
        self.adv    =self.family.rulestr2adv(self.rulestr)            
    def as_config(self):
        conf = {'family':self.family.familyname,
               'rulestr':self.rulestr,
                'alias':self.alias,}
        return conf
#     def change_adv(familyname,rulestr):

kb=kb_2dntca();
# kb.rulestr2alias('000000000060031c61c67f86a0')
kb.alias2rulestr('b3/s23')

    
# @function

# @function
def measure_temperature(sys0=None,hdist=None,*args,**kwargs):
#     varargin = measure_temperature.varargin
#     nargin = measure_temperature.nargin
    sysX=copy.copy(sys0)
    jmax=sysX.N;
    avi=sysX.rdf().astype(np.int)
    siz=avi.shape
    siz=(sysX.hmax,)+siz;
    tmp=np.zeros(siz)
    smtmp=np.zeros(siz)

    avc=avi
    i=0
    fir=np.reshape(2 ** (np.arange(0,9)),[1,3,3])
    trans=6
    mtp=0
    stp=0
    while i+1 < sysX.hmax:

        i=i + 1
        avcnew=(sysX.adv(avc,i))
        cavc=convolve_int(avc,fir,'wrap').astype(np.int);
        cavcnew=convolve_int(avcnew,fir,'wrap').astype(np.int);
        idx=np.ravel_multi_index((cavc,cavcnew),[2**9,2**9]);
        tmp[i,:,:,:]=np.expand_dims(hdist.flat[idx],0)
        if i >= trans:
            smtmpnow=np.mean(tmp[i - trans:i,:,:,:],axis=0)
            smtmp[i - trans,:,:,:]=smtmpnow
            if i >= trans + 10:
                mtp=np.mean(smtmpnow.flat)
                stpmat=((smtmp[i - trans,:,:,:] - smtmp[i - trans - trans,:,:,:]))
                a=np.mean(np.abs(stpmat.flat))
                b=abs(np.mean(stpmat.flat))
                stp=a - b
                stp1=np.mean(avcnew.flat)
                stp1=min(stp1,1 - stp1)
        avc=avcnew;
        #     im1=[avc(1,:,:)];
        if mtp < 0.02 and i > 20:
            break
    
    fam_alias=sys0.familyname+'_'+sys0.alias;
# /home/shouldsee/Documents/repos/CA_tfmat/custom_function/measure_temperature.m:55
    # s=sprintf('%s\\t%s\\t%d\\t%f\\t%f\\t%f\\n',fam_alias,num2str(sys0.od),i,mtp,stp,stp1)
    s='{}\t{}\t{:d}\t{:f}\t{:f}\t{:f}\n'.format(fam_alias,sysX.rulestr,i,mtp,stp,stp1)
# /home/shouldsee/Documents/repos/CA_tfmat/custom_function/measure_temperature.m:56
    return s
    
# if __name__ == '__main__':
#     pass
    


### Profiling loop
### Profiling loop
def profile(input_list, log = []):
    # global log
    output_data=[];    
    repeat=2;
    # input_list=[input_rulestr];
    ipt_list=input_list*repeat;
    # for i in range(5):
    l_ipt=len(input_list)
    log += ['Log of the process:'];
    logs='Starting to profile {:d} rules at {:d} replicates,\n totaling {:d} instances'.format(l_ipt,repeat,l_ipt*repeat);
    log += [logs];
    # print('Starting to profile {:d} rules at {:d} replicates,\n totaling {:d} instances'.format(l_ipt,repeat,l_ipt*repeat))

    for num,rulestr in enumerate(ipt_list):
        ca1=CA_sys(familyname,rulestr,[400,100,400]);
        ca1.rulestr2alias();
        s=measure_temperature(ca1,hdist);
        output_data+=[s];
    #     print('{:d} of {:d}'.format(num,len(ipt_list)))
        logs =('{:d} of {:d} '.format(num,len(ipt_list)));
        log += [logs];
    temp_data=[];
    # sample_data=[]
    for line in output_data:
        temp_data+=[line.rstrip('\n').split('\t')];
    sample_data=np.array(temp_data)
    # print('data is succesfully generated at {:d} replicates'.format(repeat))
    logs=('data is succesfully generated at {:d} replicates'.format(repeat))

    log  += [logs];

    # print('\n Detail of the input:')
    logs='\n Detail of the input:';
    log+=[logs];
    for k,v in ca1.__dict__.items():
        if not callable(v):
    #         print(k+str(v).ljust(-10))
    #         print("{:5} {:<15} {:<10}".format('',k, str(v)))

            logs=("{:5} {:<15} {:<10}".format('',k, str(v)));
            log+=[logs];
    return( [sample_data,log]);
    
if __name__ == '__main__':
    INPUT = "B345678/S012678"
    exp = "3f9fbe3e001fff07e07e15fea0"
    act = kb.alias2rulestr(INPUT)
    assert act == exp,'expected:%s, actual:%s' %(exp,act)
#     pass

def guess(i=None,sysX=None,dct=None):
    dimsiz = (256,2**7,24**2)
    if i is not None:
        rstr = tst_data[i][1]
        familyname='2dntca'
    if dct is not None:
        familyname = dct['family']
        rstr = dct['rulestr']
    if sysX is None:
        sysX = CA_sys(familyname,rstr,dimsiz)
        # sysX = KBs.CA_sys('2dntca',tst_data[i][1],(200,2**7,400))
        sysX.rulestr2alias()
    else:
        sysX.dimsiz = dimsiz
        sysX.change_size()# spspa.distance
    return sysX


import IPython.display as ipd
def lview(self):
    fmt = 'http://newflaw.com/view.php?rule_alias={:}'
    uri = fmt.format(self.alias)
    print uri
    ele = '<iframe src="{}" width=600 height=500></iframe>'.format(uri)
    ipd.display(ipd.HTML(ele))
    return uri


def sample(self,ini=None,adv = None):
    '''
    Sample an iterator ('CA_sys' object)
    '''
    if adv is None:
        adv = self.adv
    if ini is None:
        ini=self.rdf().astype(int)
    avc = ini
    hist = np.zeros((self.hmax,)+avc.shape,dtype=np.int)
    for i in range(self.hmax):
        hist[i]=avc
        avc=(adv(avc)) 
    return hist
def fill_ber(arr,p=0.5):
    '''
    Create bernouli using shape of arr
    '''
    return np.random.random(arr.shape) < p
def mix_adv(fA,fB,pa=0):
    '''
    Mixing two iterator
    '''
    if pa==1:
        adv = lambda arr:fA(arr)
    elif pa==0:
        adv = lambda arr:fB(arr)
    else:
        def adv(arr):
            mask = np.random.random(arr.shape)<pa
            A = fA(arr)
            B = fB(arr)
#             arr[mask] = A
#             arr[~mask] = B
            out = A*mask + B*(1-mask)
            return out
#             return arr
    return adv
def cov2cor(COV):
    D = np.diag(COV)
    COR = COV /  np.sqrt(D[:,None]*D[None,:])
    return COR


# Fast run length encoding
def rle (img):
    '''
    Source:https://www.kaggle.com/hackerpoet/even-faster-run-length-encoder
    '''
    x = np.ravel(img)
#     flat_img = img.flatten()
#     flat_img = np.where(flat_img > 0.5, 1, 0).astype(np.uint8)
    ### startswith 0->1
    ### endswith 1->0
    starts = np.array((x[:-1] == 0) & (x[1:] == 1))
    ends = np.array((x[:-1] == 1) & (x[1:] == 0))
    starts_ix = np.where(starts)[0] + 1
    ends_ix = np.where(ends)[0] + 1
    if x[0] == 1:
        starts_ix = np.insert(starts_ix,0,0)
    if x[-1] == 1:
        ends_ix = np.append(ends_ix,len(x))
    lengths = ends_ix - starts_ix
    
    return starts_ix, lengths
def _gollyrle(x):
    sl = rle(x)
    sym='bo$'
    S = 0
    out = ''
    L = 0
#     if s
    if sl[0].size!=0:
        for s,l1 in zip(*sl):
            l0 = s-S-L
            if l0>0:        
                inc = '%d%s'%(l0,sym[0]) ### number of zeros
                out = '%s%s'%(out,inc)
            inc = '%d%s'%(l1,sym[1])
            out = '%s%s'%(out,inc)
            S = s
            L = l1
    l0 = len(x)-S-L
    if l0>0:        
        inc = '%d%s'%(l0,sym[0]) ### number of zeros
        out = '%s%s'%(out,inc)
#     out='%s%s'%(out,sym[-1])
    return out
_gollyrle(map(int,list('010001001010')))
def gollyrle(arr):
    return '$'.join(map(_gollyrle,arr))
def hill(x):
    return 1-abs(2*x-1)
def sflatten(arr):
    return np.reshape(arr,(len(arr),-1))
def sexpand(arr,d=2):
    S = arr.shape
    root = int(round(S[1]**(1./d)) )
    out = np.reshape(arr,(S[0],)+(root,)*d)
    return out


def showsptime(arr,ax=None,**kwargs):
    if ax is None:
        fig,ax = plt.subplots(1,1,figsize=[12,4])    
    return ax.pcolormesh(sflatten(arr),**kwargs)
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import IPython.display as ipd
def animate(arr,html = 1,nFrame=20):
    '''
    Animate a list of 2d-array
    '''
    fig, ax = plt.subplots()
    im = ax.imshow(arr[0])
    ax.grid()
    data_gen = arr

    def run(data):
        im.set_data(data)
        return im,

    ani = animation.FuncAnimation(fig, run, 
                                  data_gen, 
                                  blit=False,
                                  interval=1000./nFrame,
                                  repeat=1, init_func=None)
    plt.close()
    if html:
        ani = ipd.HTML(ani.to_html5_video())
    return ani

wildmyron
Posts: 1466
Joined: August 9th, 2013, 12:45 am

Re: Golly scripts

Post by wildmyron » June 12th, 2018, 11:49 pm

I've been using dvgrn's find-object.py script for some time now which works great until the population in the layer being searched through starts to go up. Here's a modified version which uses Python sets instead of lists to test for cell presence. It is significantly faster on layers with populations > 10,000. I also implemented object population matching which uses the selection around the object of interest as a bounding box around the potential matches. I haven't tested it with multistate patterns, but it should work.

Edit: Buggy - use version in next post

Code: Select all

# find-object.py
# Search for another copy of the object in the current selection.
# If one is found, move the selection to the new location.
# If the selected orientation is not found, try other orientations.
# AUTHOR: Dave Green, Jan 2014
# CONTRIBUTORS: Arie Paap
# TODO:  update to find other phases (borrow code from glider-rewinder.py?)

import golly as g

r=g.getselrect()
if len(r)==0: g.exit("No selection.  Need a selected object to search with.")
searchobj=g.getcells(r)
rectcells=[r[0],r[1],r[0]+r[2]-1,r[1],r[0],r[1]+r[3]-1,r[0]+r[2]-1,r[1]+r[3]-1]
objlist=[searchobj,g.transform(searchobj,0,0,0,1,-1,0),
           g.transform(searchobj,0,0,-1,0,0,-1),g.transform(searchobj,0,0,0,-1,1,0),
           g.transform(searchobj,0,0,0,1,1,0),g.transform(searchobj,0,0,1,0,0,-1),
           g.transform(searchobj,0,0,0,-1,-1,0),g.transform(searchobj,0,0,-1,0,0,1)]
rectcelllist=[rectcells,g.transform(rectcells,0,0,0,1,-1,0),
           g.transform(rectcells,0,0,-1,0,0,-1),g.transform(rectcells,0,0,0,-1,1,0),
           g.transform(rectcells,0,0,0,1,1,0),g.transform(rectcells,0,0,1,0,0,-1),
           g.transform(rectcells,0,0,0,-1,-1,0),g.transform(rectcells,0,0,-1,0,0,1)]
normlist=[]
normrect=[]
for ii,obj in enumerate(objlist):
  sorted=g.evolve(obj,0)
  normlist+=[g.transform(sorted,-sorted[0],-sorted[1])]
  rect=g.evolve(rectcelllist[ii],0)
  normrect+=[[rect[0]-sorted[0],rect[1]-sorted[1],rect[6]-rect[0]+1,rect[7]-rect[1]+1]]

# offset=1000
# for obj in normlist:
#   g.putcells(obj, offset, 0)
#   offset+=100

g.putcells(searchobj,0,0,1,0,0,1,"xor")
all=g.getcells(g.getrect())
g.putcells(searchobj,0,0,1,0,0,1,"xor")

allcells=set()
step=len(all)%2+2
for i in range(0,len(all)-1,step):
  allcells.add(tuple(all[i:i+step])) # simplified version of two-state/multistate test

count=0
for ii, obj in enumerate(normlist):
  g.show("Object orientation "+str(count))
  g.update()
  count+=1
  # g.note(str(obj))
  for cell in allcells: # go through each cell in the pattern, check if there's a perfect match
    offsetobj=[]

    # separate search for two-state and multistate rules
    if len(obj)%2:
      # list has odd parity -- must be a multistate list, so break into groups of three
      for i in range(0,len(obj)-1,3):
        offsetobj+=[(obj[i]+cell[0],obj[i+1]+cell[1],obj[2])]
    else:
      # list has even parity -- must be a two-state list, so break into groups of two
      for i in range(0,len(obj),2):
        offsetobj+=[(obj[i]+cell[0],obj[i+1]+cell[1])]
    # g.note(str(offsetobj))
    match=1
    for c in offsetobj:
      if c not in allcells:
        match=0
        break

    if match==1:
      rect=normrect[ii]
      rect[0:2]=[rect[0]+cell[0],rect[1]+cell[1]]
      if not len(g.getcells(rect))==len(obj):
        # Don't match if number of cells in selection differs
        continue
      g.select(rect)
      g.fitsel()
      g.exit("Found something.")
g.show("No matches found")
Edit: Hmm, there's a bug with this version of the script where if it finds a false match first (i.e. additional On cells in the bbox) it fails to find any true matches. I've removed the use of continue but the bug is still present and I'm really struggling to work out why.
Last edited by wildmyron on August 20th, 2020, 2:31 am, edited 1 time in total.
The latest version of the 5S Project contains over 226,000 spaceships. There is also a GitHub mirror of the collection. Tabulated pages up to period 160 (out of date) are available on the LifeWiki.

wildmyron
Posts: 1466
Joined: August 9th, 2013, 12:45 am

Re: Golly scripts

Post by wildmyron » June 14th, 2018, 11:07 pm

Fixed a bug which meant false matches would cause true matches to be skipped.
I was caught out by Python's automatic references to lists again (this time it was to a nested list!).

Code: Select all

# find-object.py
# Search for another copy of the object in the current selection.
# If one is found, move the selection to the new location.
# If the selected orientation is not found, try other orientations.
# AUTHOR: Dave Green, Jan 2014
# CONTRIBUTORS: Arie Paap
# TODO:  update to find other phases (borrow code from glider-rewinder.py?)

import golly as g

r=g.getselrect()
if len(r)==0: g.exit("No selection.  Need a selected object to search with.")
searchobj=g.getcells(r)
rectcells=[r[0],r[1],r[0]+r[2]-1,r[1],r[0],r[1]+r[3]-1,r[0]+r[2]-1,r[1]+r[3]-1]
objlist=[searchobj,g.transform(searchobj,0,0,0,1,-1,0),
           g.transform(searchobj,0,0,-1,0,0,-1),g.transform(searchobj,0,0,0,-1,1,0),
           g.transform(searchobj,0,0,0,1,1,0),g.transform(searchobj,0,0,1,0,0,-1),
           g.transform(searchobj,0,0,0,-1,-1,0),g.transform(searchobj,0,0,-1,0,0,1)]
rectcelllist=[rectcells,g.transform(rectcells,0,0,0,1,-1,0),
           g.transform(rectcells,0,0,-1,0,0,-1),g.transform(rectcells,0,0,0,-1,1,0),
           g.transform(rectcells,0,0,0,1,1,0),g.transform(rectcells,0,0,1,0,0,-1),
           g.transform(rectcells,0,0,0,-1,-1,0),g.transform(rectcells,0,0,-1,0,0,1)]
# Mirror y-axis only
objlist=[searchobj,g.transform(searchobj,0,0,1,0,0,-1)]
rectcelllist=[rectcells,g.transform(rectcells,0,0,1,0,0,-1)]
normlist=[]
normrect=[]
for ii,obj in enumerate(objlist):
  sorted=g.evolve(obj,0)
  normlist+=[g.transform(sorted,-sorted[0],-sorted[1])]
  rect=g.evolve(rectcelllist[ii],0)
  normrect+=[[rect[0]-sorted[0],rect[1]-sorted[1],rect[6]-rect[0]+1,rect[7]-rect[1]+1]]

# offset=1000
# for obj in normlist:
#   g.putcells(obj, offset, 0)
#   offset+=100

g.putcells(searchobj,0,0,1,0,0,1,"xor")
all=g.getcells(g.getrect())
g.putcells(searchobj,0,0,1,0,0,1,"xor")

allcells=set()
step=len(all)%2+2
for i in range(0,len(all)-1,step):
  allcells.add(tuple(all[i:i+step])) # simplified version of two-state/multistate test

count=0
for ii, obj in enumerate(normlist):
  g.show("Object orientation "+str(count))
  g.update()
  count+=1
  # g.note(str(obj))
  for cell in allcells: # go through each cell in the pattern, check if there's a perfect match
    offsetobj=[]

    # separate search for two-state and multistate rules
    if len(obj)%2:
      # list has odd parity -- must be a multistate list, so break into groups of three
      for i in range(0,len(obj)-1,3):
        offsetobj+=[(obj[i]+cell[0],obj[i+1]+cell[1],obj[2])]
    else:
      # list has even parity -- must be a two-state list, so break into groups of two
      for i in range(0,len(obj),2):
        offsetobj+=[(obj[i]+cell[0],obj[i+1]+cell[1])]
    # g.note(str(offsetobj))
    match=1
    for c in offsetobj:
      if c not in allcells:
        match=0
        break

    if match==1:
      rect=normrect[ii][:]
      rect[0:2]=[rect[0]+cell[0],rect[1]+cell[1]]
      # Don't match if number of cells in selection differs
      if len(g.getcells(rect))==len(obj):
        g.select(rect)
        g.fitsel()
        g.exit("Found something.")
g.show("No matches found")
The latest version of the 5S Project contains over 226,000 spaceships. There is also a GitHub mirror of the collection. Tabulated pages up to period 160 (out of date) are available on the LifeWiki.

User avatar
gameoflifemaniac
Posts: 1205
Joined: January 22nd, 2017, 11:17 am
Location: There too

Re: Golly scripts

Post by gameoflifemaniac » September 15th, 2018, 2:43 pm

Nathaniel wrote:I got sick of calculating it by hand, so I made a Python script that calculates the heat of the current pattern (oscillator or spaceship) over a user-specified number of generations. It also outputs the maximum and minimum changes that were noticed when going from one generation to the next.

heat.py

Code: Select all

#Golly Python script
#Written by Nathaniel Johnston
#March 28, 2009

''' Calculate the heat of the current oscillator/spaceship '''       

from glife import getstring, validint 
import golly as g

def chunks(l,w):
    for i in xrange(0, len(l), 2):
        yield l[i]+(w*l[i+1])

if g.empty(): g.exit("The pattern is empty.")
s = g.getstring("Enter the period:","", "Heat calculator")

if not validint(s): 
  g.exit('Bad number: %s' % s)
numsteps = int(s)
if numsteps < 2:
  g.exit('Period must be at least 2.')

g.show('Processing...')

heat = 0;
maxheat = 0;
minheat = 9*g.getpop();

for i in range(0, numsteps):
  bb = g.getrect()
  clist = list(chunks(g.getcells(bb), bb[2]+2))
  g.run(1)
  dlist = list(chunks(g.getcells(g.getrect()), bb[2]+2))
  theat = (len(clist)+len(dlist)-2*len([x for x in set(clist).intersection( set(dlist) )]))
  heat += theat
  maxheat = max(theat, maxheat)
  minheat = min(theat, minheat)

g.show('Heat: %.4f, Max change: %d, Min change: %d' % ((float(heat)/numsteps), maxheat, minheat))
Can someone modify the script so that it finds the period of an object itself?
I was so socially awkward in the past and it will haunt me for the rest of my life.

Code: Select all

x = 21, y = 5, rule = B3/S23
b2o2bo2b2o2b2o2bobobo$o3bobobobobobobobobo$bo2bobob2o2b2o3bo2bo$2bobob
obobobobo2bo$2o3bo2bobobobo2bo2bo!

User avatar
dvgrn
Moderator
Posts: 7293
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Golly scripts

Post by dvgrn » September 15th, 2018, 2:51 pm

gameoflifemaniac wrote:Can someone modify the script so that it finds the period of an object itself?
This can be done by starting with oscar.py, copy-and pasting Nathaniel's script into a subroutine, and calling that subroutine after the line

Code: Select all

g.show("Oscillator detected (period = " + str(period) + ")")
in oscar.py. Try it! You'll probably find all kinds of new ways to get Python errors for a while, but if you look each of them up on Google, eventually you'll fix them all and the script will work.

(If you want someone else to do the coding, you'd be better off posting in the Script request thread. This one is kind of supposed to be for scripts that you complete yourself.)

M. I. Wright
Posts: 372
Joined: June 13th, 2015, 12:04 pm

Re: Golly scripts

Post by M. I. Wright » October 13th, 2018, 6:36 pm

Not sure if it's been done already, but here's a Lua thing to match a 3x3 selection to its Hensel-notation representation. Should come in handy when twiddling with a rule's individual r4r transitions.

match-napkin.lua

Code: Select all

local g = golly()

local hashes = {
    [31415962]      = "0 (birth)",           [530404524]     = "0 (survival)",
    [529404522]     = "1c (birth)",          [912400124]     = "1c (survival)",
    [529404523]     = "1e (birth)",          [189020161]     = "1e (survival)",

    [911400120]     = "2c (birth)",          [-71026502]     = "2c (survival)",
    [189020160]     = "2e (birth)",          [-571917758]    = "2e (survival)",
    [912400127]     = "2k (birth)",          [-208510179]    = "2k (survival)",
    [911400123]     = "2a (birth)",          [2057800913]    = "2a (survival)",
    [192020168]     = "2i (birth)",          [-1296297726]   = "2i (survival)",
    [909400118]     = "2n (birth)",          [-207510176]    = "2n (survival)",

    [-70026498]     = "3c (birth)",          [-726800124]    = "3c (survival)",
    [-571917759]    = "3e (birth)",          [-1264981609]   = "3e (survival)",
    [1923317244]    = "3k (birth)",          [1090604798]    = "3k (survival)",
    [2057800912]    = "3a (birth)",          [1964322194]    = "3a (survival)",
    [2058800913]    = "3i (birth)",          [-305988905]    = "3i (survival)",
    [-71026503]     = "3n (birth)",          [-723800113]    = "3n (survival)",
    [-70026497]     = "3y (birth)",          [-726800123]    = "3y (survival)",
    [2060800923]    = "3q (birth)",          [1239942225]    = "3q (survival)",
    [2057800914]    = "3j (birth)",          [1240942228]    = "3j (survival)",
    [2060800920]    = "3r (birth)",          [1239942226]    = "3r (survival)",

    [-864283798]    = "4c (birth)",          [-854086744]    = "4c (survival)",
    [-546601662]    = "4e (birth)",          [-1380048556]   = "4e (survival)",
    [-1444180072]   = "4k (birth)",          [530194514]     = "4k (survival)",
    [-305988908]    = "4a (birth)",          [-1273047602]   = "4a (survival)",
    [-1447180078]   = "4i (birth)",          [531194516]     = "4i (survival)",
    [-306988909]    = "4n (birth)",          [-1270047595]   = "4n (survival)",
    [-5420165]      = "4y (birth)",          [-545157811]    = "4y (survival)",
    [1965322196]    = "4q (birth)",          [522807454]     = "4q (survival)",
    [518562269]     = "4j (birth)",          [1606159803]    = "4j (survival)",
    [1964322193]    = "4r (birth)",          [525807463]     = "4r (survival)",
    [-306988910]    = "4t (birth)",          [-1270047596]   = "4t (survival)",
    [518562270]     = "4w (birth)",          [1606159800]    = "4w (survival)",
    [143423708]     = "4z (birth)",          [296883550]     = "4z (survival)",

    [1797604814]    = "5c (birth)",          [2035211896]    = "5c (survival)",
    [-483441751]    = "5e (birth)",          [-1928248821]   = "5e (survival)",
    [1222852148]    = "5k (birth)",          [-1529110102]   = "5k (survival)",
    [-1883842740]   = "5a (birth)",          [-1187321398]   = "5a (survival)",
    [1437399681]    = "5i (birth)",          [492115963]     = "5i (survival)",
    [-1318332495]   = "5n (birth)",          [-1169857965]   = "5n (survival)",
    [1080224867]    = "5y (birth)",          [1309831925]    = "5y (survival)",
    [-1773986061]   = "5q (birth)",          [235486005]     = "5q (survival)",
    [-1102388650]   = "5j (birth)",          [147168548]     = "5j (survival)",
    [-1773986064]   = "5r (birth)",          [235486006]     = "5r (survival)",

    [386020]        = "6c (birth)",          [-968761566]    = "6c (survival)",
    [-758004836]    = "6e (birth)",          [-1248825190]   = "6e (survival)",
    [568494839]     = "6k (birth)",          [-2003852311]   = "6k (survival)",
    [-648148160]    = "6i (birth)",          [173982214]     = "6i (survival)",
    [-1023075437]   = "6a (birth)",          [1159834581]    = "6a (survival)",
    [-1246782566]   = "6n (birth)",          [394705048]     = "6n (survival)",

    [256455398]     = "7c (birth)",          [-1368619556]   = "7c (survival)",
    [-1084579229]   = "7e (birth)",          [-762027739]    = "7e (survival)",

    [-1665406922]   = "8 (birth)",           [1241619884]    = "8 (survival)",
}

local selrect = g.getselrect()
if #selrect == 0 or selrect[3] ~= 3 or selrect[4] ~= 3 then
    g.exit("Select a 3x3 patch of cells first.")
end

for _=1, 4 do
    for _=1, 2 do
        local nbhd = hashes[g.hash(selrect)]
        if nbhd ~= nil then
            g.show(nbhd)
        end
        g.flip(1)
    end
    g.rotate(1)
end


Hashes were gathered by running another script over these two transcriptions of the table I'd had laying around.

Code: Select all

x = 27, y = 39, rule = B/S012345678
3bo2bob2ob2obobo2bo2b5o$bo2bo2bo2bo2bob12o$9bo2bobob11o$4bo2bo2bo2bobo
b2ob2obo$4bob2ob6obo2b5o$13bob9o$6bo2bo2bobob2ob2o$7b2ob4ob2ob2o$10bo
2bobob4o$6b2ob2ob3o2bo2bo$7bob2o2b2ob5o$15b6o$7bob4obo3bobo$7bo2bob9o$
7bo7b4obo$6bo2bob4obo2b2o$7bo2b2obob2ob3o$8bo3bo2b5o$9bob2obobo$10bo2b
5o$10bobo2bobo$9b2ob2o3bo$10bob2ob3o$11bo2b3o$9b2ob2o3bo$10b2ob4o$13bo
b3o$9b2ob2o3bo$10bob6o$10bo4bobo$12b3o$13bo$13bo$12b2o$13b2o$14bo$12b
2o$13bo$13b2o!

Code: Select all

x = 27, y = 39, rule = B/S012345678
3bo2bob2ob2obobo2bo2b5o$15bob2ob2ob2obo$9bo2bobob11o$4bo2bo2bo2bobob2o
b2obo$6bo2bob2obo5b2obo$13bob9o$6bo2bo2bobob2ob2o$8bo2b2o2bo2bo$10bo2b
obob4o$6b2ob2ob3o2bo2bo$9bo4bo2b2obo$15b6o$7bob4obo3bobo$12bob2ob2obo$
7bo7b4obo$6bo2bob4obo2b2o$11bo3bo2bobo$8bo3bo2b5o$9bob2obobo$14b2obo$
10bobo2bobo$9b2ob2o3bo$12bo2bobo$11bo2b3o$9b2ob2o3bo$11bo2b2o$13bob3o$
9b2ob2o3bo$12bob2obo$10bo4bobo$12b3o2$13bo$12b2o$14bo$14bo$12b2o2$13b
2o!
gamer54657 wrote:God save us all.
God save humanity.

hgkhjfgh
nutshelltlifeDiscord 'Conwaylife Lounge'

User avatar
Senso
Posts: 18
Joined: November 1st, 2018, 11:36 am
Location: I call that one "shoving match"

Re: Golly scripts

Post by Senso » November 22nd, 2018, 8:04 am

When trying a new rule or working on new CAs, I often want to generate many random soups in succession and see how they fare, to get a quick idea of pattern potentials, explosions, infinite growth, etc.

I would normally do Control-n, select a 20x20 grid, hit Control-5, lay on the space key and repeat. That got old quickly, especially having to draw the selection, so I made this script and bound it to Control-4.
It creates a random 16x16 selection and runs 200 generations, stopping if at any time the universe is empty.

Code: Select all

import time
import golly as g
import random
random.seed()

GRID_SIZE = (16, 16)
GENS = 200

def better_randfill():
    g.new('')
    g.select([-10, -10, GRID_SIZE[0], GRID_SIZE[1]])
    g.randfill(random.randrange(35, 55))
    g.select([])
    g.autoupdate(True)

    cnt = 0
    for x in range(0,GENS):

        cnt += 1
        if not g.empty():
            g.run(1)
            time.sleep(0.05)
        else:
            break
        if cnt % 10 == 0:
            g.fit()

better_randfill()
I haven't found a way to increase the delay between generations in order to get a better view of what happens (setstep() is ignored by run()).
Last edited by Senso on November 24th, 2018, 6:15 am, edited 2 times in total.

User avatar
dvgrn
Moderator
Posts: 7293
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Golly scripts

Post by dvgrn » November 22nd, 2018, 8:24 am

Senso wrote:I haven't found a way to increase the delay between generations in order to get a better view of what happens (setstep() is ignored by run()).
You could borrow various amounts of code from Golly's Scripts/Python/heisenburp.py if you want. In simplest form it's

Code: Select all

from time import sleep

delay = .1  # units are seconds
sleep(delay)
but the delay could be controlled from the keyboard while the script is running, as heisenburp.py does with the + and - keys. It doesn't exactly match Golly's behavior outside of the script. All the weird math in there with the transitions between the tickstep and delay variables is just trying to make a smooth transition between showing every generation (with variable delays) and skipping generations.

User avatar
Senso
Posts: 18
Joined: November 1st, 2018, 11:36 am
Location: I call that one "shoving match"

Re: Golly scripts

Post by Senso » November 22nd, 2018, 9:10 am

dvgrn wrote: You could borrow various amounts of code from Golly's Scripts/Python/heisenburp.py if you want. In simplest form it's

Code: Select all

from time import sleep

delay = .1  # units are seconds
sleep(delay)
but the delay could be controlled from the keyboard while the script is running, as heisenburp.py does with the + and - keys. It doesn't exactly match Golly's behavior outside of the script. All the weird math in there with the transitions between the tickstep and delay variables is just trying to make a smooth transition between showing every generation (with variable delays) and skipping generations.
Strangely, I did originally used time.sleep() between each generation run but it made Golly not terminate the script for 5-10 seconds after it was done. Nothing happening, generation 200 reached, but Golly's state is still in "script running", with the Stop button showing in the top-left. I thought time.sleep() might be messing with Golly internal loops and ditched that idea.

User avatar
dvgrn
Moderator
Posts: 7293
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Golly scripts

Post by dvgrn » November 22nd, 2018, 4:47 pm

Senso wrote:Strangely, I did originally used time.sleep() between each generation run but it made Golly not terminate the script for 5-10 seconds after it was done. Nothing happening, generation 200 reached, but Golly's state is still in "script running", with the Stop button showing in the top-left. I thought time.sleep() might be messing with Golly internal loops and ditched that idea.
I've definitely seen Golly behave significantly differently on different systems, particularly in regard to screen updates. For example, Golly on my current (newer) laptop doesn't update the screen reliably when I hold down the Space bar or Ctrl-Z, so the screen seems to freeze between updates. On other systems Space or Ctrl+Z allowed for smooth animation. Not that that's likely to be relevant here, just another example. I've never seen anything quite like what you're describing.

If you can get the same behavior back by re-adding time.sleep() calls, I'd be interested to try out that version of your script and see if it causes any trouble on my system. It really shouldn't be causing any particular trouble with Golly -- at least, no one has reported bad behavior related to time.sleep use in heisenburp.py (though maybe that's just because most people don't run it, or don't notice that you can change the speed, send new signals, etc. from inside the script.)

User avatar
Senso
Posts: 18
Joined: November 1st, 2018, 11:36 am
Location: I call that one "shoving match"

Re: Golly scripts

Post by Senso » November 23rd, 2018, 7:03 am

dvgrn wrote: If you can get the same behavior back by re-adding time.sleep() calls, I'd be interested to try out that version of your script and see if it causes any trouble on my system. It really shouldn't be causing any particular trouble with Golly -- at least, no one has reported bad behavior related to time.sleep use in heisenburp.py (though maybe that's just because most people don't run it, or don't notice that you can change the speed, send new signals, etc. from inside the script.)
Turns out it was my fault. I was doing:

Code: Select all

        if g.getpop() > 0:
            g.run(1)
            time.sleep(0.05)
        else:
            break
The Golly documentation seems to say that getpop() with no argument returns an int but it's actually not. So my script kept running until the 200th generation even when the universe was empty, explaining the ~8-10 seconds of "stalling". So casting getpop() to an int fixed my issue and I can now use time.sleep():

Code: Select all

        if int(g.getpop()) > 0:
            g.run(1)
            time.sleep(0.05)
        else:
            break

User avatar
dvgrn
Moderator
Posts: 7293
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Golly scripts

Post by dvgrn » November 23rd, 2018, 8:55 am

Senso wrote:The Golly documentation seems to say that getpop() with no argument returns an int but it's actually not. So my script kept running until the 200th generation even when the universe was empty, explaining the ~8-10 seconds of "stalling".
Makes sense -- thanks for the report. Yeah, it's not really safe for Golly to return an int for population, because a HashLife universe can so easily reach population levels that don't fit in an integer type.

User avatar
Andrew
Moderator
Posts: 808
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia
Contact:

Re: Golly scripts

Post by Andrew » November 24th, 2018, 2:43 am

Senso wrote:The Golly documentation seems to say that getpop() with no argument returns an int but it's actually not.
The first sentence in the getpop docs is "Return the current population as a string.". I'm a bit surprised that Python allows comparison of a string with an int -- seems a daft decision that is only likely to cause hard-to-find bugs. Anyway, if you only want to test if the population is non-zero then it's simpler and faster to use g.empty:

Code: Select all

if not g.empty():

User avatar
Senso
Posts: 18
Joined: November 1st, 2018, 11:36 am
Location: I call that one "shoving match"

Re: Golly scripts

Post by Senso » November 24th, 2018, 5:36 am

Andrew wrote: The first sentence in the getpop docs is "Return the current population as a string.". I'm a bit surprised that Python allows comparison of a string with an int -- seems a daft decision that is only likely to cause hard-to-find bugs. Anyway, if you only want to test if the population is non-zero then it's simpler and faster to use g.empty:

Code: Select all

if not g.empty():
Python 2 allows comparison of strings and int -- Python 3 doesn't. There's no reason not to move to Python 3. :)
Thanks for empty(), I didn't see that.

User avatar
simsim314
Posts: 1766
Joined: February 10th, 2014, 1:27 pm

Re: Golly scripts

Post by simsim314 » March 3rd, 2019, 5:29 am

Here is a script that tries to create converter from salvo of gliders to something there is already script for - well separated gliders. I think I've modified chris_c script and added something to it - not sure exactly what (probably the construction order is dynamic - and more effort is done to make it smaller).

Just wanted to drop it here, not sure I posted on the forum.

Code: Select all

import golly as g 

gld = g.parse("3o$2bo$bo!")

#ini_gld = g.parse("o$b2o$2o!", -90, 12)
#edge_shoot = g.parse("67b2o$67bobo$69bo$63b2o3bob2o$64bo3bo3bo$52b2o10bob2ob2obo$27b2o24bo11bobobobo$28bo13b2o6b3o$28bobo11b2o6bo$18bo10b2o$16b3o$15bo$15b2o$2o$bo$bob2o$2bo2bo$3b2o$18b2o$18b2o$56b2o$56b2o2$27bo3b2o$26bobo3bo5b2o3b2o$25bobo3bo7bo3bo$21b2obobo3bo5b3o5b3o$21b2obo2b4obo3bo9bo$25bobo3bobo$21b2ob2o2bo2bobo$22bobo2b2o3bo$10b2o10bobo$10b2o11bo!", -91, 8)

#ini_gld = g.parse("obo$b2o$bo!", -91, -15)
#edge_shoot = g.parse("$43b2o$43b2o5b2o$50b2o3$23bo5b2o17b2o$21b3o6bo17b2o$20bo9bobo21b2o$20b2o9b2o21b2o$5b2o$6bo$6bob2o$7bo2bo$8b2o$23b2o$23b2o4$32bo3b2o$31bobo3bo20bo$30bobo3bo19b3o$26b2obobo3bo19bo$26b2obo2b4obo17b2o$30bobo3bobo$26b2ob2o2bo2bobo$27bobo2b2o3bo$15b2o10bobo$15b2o11bo5$35b2o$35b2o9$50b2o$50b2o!", -90, -10)
#g.putcells(edge_shoot)
#g.putcells(ini_gld)

l_edge1 = g.parse("2$42b2o$42b2o5b2o$49b2o3$22bo5b2o17b2o$20b3o6bo17b2o$19bo9bobo21b2o$19b2o9b2o21b2o$4b2o$5bo$5bob2o$6bo2bo$7b2o$22b2o$22b2o4$31bo3b2o$30bobo3bo20bo$29bobo3bo19b3o$25b2obobo3bo19bo$25b2obo2b4obo17b2o$29bobo3bobo$25b2ob2o2bo2bobo$26bobo2b2o3bo$14b2o10bobo$14b2o11bo5$34b2o$34b2o9$49b2o$49b2o!", -117, 17)
l_gld1 = g.parse("obo$b2o$bo!", -119, 13)

l_edge2 = g.parse("41b2o$41b2o5b2o$48b2o3$21bo5b2o17b2o$19b3o6bo17b2o$18bo9bobo21b2o$18b2o9b2o21b2o$3b2o$4bo$4bob2o$5bo2bo$6b2o$21b2o$21b2o4$30bo3b2o$29bobo3bo$28bobo3bo$24b2obobo3bo$24b2obo2b4obo$28bobo3bobo$24b2ob2o2bo2bobo$25bobo2b2o3bo$13b2o10bobo$13b2o11bo2$59b2o$59bo$57bobo$57b2o$33b2o$33b2o6$35b2o$36bo$33b3o$33bo$34b2o$35bo$35bobo$36b2o5$34b2o$35bo$22b2o11bobo$23bo12b2o$23bobo$24b2o2b2o$28b2o4$23b2o4b2o$23b2o4b2o7$24b2o$23bobo$23bo$22b2o!", -86, 108 - 150)
l_gld2 = g.parse("obo$b2o$bo!", -89, 102 - 150)

l_edge3 = g.parse("2$43b2o$43b2o5b2o$50b2o3$23bo5b2o17b2o$21b3o6bo17b2o$20bo9bobo21b2o$20b2o9b2o21b2o$5b2o$6bo$6bob2o$7bo2bo$8b2o$23b2o$23b2o4$32bo3b2o$31bobo3bo$30bobo3bo$26b2obobo3bo$26b2obo2b4obo$30bobo3bobo$26b2ob2o2bo2bobo$27bobo2b2o3bo$15b2o10bobo$15b2o11bo2$61b2o$61bo$59bobo$59b2o$35b2o$35b2o6$37b2o$38bo$35b3o$35bo$36b2o$37bo$37bobo$38b2o5$36b2o$37bo$24b2o11bobo$25bo12b2o$25bobo$26b2o2b2o$30b2o4$25b2o4b2o$25b2o4b2o7$26b2o$25bobo$25bo$24b2o!", -88, 256 - 300)
l_gld3 = g.parse("$bobo$2b2o$2bo!", -90, 251-300)

l_edge4 = g.parse("12$69bo$67b3o$66bo$66b2o5$45bo$45b3o$48bo$47b2o$71b2o$28bo42b2o$28b3o$31bo50bo$18bo11b2o11b2o35b3o$16b3o24b2o34bo$15bo63b2o$15b2o$2o$bo$bob2o$2bo2bo$3b2o$18b2o$18b2o34b2o$53bobo$53bo$52b2o$27bo3b2o$26bobo3bo25b2o$25bobo3bo26b2o$21b2obobo3bo12b2o$21b2obo2b4obo10bo$25bobo3bobo10b3o$21b2ob2o2bo2bobo12bo$22bobo2b2o3bo$10b2o10bobo$10b2o11bo3b2o35b2o$27bobo35bo$28bo33b3o$62bo!", -108, 437-450)
l_gld4 = g.parse("$bo$2b2o$b2o!", -114, 453-450)

l_edge5 = g.parse("4$45bo$45b3o$41bo6bo$40bobo4b2o$40bobo$38b3ob2o20bo$37bo24b3o$31bo6b3ob2o17bo$31b3o6bob2o17b2o$34bo$21bo11b2o$19b3o36b2o$18bo38bo2bo$18b2o38b2o$3b2o$4bo$4bob2o$5bo2bo$6b2o$21b2o$21b2o23b2o$46bo$47b3o$49bo$30bo3b2o$29bobo3bo$28bobo3bo$24b2obobo3bo$24b2obo2b4obo$28bobo3bobo$24b2ob2o2bo2bobo$25bobo2b2o3bo$13b2o10bobo$13b2o11bo3b2o$30bobo$31bo!", -119, 635-600)
l_gld5 = g.parse("2$4bo$2bobo$3b2o!", -124, 636-600)

l_edge6 = g.parse("3$69b2o$69bobo$71bo$65b2o3bob2o$66bo3bo3bo$54b2o10bob2ob2obo$29b2o24bo11bobobobo$30bo13b2o6b3o$30bobo11b2o6bo$20bo10b2o$18b3o$17bo$17b2o$2b2o$3bo$3bob2o$4bo2bo$5b2o$20b2o$20b2o$58b2o$58b2o2$29bo3b2o$28bobo3bo5b2o3b2o$27bobo3bo7bo3bo$23b2obobo3bo5b3o5b3o$23b2obo2b4obo3bo9bo$27bobo3bobo$23b2ob2o2bo2bobo$24bobo2b2o3bo$12b2o10bobo$12b2o11bo!", -115, 777-750)
l_gld6 = g.parse("2$2bo$3b2o$2b2o!", -120, 776-750)

r_edge1 = g.parse("2$30b2o$29bo2bo$30bobo$29b2o2b2o$31b2o2bo$29b2o3b2o$30bo$29bo$30b3o$32bo6$14b2o$14b2o3$29b2o$28bobo$28bo$27b2o4$8b2o$9bo$9bobo$10b2o15b2o$27b2o3$10b2o$9bobo$9bo$8b2o3$6b2o$5bo2bo2b2o$6b2o2bobo$8b2o16b2o$8bo17bo$5b2obo2bo15b3o$5bob2obobo16bo$9bobo$6b2o2bo$4b3ob2o$3bo$4b3ob2o$6bob2o2$16b2o$16b2o7b2o$25bo$23bobo$23b2o4$3b2o$3b2o5$19bo$18bobo$18bobo$19bo$20b3o$22bo!", -64, 41)
r_gld1 = g.parse("b2o$2o$2bo!", -30, 118)

r_edge2 = g.parse("$30b2o$30bo$28bobo$28b2o$24bo$23bobo$23bobo$24bo8$13b2o$14bo18b2o$14bobo16bobo$15b2o18bo$35b2o2$28b2obo$28b2ob3o$34bo$28b2ob3o$29bobo$29bobo$30bo$6b2o$5bo2bo2b2o$6b2o2bobo13b2o$8b2o16bobo$2bo5bo19bo$bobob2obo2bo16b2o$2b2obob2obobo$9bobo$6b2o2bo$4b3ob2o$3bo$4b3ob2o$6bob2o2$16b2o$16b2o7b2o$25bo$23bobo$23b2o4$3b2o$3b2o5$19bo$18bobo$18bobo$19bo$20b3o$22bo!", 76-150, 56)
r_gld2 = g.parse("2o$obo$o!", 111-150, 121)

r_edge3 = g.parse("3$34b2o$34bo$32bobo$32b2o7$37b2o$37b2o2$49b2o$49bo$47bobo$47b2o$10b2o$9bobo$9bo$8b2o3$18b2o$18b2o3$23b2o$24bo$21b3o$21bo4$39b2o$39bobo$14b2o25bo$15bo25b2o$15bobo15b2o$16b2o15b2o10$14b2o$13bo2bo2b2o$14b2o2bobo13b2o$16b2o16bobo$10bo5bo19bo$9bobob2obo2bo16b2o$10b2obob2obobo$17bobo$14b2o2bo$12b3ob2o$11bo$12b3ob2o$14bob2o2$24b2o$24b2o7b2o$33bo$31bobo$31b2o4$11b2o$11b2o5$27bo$26bobo$26bobo$27bo$28b3o$30bo!", 252-300, 25)
r_gld3 = g.parse("2$2b2o$b2o$3bo!", 293-300, 111)

r_edge4 = g.parse("11$61bo$59b3o$58bo$58b2o4$83b2o$83b2o3$89b2o$89b2o$85b2o$85b2o4$90b2o$90b2o4$35b2o6b2o$35bo7bo5b2o15b2o$36b3o5b3obobo14bo2bo2b2o$38bo7bobo7b2o8b2o2bobo$47b2o7b2o10b2o$68bo$65b2obo2bo$27b2o36bob2obobo10b2o$27b2o3b2o35bobo11bo$32b2o32b2o2bo13b3o$64b3ob2o16bo$63bo$19b2o12b2o29b3ob2o$20bo6b2o4bo32bob2o$17b3o7b2o5b3o$17bo18bo39b2o$76b2o7b2o$85bo$83bobo$83b2o4$63b2o$63b2o5$79bo$78bobo$78bobo$79bo$80b3o$82bo!", 402-450, 17)
r_gld4 = g.parse("bo$2o$obo!", 497-450, 88)

r_edge5 = g.parse("10$45bo$43b3o$42bo$42b2o4$67b2o$67b2o3$73b2o$73b2o$69b2o$69b2o4$74b2o$74b2o4$19b2o6b2o$19bo7bo5b2o15b2o$20b3o5b3obobo14bo2bo2b2o$22bo7bobo7b2o8b2o2bobo$31b2o7b2o10b2o$52bo$49b2obo2bo$11b2o36bob2obobo10b2o$11b2o3b2o35bobo11bo$16b2o32b2o2bo13b3o$48b3ob2o16bo$47bo$3b2o12b2o29b3ob2o$4bo6b2o4bo32bob2o$b3o7b2o5b3o$bo18bo39b2o$60b2o7b2o$69bo$67bobo$67b2o4$47b2o$47b2o5$63bo$62bobo$62bobo$63bo$64b3o$66bo!", 568-600, 18)
r_gld5 = g.parse("bo$2o$obo!", 647-600, 88)

r_edge6 = g.parse("5$30b2o$30bo$28bobo$28b2o13b2o$43b2o3$7b2o40b2o$7b2o40b2o$45b2o$45b2o4$50b2o$50b2o5$26b2o$25bo2bo2b2o$17b2o7b2o2bobo$17b2o9b2o$28bo$25b2obo2bo$25bob2obobo10b2o$29bobo11bo$26b2o2bo13b3o$24b3ob2o16bo$23bo$24b3ob2o$26bob2o2$36b2o$36b2o7b2o$45bo$43bobo$43b2o4$23b2o$23b2o5$39bo$38bobo$38bobo$39bo$40b3o$42bo!", 681-750, 57)
r_gld6 = g.parse("bo$2o$obo!", 736-750, 118)

l_edge = [l_edge1,l_edge2,l_edge3,l_edge4,l_edge5,l_edge6,r_edge1,r_edge2,r_edge3,r_edge4,r_edge5,r_edge6]
l_gld = [l_gld1,l_gld2,l_gld3,l_gld4,l_gld5,l_gld6,r_gld1,r_gld2,r_gld3,r_gld4,r_gld5,r_gld6]

clock_insert = g.parse("52$306bob2o$306b2obo2$304b5o$304bo4bo2b2o$307bo2bo2bo$307b2obobo$304bo5bob2o$303bobo4bo$303bo2bo2b2o$304b2o9$296b2o$296b2o8$282b2o22b2o$281bo2bo21bo$282b2o23b3o$309bo6$299b2o$299bo$300b3o$302bo7$352bob2o$352b2obo2$350b5o$350bo4bo2b2o$353bo2bo2bo$353b2obobo$350bo5bob2o$228bo120bobo4bo$228b3o118bo2bo2b2o$224bo6bo118b2o$223bobo4b2o$223bobo$221b3ob2o20bo$220bo24b3o$214bo6b3ob2o17bo$214b3o6bob2o17b2o$217bo$204bo11b2o$202b3o36b2o99b2o$201bo38bo2bo98b2o$201b2o38b2o$186b2o$187bo$187bob2o$188bo2bo$189b2o$204b2o$204b2o23b2o97b2o22b2o$229bo97bo2bo21bo$230b3o95b2o23b3o45bob2o$232bo122bo45b2obo$213bo3b2o$212bobo3bo180b5o$211bobo3bo181bo4bo2b2o$207b2obobo3bo185bo2bo2bo$207b2obo2b4obo74bo108b2obobo$211bobo3bobo73b3o49b2o52bo5bob2o$207b2ob2o2bo2bobo69bo6bo48bo52bobo4bo$208bobo2b2o3bo69bobo4b2o49b3o49bo2bo2b2o$196b2o10bobo77bobo57bo50b2o$196b2o11bo76b3ob2o20bo$285bo24b3o$191bo87bo6b3ob2o17bo$189b3o87b3o6bob2o17b2o$175bo12bo93bo$164b2o7b3o12b2o79bo11b2o$165bo6bo94b3o36b2o$165bobo4b2o34bo57bo38bo2bo$166b2o38b3o57b2o38b2o83b2o$205bo45b2o138b2o$192b2o11b2o11bo33bo$192b2o24b3o31bob2o$221bo31bo2bo$220b2o32b2o$235b2o32b2o$235bo33b2o23b2o$232b2obo58bo56bo$231bo2bo60b3o53b3o23b2o22b2o$232b2o63bo49bo6bo21bo2bo21bo$217b2o59bo3b2o62bobo4b2o22b2o23b3o$181b2o34b2o58bobo3bo62bobo55bo$181bobo92bobo3bo61b3ob2o20bo$183bo88b2obobo3bo61bo24b3o$157b2o24b2o87b2obo2b4obo53bo6b3ob2o17bo$158bo45b2o3bo66bobo3bobo52b3o6bob2o17b2o$155b3o46bo3bobo61b2ob2o2bo2bobo55bo$155bo9b2o24b2o12bo3bobo61bobo2b2o3bo43bo11b2o53b2o$164bobo23bobo13bo3bobob2o45b2o10bobo49b3o36b2o28bo$164bo25bo13bob4o2bob2o45b2o11bo49bo38bo2bo28b3o$163b2o24b2o12bobo3bobo112b2o38b2o31bo$203bobo2bo2b2ob2o36b2o4bo50b2o$204bo3b2o2bobo28b2o7bobo2bobo50bo$212bobo10b2o17bo9b4o2bo49bob2o$213bo11b2o17bobo6bo4bobo50bo2bo$245b2o6b2o2b2ob2o50b2o$263bo63b2o$228b2o27b2ob4o2bo60b2o23b2o$228b2o27b2obo3b3o85bo$263bo89b3o$263b2o90bo$336bo3b2o$335bobo3bo$334bobo3bo$278bo51b2obobo3bo$278b3o49b2obo2b4obo$281bo52bobo3bobo$280b2o48b2ob2o2bo2bobo$295b2o34bobo2b2o3bo$295bo23b2o10bobo$292b2obo23b2o11bo$291bo2bo$218b2o72b2o$218b2o57b2o$277b2o2$251b2o$239b2o9bobo4b2o$239bobo8bo6bo6b2o3bo$241bo7b2o7b3o3bo3bobo$241b2o17bo4bo3bobo$266bo3bobob2o$264bob4o2bob2o$263bobo3bobo$263bobo2bo2b2ob2o$264bo3b2o2bobo$272bobo10b2o$273bo11b2o8$268b2o$268b2o$289bo$287b3o$286bo$286b2o165b2o$445b2o5bobo$306bo138bobo4bo$304b3o140bo2b2ob4o$303bo142b2obobobo2bo$290b2o11b2o11bo130bobobobo$290b2o24b3o128bobob2o$319bo128bo$318b2o$333b2o126b2o$258b2o73bo118b2o7bo$258b2o70b2obo118b2o5bobo$329bo2bo126b2o$330b2o$315b2o$279b2o34b2o$279bobo$281bo$281b2o$302b2o3bo141b2o18b2o$302bo3bobo141bo18bo$289b2o12bo3bobo137b3o21bo$288bobo13bo3bobob2o133bo2b3o14b5o$288bo13bob4o2bob2o135bo2bo13bo$287b2o12bobo3bobo138b2o2bobo12b3o$301bobo2bo2b2ob2o139b2o15bo$302bo3b2o2bobo154b4o$310bobo10b2o137b2o3bo3b2o$311bo11b2o137b2o4b3o2bo$470bob2o$447bo22bo$307bo18bo118b3o21b2o$307b3o6b2o6b3o117bo$310bo5b2o5bo120b2o$309b2o12b2o136b2o$461bo$462b3o$322b2o52bo87bo$317b2o3b2o50b3o$317b2o54bo$373b2o59b2o$433bobo5b2o$350bo42bo39bo7b2o$328bo7bo13b3o38b3o38b2o$326b3o5b3o16bo36bo$325bo7bo18b2o23b2o11b2o11bo42bo$325b2o6b2o42b2o24b3o36b2obobo$406bo34bobobobo$405b2o31bo2bobobob2o$420b2o16b4ob2o2bo$420bo21bo4bobo$417b2obo19bobo5b2o$416bo2bo20b2o$343b2o72b2o$343b2o57b2o$366b2o34b2o$366bobo$368bo$356b2o10b2o$356bo32b2o3bo$346b2o9b3o29bo3bobo$347bo11bo16b2o12bo3bobo$346bo28bobo13bo3bobob2o$346b2o27bo13bob4o2bob2o$374b2o12bobo3bobo$388bobo2bo2b2ob2o$389bo3b2o2bobo$397bobo10b2o$398bo11b2o220$431b2o11bo$431b2o10bobo$443bobo2b2o3bo$442b2ob2o2bo2bobo$446bobo3bobo12b2o$442b2obo2b4obo13bo27b2o$442b2obobo3bo13bobo28bo$446bobo3bo12b2o16bo11bo$447bobo3bo29b3o9b2o$448bo3b2o32bo$473b2o10b2o$474bo$474bobo$439b2o34b2o$439b2o57b2o$424b2o72b2o$423bo2bo$422bob2o$422bo$421b2o$436b2o$436bo$437b3o24b2o42b2o6b2o$394b2o43bo11b2o11b2o23b2o18bo7bo$386b2o5bobo56bo36bo16b3o5b3o$386bobo4bo55b3o38b3o13bo7bo$388bo2b2ob4o51bo42bo$387b2obobobo2bo$388bobobobo73b2o$388bobob2o75bo54b2o$389bo76b3o50b2o3b2o$466bo52b2o$402b2o$393b2o7bo$375bo17b2o5bobo115b2o12b2o$375b3o22b2o117bo5b2o5bo$378bo137b3o6b2o6b3o$377b2o137bo18bo2$503b2o11bo$369b2o132b2o10bobo$369bo145bobo2b2o3bo$366b2obo20b2o122b2ob2o2bo2bobo$366bo2b3o4b2o13bo126bobo3bobo12b2o$367b2o3bo3b2o10b3o123b2obo2b4obo13bo$369b4o15bo125b2obobo3bo13bobo$369bo15b2obo129bobo3bo12b2o$370b3o12bobob2o128bobo3bo$373bo13bo3bo128bo3b2o$368b5o14b3o2bo152b2o$368bo21b3o153bo$370bo18bo156bobo$369b2o18b2o120b2o34b2o$511b2o$496b2o$495bo2bo$494bob2o70b2o$494bo73b2o$493b2o$379b2o127b2o$378bobo5b2o120bo$378bo7b2o121b3o24b2o$377b2o132bo11b2o11b2o$524bo$391bo129b3o$387b2obobo128bo$386bobobobo$383bo2bobobob2o146b2o$383b4ob2o2bo148bo$387bo4bobo143b3o$385bobo5b2o143bo$385b2o171b2o$558b2o8$541b2o11bo$541b2o10bobo$553bobo2b2o3bo$552b2ob2o2bo2bobo$556bobo3bobo$552b2obo2b4obo$552b2obobo3bo$556bobo3bo4bo17b2o$557bobo3bo3b3o7b2o7bo$558bo3b2o6bo6bo8bobo$569b2o4bobo9b2o$575b2o2$549b2o$549b2o57b2o$534b2o72b2o$533bo2bo$495bo11b2o23bob2o$494bobo10b2o23bo$486bo3b2o2bobo34b2o$485bobo2bo2b2ob2o48b2o$485bobo3bobo52bo$486bob4o2bob2o49b3o$488bo3bobob2o51bo$487bo3bobo$486bo3bobo$486b2o3bo$472bo90b2o$472b3o89bo$475bo85b3o3bob2o27b2o$474b2o23b2o60bo2b4ob2o27b2o$499b2o63bo$514b2o50b2ob2o2b2o6b2o$513bo2bo50bobo4bo6bobo17b2o11bo$514b2obo49bo2b4o9bo17b2o10bobo$517bo50bobo2bobo7b2o28bobo2b2o3bo$517b2o50bo4b2o36b2ob2o2bo2bobo$430bo31b2o38b2o112bobo3bobo12b2o24b2o$430b3o28bo2bo38bo49bo11b2o45b2obo2b4obo13bo25bo$433bo28b2o36b3o49bobo10b2o45b2obobo3bo13bobo23bobo$432b2o53b2o11bo43bo3b2o2bobo61bobo3bo12b2o24b2o9bo$487bo55bobo2bo2b2ob2o61bobo3bo46b3o$459b2o17b2obo6b3o52bobo3bobo66bo3b2o45bo$460bo17b2ob3o6bo53bob4o2bob2o87b2o24b2o$457b3o24bo61bo3bobob2o88bo$457bo20b2ob3o61bo3bobo92bobo$423bo55bobo62bo3bobo58b2o34b2o$423b3o23b2o22b2o4bobo62b2o3bo59b2o$426bo21bo2bo21bo6bo49bo63b2o$425b2o22b2o23b3o53b3o60bo2bo$476bo56bo58bob2o$532b2o23b2o33bo$557b2o32b2o$572b2o32b2o$571bo2bo31bo$572b2obo31b3o24b2o$575bo33bo11b2o11b2o$435b2o138b2o45bo$435b2o83b2o38b2o57b3o38b2o$519bo2bo38bo57bo34b2o4bobo$520b2o36b3o94bo6bo$545b2o11bo79b2o12b3o7b2o$545bo93bo12bo$517b2o17b2obo6b3o87b3o$518bo17b2ob3o6bo87bo$515b3o24bo$515bo20b2ob3o76bo11b2o$427b2o50bo57bobo77bobo10b2o$422b2o2bo2bo49b3o49b2o4bobo69bo3b2o2bobo$422bo4bobo52bo48bo6bo69bobo2bo2b2ob2o$419b2obo5bo52b2o49b3o73bobo3bobo$420bobob2o108bo74bob4o2bob2o$419bo2bo2bo185bo3bobob2o$419b2o2bo4bo181bo3bobo$424b5o180bo3bobo$609b2o3bo$423bob2o45bo122bo$423b2obo45b3o23b2o95b3o$475bo21bo2bo97bo$474b2o22b2o97b2o23b2o$622b2o$637b2o$636bo2bo$637b2obo$640bo$640b2o$585b2o38b2o$484b2o98bo2bo38bo$484b2o99b2o36b3o$610b2o11bo$610bo$582b2o17b2obo6b3o$583bo17b2ob3o6bo$580b3o24bo$580bo20b2ob3o$602bobo$596b2o4bobo$476b2o118bo6bo$471b2o2bo2bo118b3o$471bo4bobo120bo$468b2obo5bo$469bobob2o$468bo2bo2bo$468b2o2bo4bo$473b5o2$472bob2o$472b2obo7$525bo$525b3o$528bo$527b2o6$518bo$518b3o23b2o$521bo21bo2bo$520b2o22b2o8$530b2o$530b2o9$522b2o$517b2o2bo2bo$517bo4bobo$514b2obo5bo$515bobob2o$514bo2bo2bo$514b2o2bo4bo$519b5o2$518bob2o$518b2obo!", -519, -289)
gld_r_clock = g.parse("b2o$2o$2bo!", 119, 390)
gld_l_clock = g.parse("o$b2o$2o!", -332, -177)

for i in range(6):
	l_gld[i] = g.transform(l_gld[i], -512, -512)
	l_gld[i + 6] = g.transform(l_gld[i+6], 512, 512)


gld_clock = []
gld_clock.extend(gld_l_clock)
gld_clock.extend(gld_r_clock)


l_edge.append(clock_insert)
l_gld.append(gld_clock)

init_location = 75 
def is_there(x, y, gl):
	l = len(gl)
	for i in range(1, l, 2):
		xg = gl[i - 1]
		yg = gl[i]
		
		if g.getcell(x + xg, y + yg) == 0:
			return False
	
	for i in range(1, l, 2):
		xg = gl[i - 1]
		yg = gl[i]
		g.setcell(x + xg, y + yg, 0)
	return True
	

def	is_there_glider(x, y):
	for i in range(4):
		gl = g.evolve(gld, i)
		
		if is_there(x, y, gl):
			return i
			
	return -1
	
def find_and_remove_all():
	cells = g.getcells(g.getrect())
	
	l = len(cells)
	res = []
	
	for i in range(1, l, 2):
		x = cells[i - 1]
		y = cells[i]
		
		for k in range(-1, 2):
			for l in range(-1, 2):
				idx = is_there_glider(x + k, y + l)
				if idx >= 0:
					res.append((x + k, y + l, idx))

	return res

def find_all_glider_idx(gliders_in, ini_rect, count):
	
	gliders_in.sort(key=lambda tup: (1.01 * tup[0] + tup[1]))
	valids = []
	for g_i in range(len(gliders_in)):
		for edge_i in range(len(l_edge)):
			edge_shoot = l_edge[edge_i]
			ini_gld = l_gld[edge_i]
		
			g.new("")
			step_d = 128
			x, y, idx = gliders_in[g_i]
			g.putcells(edge_shoot, x, y)
			g.putcells(g.evolve(ini_gld, idx), x, y)

			for i in range(len(gliders_in)):
				if i == g_i: 
					continue 
					
				x, y, idx = gliders_in[i]
				g.putcells(g.evolve(gld, idx), x - step_d - 512, y + step_d + 512)

			pop = int(g.getpop())
			g.setstep(3)
			g.step()
			g.step()
			g.step()
			g.step()
			g.step()
			g.step()
			g.step()
			
			if count == len(g.getcells([ini_rect[0] + 2 * step_d, ini_rect[1] - 2 * step_d, ini_rect[2], ini_rect[3]])) and (pop ==  int(g.getpop()) or pop - 5 ==  int(g.getpop())):
				valids.append((g_i,edge_i))
				break 

	return valids

def recursive_search(gliders_in, ini_rect, count, depth_trials):
	g.show("remains glider " + str(count/ 10))
	g.update()
	
	valids = find_all_glider_idx(gliders_in, ini_rect, count)	
	
	if len(valids) == 0:
		return False
		
	if len(gliders_in) % 2 == 0:
		valids.reverse()

	g_i,edge_i = valids[0]
	
	if edge_i == 12:
		del valids[0]
		valids.append((g_i, edge_i))
	
	seq = []
	
	for g_i,edge_i in valids:
		if edge_i == 12 and depth_trials < 3:
			continue 
		else:
			depth_trials = 0 
			
		new_gliders_in = []
		for i in range(len(gliders_in)):
			if i == g_i: 
				continue 
			
			new_gliders_in.append(gliders_in[i])
			
		if len(new_gliders_in) > 0:
			recurse_seq, depth_trials = recursive_search(new_gliders_in, ini_rect, count - 10, depth_trials)
			
			if recurse_seq == False:
				depth_trials += 1 
				continue
			
			seq.append((g_i,edge_i))
			
			for j, edge_j in recurse_seq:
				if j >= g_i:
					seq.append((j + 1, edge_j))
				else:
					seq.append((j, edge_j))
			
			return seq, depth_trials
		else:
			return [(g_i,edge_i)], depth_trials
			
	if len(seq) == 0:
		return False, depth_trials
	
	return seq, depth_trials
			
ini_rect = g.getrect()
count = len(g.getcells(ini_rect))
gliders_in = find_and_remove_all()

seq, _ = recursive_search(gliders_in, ini_rect, count, 0)	

g.show(str(seq))

if seq == False:
	g.new("")
	for x, y, idx in gliders_in:
		g.putcells(g.evolve(gld, idx), x, y)
	g.exit("Failed to solve")

step_d = 250

for i,edge_i in seq:
	if edge_i == 12:
		step_d = 350
		
total_d = len(gliders_in) * step_d
g.new("")
total_l = total_d
total_r = total_d

for i,edge_i in seq:
	x, y, idx = gliders_in[i]
	edge_shoot = l_edge[edge_i]
	ini_gld = l_gld[edge_i]

	g.putcells(edge_shoot, x + total_d, y - total_d,)
	
	if edge_i < 6:
		g.putcells(g.evolve(ini_gld, idx), x, y - 2 * total_d)
	elif edge_i < 12:	
		g.putcells(g.evolve(ini_gld, idx), x + 2 * total_d, y)
	else:
		pass
		g.putcells(g.evolve(gld_r_clock, idx), x + 2 * total_d, y)
		g.putcells(g.evolve(gld_l_clock, idx),  x, y - 2 * total_d)
		
	total_d -= step_d
	

User avatar
dvgrn
Moderator
Posts: 7293
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Golly scripts

Post by dvgrn » March 3rd, 2019, 11:32 am

simsim314 wrote:Here is a script that tries to create converter from salvo of gliders to something there is already script for - well separated gliders. I think I've modified chris_c script and added something to it - not sure exactly what (probably the construction order is dynamic - and more effort is done to make it smaller).

Just wanted to drop it here, not sure I posted on the forum.
You did post it once, in the Construction Practice thread, but here is probably a better place for it.

It would probably help a lot if all of these utility scripts had a name on a header line -- just

# well-separated-gliders-to-salvo.py

or something like that -- and if that exact name also got consistently mentioned in the text of post. That would make it a bit easier to find scripts with at least a Google search.

The forums' search function has two horrible problems as far as script searching goes. One is that if you try to search for a word with hyphens in it, it falsely claims that the word is never used on the forums -- but if you take out the hyphens and search for the separate words, it can find what you're looking for after all.

The other is that searches don't find anything that's inside

Code: Select all

 boxes. (!)

I've started to make a habit of checking in scripts like this to [url=https://github.com/dvgrn/b3s23life]a new subfolder on GitHub[/url] whenever I finish one, or whenever I find myself having to dig one up from an old forum posting.  Maybe you could make a Utility Scripts area somewhere and do the same?  Or if you like the looks of my b3s23life repository, I'll certainly accept contributions.

User avatar
roolif
Posts: 12
Joined: November 18th, 2019, 7:59 pm

Re: Golly scripts

Post by roolif » November 23rd, 2019, 7:43 am

This is an alternative to toChangeState.lua

It uses a table of old/new values just outside topleft corner of the selected area. If not present, a null table is placed there and the program exits. Then, you can edit the third column, set the desired new states, and run the script again.

It checks for a valid table: a 2 x g.numstates() rectangle surrounded by a border of cells with g.numstates() value.

Enjoy !

Code: Select all

-- ReStater.lua
-- Change any state to any other in the current selection.
-- A table with the new state values is read from just outside topleft.
-- If not present, a template ( 1st 7 states, as in LifeHistory ) is placed there.
-- Author: roolif, Nov 2019.

local g = golly()
local gp = require "gplus"
local pattern = gp.pattern

local setcell = g.setcell
local getcell = g.getcell


function ChkStTbl( lft, top, max )
  ok = true
  for x = 1, 4 do
    ok = ok and max == getcell(lft-x,top,max)
    ok = ok and max == getcell(lft-x,top+max+1)
  end
  for st = 0, max-1 do
     ok = ok and max == getcell(lft-4,top+st+1)
     ok = ok and st == getcell(lft-3,top+st+1)
     ok = ok and max == getcell(lft-1,top+st+1)
  end
  return ok
end

local r = gp.rect(g.getselrect())
if r.empty then g.exit("There is no selection.") end
-- ttd: chk 4 LifeHistory rule

local maxstate = g.numstates() - 1

-- chk 4 State Table

npi = ChkStTbl( r.left, r.top, maxstate )

if npi then
   local NewState = {}
   for st = 0 ,maxstate do
     NewState[st] = getcell(r.left-2,r.top+st+1)
   end
   for row = r.top, r.bottom do
      for col = r.left, r.right do
         setcell(col, row, NewState[getcell(col,row)])
      end
   end
   else
      local tmplate = pattern("4F$F2.F$F2AF$F2BF$F2CF$F2DF$F2EF$4F$4F!")
      tmplate.put(r.left-4,r.top)
end

User avatar
gameoflifemaniac
Posts: 1205
Joined: January 22nd, 2017, 11:17 am
Location: There too

Re: Golly scripts

Post by gameoflifemaniac » December 7th, 2019, 10:15 am

Dvgrn and I made this script called diehard.lua:

Code: Select all

local g = golly()
local gp = require "gplus"
local selection = g.getselrect()
if #selection == 0 then
   g.exit("There is no selection.")
end
largestlifespanfoundsofar = 0
runpatterns = 0
founddiehards = 0
g.addlayer()
g.setlayer(0)
while ( true ) do
   g.new("")
   g.select(selection)
   g.randfill(37)
   g.run(2000)
   runpatterns = runpatterns+1
   if tonumber(g.getpop()) == 0 then
      founddiehards = founddiehards+1
      g.reset()
      count = 0
      while ( true ) do
         g.run(1)
         count = count+1
         if tonumber(g.getpop()) == 0 then
            if (count > largestlifespanfoundsofar) then
               largestlifespanfoundsofar = count
               g.show(""..runpatterns.." patterns run, found "..largestlifespanfoundsofar.."-generation diehard")
               g.reset()
               g.setlayer(1)
               g.dellayer()
               g.duplicate()
               g.setname(largestlifespanfoundsofar.."-generation diehard")
               g.update()
               g.setlayer(0)
            end
            break
         end
      end
   end
end
What can I do to make it look more professional?
I was so socially awkward in the past and it will haunt me for the rest of my life.

Code: Select all

x = 21, y = 5, rule = B3/S23
b2o2bo2b2o2b2o2bobobo$o3bobobobobobobobobo$bo2bobob2o2b2o3bo2bo$2bobob
obobobobo2bo$2o3bo2bobobobo2bo2bo!

Naszvadi
Posts: 703
Joined: May 7th, 2016, 8:53 am
Contact:

Re: Golly scripts

Post by Naszvadi » March 1st, 2020, 1:53 pm

Generating inputs for an extended gliders and oscillator database - just like Eppstein's. Parsing downloaded forum entries is so much fun! My average oneliner did the rest: (really, I am serious, fastmade):

Code: Select all

sed '/<div class="codebox">/,/<\/code><\/pre>/!d' viewtopic.php\?f=11* \
    |grep -Pzoe 'x *=[^<>]{1,9999}!\n*' \
    |perl -pe 's/\0/\n/g'|perl -pe 's/!x/!\nx/g'|perl -0 -pe 's/\n(?=[^x])/:/g' \
    | sed 's/:/\\n/;s/://g' | perl -pe 's/\t| //g'| grep -P '\\n[0-9boBO$]+!' \
    | sort -V | uniq \
    | perl -pe 's/x=(\d+),y=(\d+)/($1>$2)?"$1,$2,$&":"$2,$1,$&"/e' \
    | sort -V -s | sed 's/[^,]*,[^,]*,//'

## not part of the oneliner  J
Collected out all patterns from a thread I downloaded before, filtered keeping bo$ rle-s only, deduplicated and sorted on bounding box. Rather messy, gnuish, with antipatterns, but usable. I am aware about other tools p.ex. xmllint - failed on parsing invalid htmls.

User avatar
LaundryPizza03
Posts: 1057
Joined: December 15th, 2017, 12:05 am
Location: Unidentified location "https://en.wikipedia.org/wiki/Texas"

Re: Golly scripts

Post by LaundryPizza03 » March 2nd, 2020, 12:14 am

Naszvadi wrote:
March 1st, 2020, 1:53 pm
Generating inputs for an extended gliders and oscillator database - just like Eppstein's. Parsing downloaded forum entries is so much fun! My average oneliner did the rest: (really, I am serious, fastmade):

Code: Select all

sed '/<div class="codebox">/,/<\/code><\/pre>/!d' viewtopic.php\?f=11* \
    |grep -Pzoe 'x *=[^<>]{1,9999}!\n*' \
    |perl -pe 's/\0/\n/g'|perl -pe 's/!x/!\nx/g'|perl -0 -pe 's/\n(?=[^x])/:/g' \
    | sed 's/:/\\n/;s/://g' | perl -pe 's/\t| //g'| grep -P '\\n[0-9boBO$]+!' \
    | sort -V | uniq \
    | perl -pe 's/x=(\d+),y=(\d+)/($1>$2)?"$1,$2,$&":"$2,$1,$&"/e' \
    | sort -V -s | sed 's/[^,]*,[^,]*,//'

## not part of the oneliner  J
Collected out all patterns from a thread I downloaded before, filtered keeping bo$ rle-s only, deduplicated and sorted on bounding box. Rather messy, gnuish, with antipatterns, but usable. I am aware about other tools p.ex. xmllint - failed on parsing invalid htmls.
Which thread did you search, and which patterns did you find?

Code: Select all

x = 4, y = 3, rule = B3-q4z5y/S234k5j
2b2o$b2o$2o!
LaundryPizza03 at Wikipedia

The latest edition of new-gliders.db.txt and oscillators.db.txt have 31150 spaceships and 1205 oscillators from outer-totalistic rules. You are invited to help!

Naszvadi
Posts: 703
Joined: May 7th, 2016, 8:53 am
Contact:

Re: Golly scripts

Post by Naszvadi » March 2nd, 2020, 3:32 am

LaundryPizza03 wrote:
March 2nd, 2020, 12:14 am
Naszvadi wrote:
March 1st, 2020, 1:53 pm
Generating inputs for an extended gliders and oscillator database - just like Eppstein's. Parsing downloaded forum entries is so much fun! My average oneliner did the rest: (really, I am serious, fastmade):

Code: Select all

##
Collected out all patterns from a thread I downloaded before, filtered keeping bo$ rle-s only, deduplicated and sorted on bounding box. Rather messy, gnuish, with antipatterns, but usable. I am aware about other tools p.ex. xmllint - failed on parsing invalid htmls.
Which thread did you search, and which patterns did you find?
The 5s project thread. No new patterns. Basically, many contributions are on this forum as unstructured data, so extracting and organizing them upon some properties might be a challenge. An extended version of Eppstein's glider.db is a holy grail despite the increasing number of known spaceships in alien life-like isotropic rules.

My 2cents are only adding four columns: the maximal and minimal isotropic rule supported by a pattern - not touching the corresponding outer-totalistic columns (even if they are senseless for many patterns), and the von Neumann supported rules per pattern.
And I'd allow oscillators and pseudo still lifes as well.

For generating such db, there is a need for a cleartext pattern extractor, a deduplicator (posix CLI tools are more than enough, see my "work"), and golly/oscar2 with hardcoded period bound (like 10**5 or similar) for verification and determing speed and period. A typical caveat is an oscillator stamp collection.

User avatar
Ian07
Posts: 623
Joined: September 22nd, 2018, 8:48 am

Re: Golly scripts

Post by Ian07 » May 30th, 2020, 2:30 am

Has this ever happened to you?

Code: Select all

x = 621, y = 209, rule = B3/S23
598bo$596bobo$597b2o$610bo$601bo6b2o$602b2o5b2o8bo$601b2o15bo$478bo
139b3o$479b2o113bo19bo$478b2o115bo18bobo$593b3o18b2o3$508bobo$508b2o$
509bo76bo$483bo11bobo48bob2o3bo33b2o14bob2o11b3o$481bobo12b2o44bo3b2ob
o3bobo30b2o11bo3b2obo2b2o7bo$482b2o12bo44bobo9b2o43bobo8b2o8bo$542bo3b
2obo49bo3b2obo$546bob2o3b2o35b2o11bob2o2b2o$493b3o57bobo35b2o16b2o8b2o
$495bo57bo36bo27b2o$494bo125bo$616bo$610b2o3b2o$490bo102b3o13bobo3bobo
$490b2o103bo15bo$12bo476bobo102bo$13bo$11b3o587b2o$602b2o5b2o4b3o$166b
obo432bo6b2o5bo$15bobo148b2o442bo5bo$16b2o149bo8bo420b2o$16bo159bobo
417bobo$172bo3b2o420bo$9bo161bo$7bobo161b3o$8b2o5$obo$b2o$bo9$157bo$
157bobo$28bo128b2o$29b2o$28b2o4$158bobo$158b2o$159bo$33bo11bobo$31bobo
12b2o$32b2o12bo103$43b3o$45bo$44bo3$40bo$40b2o$39bobo3$157b2o$157bobo$
157bo4$176b2o$176bobo$176bo2$5bo$5b2o170bo$4bobo169b2o$176bobo2$173b2o
$8b2o14b3o145b2o$7bobo16bo147bo$9bo15bo2$16bo$16b2o149bo5b2o$15bobo
148b2o5bobo$166bobo4bo2$11b3o$13bo$12bo!
Manually adjusting Catagolue syntheses to fit with the incremental-to-continuous script can be a pain. But not with the Offset Standardi(s|z)er™!

Watch what happens when we take this synthesis and run it through the Offset Standardi(s|z)er™ first before using the incremental-to-continuous script:

Code: Select all

x = 609, y = 197, rule = B3/S23
586bo$584bobo$585b2o$598bo$589bo6b2o$590b2o5b2o8bo$589b2o15bo$460bo
145b3o$461b2o119bo19bo$460b2o121bo18bobo$581b3o18b2o3$490bobo$490b2o$
491bo82bo$465bo11bobo54bob2o3bo33b2o14bob2o11b3o$463bobo12b2o50bo3b2ob
o3bobo30b2o11bo3b2obo2b2o7bo$464b2o12bo50bobo9b2o43bobo8b2o8bo$530bo3b
2obo49bo3b2obo$534bob2o3b2o35b2o11bob2o2b2o$475b3o63bobo35b2o16b2o8b2o
$477bo63bo36bo27b2o$476bo131bo$604bo$598b2o3b2o$472bo108b3o13bobo3bobo
$472b2o109bo15bo$471bobo108bo2$589b2o$590b2o5b2o4b3o$589bo6b2o5bo$598b
o5bo$585b2o$584bobo$586bo4$12bo$13bo$11b3o2$142bobo$15bobo124b2o$16b2o
125bo8bo$16bo135bobo$148bo3b2o$9bo137bo$7bobo137b3o$8b2o5$obo$b2o7bo
133bo$bo9b2o129b2o$10b2o131b2o4$140bobo$140b2o$141bo$15bo11bobo$13bobo
12b2o$14b2o12bo103$25b3o$27bo$26bo2$152b2o$22bo129bobo$22b2o128bo$21bo
bo$5bo137b2o$5b2o135b2o9bo$4bobo137bo7b2o$152bobo2$149b2o$8b2o14b3o
121b2o$7bobo16bo123bo$9bo15bo2$16bo$16b2o125bo5b2o$15bobo124b2o5bobo$
142bobo4bo2$11b3o$13bo$12bo!
For just three easy payments of $19.99, you too can get your hands on an Offset Standardi(s|z)er™. Don't wait, call now!

Code: Select all

"""
This script is meant to be used in conjunction with the incremental-to-continuous script made by Chris Cain and Dave Greene:
https://github.com/dvgrn/b3s23life/blob/master/synthesis-tools/incremental-to-continuous-synthesis.py

This script converts the syntheses displayed on Catagolue object pages into a format understood by the script above.
To use it, paste a synthesis from Catagolue into Golly, run this script first, then run the incremental-to-continuous script

Created by Ian07, with the exception of the find_best_selection() function

Caveats:
-Vertical offset between steps still isn't allowed, but this shouldn't be a problem if you're copying the synthesis straight from Catagolue
-The script assumes that any 30-or-more-wide gap with no cells is a boundary between synthesis steps; if the synthesis in question involves an xWSS that has been converted into gliders
-If your synthesis contains especially large individual steps or especially large spaces, you may need to increase the maximum offset check
-Sometimes an "openClipboard failed" error appears. I don't know what causes this, but it doesn't appear to actually affect anything; just a minor annoyance.
-The actual incremental-to-continuous script isn't perfect, and tends to fail in situations involving intermediate oscillators and relatively large individual steps
"""

import golly as g

# Reset the universe so script works correctly
g.select(g.getrect())
g.copy()
g.new("output")
g.paste(0, 0, "or")

max_offset_check = int(g.getstring("Enter maximum offset to check for:","128"))

# Copied this function from the original script, with slight modifications:
def find_best_selection():
    r = g.getrect()
    all = g.getcells(r)
    sep = 33 # minimum possible offset is 30 empty columns + width of a single glider, no need to check for lower values
    # - run the pattern for 4096 ticks, get the new settled pattern
    # - try XORing new pattern with original pattern for every possible offset up to 512
    # - one of the offsets should give the lowest total population
    #      (will probably decrease the population instead of increasing it,
    #       unless what is being built is a prolific puffer or gun or some such)
    bestscore, bestsep = len(all),-1  # = population * 2
    allplus = g.evolve(all, 4096)
    g.addlayer()
    while sep<=max_offset_check:
        g.show("Finding stage spacing -- testing " + str(sep))
        g.new("sep=" + str(sep))
        g.putcells(all)
        g.putcells(allplus,sep,0,1,0,0,1,"xor")
        score = int(g.getpop())
        if bestscore>score: bestscore, bestsep = score, sep
        sep += 1
    g.dellayer()
    
    sep = bestsep
    g.show("found separation: " + str(sep))
    bestblockscore, bestoffset = -999999, -1
    for offset in range(sep):
        g.select([r[0]-offset, r[1], sep, r[3]])
        g.update()
        blockscore = 0
        for blockx in range(r[0]-offset,r[0]+r[2],sep):
            g.select([blockx, r[1], sep, r[3]])
            blockrect = g.getselrect()
            block = g.getcells(blockrect)
            if len(block)==0:  # ran into empty block, this must not be the right separation
                g.exit("Invalid pattern format found at separation = " + str(sep) + ": selected block is empty.")
            g.shrink(1)
            shrunkblockrect = g.getselrect()
            leftdiff = shrunkblockrect[0]-blockrect[0]
            rightdiff = (blockrect[0]+blockrect[2])-(shrunkblockrect[0]+shrunkblockrect[2])
            blockscore += leftdiff + rightdiff
            if leftdiff<10: blockscore -= (10-leftdiff)**2
            if rightdiff<10: blockscore -= (10-rightdiff)**2
        if blockscore>bestblockscore: bestblockscore, bestoffset = blockscore, offset
    g.select([r[0]-bestoffset, r[1], r[2]+offset, r[3]])
    return sep

# Current "streak" of empty columns; if this reaches >=30 it deems that a new step because that's how Catagolue displays syntheses
empty_columns = 0

# Point at which the most recent step began
last_x = 0

width = g.getrect()[2]
height = g.getrect()[3]

# Each synthesis step, formatted as a list of cell lists
step_list = []

for column in range(width+1):
    g.select([column, 0, 1, height])
    
    if len(g.getcells(g.getselrect())) == 0:
        empty_columns += 1
    else:
        if empty_columns >= 30:
            g.select([last_x, 0, column - last_x - empty_columns, height])
            step_list.append(g.getcells(g.getselrect()))

            # Mark this as the start of the next synthesis step
            last_x = column

        empty_columns = 0

# Once we've reached the end, append the final step
g.select([last_x, 0, column - last_x, height])
step_list.append(g.getcells(g.getselrect()))

g.addlayer()
max_offset = 0

# Record the offset value between each pair of consecutive steps
offset_list = []

for i in range(len(step_list) - 1):
    g.putcells(step_list[i])
    g.putcells(step_list[i+1])

    current_offset = find_best_selection()
    offset_list.append(current_offset)
    max_offset = max(max_offset, current_offset)

    g.select(g.getrect())
    g.clear(0)

# Now let's put them all back together, this time with equal offset rather than equal separation
total_offset_so_far = 0

# The first fencepost
g.putcells(step_list[0])

for i in range(len(offset_list)):
    total_offset_so_far += max_offset - offset_list[i]
    g.putcells(step_list[i+1], total_offset_so_far)

#Reset universe again so next script works correctly
g.select(g.getrect())
g.copy()
g.new("output")
g.paste(0, 0, "or")

g.show("Offset standardization complete. Maximum offset: " + str(max_offset))

User avatar
LaundryPizza03
Posts: 1057
Joined: December 15th, 2017, 12:05 am
Location: Unidentified location "https://en.wikipedia.org/wiki/Texas"

Re: Golly scripts

Post by LaundryPizza03 » June 5th, 2020, 5:39 pm

This one can speed up the process of inflating a pattern:

Code: Select all

import golly as g
from glife import *

# inflate.py
# Automatically inflates the current pattern
# LaundryPizza03, 2020

try:
    scale = g.getstring("Scale factor")
    scale = int(scale)
except:
    g.exit("Bad integer value: {0}".format(scale))

P0 = g.getcells(g.getrect())
g.new('')
for x in range(scale):
    for y in range(scale):
        g.putcells(P0,x,y,scale,0,0,scale)

Code: Select all

x = 4, y = 3, rule = B3-q4z5y/S234k5j
2b2o$b2o$2o!
LaundryPizza03 at Wikipedia

The latest edition of new-gliders.db.txt and oscillators.db.txt have 31150 spaceships and 1205 oscillators from outer-totalistic rules. You are invited to help!

Post Reply