Golly scripts

For scripts to aid with computation or simulation in cellular automata.
AlbertArmStain
Posts: 1185
Joined: January 28th, 2022, 7:18 pm
Location: Planet Z

Re: Golly scripts

Post by AlbertArmStain » April 7th, 2023, 2:23 pm

I’ve requested a kind of script for golly similar to octohash, but completely independent. It was mentioned here, but I really don’t know where to start. As mentioned before, it will be able to take an unstable object/component and find a way to make a 1g seed for it without using splitters.
calcyman wrote:
April 2nd, 2023, 8:37 am
dvgrn wrote:
March 31st, 2023, 5:48 pm
However, it now seems unlikely that any synchronized perpendicular gliders will be needed at all, let alone clock-inserter recipes. The script will probably be able to build a spacefiller seed using a much smaller toolkit of turners and splitters. So now we're just working out exactly what needs to be in the script's toolkit.
Consider the following list of eight OTTs, arranged in increasing order of estimated cost. They're all slmake-constructible in all orientations:

Code: Select all

# [[ ZOOM 2 THEME Book WIDTH 800 HEIGHT 480 ]]
x = 366, y = 35, rule = B3/S23
352bo$353b2o$351b2o$353bo4$359bo$359b3o$362bo$361b2o$204bo46bo99bo$
203bobo44bobo97bobo11b2o$107b2o95bobo44b2o96bobo12b2o$51b2o53bobo49b2o
45b2o48b2o92b2o$52bo52bobo50b2o50b2o42bobo$52bobo51bo102bo2bo41b2o103b
2o$6bo46b2o154bo2bo93bo40b2o10bobo$5bobo155b2o45b2o93bobo39bobo10bo$6b
2o155b2o141b2o40bobo$349bo3$313b2o$3o47b3o47b3o47b3o47b3o47b3o47b3o9bo
bo35b3o$2bo49bo49bo49bo49bo49bo49bo8bobo38bo$bo49bo49bo49bo49bo49bo49b
o10bo38bo5$308b2o$307bo2bo$308bo2bo$309b2o!
I think that it's reasonable to do something like the following pseudocode:

Code: Select all

for each salvo:
    while salvo is nonempty:
        for each of the eight OTTs:
            for each glider in the salvo:
                if this glider can be built by that OTT (in either orientation) without interfering with the rest of the salvo:
                    subtract it from the salvo and jump to the 'while' statement.
This is using the same 'backward' approach as slmake, constructing the gliders in the salvo in reverse order. It's guaranteed to be successful, because the last OTT is just a single-glider version of the clock inserter, and we know that there's always a glider at the front of the salvo that can be inserted in this manner. By greedily choosing the first OTT in the list that works at each stage, it should be very rare (maybe so rare as to never be needed in the spacefiller synthesis!) that it falls back on the last OTT, but even that isn't terribly expensive*.

* the adjustable splitter is 3sL, so if we use the clock inserter then the cost for that glider is 9sL, just over twice the 4sL cost for a glider that's built using the cheapest mechanism (or 3x the absolute minimum if we can use a 2sL non-adjustable splitter).
Has anyone tried making a script at this request?

User avatar
calcyman
Moderator
Posts: 2920
Joined: June 1st, 2009, 4:32 pm

Re: Golly scripts

Post by calcyman » April 7th, 2023, 2:29 pm

AlbertArmStain wrote:
April 7th, 2023, 2:23 pm
I’ve requested a kind of script for golly similar to octohash, but completely independent. It was mentioned here, but I really don’t know where to start. As mentioned before, it will be able to take an unstable object/component and find a way to make a 1g seed for it without using splitters.
calcyman wrote:
April 2nd, 2023, 8:37 am
(quote elided)
Has anyone tried making a script at this request?
Why did you quote an entire post of mine from a different thread about something completely irrelevant to your script request?
What do you do with ill crystallographers? Take them to the mono-clinic!

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

Re: Golly scripts

Post by dvgrn » April 7th, 2023, 3:01 pm

AlbertArmStain wrote:
April 7th, 2023, 2:23 pm
Has anyone tried making a script at this request?
No.

The link you gave above outlined exactly how to make the script -- most of the code already exists, though admittedly it's my code so it's fairly terrible -- but it also lists a series of objections to the idea of bothering to make such a script.

Here would probably be a good place to explain how you expect the script to accomplish the "not limited to 3 objects" part of the description, without taking months or years for each individual search. As far as I can tell, that's what your idea amounts to: basically "octohash without a database".

User avatar
SuperSupermario24
Posts: 121
Joined: July 22nd, 2014, 12:59 pm
Location: Within the infinite expanses of the Life universe

Re: Golly scripts

Post by SuperSupermario24 » April 15th, 2023, 6:03 am

So I was looking through some of my Golly stuff and I realized I never actually posted this "random fill" script I made. So, uh, here it is:

Code: Select all

--[[
random-fill.lua
by SSM24, last updated 2020-11-16

This script fills the selection randomly, with density and states 
determined by the user.

This script is partially based off of the random-fill.py script by 
Andrew Trevorrow from the Golly Scripts Database, but contains the 
following additional features:

 - non-contiguous state ranges

 - ability to save the default input for each rule individually 
   rather than one for every single rule

 - non-integer density percentage values

 - state multipliers, allowing certain states to be more likely than 
   others

Additionally, it is significantly faster than the Python version, 
although Golly's native random fill is still faster.

Usage information:

 - Inputs are of the form "density, <states and ranges>".

 - Values are delimited by commas. Ranges are of the form min-max. 
   Multipliers are given with a colon (:) or lowercase x.

 - If no states are provided, every live state in the rule will be 
   used.

 - Spaces are optional. (They are immediately stripped from the input 
   if they are present.)

Example inputs:
50               - 50% density using every live state in the rule
20, 1            - 20% density using only state 1
100, 1-20        - 100% density using states 1 through 20
0.1, 1, 4-7, 10  - 0.1% density using states 1, 4 through 7, and 10
50, 1:20, 2      - 50% density with a 20x multiplier on state 1
100, 1-4x10, 5   - 100% density with a 10x multiplier on 1 through 4
]]

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

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

-- if true, save default inputs
local saveinputs = true

-- set to true if you want to benchmark the random fill process
-- might only work properly on Windows?
local benchmark = false

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

-- plain replace function because lua doesn't have one for some reason
function string.replace(s, orig, new)
    -- escape magic characters
    orig = orig:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%0")
    -- perform the subsitution
    return s:gsub(orig, new)
end

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

-- get the selection rectangle
local r = gp.rect(g.getselrect())
if r.empty then
    g.exit("There is no selection.")
end

local maxlive = g.numstates() - 1

local rule = g.getrule()

local prompt = [[
Enter density, followed by the states that should be included.

Separate ranges or individual values with commas. 
Use format min-max for ranges.
Leave states blank to use every state.]]

-- default is just "50" for 2-state rules
local default = "50"

-- if rule has more than 2 states, add a range
if g.numstates() > 2 then
    default = default..",".."1-"..maxlive
end

local inipath = g.getdir("data").."random-fill-lua.ini"

local rulefound = false

-- get previous used values for rule if they exist
if saveinputs then
    file = io.open(inipath, "r")
    if file then
        local line = ""
        -- check every line until rule is found or end of file is reached
        while line ~= nil do
            -- if rule is found, grab input and break from loop
            if line:find(rule) then
                local _,result = gp.split(line, "=")
                default = result
                rulefound = true
                break
            end
            line = file:read()
        end
        file:close()
    end
end

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

-- get user input and put it in a table
local input = g.getstring(prompt, default, "Randomly fill selection")

-- if blank, silently exit
if input == "" then g.exit() end

-- save input to ini file
if saveinputs then

    -- if rule was found earlier, overwrite its entry
    if rulefound then
    
        -- function so that pcall() can be used to handle write failure
        local function update()
        
            -- copy file to string
            file = io.open(inipath, "r")
            local contents = file:read("a")
            file:close()
            
            -- replace old line with new line
            contents = contents:replace(rule.."="..default, rule.."="..input)
            
            -- overwrite file with new contents
            file = io.open(inipath, "w+")
            file:write(contents)
            file:close()
        end
        
        -- show message if file could not be written to
        if not pcall(update) then
            g.warn("Could not save input to ini file.")
        end
    
    -- if rule was not found, add a new entry for it 
    -- this will create the file if it doesn't exist
    else
    
        local function update()
            file = io.open(inipath, "a")
            file:write(rule.."="..input.."\n")
            file:close()
        end
        
        if not pcall(update) then
            g.warn("Could not save input to ini file.")
        end
    end
end

 -- strip all spaces from the result
input = input:gsub(" ", "")

-- take the input, split it, and put it in a table
local results = table.pack(gp.split(input, ","))

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

-- get density and then remove it from table
local density = tonumber(results[1])
table.remove(results, 1)

-- check if density is valid
if density == nil then
    g.exit("Density must be a number.")
elseif density < 0 or density > 100 then
    g.exit("Density must be in the range 0-100.")
end

-- put density in range 0.0 - 1.0
-- math.random() is faster than math.random(100)
density = density / 100

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

-- use a range containing all states if states were blank
if #results == 0 then
    table.insert(results, "1-"..maxlive)
end

-- collect all states into a table
local states = {}

-- for every entry in the table
for i = 1, #results do
    
    -- get result
    local result = results[i]
    
    -- replace any lowercase Xs with a colon for parsing
    result = result:replace("x", ":")

    -- get the state/range and the multiplier
    local str, mult = gp.split(result, ":")
    
    -- if weight not provided, default to 1
    mult = mult or 1
    
    -- add to states table the number of times given by multiplier
    for _ = 1, mult do
    
        -- if it's a range, add every state within it
        if str:find("%d%-%d") ~= nil then
            
            -- get min and max values
            local minval, maxval = gp.split(str, "-")
            minval = math.tointeger(minval)
            maxval = math.tointeger(maxval)
            
            -- make sure both are valid integers
            if minval == nil or maxval == nil then
                g.exit("\""..str.."\" is not a valid state or range.")
            end
            
            -- make sure both are within range
            if minval < 0 or minval > maxlive 
            or maxval < 0 or maxval > maxlive then
                g.exit("Values must be in range 0-"..maxlive..".")
            end
            
            -- reverse if min > max for some reason
            local inc = 1
            if minval > maxval then inc = -1 end
            
            -- add states between min and max to table
            for j = minval, maxval, inc do
                table.insert(states, j)
            end
        
        -- if it's an individual value, add that value
        elseif math.tointeger(str) ~= nil then
        
            local num = math.tointeger(str)
            
            -- make sure value is within range
            if num < 0 or num > maxlive then
                g.exit("Values must be in range 0-"..maxlive..".")
            end
            
            -- add to states table
            table.insert(states, num)
        
        -- if any value is invalid, exit
        else
            g.exit("\""..str.."\" is not a valid state or range.")
        end
    end
end

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

--[[
-- debugging, lists all states in the states table
local str = ""
for i = 1, #states do
    str = str..tostring(states[i]).." "
end
g.show(str)
]]

local numstates = #states
local currstate = 0

local oldsecs = os.time()

-- function to update the view if enough time has passed
function checktime()
    local newsecs = os.time()
    if newsecs - oldsecs >= 1 then
        oldsecs = newsecs
        g.update()
    end
end

local starttime = os.clock()

-- the following is the generation of the random fill
--
-- it's done like this to minimize the amount of math.random() calls
-- in case of special conditions

-- if only one state, don't generate currstate every time
if numstates == 1 then
    currstate = states[1]
    -- if density is 100%, just fill the selection
    if density == 1.0 then
        for y = r.top, r.bottom do
            for x = r.left, r.right do
                g.setcell(x, y, currstate)
            end
            checktime()
        end
    -- otherwise, randomly fill it
    else
        for y = r.top, r.bottom do
            for x = r.left, r.right do
                if math.random() < density then
                    g.setcell(x, y, currstate)
                else
                    g.setcell(x, y, 0)
                end
            end
            checktime()
        end
    end
-- if more than one state, generate currstate every time
else
    -- if density is 100%, completely fill it
    if density == 1.0 then
        for y = r.top, r.bottom do
            for x = r.left, r.right do
                currstate = states[math.random(numstates)]
                g.setcell(x, y, currstate)
            end
            checktime()
        end
    -- otherwise, randomly fill it
    else
        for y = r.top, r.bottom do
            for x = r.left, r.right do
                if math.random() < density then
                    currstate = states[math.random(numstates)]
                    g.setcell(x, y, currstate)
                else
                    g.setcell(x, y, 0)
                end
            end
            checktime()
        end
    end
end

-- end of random fill generation

local endtime = os.clock()

if benchmark then
    local timetaken = string.format("%.3f", endtime-starttime)
    g.show("Took "..timetaken.." seconds.")
end
The comment at the top should hopefully contain all the info you need. As you can see I made this quite some time ago (it says updated 2020 but I think most of this was actually made in 2018). I think I was holding off on posting it until I implemented some additional features, but then I apparently never did and I don't even remember what they would've been at this point.

I don't plan on maintaining this or adding any additional features at this point, though of course anyone's free to add stuff on their own :p

Code: Select all

bobo2b3o2b2o2bo3bobo$obobobo3bo2bobo3bobo$obobob2o2bo2bobo3bobo$o3bobo3bo2bobobobo$o3bob3o2b2o3bobo2bo!

AlbertArmStain
Posts: 1185
Joined: January 28th, 2022, 7:18 pm
Location: Planet Z

Re: Golly scripts

Post by AlbertArmStain » May 1st, 2023, 7:14 am

calcyman wrote:
April 7th, 2023, 2:29 pm
Why did you quote an entire post of mine from a different thread about something completely irrelevant to your script request?
I forgot that it pings you, they were intended to be separated from each other, but it look like I’m still talking about the top on. I’m supposed to be talking about making a script for both ideas

User avatar
b3s23love
Posts: 93
Joined: May 24th, 2023, 6:30 am
Location: The (Life?) Universe

Re: Golly scripts

Post by b3s23love » September 22nd, 2023, 12:53 pm

This is a script I got from EvinZL in a PM that may help with setting up LLS searches.

Code: Select all

import golly as g

if len(g.getselrect()) == 0: g.exit("No selection")
sr = g.getselrect()
generations = int(g.getstring("How many generations back?"))

cell_arr = []
for y in range(sr[1], sr[1]+sr[3]):
    row = []
    for x in range(sr[0], sr[0]+sr[2]):
        row.append(g.getcell(x, y))
    cell_arr.append(row)


search_file = ""
def writecells(l):
    global search_file
    for _ in range(sr[2]+2):
        search_file += "0 "
    search_file += "\n"
    for i in cell_arr:
        search_file += "0 "
        for j in i:
            search_file += l[j] + " "
        search_file += "0\n"
    for _ in range(sr[2]+2):
        search_file += "0 "
    search_file += "\n\n"

writecells("0**00")
for _ in range(generations-1):
    writecells("0****")
writecells("01010")

g.setclipstr(search_file)
How to use it:
EvinZL wrote:
September 21st, 2023, 6:59 pm
Create the search in LifeHistory, with 1 and 3=cells that are on in the final state, others=cells that must be off in the final state, 1 and 2=cells that can be on in the starting state and the intermediate states, and 3, 4=cells that can be on in the intermediate states but not the starting state. All other cells must be off in all states. Select the search, then run it, and it will paste the LLS input to clipboard.
EvinZL wrote:
September 22nd, 2023, 12:16 pm
State 0: must be off in all generations
State 1: must be on in final generation, can be on in starting and intermediate generations
State 2: must be off in final generation, can be on in starting and intermediate generations
State 3: must be on in final generation, can be on in intermediate but NOT starting generation
State 4: must be off in final generation, can be on in intermediate but NOT starting generation
EvinZL used this script to generate the diehard-related LLS files.

User avatar
EvinZL
Posts: 838
Joined: November 8th, 2018, 4:15 pm
Location: A tungsten pool travelling towards the sun
Contact:

Re: Golly scripts

Post by EvinZL » October 13th, 2023, 7:45 pm

This is a script to download rule files from LifeWiki to Golly's rules folder

Code: Select all

import golly as g
import urllib.request
rulename = g.getstring("Rule name:", "StateInvestigator", "Get rule")
try:
    req = urllib.request.urlopen(f"https://conwaylife.com/w/index.php?title=Rule:{rulename}&action=raw")
except:
    g.exit("Rule not found")
rulefile = req.read()
path = g.getdir("rules") + rulename + ".rule"
with open(path, "wb") as f:
    f.write(rulefile)
g.setrule(rulename)
g.exit(f"Saved rule {rulename}")

User avatar
johamwit
Posts: 54
Joined: September 12th, 2021, 6:58 am

Re: Golly scripts

Post by johamwit » November 19th, 2023, 3:05 pm

EvinZL wrote:
October 13th, 2023, 7:45 pm
This is a script to download rule files from LifeWiki to Golly's rules folder

Code: Select all

import golly as g
import urllib.request
...
No urllib module in lua :(
Oh my God! -- it's full of search space!

User avatar
NimbleRogue
Posts: 506
Joined: January 11th, 2021, 11:48 pm

Re: Golly scripts

Post by NimbleRogue » November 27th, 2023, 1:43 am

May13 wrote:
August 26th, 2022, 2:06 am

My modification (I call it nbsearch2a) does not affect performance, but makes searching easier and supports bounded grids:
carsoncheng asked me to put my slight modification for ship searching here
All I did was add this if statement if g.getcells(g.getrect())!=testcells: to check if the thing is a ship, and it can be easily commented out when needed, and a simple symmetry Ship for only symmetries that support ships

Code: Select all

import golly as g
import math
import random
from timeit import default_timer as timer
from glife.text import make_text
from glife import getminbox, rect

autoRAB=True #automatically detect rule, algorithm and border
group_by_period=True
auto_exclude=50 #maximal number of oscillators of specific period (if group_by_period)
show_periods=True #print list of periods (if group_by_period)
auto_stop=True #stop if no more oscillators can be added (if group_by_period)
max_period=200
result_spacing=250
stab_step=8
x=15
main_fill=75
count_update=1000

if autoRAB:
    rule=g.getrule()
    algo=g.getalgo()
    if ":" in rule:
        rule,bound=rule.split(":")
        bound=":"+bound
    else: bound=""
else:
    rule=g.getstring("Rule:","B3/S23")
    algo=g.getstring("Algorithm:","QuickLife")
    bound=g.getstring("Bound:","T32,32")
if not (bound=="" or bound.startswith(":")): bound=":"+bound
s=g.getstring("Symmetry","All")

symm=0
if s!="All":
    symm=s

#symm can be either C1, C2_1, C2_2, C2_4, D2_x, D2_+1, D2_+2, C4_1, C4_4, D4_x1, D4_x4, D4_+1, D4_+2, D4_+4, D8_1, D8_4, All, Rot

max_period+=1
oc=[0]*max_period

if rule=="GlideLife":
    exclude_periods=[1,2,4,6,12,16]
if rule=="olife":
    exclude_periods=[1,2,3,4,5,6,9,10,12,15,18,20,26,30,35]
if rule=="B3/S23":
    exclude_periods=[1,2,3,6,8,4,5,10,15,30,14]
if rule in ["tlife","B3/S2-i34q"]:
    exclude_periods=[1,2,4,5,160]
if rule=="salad":
    exclude_periods=[1,2,4]
if rule=="B2inkce_S12":
    exclude_periods=[1,2,4]
if rule=="B3678/S235678":
    exclude_periods=[1,2,3,4,6,12,8]
if rule=="MoveIt":
    exclude_periods=[1,2,3,4,6,8,12,16,24,32,48]
if rule=="B35678/S357":
    exclude_periods=[1,2,6,4,3,8,12,35,5,14,24,13,10,15]
if rule=="B2-aS12":
    exclude_periods=[1,14,6,3,2,4,7,26,42,9,28,12,78,16,48,236,24,84,182,13,156,5,130,21,10,208,234,15,11,70,8,19]
if rule=="B2i35r_S023-a4i":
    exclude_periods=[1,2,4]
if rule=="B2in3S02-in3-n":
    exclude_periods=[1,4,5,8,7,6,20,12,28,2,9,16,36,10,42,72,14,56,3,40,63,30,140,45,35]
if rule=="B37/S2-i34q":
    exclude_periods=[1,2,5,4,3,20]
if rule=="B3/S235e":
    exclude_periods=[1,2,15,3,5,10,8,30,4,6,14,40]
if rule=="randomnn":
    exclude_periods=[20,4,14,7,28,140,84]
if rule=="Rotator":
    exclude_periods=[12,40,120,4,60,10,20,24,8]
if rule=="B2ein3/S13":
    exclude_periods=[1,2,4,6,5,10]
if rule=="PuffLife":
    exclude_periods=[1,2,4,8,3,6,16,12]
if rule=="B3-k/S2-i3-k4cen":
    exclude_periods=[1,2,5,4,3]
if rule=="B34ek5ak/S2-c34iz5y":
    exclude_periods=[1,2,4,6]
if rule=="B35/S2":
    exclude_periods=[2,4,1,3]
if rule=="B3568/S256":
    exclude_periods=[1,2,3,4,6,12,5,10,15,20,14,42]
if rule=="B356/S234i":
    exclude_periods=[1,2,4,6,12]
if rule=="b2ce3aiys12aei3r":
    exclude_periods=[1,2,4,6,7,8,10,12,14,15,24,26,28,29,30,58,42,62,94,126,138,170,186,202,234,266]
if rule=="B3-cnry4-acery5i/S23-a4-jknqr5y8":
    exclude_periods=[1,2,36,92,4,28,12,18,8]
if rule=="B3/S23-a4eiktz":
    exclude_periods=[1,2,4,5,10,78,7,14,9,36,3,6]
if rule=="B34e5e/S2-in3-y5jk":
    exclude_periods=[1,2,4,10,13,26,6,3]
if rule=="B2e3-r5i8/S23-a4kz7c":
    exclude_periods=[1,2,7,14,4]
if rule=="B3/S23-e4k":
    exclude_periods=[1,2,4,5,6,10,98,294,14,22,12]
if rule=="B34aq5c/S135":
    exclude_periods=[1,2,4,3,6,13,26,8,12,52,39]
if rule=="B2-a3/S1c23-ainr4cekn":
    exclude_periods=[1,2,4,12,31,6,62,8,5,10,124,28,20]
if rule=="B2-a3-in/S23":
    exclude_periods=[1,2,6,4,44,12,3,16,9,18,132,5,8,36,220,20,30,22,60]
if rule=="B2-a3-in/S235c":
    exclude_periods=[1,2,4,3,6,44,12,10,20,132,16,60,5,58,8]

exclude_periods=[]

def all_periods(q):
    if not show_periods: return ""
    gmin = gmax = 0
    text = "["
    for i in q+[0]:
        if i==0: text += str(gmin) + ("-"+str(gmax)+"]" if gmax>gmin else "]")
        elif gmin==0: gmin = gmax = i
        elif i-gmax==1: gmax = i
        else:
            text += str(gmin) + ("-"+str(gmax)+"," if gmax>gmin else ",")
            gmin = gmax = i
    return text

def osc_test():
    global exclude_periods
    if g.empty():
        return False
    testcells=g.getcells(g.getrect())
    testpop=g.getpop() # String representation
    testhash=g.hash(g.getrect())
    for i in range(1,max_period):
        g.run(1)
        if g.empty():
            return False
        if g.getpop()==testpop and g.hash(g.getrect())==testhash:
            if g.getcells(g.getrect())!=testcells:
                if i not in exclude_periods:
                    if auto_exclude==1: exclude_periods+=[i]
                    return i
                return 0
    return 0


def put_symm(cell_list,x0=0,y0=0,axx=1,axy=0,ayx=0,ayy=1,mode="or"):
    global symm
   
    if s=="All":
        symm=["C1", "C2_1", "C2_2", "C2_4", "D2_x", "D2_+1", "D2_+2", "C4_1", "C4_4", "D4_x1", "D4_x4", "D4_+1", "D4_+2", "D4_+4", "D8_1", "D8_4"][random.randrange(16)]
    if s=="Ship":
        symm=["C1", "D2_x","D2_+1", "D2_+2"][random.randrange(4)]
    if s=="Rot":
        symm=["C1", "C2_1", "C2_2", "C2_4", "C4_1", "C4_4"][random.randrange(6)]
   
    # g.putcells(cell_list,x0,y0,axx,axy,ayx,ayy,mode)
    if symm=="C2_1" or symm=="C4_1" or symm=="D4_+1" or symm=="D8_1" or symm=="D4_x1":
        g.putcells(cell_list,-x0,-y0,-axx,-axy,-ayx,-ayy,mode)
   
    if symm=="C4_1" or symm=="D8_1":
        g.putcells(cell_list,y0,-x0,ayx,ayy,-axx,-axy,mode)
        g.putcells(cell_list,-y0,x0,-ayx,-ayy,axx,axy,mode)
   
    if symm=="C2_2" or symm=="D4_+2":
        g.putcells(cell_list,-x0-1,-y0,-axx,-axy,-ayx,-ayy,mode)
   
    if symm=="C2_4" or symm=="C4_4" or symm=="D4_+4" or symm=="D8_4" or symm=="D4_x4":
        g.putcells(cell_list,-x0-1,-y0-1,-axx,-axy,-ayx,-ayy,mode)
   
    if symm=="D2_+1" or symm=="D8_1" or symm=="D4_+1":
        g.putcells(cell_list,-x0,y0,-axx,-axy,ayx,ayy,mode)
   
    if symm=="D4_+1" or symm=="D8_1" or symm=="D4_+2":
        g.putcells(cell_list,x0,-y0,axx,axy,-ayx,-ayy,mode)
   
    if symm=="D2_+2" or symm=="D4_+2" or symm=="D4_+4" or symm=="D8_4":
        g.putcells(cell_list,-x0-1,y0,-axx,-axy,ayx,ayy,mode)
   
    if symm=="D4_+4" or symm=="D8_4":
        g.putcells(cell_list,x0,-y0-1,axx,axy,-ayx,-ayy,mode)
   
    if symm=="C4_4" or symm=="D8_4":
        g.putcells(cell_list,y0,-x0-1,ayx,ayy,-axx,-axy,mode)
        g.putcells(cell_list,-y0-1,x0,-ayx,-ayy,axx,axy,mode)
   
    if symm=="D8_4":
        g.putcells(cell_list,-y0-1,-x0-1,-ayx,-ayy,-axx,-axy,mode)
   
    if symm=="D2_x" or symm=="D8_1" or symm=="D8_4" or symm=="D4_x1" or symm=="D4_x4":
        g.putcells(cell_list,y0,x0,ayx,ayy,axx,axy,mode)
   
    if symm=="D4_x1" or symm=="D8_1":
        g.putcells(cell_list,-y0,-x0,-ayx,-ayy,-axx,-axy,mode)
   
    if symm=="D4_x4" or symm=="D8_4":
        g.putcells(cell_list,-y0-1,-x0-1,-ayx,-ayy,-axx,-axy,mode)

def clear_layer():
    r = g.getrect()
    if r:
        g.select(r)
        g.clear(0)
    return

def main():
    global oc
    g.new("RandOsc")
    g.setrule(rule+bound)
    g.setalgo(algo)
    g.setbase(2)
    
    test_layer=g.getlayer()
    if g.numlayers()<g.maxlayers():
        results_layer=g.addlayer()
        g.setname('OscResults')
        g.setrule(rule)
        for i in range(max_period-1):
                t = make_text(str(i+1), "mono")
                t.put((i+1)*result_spacing,0)
        g.setlayer(test_layer)
    else:
        resultslayer=-1
    results=0
   
    count=0
    prevcount=0
    t_start=timer()
    t_prev=t_start
    while True:
        clear_layer()
       
        g.select([0,0,x,x])
        g.randfill(main_fill)
        cell_list=g.getcells([0,0,x,x])
        # g.clear(0)
        put_symm(cell_list)
        
        g.setstep(stab_step)
        g.step()
       	test=osc_test()
        if test>0:
            osc = g.getcells(g.getrect())
            if group_by_period:
                if oc[test]==0: results+=1
                if oc[test]<auto_exclude:
                    if results_layer>=0:
                        oc[test]+=1
                        g.setlayer(results_layer)
                        g.putcells(osc, result_spacing*test, result_spacing*oc[test])
                        g.setname('OscResults (%d)' % results)
                        g.fit()
                        g.update()
                        g.setlayer(test_layer)
                        g.setname('RandOsc (%d)' % results)
                    else:
                        return True
            else:
                results+=1
                if results_layer>=0:
                    g.setlayer(results_layer)
                    g.putcells(osc, result_spacing*results, 0)
                    g.setname('OscResults (%d)' % results)
                    g.fit()
                    g.update()
                    g.setlayer(test_layer)
                    g.setname('RandOsc (%d)' % results)
                else:
                    return True
       
        count+=1
        if count%count_update==0:
            t_end=timer()
            g.show("%d results found after %d soups tested (%d/sec current, %d/sec overall)" % (results, count, (count-prevcount)/(t_end-t_prev), (count)/(t_end-t_start))+" "+all_periods([i for i in range(max_period) if oc[i]>0]))
            g.select([])
            g.update()
            g.new("")
            g.setbase(2)
            t_prev=t_end
            prevcount=count
            if auto_stop and auto_exclude>0:
                if sum(oc)==(max_period-1-len([i for i in exclude_periods if i<max_period]))*auto_exclude: break
main()

Code: Select all

x = 4, y = 3, rule = B3-cnqy4e5kr6n7c/S2-i3-ay4einrtyz5cejn6cin78
bo$3o$ob2o!
14c/85265o

Code: Select all

x = 10, y = 4, rule = B2-an3-iqy4iknrtz5aijqy6aei78/S02ck3nqy4eiqrtwy5-ekq6-i78
2bo4bo$3b4o$ob6obo$2b6o!

User avatar
Tawal
Posts: 198
Joined: October 8th, 2023, 7:20 am

Re: Golly scripts

Post by Tawal » February 12th, 2024, 4:55 pm

Hey, these are my first steps with Python.
Please bear with me :P

About this 2 old scripts :
dvgrn wrote:
February 11th, 2024, 10:15 am
Tawal wrote:
February 11th, 2024, 9:17 am
About this 2 scripts :
save-bmp.py
save-image.py

Are they Python3 compatible ?
No. I don't believe any of those scripts have been updated since Golly moved to Python 3.x. An 'xrange' error is unambiguous evidence of a Python 2.x script.

https://python2to3.com/ can probably fix most such errors for you very quickly, but no guarantees. If anyone is willing to run through all of those old scripts and upgrade them to Python 3, I can work to get the old versions replaced.

Otherwise it might be simplest just to remove that page from conwaylife.com -- a GitHub or GitLab repo would probably be a better way to serve up that kind of content these days.
The site python2to3.com works fine :)
So, I've managed a script that produces a number (requested from the user) of image files of the selected pattern according to a generation increment (requested from the user).
The user is also asked where to save the files.

The original idea was to create a GIF but I don't have the knowledge to do that ...
If anyone would like to correct me and help me create the GIF file, they are most welcome.

At least you have all the image files to create a GIF with the software of your choice :)

Code: Select all

# Uses PIL (Python Imaging Library) to save the current selection or pattern
# in a user-specified image file (.png/.bmp/.gif/.tif/.jpg).
# See http://www.pythonware.com/products/pil/ for more info about PIL.
# Author: Andrew Trevorrow (andrew@trevorrow.com), Oct 2009.

import golly as g
import os.path
import webbrowser
import urllib.request, urllib.parse, urllib.error

try:
   from PIL import Image
except:
   g.exit("You need to install PIL (Python Imaging Library).")

### Rapid exit if iznogood ...
if g.empty(): g.exit("There is no pattern.")
# prevent Image.new allocating a huge amount of memory
prect = g.getrect()
srect = g.getselrect()
if len(srect) > 0: prect = srect    # save selection rather than pattern
wd = prect[2]
ht = prect[3]
if wd * ht >= 100000000:
   g.exit("Image area is restricted to < 100 million cells.")


#set initial directory for the save dialog
initdir = ""
savename = g.getdir("data") + "save-image.ini"
try:
   # try to get the directory saved by an earlier run
   f = open(savename, 'r')
   initdir = f.readline()
   f.close()
except:
   # this should only happen the very 1st time
   initdir = g.getdir("data")


# remove any existing extension from layer name and append .png
initfile = g.getname().split('.')[0] + ".png"


# prompt user for the number of images
gens = int( g.getstring("Enter the number of images : ", "", "Make a GIF") )

# prompt user for the step (one image each n genearations)
step = int( g.getstring("Enter the step \n  (one image each n genearations) :", "", "Make a GIF") )


# prompt user for output file (image type depends on extension)
outfile = g.savedialog("Make a GIF - Save image files",
                     "PNG (*.png)|*.png" ,
                     initdir, initfile)


# iteration 'step' by 'step' and get image
for nb_img in range(gens) :
   prect = g.getrect()
   srect = g.getselrect()
   if len(srect) > 0: prect = srect    # save selection rather than pattern
   x = prect[0]
   y = prect[1]
   wd = prect[2]
   ht = prect[3]

   # create RGB image filled initially with state 0 color
   multistate = g.numstates() > 2
   colors = g.getcolors()          # [0,r0,g0,b0, ... N,rN,gN,bN]
   im = Image.new("RGB", (wd,ht), (colors[1],colors[2],colors[3]))

   # get a row of cells at a time to minimize use of Python memory
   cellcount = 0
   for row in range(ht):
      cells = g.getcells( [ x, y + row, wd, 1 ] )
      clen = len(cells)
      if clen > 0:
         inc = 2
         if multistate:
            # cells is multi-state list (clen is odd)
            inc = 3
            if clen % 3 > 0: clen -= 1    # ignore last 0
         for i in range(0, clen, inc):
            if multistate:
               n = cells[i+2] * 4 + 1
               im.putpixel((cells[i]-x,row), (colors[n],colors[n+1],colors[n+2]))
            else:
               im.putpixel((cells[i]-x,row), (colors[5],colors[6],colors[7]))
            cellcount += 1
            if cellcount % 1000 == 0:
               # allow user to abort huge pattern/selection
               g.dokey( g.getkey() )

   if cellcount == 0:
      g.show("Pattern died toot early - Only get " + str(nb_img) + " images.")
      break

   g.show("Creating images...")

   outfile = outfile.split('.')[0]  + "." + str(nb_img) + ".png"
   if len(outfile) > 0:
      im.save(outfile)
      g.run(step)



# remember file's directory for next time
try:
   f = open(savename, 'w')
   f.write(os.path.dirname(outfile))
   f.close()
except:
   g.warn("Unable to save directory to file:\n" + savename)

g.reset()
Edit:
I used a hard coded version to make my avatar.

User avatar
Tawal
Posts: 198
Joined: October 8th, 2023, 7:20 am

Re: Golly scripts

Post by Tawal » February 16th, 2024, 2:59 pm

Tawal wrote:
February 12th, 2024, 4:55 pm
...
The original idea was to create a GIF but I don't have the knowledge to do that ...
If anyone would like to correct me and help me create the GIF file, they are most welcome.
...
Well, I've had a bit of a look at the Python language and I've realised what I was kindly asking for :)

Here's a script that creates an animated GIF file from a selected pattern. It's intended for small patterns rather than large ones.
It opens a window asking the user for 5 parameters:
- The duration of the GIF in tenths of a second.
- The number of captures to take from the selection.
- The incremental step of the generations.
- The number of the first capture to be inserted at the beginning of the GIF, creating a start delay.
- The number of the last capture to be added at the end of the GIF, creating an end delay.

On the Python side, the script uses Python 3, of course.
It also uses several libraries:
- PIL = Python Imaging Library
- tkinter
- ttk
- tkfont
I wrote the script on a Linux Debian 12 and it works very well for me.
There may be some modifications to make it work on Windows or MacOSX.

As this is really my very first script in Python, I'm quite happy with it.

Well, enough said, the script :

make-a-gif.py

Code: Select all

# Original Script : save-image..py
      # Uses PIL (Python Imaging Library) to save the current selection or pattern
      # in a user-specified image file (.png/.bmp/.gif/.tif/.jpg).
      # See http://www.pythonware.com/products/pil/ for more info about PIL.
      # Author: Andrew Trevorrow (andrew@trevorrow.com), Oct 2009.

# Modifications : make-a-gif.py
      # Always use PIL (see above).
      # Also use 'tkinter', 'ttk' and 'tkfont'.
      # See https://docs.python.org/fr/3/library/tk.html for more about tkinter, ttk and tkfont.
      # Now the script makes an animated GIF file from the selected pattern
      #+ and its evolving according to a generation step.
      # It has 5 user-provided parameters :
      #     - Duration : the duration of the GIF file in tenths of a second.
      #     - Number of images to capture from the selected pattern.
      #     - Step for the evolving pattern (must be an integer).
      #     - Starting Delay : Number of the first captured image
      #        to insert at the beginning of the GIF file.
      #     - Ending Delay : Number of the last captured image
      #        to append at the end of the GIF file.
      # The script creates a new working layer which is destroyed at the end.
      #+ So it doesn't touch the original pattern layer :)
      # The script stops to capture if the pattern dies. The GIF file is created yet.
      #+ This affects the start and end delays.
# Contributor : Tawal (https://conwaylife.com/forums - user : Tawal), Feb 2024.

import golly as g
import os.path
import urllib.request, urllib.parse, urllib.error

try:
   from PIL import Image
except:
   g.exit("You need to install 'PIL' (Python Imaging Library).")

try:
   import tkinter as tk
   from tkinter import ttk
   from tkinter import font
except:
   g.exit("You need to install 'tkinter' (Tkinter - Python Module.")

# Rapid exit if iznogood ...
srect = g.getselrect()
if len(srect) == 0:
   g.exit("There is no selection.")
x = srect[0]
y = srect[1]
wd = srect[2]
ht = srect[3]
# prevent Image.new allocating a huge amount of memory
if wd * ht >= 100000000:
   g.exit("Image area is restricted to < 100 million cells.")

# for all further normal exit
def clean_exit(msg):
   g.dellayer()
   g.exit(msg)

# get the rule's colors
multistate = g.numstates() > 2
colors = g.getcolors()          # [0,r0,g0,b0, ... N,rN,gN,bN]

# new working layer (deleted at the end of script).
initfile = g.getname().split('.')[0] + ".gif" # remove any existing extension from layer name and append .gif
g.copy()
gif_layer = g.addlayer()
g.setname(initfile)
g.paste(x, y, "or")
g.update()

#set initial directory for the save dialog
initdir = ""
savename = g.getdir("data") + initfile
try:
   # try to get the directory saved by an earlier run
   f = open(savename, 'r')
   initdir = f.readline()
   f.close()
except:
   # this should only happen the very 1st time
   initdir = g.getdir("data")


# Multi-entry window
end_script= False
def button_exit_clicked():
   global end_script
   in_win.destroy()
   end_script = True

def button_OK_clicked():
   global duration, n_im, step, start_delay, end_delay
   n_im = n_imSV.get()
   duration = durationSV.get()
   step = stepSV.get()
   start_delay = start_delaySV.get()
   end_delay = end_delaySV.get()
   in_win.destroy()

def OnValidate(S):
   if S.isdigit():
      return True
   return False

in_win = tk.Tk()
in_win.title("Make a GIF")
in_win.geometry('520x320')
in_win.eval('tk::PlaceWindow . center')
valid_inputs = (in_win.register(OnValidate), '%S')

intro_lbl = ttk.Label(in_win, underline=80, text="Make an animated GIF file from the selected pattern")
intro_f = font.Font(intro_lbl, intro_lbl.cget("font"))
intro_f.configure(underline=True, weight=font.BOLD)
intro_lbl.configure(font=intro_f)

duration_lbl = tk.Label(in_win, text="Duration of GIF   (x0.1s)   :  ")
durationSV = tk.StringVar()
duration_ask = tk.Entry(in_win, textvariable=durationSV, width=6, justify=tk.RIGHT, validate="key", vcmd=valid_inputs)

n_im_lbl = tk.Label(in_win, text="Number of captures to take from pattern   :  ")
n_imSV = tk.StringVar()
n_im_ask = tk.Entry(in_win, textvariable=n_imSV, width=6, justify=tk.RIGHT, validate="key", vcmd=valid_inputs)

step_lbl = tk.Label(in_win, text="Generation Step   (integer)   :  ")
stepSV = tk.StringVar()
step_ask = tk.Entry(in_win, textvariable=stepSV, width=6, justify=tk.RIGHT, validate="key", vcmd=valid_inputs)
step_ask.insert(0, "1")

start_delay_lbl = tk.Label(in_win, text="Number of first same frames   (starting delay)   :  ")
start_delaySV = tk.StringVar()
start_delay_ask = tk.Entry(in_win, textvariable=start_delaySV, width=6, justify=tk.RIGHT, validate="key", vcmd=valid_inputs)
start_delay_ask.insert(0, "0")

end_delay_lbl = tk.Label(in_win, text="Number of last same frames   (ending delay)   :  ")
end_delaySV = tk.StringVar()
end_delay_ask = tk.Entry(in_win, textvariable=end_delaySV, width=6, justify=tk.RIGHT, validate="key", vcmd=valid_inputs)
end_delay_ask.insert(0, "0")

button_exit = tk.Button(in_win, text="Exit", width=8, command=button_exit_clicked)
button_OK = tk.Button(in_win, text='Make GIF', width=8, command=button_OK_clicked)

in_win.columnconfigure(0, weight=1)
in_win.columnconfigure(1, weight=15)
in_win.columnconfigure(2, weight=15)
in_win.columnconfigure(3, weight=15)
in_win.columnconfigure(4, weight=1)
in_win.rowconfigure(0, weight=20)
in_win.rowconfigure(1, weight=2)
in_win.rowconfigure(2, weight=2)
in_win.rowconfigure(3, weight=2)
in_win.rowconfigure(4, weight=2)
in_win.rowconfigure(5, weight=2)
in_win.rowconfigure(6, weight=15)

intro_lbl.grid(column=0, row=0, columnspan=4)

duration_lbl.grid(column=1, row=1, columnspan=2, stick=tk.E)
duration_ask.grid(column=3, row=1, stick=tk.W)

n_im_lbl.grid(column=1, row=2, columnspan=2, stick=tk.E)
n_im_ask.grid(column=3, row=2, stick=tk.W)

step_lbl.grid(column=1, row=3, columnspan=2, stick=tk.E)
step_ask.grid(column=3, row=3, stick=tk.W)

start_delay_lbl.grid(column=1, row=4, columnspan=2, stick=tk.E)
start_delay_ask.grid(column=3, row=4, stick=tk.W)

end_delay_lbl.grid(column=1, row=5, columnspan=2, stick=tk.E)
end_delay_ask.grid(column=3, row=5, stick=tk.W)

button_OK.grid(column=3, row=6, sticky=tk.W)
button_exit.grid(column=1, row=6, sticky=tk.W, padx=50)

duration_ask.focus()
in_win.mainloop()

if end_script:         # exit if exit_button is clicked
   clean_exit('Exited')

# then prompt user for output GIF file.
outfile = g.savedialog("Make a GIF - Save image file",
                     "GIF (*.gif)|*.gif" ,
                     initdir, initfile)
if len(outfile) < 1 :     # exit if empty string or cancel choice
   clean_exit('Exited')

# create images list
g.show("Please wait while creating GIF file ...")
list_im = []

for k in range(int(n_im)):
   # create RGB image filled initially with state 0 color
   im = Image.new("RGB", (wd,ht), (colors[1],colors[2],colors[3]))

   # get a row of cells at a time to minimize use of Python memory
   cellcount = 0
   for row in range(ht):
      cells = g.getcells( [ x, y + row, wd, 1 ] )
      clen = len(cells)
      if clen > 0:
         inc = 2
         if multistate:
            # cells is multi-state list (clen is odd)
            inc = 3
            if clen % 3 > 0: clen -= 1    # ignore last 0
         for i in range(0, clen, inc):
            if multistate:
               n = cells[i+2] * 4 + 1
               im.putpixel((cells[i]-x,row), (colors[n],colors[n+1],colors[n+2]))
            else:
               im.putpixel((cells[i]-x,row), (colors[5],colors[6],colors[7]))
            cellcount += 1
            if cellcount % 1000 == 0:
               # allow user to abort huge pattern/selection
               g.dokey( g.getkey() )
   # stop to capture if pattern dies
   if cellcount == 0:
      g.show("Pattern died too early - Only get " + str(k) + " images.")
      break

   if k == 0:             # insert the frames for starting delay
      for j in range(int(start_delay)):
         list_im.append(im)
   else:
      list_im.append(im)
   g.run(int(step))

if int(end_delay) > 0:         # append the frames for ending delay
   for i in range(int(end_delay)):
         list_im.append(im)

# convert the images list in a GIF file
list_im[0].save(outfile,
               save_all=True, append_images=list_im[1:], optimize=True, duration=int(duration), loop=0)

# remember file's directory for next time
try:
   f = open(savename, 'w')
   f.write(os.path.dirname(outfile))
   f.close()
except:
   g.warn("Unable to remember save location and last used filename for next run !")

# clean and exit
clean_exit('Animated GIF created here : '+ outfile)
And what it looks like :
Image

Edit:
Clean version of script (not a draft :P)
And smaller image.

User avatar
otismo
Posts: 1185
Joined: August 18th, 2010, 1:41 pm
Location: Florida
Contact:

Re: Golly scripts

Post by otismo » February 17th, 2024, 12:09 pm

ThankYou Tawal for Sharing !

(also want to note that we have giffer.lua)
"One picture is worth 1000 words; but one thousand words, carefully crafted, can paint an infinite number of pictures."
- autonomic writing
forFUN : http://viropet.com
Art Gallery : http://cgolart.com
Video WebSite : http://conway.life

User avatar
May13
Posts: 760
Joined: March 11th, 2021, 8:33 am

Re: Golly scripts

Post by May13 » February 19th, 2024, 9:08 am

Version 0.3 of collector.py

Code: Select all

# collector.py
# Version 0.3
# Written by May13, December 2022 - February 2024

# This script contains modified part of script written by Nathaniel Johnston, June 2009.
# Source: https://conwaylife.com/forums/viewtopic.php?p=465#p465

import golly as g
from math import ceil

fontNumber = 0
space = 2
scale = 10
count = 100
exponential = False
mx = 30
my = 30

filename = g.getstring("Enter file name:","oscillators.txt")

# --------------------------------------------------------------------

def chunks(l, n):
    for i in range(0, len(l), n):
        yield l[i:i+n]

# --------------------------------------------------------------------

def giveRLE(rl_list):
   rle_res = ""
   rle_len = 1
   rl_y = rl_list[0][1] - 1
   rl_x = 0
   for rl_i in rl_list:
      if rl_i[1] == rl_y:
         if rl_i[0] == rl_x + 1:
            rle_len += 1
         else:
            if rle_len == 1: rle_strA = ""
            else: rle_strA = str (rle_len)
            if rl_i[0] - rl_x - 1 == 1: rle_strB = ""
            else: rle_strB = str (rl_i[0] - rl_x - 1)

            rle_res = rle_res + rle_strA + "o" + rle_strB + "b"
            rle_len = 1
      else:
         if rle_len == 1: rle_strA = ""
         else: rle_strA = str (rle_len)
         if rl_i[1] - rl_y == 1: rle_strB = ""
         else: rle_strB = str (rl_i[1] - rl_y)
         if rl_i[0] == 1: rle_strC = "b"
         elif rl_i[0] == 0: rle_strC = ""
         else: rle_strC = str (rl_i[0]) + "b"
         
         rle_res = rle_res + rle_strA + "o" + rle_strB + "$" + rle_strC
         rle_len = 1

      rl_x = rl_i[0]
      rl_y = rl_i[1]
   
   if rle_len == 1: rle_strA = ""
   else: rle_strA = str (rle_len)
   rle_res = rle_res[2:] + rle_strA + "o"
   
   return rle_res
      
# --------------------------------------------------------------------

def get_RLE(rect):
	clist = list(chunks(g.getcells(rect), 2))
	mcc = [rect[0], rect[1]]
	n = g.getselrect()[1]-rect[1]
	clist = [[x[0]-mcc[0],x[1]-mcc[1]] for x in clist]
	if n>1: return str(n)+'$'+giveRLE(clist)
	if n==1: return '$'+giveRLE(clist)
	return giveRLE(clist)

rule = g.getrule()
ruleHistory = rule + 'History'
font0 = {
'0': 'obobo2$o3bo2$o3bo2$o3bo2$obobo!',
'1': '2bo2$obo2$2bo2$2bo2$obobo!',
'2': 'obobo2$4bo2$obobo2$o2$obobo!',
'3': 'obobo2$4bo2$obobo2$4bo2$obobo!',
'4': 'o3bo2$o3bo2$obobo2$4bo2$4bo!',
'5': 'obobo2$o2$obobo2$4bo2$obobo!',
'6': 'obobo2$o2$obobo2$o3bo2$obobo!',
'7': 'obobo2$4bo2$4bo2$4bo2$4bo!',
'8': 'obobo2$o3bo2$obobo2$o3bo2$obobo!',
'9': 'obobo2$o3bo2$obobo2$4bo2$obobo!'
}
font = dict()
for i in font0.keys():
	font[i] = g.parse(font0[i])
def scan_number(u, v):
	cells = g.getcells([u, v, 5, 9])
	if cells == []: return ''
	for i in font.keys():
		if len(font[i]) != len(cells): continue
		p = font[i][0] - cells[0]
		q = font[i][1] - cells[1]
		for j in range(2, len(font[i]), 2):
			if font[i][j] - cells[j] != p: break
			if font[i][j+1] - cells[j+1] != q: break
		else:
			return i
	return ''
rect = g.getrect()
upos = set()
text = []
if rect==[]: rect=[0,0,0,0]
for u in range(rect[0], rect[0]+rect[2], scale):
	for v in range(rect[1], rect[1]+rect[3], scale):
		cells = g.getcells([u, v, 5, 9])
		n = []
		U = u
		while True:
			c = scan_number(U, v)
			if c == '': break
			n.append(c)
			U += 8
		if len(n) > 0:
			n = ''.join(n)
			text.append((n, u, v))
			upos.add(u)
upos = sorted(upos)
patterns = []
for i in range(len(text)):
	T = text[i][0]
	u1 = text[i][1]
	v1 = text[i][2]
	if upos.index(u1) == len(upos)-1: u2 = rect[0]+rect[2]+int(T)
	else: u2 = upos[upos.index(u1)+1]
	if i == len(text)-1 or text[i+1][1] > u1: v2 = rect[1]+rect[3]+int(T)
	else: v2 = text[i+1][2]
	u1 += len(T)*8-3
	#u1 += len(T)*6+2
	g.select([u1, v1, u2-u1, v2-v1])
	g.update()
	gen0 = g.getcells(g.getselrect())
	g.setrule(ruleHistory)
	g.run(int(T))
	g.shrink(True)
	rectT = g.getselrect()
	g.reset()
	g.setrule(rule)
	g.run(int(T))
	genT = g.getcells(rectT)
	g.reset()
	g.select(rectT)
	g.update()
	if gen0 != genT or rectT == []: g.warn('Invalid period-' + T + ' oscillator!')
	else:
		g.shrink()
		rle = get_RLE(rectT)
		rle = f'#{T} {rectT[2]} {rectT[3]}:\n' + rle + '!\n'
		patterns.append((int(T),rle))
if patterns!=[]:
	try:
		with open(filename) as f: l = f.read().split('!\n')
		if l != ['']:
			if l[-1] == '': del l[-1]
			while l[-1].endswith('!') or l[-1].endswith('\n'): l[-1] = l[-1][:-1]
			for i in l: patterns.append((int(i[1:i.index(' ')]) , i+'!\n'))
	except: pass
	e0 = lambda e: e[0]
	patterns.sort(key=e0)
	duplicates = set()
	for i in range(len(patterns)-1):
		if patterns[i][0] == patterns[i+1][0]: duplicates.add(patterns[i][0])
	if len(duplicates) > 0: g.warn("Duplicate entries: "+str(sorted(duplicates)))
	with open(filename, 'w') as f:
		for i in patterns: f.write(i[1])


g.setrule(rule)
fonts0 = [

[{
"0": "*.*.*$.....$*...*$.....$*...*$.....$*...*$.....$*.*.*!",
"1": "..*..$.....$*.*..$.....$..*..$.....$..*..$.....$*.*.*!",
"2": "*.*.*$.....$....*$.....$*.*.*$.....$*....$.....$*.*.*!",
"3": "*.*.*$.....$....*$.....$*.*.*$.....$....*$.....$*.*.*!",
"4": "*...*$.....$*...*$.....$*.*.*$.....$....*$.....$....*!",
"5": "*.*.*$.....$*....$.....$*.*.*$.....$....*$.....$*.*.*!",
"6": "*.*.*$.....$*....$.....$*.*.*$.....$*...*$.....$*.*.*!",
"7": "*.*.*$.....$....*$.....$....*$.....$....*$.....$....*!",
"8": "*.*.*$.....$*...*$.....$*.*.*$.....$*...*$.....$*.*.*!",
"9": "*.*.*$.....$*...*$.....$*.*.*$.....$....*$.....$*.*.*!",
"M": "*...*$.....$*.*.*$.....$*.*.*$.....$*...*$.....$*...*!",
"N": "*.*..$.....$*...*$.....$*...*$.....$*...*$.....$*...*!",
"c": ".....$.....$.....$.....$..*.*$.....$*....$.....$..*.*!",
"d": "....*$.....$....*$.....$..*.*$.....$*...*$.....$..*.*!",
"o": ".....$.....$.....$.....$..*..$.....$*...*$.....$..*..!",
"+": ".....$.....$..*..$.....$*.*.*$.....$..*..$.....$.....!",
"/": "....*$.....$....*$.....$..*..$.....$*....$.....$*....!",
"(": "....*$.....$..*..$.....$..*..$.....$..*..$.....$....*!",
")": "*....$.....$..*..$.....$..*..$.....$..*..$.....$*....!",
",": ".....$.....$.....$.....$.....$.....$..*..$.....$..*..!",
},5,8,9],

[{
"0": "*..*..*$.......$.......$*.....*$.......$.......$*.....*$.......$.......$*..*..*",
"1": "...*...$.......$*......$...*...$.......$.......$...*...$.......$.......$*..*..*",
"2": "*..*..*$.......$.......$......*$...*...$.......$*......$.......$.......$*..*..*",
"3": "*..*..*$.......$.......$......*$...*...$.......$......*$.......$.......$*..*..*",
"4": "......*$.......$.......$...*..*$.......$.......$*..*..*$.......$.......$......*",
"5": "*..*..*$.......$.......$*......$...*...$.......$......*$.......$.......$*..*..*",
"6": "*..*..*$.......$.......$*......$...*...$.......$*.....*$.......$.......$*..*..*",
"7": "*..*..*$.......$.......$.....*.$.......$.......$....*..$.......$.......$...*...",
"8": "*..*..*$.......$.......$*.....*$...*...$.......$*.....*$.......$.......$*..*..*",
"9": "*..*..*$.......$.......$*.....*$...*...$.......$......*$.......$.......$*..*..*",
"M": "*..*..*$.......$...*...$*.....*$.......$.......$*.....*$.......$.......$*.....*",
"N": "*..*...$.......$.......$*.....*$.......$.......$*.....*$.......$.......$*.....*",
"c": ".......$.......$.......$...*..*$.......$.......$*......$.......$.......$...*..*",
"d": "......*$.......$.......$...*..*$.......$.......$*.....*$.......$.......$...*..*",
"o": ".......$.......$.......$...*...$.......$.......$*.....*$.......$.......$...*...",
"+": ".......$...*...$.......$.......$*..*..*$.......$.......$...*...$.......$.......",
"/": "......*$.......$.......$....*..$.......$.......$..*....$.......$.......$*......",
"(": "....*..$.......$.......$..*....$.......$.......$..*....$.......$.......$....*..",
")": "..*....$.......$.......$....*..$.......$.......$....*..$.......$.......$..*....",
",": ".......$.......$.......$.......$.......$.......$...*...$.......$.......$..*....",
},7,10,10],

]

fonts = fonts0.copy()

for i in range(len(fonts0)):
	for j in fonts0[i][0].keys():
		fonts[i][0][j] = fonts0[i][0][j].replace('*','o').replace('.','b')

font = fonts[fontNumber]
fw = font[1]
ft = font[2]
my = max(my,font[3])
font = font[0]
snap = lambda x: ceil(x/scale)*scale
f = open(filename)
l = [i.split(":\n") for i in f.read().split("#")[1:]]
f.close()
l = [i[0].split(" ") + [i[1]] if i != ["\n"] else [] for i in l]
g.new("Collection")
dx = 0
dy = 0
tx = 0
j = 1
textState = 0
patternState = 0
states=[".","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","pA"]
for i in range(len(l)):
	if l[i]==[]:
		dx += snap(tx+space)
		dy = 0
		tx = 0
		if exponential: count *= 2
		continue
	if len(l[i])==5: patternState = int(l[i][3])
	elif len(l[i])==6:
		textState = int(l[i][3])
		patternState = int(l[i][4])
	p = l[i][0]
	i00 = ""
	i0 = "0"
	if "/" in p: i00=p.split("/")[1]
	else: i00 = p
	for ch in i00:
		if ch in "0123456789": i0 += ch
	i0 = int(i0)
	if count>0 and (ceil(i0/count)!=ceil(j/count) or i0<j):
		dx += snap(tx+space)
		dy = 0
		tx = 0
		if exponential: count *= 2
	fx = 0
	for k in range(len(p)):
		char = font[p[k]]
		if textState > 0: char = char.replace("*",states[textState])
		g.putcells(g.parse(char), dx+fx, dy)
		fx += ft
	fx = snap(max(fx+fw-ft+space, mx))
	if patternState > 0: l[i][-1] = l[i][-1].replace("b",".").replace("o",states[patternState]).replace("*",states[patternState])
	g.putcells(g.parse(l[i][-1]), dx+fx, dy)
	tx = max(tx, fx+int(l[i][1]))
	dy += snap(max(int(l[i][2])+space, my))
	j = i0
g.fit()
New important feature: collector.py v0.3 can scan oscillators from pattern and add them to the database. Example:
  • Open Golly, then make this pattern:

    Code: Select all

    x = 193, y = 61, rule = B3/S23
    2bo7b2o18bobobo3bobobo57bo3bo5bo43bo$10b2o41b3o30b3o63b3o$obo31bo3bo3b
    o57bo3bo3bobo40bo$51bo5bo2b2o18b2o2bo5bo45b2o13b2o$2bo27bobobo3bobobo
    8bo4b2o4bo16bo4b2o4bo9bobobo5bo26bo$51bo11bo14bo11bo46bobo$2bo31bo3bo
    3bo19b2o14b2o24bo5bo27b2o$148bo$obobo25bobobo3bobobo61bo3bobobo34bo$
    56b2o26b2o63bo$56bo28bo61b3o$57b3o22b3o$59bo22bo2$160bo$125b2o24b2o5bo
    bo$126bo24bo7b2o$124bo13b2o12b3o$122b4o15b2o11bo$121bo14b2obo$obobo13b
    2o100bo2b3o10b2o3bob2o$10b3o5b2o101b2o2bo10b2obo3b2o$4bo11b2o105b2o16b
    ob2o$16b2o105bo14b2o$obobo119bo6b3o7b2o26bo$121b3o7bo38b2o$o120bo5b2o
    3bo11bo24b2o$128bo14bobo$obobo120b3o15b2o$125bo2$187bo$168b2o15b3o$
    167bobo14bo$142b2o24bo11bo3b2o5bo$141b2o38bo7b3o$143bo26b2o7b3o6bo$
    173b2o14bo$168b2obo16b2o$168b2o3bob2o10bo2b2o$168b2obo3b2o10b3o2bo$
    173bob2o14bo$158bo11b2o15b4o$158b3o12b2o13bo$152b2o7bo24bo$152bobo5b2o
    24b2o$152bo4$163b3o$163bo$165bo$164bo$173b2o$173bobo$175bo$160b2o13b2o
    $161bo$158b3o$158bo!
    
  • Run collector.py, then enter filename;
  • Then you will see an oscillator collection like this one:

    Code: Select all

    x = 103, y = 151, rule = B3/S23
    2bo27b2o$30b2o$obo2$2bo2$2bo2$obobo22$obobo33b2o$30b3o5b2o$4bo31b2o$
    36b2o$obobo2$o2$obobo22$obobo3bobobo$33b3o30b3o$4bo3bo3bo$31bo5bo2b2o
    18b2o2bo5bo$obobo3bobobo18bo4b2o4bo16bo4b2o4bo$31bo11bo14bo11bo$4bo3bo
    3bo29b2o14b2o2$obobo3bobobo$36b2o26b2o$36bo28bo$37b3o22b3o$39bo22bo18$
    o3bo5bo53bo$62b3o$o3bo3bobo50bo$46b2o13b2o$obobo5bo36bo$47bobo$4bo5bo
    37b2o$58bo$4bo3bobobo44bo$59bo$57b3o4$70bo$35b2o24b2o5bobo$36bo24bo7b
    2o$34bo13b2o12b3o$32b4o15b2o11bo$31bo14b2obo$30bo2b3o10b2o3bob2o$31b2o
    2bo10b2obo3b2o$33b2o16bob2o$33bo14b2o$34bo6b3o7b2o26bo$31b3o7bo38b2o$
    31bo5b2o3bo11bo24b2o$38bo14bobo$35b3o15b2o$35bo2$97bo$78b2o15b3o$77bob
    o14bo$52b2o24bo11bo3b2o5bo$51b2o38bo7b3o$53bo26b2o7b3o6bo$83b2o14bo$
    78b2obo16b2o$78b2o3bob2o10bo2b2o$78b2obo3b2o10b3o2bo$83bob2o14bo$68bo
    11b2o15b4o$68b3o12b2o13bo$62b2o7bo24bo$62bobo5b2o24b2o$62bo4$73b3o$73b
    o$75bo$74bo$83b2o$83bobo$85bo$70b2o13b2o$71bo$68b3o$68bo!
    
Moreover, you can merge input pattern with database. For example, if oscillators.txt already contained pulsar, you would get this:

Code: Select all

x = 103, y = 181, rule = B3/S23
2bo27b2o$30b2o$obo2$2bo2$2bo2$obobo22$obobo33b2o$30b3o5b2o$4bo31b2o$
36b2o$obobo2$o2$obobo22$obobo$33b3o3b3o$4bo$31bo4bobo4bo$obobo26bo4bob
o4bo$31bo4bobo4bo$4bo28b3o3b3o2$obobo28b3o3b3o$31bo4bobo4bo$31bo4bobo
4bo$31bo4bobo4bo2$33b3o3b3o17$obobo3bobobo$33b3o30b3o$4bo3bo3bo$31bo5b
o2b2o18b2o2bo5bo$obobo3bobobo18bo4b2o4bo16bo4b2o4bo$31bo11bo14bo11bo$
4bo3bo3bo29b2o14b2o2$obobo3bobobo$36b2o26b2o$36bo28bo$37b3o22b3o$39bo
22bo18$o3bo5bo53bo$62b3o$o3bo3bobo50bo$46b2o13b2o$obobo5bo36bo$47bobo$
4bo5bo37b2o$58bo$4bo3bobobo44bo$59bo$57b3o4$70bo$35b2o24b2o5bobo$36bo
24bo7b2o$34bo13b2o12b3o$32b4o15b2o11bo$31bo14b2obo$30bo2b3o10b2o3bob2o
$31b2o2bo10b2obo3b2o$33b2o16bob2o$33bo14b2o$34bo6b3o7b2o26bo$31b3o7bo
38b2o$31bo5b2o3bo11bo24b2o$38bo14bobo$35b3o15b2o$35bo2$97bo$78b2o15b3o
$77bobo14bo$52b2o24bo11bo3b2o5bo$51b2o38bo7b3o$53bo26b2o7b3o6bo$83b2o
14bo$78b2obo16b2o$78b2o3bob2o10bo2b2o$78b2obo3b2o10b3o2bo$83bob2o14bo$
68bo11b2o15b4o$68b3o12b2o13bo$62b2o7bo24bo$62bobo5b2o24b2o$62bo4$73b3o
$73bo$75bo$74bo$83b2o$83bobo$85bo$70b2o13b2o$71bo$68b3o$68bo!
Version 0.2: viewtopic.php?p=159028#p159028
To do:
  • Support more fonts
  • Implement faster scanning algorithm
The latest version of hex-gliders.db have 640 gliders from OT hexagonal rules. Let's find more!
My CA (13 rules)
My scripts: new-glider.py, nbsearch2a.py, collector.py v0.3 (new version)

Post Reply