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

3D.lua

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

3D.lua

Postby Andrew » May 15th, 2018, 6:51 pm

Golly 3.2 includes a new script called 3D.lua that lets you explore three-dimensional cellular automata. This thread is for reporting bugs in 3D.lua, discussing how it could be improved, posting interesting 3D patterns and rules, etc.
User avatar
Andrew
Moderator
 
Posts: 671
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: 3D.lua

Postby Andrew » May 15th, 2018, 7:00 pm

Here is a 3D version of oscar.lua:
-- This script can only be run from within 3D.lua.
-- It's an oscillator analyzer for 3D cellular automata.
-- Author: Andrew Trevorrow (andrew@trevorrow.com), Mar 2018.
-- Modified to handle BusyBoxes, May 2018.

local g = golly()
local op = require "oplus"  -- for op.process

-- initialize lists
local hashlist = {} -- for pattern hash values
local genlist = {}  -- corresponding generation counts
local poplist = {}  -- corresponding population counts
local boxlist = {}  -- corresponding bounding boxes

local busyboxes = GetRule():find("^BusyBoxes")
local bbmirror = busyboxes and not GetRule():find("W")

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

local function HashPattern(bbox)
    local minx, maxx, miny, maxy, minz, maxz = table.unpack(bbox)
    local hash = 31415962
    for z = minz, maxz do
        local zshift = z - minz
        for y = miny, maxy do
            local yshift = y - miny
            for x = minx, maxx do
                if GetCell(x, y, z) > 0 then
                    -- ~ is Lua's bitwise XOR
                    hash = (hash * 1000003) ~ zshift
                    hash = (hash * 1000003) ~ yshift
                    hash = (hash * 1000003) ~ (x - minx)
                end
            end
        end
    end
    return hash
end

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

local function show_spaceship_speed(period, deltax, deltay, deltaz)
    -- we found a moving oscillator
    if (deltax == 0 and deltay == 0) or
       (deltax == 0 and deltaz == 0) or
       (deltay == 0 and deltaz == 0) then
        local speed = tostring(deltax + deltay + deltaz)    -- only one is > 0
        if period == 1 then
            SetMessage("Orthogonal spaceship detected\n(speed = "..speed.."c)")
        else
            SetMessage("Orthogonal spaceship detected\n(speed = "..speed.."c/"..period..")")
        end
    elseif
        (deltax == 0 and deltay == deltaz) or
        (deltay == 0 and deltax == deltaz) or
        (deltaz == 0 and deltax == deltay) or
        (deltax == deltay and deltay == deltaz) then
        local speed = deltax..","..deltay..","..deltaz
        if period == 1 then
            SetMessage("Diagonal spaceship detected\n(speed = "..speed.."c)")
        else
            SetMessage("Diagonal spaceship detected\n(speed = "..speed.."c/"..period..")")
        end
    else
        local speed = deltax..","..deltay..","..deltaz
        SetMessage("Oblique spaceship detected\n(speed = "..speed.."c/"..period..")")
    end
end

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

local function oscillating()
    -- return true if the pattern is empty, stable or oscillating
    local popcount = GetPopulation()
    if popcount == 0 then
        SetMessage("The pattern is empty.")
        return true
    end

    local gencount = GetGeneration()

    -- get the current pattern's bounding box
    -- ie. { minx, maxx, miny, maxy, minz, maxz }
    local pattbox = GetBounds()
   
    -- calculate a hash value for the current pattern
    local h = HashPattern(pattbox)
   
    local p1 = 1
    if busyboxes then
        -- stable patterns in BusyBoxes have a period of 6
        p1 = 6
        -- in some phases the pattern doesn't change
        h = h + (gencount % 6)
    end

    -- determine where to insert h into hashlist
    local pos = 1
    local listlen = #hashlist
    while pos <= listlen do
        if h > hashlist[pos] then
            pos = pos + 1
        elseif h < hashlist[pos] then
            -- shorten lists and append info below
            for i = 1, listlen - pos + 1 do
                table.remove(hashlist)
                table.remove(genlist)
                table.remove(poplist)
                table.remove(boxlist)
            end
            break
        else
            -- h == hashlist[pos] so pattern is probably oscillating, but just in
            -- case this is a hash collision we also compare pop count and box size
            local box = boxlist[pos]
            if popcount == poplist[pos] and
                pattbox[2]-pattbox[1] == box[2]-box[1] and
                pattbox[4]-pattbox[3] == box[4]-box[3] and
                pattbox[6]-pattbox[5] == box[6]-box[5] then
                local period = gencount - genlist[pos]
                if pattbox[1] == box[1] and
                   pattbox[3] == box[3] and
                   pattbox[5] == box[5] then
                    -- pattern hasn't moved
                    if period == p1 then
                        SetMessage("The pattern is stable.")
                    else
                        SetMessage("Oscillator detected (period = "..period..")")
                    end
                    return true
                elseif bbmirror then
                    -- ignore spurious spaceship if BusyBoxes in mirror mode
                    pos = pos + 1
                else
                    local deltax = math.abs(box[1] - pattbox[1])
                    local deltay = math.abs(box[3] - pattbox[3])
                    local deltaz = math.abs(box[5] - pattbox[5])
                    show_spaceship_speed(period, deltax, deltay, deltaz)
                    return true
                end
            else
                -- look at next matching hash value or insert if no more
                pos = pos + 1
            end
        end
    end
   
    -- store hash/gen/pop/box info at same position in various lists
    table.insert(hashlist, pos, h)
    table.insert(genlist, pos, gencount)
    table.insert(poplist, pos, popcount)
    table.insert(boxlist, pos, pattbox)
   
    return false
end

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

SetMessage("Checking for oscillation... (hit escape to abort)")
Update()
local oldms = g.millisecs()
while not oscillating() do
    -- get and ignore most user events so they don't get queued up
    -- and then acted on after this script terminates, but we do call
    -- op.process so user has access to some safe menu items
    op.process(g.getevent())
    Step()
    local newms = g.millisecs()
    if newms - oldms >= 1000 then   -- show pattern every second
        oldms = newms
        Update()
    end
end


Below is a 3D.lua startup script that lets you type alt-O (option-O on a Mac) to run oscar3d.lua. Save the following script in a file called something like 3D-startup.lua, edit the path passed into RunScript to match where you saved oscar3d.lua, then run 3D.lua, select File > Set Startup Script and choose 3D-startup.lua. Exit 3D.lua. Now whenever you start 3D.lua it will automatically execute your script and you can use that keyboard shortcut.
-- This script shows how to override the HandleKey function in 3D.lua
-- to create a keyboard shortcut for running a particular script.

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

local savedHandler = HandleKey

function HandleKey(event)
    local _, key, mods = split(event)
    if key == "o" and mods == "alt" then
        RunScript(g.getdir("app").."My-3D-files/oscar3d.lua")
    else
        -- pass the event to the original HandleKey
        savedHandler(event)
    end
end

-- enable the next line to verify that this script is being called
-- g.note("my 3D startup script has been executed", false)


Note that I strongly recommend you create a new folder to store all your 3D scripts and patterns. Create it in your Golly folder (as in the above example). Golly 3.2 allows you to click on those files in the Show Files panel, automatically start up 3D.lua if it isn't running, and either run a .lua script or load a .rle3 pattern.
User avatar
Andrew
Moderator
 
Posts: 671
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: 3D.lua

Postby Andrew » May 15th, 2018, 7:04 pm

A few editing tips:

- If you select View > Initial View, then hit the up arrow 4 times and the right arrow 4 times, the XY plane will be parallel to the screen.

- Use the M, D and S keys to switch quickly from Move mode to Draw/Select mode and back.

- In Draw or Select mode, use the comma/period keys to move the active plane closer/further to see nearby live cells.

- Build things from back to front.

- Consider writing a script. Open your text editor, create an empty file and gradually construct a pattern by entering more and more script commands. After adding a few commands, select all, copy, switch from your editor to 3D.lua and type shift-R to run the commands in the clipboard. Repeat until it works!

Here's an example script that constructs 2 colliding gliders in a 40x40x40 grid using the rule 3D5..7/6:
-- for 3D.lua
NewPattern()
SetGridSize(40)
SetRule("3D5..7/6")
MoveMode() -- set cursor to hand

-- build a SE glider in middle of grid
SetCell( 0,  1, -1, 1)
SetCell( 1,  0, -1, 1)
SetCell(-1, -1, -1, 1)
SetCell( 0, -1, -1, 1)
SetCell( 1, -1, -1, 1)
SetCell( 0,  1,  0, 1)
SetCell( 1,  0,  0, 1)
SetCell(-1, -1,  0, 1)
SetCell( 0, -1,  0, 1)
SetCell( 1, -1,  0, 1)

-- move it up and left
SelectAll()
CutSelection()
Paste()
DoPaste(-10,10,0,"or")

-- create a NW glider and move it down and right
-- so the gliders collide to form a stable block
Paste()
RotatePaste("z")
RotatePaste("z")
DoPaste(9,-11,0,"or")

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

Re: 3D.lua

Postby Andrew » May 15th, 2018, 7:30 pm

3D.lua's default rule is 3D5..7/6. A quick way to switch to that rule is to select Control > Set Rule, delete the current rule and hit OK.

Carter Bays discusses this rule at some length here:
http://www.complex-systems.com/pdf/01-3-1.pdf

The following pattern is based on figure 7 in that paper and has two stable walls with a pentadecathlon in the two middle planes. Copy the pattern, switch to 3D.lua and hit shift-O to open it, then switch to Draw or Select mode so you can see the pentadecathlon in the active plane.
3D version=1 size=30 pos=3,3,9
# a pentadecathlon inside two stable walls
x=24 y=24 z=12 rule=3D5,6,7/6
6booboobbooboo$6booboobbooboo$bboo16boo$bboo16boo3$oo20boo$oo20boo$$
oo20boo$oo20boo3$oo20boo$oo20boo$$oo20boo$oo20boo3$bboo16boo$bboo16b
oo$6booboobbooboo$6booboobbooboo/6booboobbooboo$6booboobbooboo$3bo16b
o$bbo18bo3$oo20boo$oo20boo$$oo20boo$oo20boo3$oo20boo$oo20boo$$oo20b
oo$oo20boo3$bbo18bo$3bo16bo$6booboobbooboo$6booboobbooboo/$$3bo16bo
$bbo18bo17$bbo18bo$3bo16bo/$$4b16o$3b18o$bboobooboobooboobooboo$bb20o
$bb20o$bboobooboobooboobooboo$bb20o$bb20o$bboobooboobooboobooboo$bb
20o$bb20o$bboobooboobooboobooboo$bb20o$bb20o$bboobooboobooboobooboo
$bb20o$bb20o$bboobooboobooboobooboo$3b18o$4b16o//6booboobbooboo$6boo
boobbooboo5$oo20boo$oo20boo$$oo20boo$oo7bo4bo7boo$7boob4oboo$9bo4bo
$oo20boo$oo20boo$$oo20boo$oo20boo5$6booboobbooboo$6booboobbooboo/6b
ooboobbooboo$6booboobbooboo5$oo20boo$oo20boo$$oo20boo$oo7bo4bo7boo$
7boob4oboo$9bo4bo$oo20boo$oo20boo$$oo20boo$oo20boo5$6booboobbooboo$
6booboobbooboo//$$4b16o$3b18o$bboobooboobooboobooboo$bb20o$bb20o$bb
oobooboobooboobooboo$bb20o$bb20o$bboobooboobooboobooboo$bb20o$bb20o
$bboobooboobooboobooboo$bb20o$bb20o$bboobooboobooboobooboo$bb20o$bb
20o$bboobooboobooboobooboo$3b18o$4b16o/$$3bo16bo$bbo18bo17$bbo18bo$
3bo16bo/6booboobbooboo$6booboobbooboo$3bo16bo$bbo18bo3$oo20boo$oo20b
oo$$oo20boo$oo20boo3$oo20boo$oo20boo$$oo20boo$oo20boo3$bbo18bo$3bo16b
o$6booboobbooboo$6booboobbooboo/6booboobbooboo$6booboobbooboo$bboo16b
oo$bboo16boo3$oo20boo$oo20boo$$oo20boo$oo20boo3$oo20boo$oo20boo$$oo
20boo$oo20boo3$bboo16boo$bboo16boo$6booboobbooboo$6booboobbooboo!

A good editing exercise: create a larger grid (use View > Set Grid Size) and extend the walls in the above pattern so you can fit a Gosper gun in the middle. Unfortunately there is not quite enough room at the corner of the two walls for a glider to escape. If you can figure out a way to stabilize those corners so a glider can escape then I think you'll be the first person to discover a glider gun in any 3D rule (please correct me if I'm wrong about that!).
User avatar
Andrew
Moderator
 
Posts: 671
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: 3D.lua

Postby Andrew » May 15th, 2018, 7:46 pm

Tom Rokicki discovered an interesting rule with a simple replicator:
3D version=1 size=40 pos=20,18,18
# replicator found by Tom Rokicki, March 2018
x=1 y=4 z=4 rule=3D4,7/5,8
$o$o/o$o$o$o/o$o$o$o/$o$o!

Stuff can be added behind the replicator to create various spaceships:
3D version=1 size=40 pos=18,18,18
# 7c/7 spaceship found by Andrew Trevorrow, April 2018
x=5 y=4 z=4 rule=3D4,7/5,8
$o3bo$ooboo$bbo/4bo$bobbo$bb3o$ooboo/4bo$4bo$bobbo$o3bo/$4bo$4bo!

3D version=1 size=40 pos=19,18,18
# 10c/10 spaceship found by Andrew Trevorrow, April 2018
x=2 y=4 z=4 rule=3D4,7/5,8
$bo$bo/bo$bo$bo$oo/oo$bo$bo$bo/$bo$bo!
User avatar
Andrew
Moderator
 
Posts: 671
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: 3D.lua

Postby rowett » May 15th, 2018, 8:45 pm

This script creates a globe:
  1. Copy it to the clipboard
  2. Switch to Golly
  3. Run 3D.lua
  4. Press Shift-R to run the script you copied to the clipboard in step 1
  5. Now rotate with the mouse...
Looks best with Depth Shading switched on (Alt-D to toggle).

-- for 3D.lua
NewPattern()
SetGridSize(100)
SetRule("3D4..9/8..12")
RandomPattern(100, false, true)
Step()
Step()
rowett
Moderator
 
Posts: 865
Joined: January 31st, 2013, 2:34 am
Location: UK

Re: 3D.lua

Postby wildmyron » May 15th, 2018, 10:51 pm

As I said on golly-test, this is fantastic.

Not much further to contribute, but I did find a p30 spaceship in Tom's rule:
3D version=1 size=60 pos=2,27,27
x=4 y=6 z=5 rule=3D4,7/5,8
3$bo/$oo$3bo$3bo/bo$3bo$3bo$3bo$oboo$bo/$b3o$obbo$3bo$3bo$bo/$$3bo$
bboo!


I've put it in a size 60 grid because oscar3d.lua gives strange results if the pattern crosses the boundaries of the grid.
wildmyron
 
Posts: 790
Joined: August 9th, 2013, 12:45 am

Re: 3D.lua

Postby Andrew » May 16th, 2018, 9:10 am

wildmyron wrote:I've put it in a size 60 grid because oscar3d.lua gives strange results if the pattern crosses the boundaries of the grid.

Yep, oscar.lua/py have the same problem with spaceships in small bounded grids. There's probably a way to handle those cases but I don't see an obvious solution. Hopefully someone smarter than me can come up with a fix.
User avatar
Andrew
Moderator
 
Posts: 671
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: 3D.lua

Postby Andrew » May 17th, 2018, 12:21 am

Found another paper by Carter Bays with more spaceships in various 3D rules:

A Note About the Discovery of Many New Rules for the Game of Three-Dimensional Life
http://wpmedia.wolfram.com/uploads/site ... 16-4-7.pdf

Entering those spaceships into 3D.lua is a real pain. I've tried getting in touch with Prof Bays (via his sc.edu email address) to see if digital versions of the patterns are available, but no luck so far.
User avatar
Andrew
Moderator
 
Posts: 671
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: 3D.lua

Postby wildmyron » May 17th, 2018, 12:42 am

Andrew wrote:Found another paper by Carter Bays with more spaceships in various 3D rules:

A Note About the Discovery of Many New Rules for the Game of Three-Dimensional Life
http://wpmedia.wolfram.com/uploads/site ... 16-4-7.pdf

Entering those spaceships into 3D.lua is a real pain. I've tried getting in touch with Prof Bays (via his sc.edu email address) to see if digital versions of the patterns are available, but no luck so far.


A quick script based on the cell lists for the two larger ships gives these patterns:
3D version=1 size=40 pos=20,20,20
#C (4, 0, 0)c/8 ship from Bays, C., 2006, Complex Systems, 16, 381–386
x=5 y=6 z=8 rule=3D4,7/5
$$bo$bo/$$o$o/$3o$4bo$4bo$3o/o$o3bo$4bo$4bo$o3bo$o/o$o3bo$4bo$4bo$o
3bo$o/$3o$4bo$4bo$3o/$$o$o/$$bo$bo!

3D version=1 size=40 pos=20,20,22
#C (1, 1, 0)c/2 ship from Bays, C., 2006, Complex Systems, 16, 381–386
x=6 y=6 z=4 rule=3D8/5
$bbo$$4bo/boo$3o$$bobboo$bboboo$4bo/boo$3o$$bobboo$bboboo$4bo/$bbo$$
4bo!


Bays mentions that the others are readily found, so maybe some soup searching in those rules will be sufficient to rediscover them.

Edit: Well, I tried manual soup searching a range of those rules and that hasn't worked out too well. I'm sure it won't take too long with a shared effort to manually import them into Golly. Have you done so for any of them?

Note: there's a typo in Fig. 1. - the rule for the last two ships should be 8/5. For anyone else trying to figure it out, Fig 1. shows one phase for all the ships, with the rule string below the ship and the period in brackets. Fig 2-5 show all phases of each of these ships, spread across the 4 figures.

Here's the other 8/5 ship (one half of the other ship minus the tagalong):
3D version=1 size=40 pos=24,23,22
#C small (1, 1, 0)c/2 ship from Bays, C., 2006, Complex Systems, 16, 381–386
x=2 y=3 z=4 rule=3D8/5
o/oo$oo$o/oo$oo$o/o!


Edit 2: Some more of the small ships from the paper. The comments above and below citing the 2006 paper don't actually reflect the first time they were published in all cases, but I'm not sure which paper they were all first published in. Also, rotated the p8 ship above (in 3D4,7/5) to travel along the x-axis.

3D version=1 size=40 pos=19,18,19
#C (2,0,0)c/4 ship from Bays, C., 2006, Complex Systems, 16, 381–386
x=2 y=4 z=4 rule=3D2,3/5
o$bo$bo$o/bo3$bo/bo3$bo/o$bo$bo$o!

3D version=1 size=40 pos=22,17,19 gen=7
#C (1,0,0)c/3 ship from Bays, C., 2006, Complex Systems, 16, 381–386
x=2 y=7 z=2 rule=3D2,5/5
bo$bo$oo$bo$oo$bo$bo/bo$$bo$$bo$$bo!

3D version=1 size=40 pos=20,18,19 gen=7
#C (1,0,0)c/2 ship from Bays, C., 2006, Complex Systems, 16, 381–386
x=3 y=6 z=3 rule=3D2,7/5
$bbo$obo$obo$bbo/bbo5$bbo/$bbo$obo$obo$bbo!
wildmyron
 
Posts: 790
Joined: August 9th, 2013, 12:45 am

Re: 3D.lua

Postby wildmyron » May 17th, 2018, 2:01 am

Separate post because this is a quite different topic.

Andrew:

I found the order of the ',' and '.' keys to be unintuitive whilst editing. Specifically, ',' increases the coordinate of the active plane along the axis and '.' decreases it. I'm sure I can get used to it, but it felt the wrong way around to me. Maybe this is related to my dislike of inverting the mouse for scrolling :).
wildmyron
 
Posts: 790
Joined: August 9th, 2013, 12:45 am

Re: 3D.lua

Postby Andrew » May 17th, 2018, 3:32 am

wildmyron wrote:I found the order of the ',' and '.' keys to be unintuitive whilst editing. Specifically, ',' increases the coordinate of the active plane along the axis and '.' decreases it. I'm sure I can get used to it, but it felt the wrong way around to me.

I guess it depends on where those keys are on your keyboard and how you think of the characters above them. On my keyboard the comma key has "<" above it and it's to the left of the period key which has ">" above it. So I think of the "<" symbol as meaning "decrease the distance between me and the active plane" and ">" as meaning "increase that distance". That's assuming the active plane is in its default XY position in the initial view, which is how I prefer to edit things.

Note that you can easily swap the behavior of those keys by adding this code to your startup script (at the start of your HandleKey function):
    if event == "key , none" then
        savedHandler("key . none")
        return
    elseif event == "key . none" then
        savedHandler("key , none")
        return
    end
User avatar
Andrew
Moderator
 
Posts: 671
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: 3D.lua

Postby Andrew » May 17th, 2018, 3:55 am

wildmyron wrote: I tried manual soup searching a range of those rules and that hasn't worked out too well. I'm sure it won't take too long with a shared effort to manually import them into Golly. Have you done so for any of them?

No, I gave up doing that after entering a few patterns from the earlier papers (although that was before I had undo/redo working which made the task even harder!). Still hoping to hear from Bays.

Below is a quick-and-dirty script I wrote to do automated searches, so feel free to modify/improve it. One problem with it is the random density range given to RandomPattern. As mentioned in the comment above it, some rules have a small density range at which interesting patterns emerge; eg. for 3D4,7/5,8 the "goldilocks" density is about 8%. A little below that and patterns die out; a little above it and they explode. So the script really needs to do a pre-search phase where it tries to find the goldilocks density, then uses that in the search phase.

If you change the line to RandomPattern(8) when searching 3D4,7/5,8 then you'll discover spaceships much faster. For example, I found the following in a few seconds after doing such a search (maybe I was just lucky):
3D version=1 size=20 pos=7,6,2 gen=0
# 4c/4 orthogonal spaceship
x=5 y=6 z=6 rule=3D4,7/5,8
$$bbo$bbo/$$o3bo$o3bo/bbo$o3bo$o$o$o3bo$bbo/bbo$o3bo$o$o$o3bo$bbo/$$
o3bo$o3bo/$$bbo$bbo!


Here is my search-rule.lua script (I use alt-S in my startup script to run it):
-- This script must be run from within 3D.lua.
-- It searches the current rule for oscillators and spaceships.
-- Author: Andrew Trevorrow (andrew@trevorrow.com), May 2018.

local g = golly()
local op = require "oplus"  -- for op.process

SetGridSize(20)         -- smaller is better? (definitely faster)
local min_period = 3    -- ignore oscillators with periods less than this

-- initialize lists
local hashlist = {}     -- for pattern hash values
local genlist = {}      -- corresponding generation counts
local poplist = {}      -- corresponding population counts
local boxlist = {}      -- corresponding bounding boxes

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

local function HashPattern(bbox)
    local minx, maxx, miny, maxy, minz, maxz = table.unpack(bbox)
    local hash = 31415962
    for z = minz, maxz do
        local zshift = z - minz
        for y = miny, maxy do
            local yshift = y - miny
            for x = minx, maxx do
                if GetCell(x, y, z) > 0 then
                    -- ~ is Lua's bitwise XOR
                    hash = (hash * 1000003) ~ zshift
                    hash = (hash * 1000003) ~ yshift
                    hash = (hash * 1000003) ~ (x - minx)
                end
            end
        end
    end
    return hash
end

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

local function oscillating(popcount)
    -- return >= 1 if the pattern is stable or oscillating, otherwise 0

    -- get the current pattern's bounding box
    -- ie. { minx, maxx, miny, maxy, minz, maxz }
    local pattbox = GetBounds()
   
    -- calculate a hash value for the current pattern
    local h = HashPattern(pattbox)
   
    -- determine where to insert h into hashlist
    local gencount = GetGeneration()
    local pos = 1
    local listlen = #hashlist
    while pos <= listlen do
        if h > hashlist[pos] then
            pos = pos + 1
        elseif h < hashlist[pos] then
            -- shorten lists and append info below
            for i = 1, listlen - pos + 1 do
                table.remove(hashlist)
                table.remove(genlist)
                table.remove(poplist)
                table.remove(boxlist)
            end
            break
        else
            -- h == hashlist[pos] so pattern is probably oscillating, but just in
            -- case this is a hash collision we also compare pop count and box size
            local box = boxlist[pos]
            if popcount == poplist[pos] and
                pattbox[2]-pattbox[1] == box[2]-box[1] and
                pattbox[4]-pattbox[3] == box[4]-box[3] and
                pattbox[6]-pattbox[5] == box[6]-box[5] then
                local period = gencount - genlist[pos]
                return period
            else
                -- look at next matching hash value or insert if no more
                pos = pos + 1
            end
        end
    end
   
    -- store hash/gen/pop/box info at same position in various lists
    table.insert(hashlist, pos, h)
    table.insert(genlist, pos, gencount)
    table.insert(poplist, pos, popcount)
    table.insert(boxlist, pos, pattbox)
   
    return 0
end

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

local pattcount = 0
local oldms = 0
while true do
    -- get and ignore most user events so they don't get queued up
    -- and then acted on after this script terminates, but we do call
    -- op.process so user has access to some safe menu items
    op.process(g.getevent())
   
    -- write another function to find the rule's goldilocks density!!!
    -- note that some (many?) rules have a very small density range at which
    -- interesting patterns emerge; eg. for 3D4,7/5,8 the density is around 8%
    RandomPattern(math.random(5,50))
   
    -- initialize lists
    hashlist = {}
    genlist = {}
    poplist = {}
    boxlist = {}
    pattcount = pattcount + 1
    local newms = g.millisecs()
    if newms - oldms >= 1000 then   -- show msg every second
        oldms = newms
        SetMessage("Searching... (hit escape to abort) patterns="..pattcount)
        Update()
    end
    local initpop = GetPopulation()
    while true do
        local pop = GetPopulation()
        if pop == 0 then
            break           -- pattern died
        elseif pop > initpop then
            break           -- pattern exploded
        end
        local period = oscillating(pop)
        if period >= min_period then
            SetMessage("Found oscillator/spaceship with period = "..period)
            goto found_something
        elseif period > 0 then
            break           -- stable or oscillator < min_period
        end
        Step()
        if GetGeneration() % 100 == 0 then
            -- allow user to abort script in case pattern fills grid but pop <= initpop
            op.process(g.getevent())
            Update()
            if GetGeneration() >= 500 then break end
        end
    end
end
::found_something::


Edit: Added some code near the end to allow user to abort script in case a pattern shrinks initially but then expands to fill grid but never exceeds initpop. That logic needs to be improved!
User avatar
Andrew
Moderator
 
Posts: 671
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: 3D.lua

Postby wildmyron » May 17th, 2018, 5:27 am

Andrew: Thanks for the tip on comma and period keys - I think I will do that. As you say, it depends on how you interpret them and that's a subjective matter.

Andrew wrote:Here is my search-rule.lua script (I use alt-S in my startup script to run it):
<snip code>


Thanks for posting this. Two remaining scripts which are contenders for 3D versions are the rule range script to determine which rules a pattern works in, and the rule mutation script for exploring the rule space by mutating the current rule. I may give those a go over the weekend.

Here are a few links to other people's work on visualising 3D cellular automata and exploring rules which I found interesting. They're not so much suggestions, more possible inspiration.

Visions of Chaos includes several 3D cellular automata simulations, including semi-totalistic CA on the Moore neighbourhood and the von Neumann neighbourhood, the same but with generations, 3D Cyclic Cellular Automata, 3D Stochastic Cellular Automata (birth and survival are probablistic), and others. My understanding is that VoC focuses on rule exploration and visualisation, but not so much on editing. I haven't actually used it myself though so I may be unaware of some features. This post has a video with some 2-state 3D CA (but I can't find the rules used) and lists some generations rules that Jason (the dev) found interesting. A more recent video also includes some 2 state rules. They are:
  • Clouds 1 - 3D13..26/13,14,17..19 (3D Moore) - A rule which forms cavernous structures on a 3-toroid, the initial soup needs to fill the whole grid else the pattern will die out. 50% initial density works well
  • Clouds 2 - 3D13..26/13,14 (3D Moore) - Very similar to Clouds 1, but seems to die out a bit easier. Better with higher initial density (60-70%).
  • Crystal Growth 1 - 3D0..6/1,3 (3D von Neumann) - like a 3D version of Gnarl
Note to others: To fill the grid with a random pattern, press 5 and enter 50f for a 50% fill, 'f' for full grid.

This video shows another 3D cellular automata simulator using an OpenGL renderer which supports an unbounded grid (up to +/-MAX_INT/2). I'm mentioning this one because of the editing technique which is very different. There is one active cell and the movement keys move the whole grid around while the active cell remains fixed. It uses a perspective projection which in this case I think helps to visualise the 3D structure in the pattern being built up.
wildmyron
 
Posts: 790
Joined: August 9th, 2013, 12:45 am

Re: 3D.lua

Postby A for awesome » May 17th, 2018, 9:00 pm

Andrew wrote:Found another paper by Carter Bays with more spaceships in various 3D rules:

A Note About the Discovery of Many New Rules for the Game of Three-Dimensional Life
http://wpmedia.wolfram.com/uploads/site ... 16-4-7.pdf

Entering those spaceships into 3D.lua is a real pain. I've tried getting in touch with Prof Bays (via his sc.edu email address) to see if digital versions of the patterns are available, but no luck so far.

One thing I would mention about that paper is that it discards all rules allowing lightspeed growth in its analysis. While this seems justified in the cases of rules having B4-or-below transitions or arguably B58, rules with B56 and B57, in my opinion, still deserve analysis — lightspeed growth engines would be very rare (equivalent to non-dying-out patterns in B56/S45 and B57/S46; I've apgsearched both and found 2 SLs in 12.5M soups in B57/S46 and none in 30+M soups in B56/S45). Sure, lightspeed growth will occasionally occur, but will it have much significant impact on the dynamics of the rule?
x₁=ηx
V ⃰_η=c²√(Λη)
K=(Λu²)/2
Pₐ=1−1/(∫^∞_t₀(p(t)ˡ⁽ᵗ⁾)dt)

$$x_1=\eta x$$
$$V^*_\eta=c^2\sqrt{\Lambda\eta}$$
$$K=\frac{\Lambda u^2}2$$
$$P_a=1-\frac1{\int^\infty_{t_0}p(t)^{l(t)}dt}$$

http://conwaylife.com/wiki/A_for_all

Aidan F. Pierce
User avatar
A for awesome
 
Posts: 1620
Joined: September 13th, 2014, 5:36 pm
Location: 0x-1

Re: 3D.lua

Postby gameoflifemaniac » May 18th, 2018, 3:07 pm

I can't paste a pattern to a different layer!
https://www.youtube.com/watch?v=q6EoRBvdVPQ
One big dirty Oro. Yeeeeeeeeee...
User avatar
gameoflifemaniac
 
Posts: 600
Joined: January 22nd, 2017, 11:17 am
Location: Poland

Re: 3D.lua

Postby Andrew » May 18th, 2018, 7:46 pm

gameoflifemaniac wrote:I can't paste a pattern to a different layer!

When you do a paste, the paste pattern (red cells) appears in the middle of the grid. Using any cursor, click anywhere on the paste pattern and drag it into your active plane. (And just in case you haven't read the help, you can ctrl-click or right-click anywhere to get a pop-up menu with various paste actions.)

It *is* annoying to have to drag the paste pattern to the active plane, so I plan to change things so that if the active plane is visible when doing a paste, and the clipboard pattern is 1 cell thick (in any direction), then the paste pattern will appear in the middle of the active plane.

I also plan to allow pasting in normal 2D patterns, so you can do things like open Help > Life Lexicon, copy a pattern and then paste it into 3D.lua.
User avatar
Andrew
Moderator
 
Posts: 671
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: 3D.lua

Postby rowett » May 19th, 2018, 6:57 pm

Two more globes:
-- for 3D.lua
NewPattern()
SetGridSize(100)
SetRule("3D10..25/6,7")
RandomPattern(100, false, true)

-- for 3D.lua
NewPattern()
SetGridSize(100)
SetRule("3D10..23,25/6,8")
RandomPattern(100, false, true)

Copy the to clipboard, open 3D.lua in Golly and press Shift-R. Then run the pattern.
rowett
Moderator
 
Posts: 865
Joined: January 31st, 2013, 2:34 am
Location: UK

Re: 3D.lua

Postby wildmyron » May 21st, 2018, 3:45 am

An update to Andrew's search-rule.lua:

The main changes are to generate a small soup in a larger grid and to allow symmetric soups to be searched. The search is able to find most of the small ships in Bays' 2006 paper with appropriate rule and symmetry settings. Adjustment of the soup_size and soup_range parameters is required in some cases in order to find the ships in a reasonable time. It will also find various 3D4,7/5,8 photons, including the 10c/10 if D2_+2 symmetry is used.

Oscillators and spaceships have different minimum period parameters so it is possible to avoid frequently finding common oscillators whilst searching for low period spaceships. I'm not sure what effect grid size has on the search but I typically use a size between 30-50.

-- This script must be run from within 3D.lua.
-- It searches the current rule for oscillators and spaceships.
-- Author: Andrew Trevorrow (andrew@trevorrow.com), May 2018.
-- Contributor(s): Arie Paap

-- Changelist
-- v0.2
-- - Run search in larger grid with fixed size small soup
-- - Manually generate soup with SetCell() for performance
-- - Support D2 and D4 symmetry (applied to YZ plane)
--
-- v0.1 - Initial release
-- - Generate random soups in a 3D CA.
-- - Evolve soups and stop search when a periodic pattern is found.

local g = golly()
local op = require "oplus"  -- for op.process

-- search parameters
local soup_size = 4         -- size of the base random soup
local min_period = 5        -- ignore oscillators with periods less than this.
local min_ship_period = 2   -- ignore spaceships with periods less than this.
local soup_sym = "D4_+4"    -- soup symmetry to search: "C1", "D2_+1", "D2_+2", "D4_+1", "D4_+2", "D4_+4".
local soup_range = {30, 40} -- soup density range

-- initialize lists
local hashlist = {}         -- for pattern hash values
local genlist = {}          -- corresponding generation counts
local poplist = {}          -- corresponding population counts
local boxlist = {}          -- corresponding bounding boxes

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

local function SymSoup(size, perc, sym)
    -- Symmetry is applied to the YZ plane (ships with D4 symmetry travel along x-axis)
    -- sym can be one of "C1", "D2_+1", "D2_+2", "D4_+1", "D4_+2", "D4_+4"
   
    -- Do random soup generation to replace calls to RandomPattern()
    ClearCells()
    -- May not be necessary, but clear history to prevent potential problems.
    -- Would be nice to disable Undo History instead.
    ClearUndoRedo()
   
    -- Determine symmetry operations
    local ysym, zsym = false, false
    if sym:sub(1, 2) == "C1" then
    if sym:sub(1, 2) == "D2" then
        if sym:sub(4, 5) == "+1" then
            zsym = 0
        elseif sym:sub(4, 5) == "+2" then
            zsym = -1
        else
            g.warn("Unsupported D2 symmetry string")
            return false
        end
    elseif sym:sub(1, 2) == "D4" then
        if sym:sub(4, 5) == "+1" then
            ysym, zsym = 0, 0
        elseif sym:sub(4, 5) == "+2" then
            ysym, zsym = 0, -1
        elseif sym:sub(4, 5) == "+4" then
            ysym, zsym = -1, -1
        else
            g.warn("Unsupported D4 symmetry string "..sym)
            return false
        end
    else
        g.warn("Unsupported symmetry string")
        return false
    end
   
    -- Generate the soup
    maxval = size-1
    for z = 0, maxval do
        for y = 0, maxval do
            for x = 0, maxval do
                if math.random(0,99) < perc then
                    SetCell(x, y, z, 1)
                    if zsym then
                        -- First symmetry plane
                        SetCell(x, y, zsym-z, 1)
                        if ysym then
                            -- Second symmetry planes
                            SetCell(x, ysym-y, z, 1)
                            SetCell(x, ysym-y, zsym-z, 1)
                        end
                    end
                end
            end
        end
    end
   
    return true
end

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

local function HashPattern(bbox)
    local minx, maxx, miny, maxy, minz, maxz = table.unpack(bbox)
    local hash = 31415962
    for z = minz, maxz do
        local zshift = z - minz
        for y = miny, maxy do
            local yshift = y - miny
            for x = minx, maxx do
                if GetCell(x, y, z) > 0 then
                    -- ~ is Lua's bitwise XOR
                    hash = (hash * 1000003) ~ zshift
                    hash = (hash * 1000003) ~ yshift
                    hash = (hash * 1000003) ~ (x - minx)
                end
            end
        end
    end
    return hash
end

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

local function oscillating(popcount)
    -- return >= 1 if the pattern is stable or oscillating, otherwise 0

    -- get the current pattern's bounding box
    -- ie. { minx, maxx, miny, maxy, minz, maxz }
    local pattbox = GetBounds()
   
    -- calculate a hash value for the current pattern
    local h = HashPattern(pattbox)
   
    -- determine where to insert h into hashlist
    local gencount = GetGeneration()
    local pos = 1
    local listlen = #hashlist
    while pos <= listlen do
        if h > hashlist[pos] then
            pos = pos + 1
        elseif h < hashlist[pos] then
            -- shorten lists and append info below
            for i = 1, listlen - pos + 1 do
                table.remove(hashlist)
                table.remove(genlist)
                table.remove(poplist)
                table.remove(boxlist)
            end
            break
        else
            -- h == hashlist[pos] so pattern is probably oscillating, but just in
            -- case this is a hash collision we also compare pop count and box size
            local box = boxlist[pos]
            if popcount == poplist[pos] and
                    pattbox[2]-pattbox[1] == box[2]-box[1] and
                    pattbox[4]-pattbox[3] == box[4]-box[3] and
                    pattbox[6]-pattbox[5] == box[6]-box[5] then
                local period = gencount - genlist[pos]
                local moving = true
                if box[1] - pattbox[1] == 0 and box[3] - pattbox[3] == 0 and
                        box[5] - pattbox[5] == 0 then
                    moving = false
                end
                return period, moving
            else
                -- look at next matching hash value or insert if no more
                pos = pos + 1
            end
        end
    end
   
    -- store hash/gen/pop/box info at same position in various lists
    table.insert(hashlist, pos, h)
    table.insert(genlist, pos, gencount)
    table.insert(poplist, pos, popcount)
    table.insert(boxlist, pos, pattbox)
   
    return 0, nil
end

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

local pattcount = 0
local oldms = 0
while true do
    -- get and ignore most user events so they don't get queued up
    -- and then acted on after this script terminates, but we do call
    -- op.process so user has access to some safe menu items
    op.process(g.getevent())
   
    -- write another function to find the rule's goldilocks density!!!
    -- note that some (many?) rules have a very small density range at which
    -- interesting patterns emerge; eg. for 3D4,7/5,8 the density is around 8%
    if not SymSoup(soup_size, math.random(table.unpack(soup_range)), soup_sym) then
        goto found_something
    end
   
    -- initialize lists
    hashlist = {}
    genlist = {}
    poplist = {}
    boxlist = {}
    pattcount = pattcount + 1
    local newms = g.millisecs()
    local initpop = GetPopulation()
    while true do
        local pop = GetPopulation()
        if pop == 0 then
            break           -- pattern died
        elseif pop > 2 * initpop then
            break           -- pattern exploded
        end
        local period, moving = oscillating(pop)
        if moving and period >= min_ship_period then
            SetMessage("Found spaceship with period = "..period)
            goto found_something
        elseif (not moving) and period >= min_period then
            SetMessage("Found oscillator with period = "..period)
            goto found_something
        elseif period > 0 then
            break           -- stable or low period oscillator or spaceship
        end
        Step()
    end
    if newms - oldms >= 1000 then   -- show msg every second
        oldms = newms
        SetMessage("Searching... (hit escape to abort) patterns="..pattcount)
        Update()
    end
end
::found_something::


Andrew: I started out using the 3D.lua api to generate the random soups but switched to manually generating the soup for performance reasons. Here are some observations about the api:

  • The "full" option to RandomPattern() is nice, but it's inconvenient that there's no way to fill a smaller selection. I wanted to generate a small soup in a larger grid so I changed the grid size down, random filled, and changed the grid size back up again (to 40) . The last step is very slow (~15ms).
  • I also checked NewPattern(). This was surprisingly slow for larger grids, once again taking 15-20ms for a size 40 grid.
  • I don't know what the best way to deal with Undo History is in the search script. I'm not even sure if it would have an impact, but it would be nice to be able disable it. Instead, I call ClearUndoRedo() for every soup - which seems like overkill.
  • There's no way to manually reset the generation count after calling ClearCells().
  • Copying and pasting a random soup in order to generate symmetric soups seemed to be reasonable, performance wise, but it does clobber the clipboard of course. It turned out to be easier and faster to do that directly as part of the soup creation. I hope the api for cell lists will make this easy and fast.

It feels bizarre that so much Golly functionality is being completely re-implemented in lua in order run 3D CA. Did you consider implementing a 3D neighbourhood natively in Golly in a similar way to LtL? I only ask because I'm curious about the trade-offs that you had to make. On the other hand, I wonder how much of this editing and simulation functionality which has been directly implemented in 3D.lua will be reusable by you or someone else wanting to simulate CA on other neighbourhoods not supported natively by Golly.

Edit: Fixed missing assignment to maxval. Which means I've been doing a lot of testing with a script that didn't assign to maxval. Only noticed it after restarting 3D.lua. Also fixed broken C1 symmetry support. These symmetry labels are of course wrong, but I'm referring to a 2D plane so they are applicable.

Edit 2: Apologies for the non-functional versions of the script. Should work for all supported symmetry modes now, and fail gracefully if an unsupported symmetry string is used. Here's a few nice results - found with C1 search, Grid size 41, soup_size 15, soup_range = {8, 8}.

56c/56 spaceship in 3D4,7/5,8.
3D version=1 size=40 pos=15,17,17 gen=94069
x=10 y=6 z=6 rule=3D4,7/5,8
$$7bo/$$7bobo$9bo$7bo/7bo$7bobo$9bo$9bo$6bobbo/$9bo$9bo$oo7bo$6bobb
o$7bo/$7bo$6bobbo$6bobbo$o6boo/3$7bo!


30c/30 spaceship in 3D4,7/5,8
3D version=1 size=40 pos=19,18,18 gen=113731
x=4 y=5 z=6 rule=3D4,7/5,8
$bo$bo/$3bo$oboo/bboo$3bo$3bo$3bo$bo/3bo$obbo$3bo$3bo/$b3o$3bo$oo/$$
bo!
wildmyron
 
Posts: 790
Joined: August 9th, 2013, 12:45 am

Re: 3D.lua

Postby Andrew » May 21st, 2018, 8:31 am

wildmyron wrote:An update to Andrew's search-rule.lua...

Thanks for those improvements!

The "full" option to RandomPattern() is nice, but it's inconvenient that there's no way to fill a smaller selection. I wanted to generate a small soup in a larger grid so I changed the grid size down, random filled, and changed the grid size back up again (to 40).

That seems a rather clumsy way to do it. Better to write your own custom code:
-- for 3D.lua: create a small random pattern in middle of grid
NewPattern()
local perc = 50
local halfwd = GetGridSize()//6
for z = -halfwd, halfwd do
    for y = -halfwd, halfwd do
        for x = -halfwd, halfwd do
            if math.random(0,99) < perc then
                SetCell(x, y, z, 1)
            end
        end
    end
end


I also checked NewPattern(). This was surprisingly slow for larger grids, once again taking 15-20ms for a size 40 grid.

Probably due to the SetActivePlane call. That's not really necessary when calling NewPattern from a script so I'll avoid that in the next release (ditto for the InitialView call).

I don't know what the best way to deal with Undo History is in the search script. I'm not even sure if it would have an impact, but it would be nice to be able disable it. Instead, I call ClearUndoRedo() for every soup - which seems like overkill.

There is no undo history being recorded while running a user script, so no need to call ClearUndoRedo() anywhere. 3D.lua saves the entire state before running a script so it can easily be restored by an Undo after the script finishes. However, if the script calls NewPattern or RandomPattern or OpenPattern then there will be nothing to undo as those functions call ClearUndoRedo. (This is consistent with how Golly scripts behave, although the implementation in 3D.lua is a lot simpler.)

There's no way to manually reset the generation count after calling ClearCells().

That's really what NewPattern is for. Probably safer not to call any functions not documented in 3D.lua's help. I'm avoiding the temptation to provide a SetGenCount function because Golly's equivalent g.setgen command was probably the single biggest cause of bugs in the undo/redo code!

Copying and pasting a random soup in order to generate symmetric soups seemed to be reasonable, performance wise, but it does clobber the clipboard of course.

It's very easy to save and restore the clipboard using g.getclipstr and g.setclipstr.

It turned out to be easier and faster to do that directly as part of the soup creation. I hope the api for cell lists will make this easy and fast.

Let me know exactly what functions you'd like to see and I'll try and implement them. Or even better, write the desired functions in your own copy of 3D.lua so you can test them out. I'll be happy to merge them into the next release.

It feels bizarre that so much Golly functionality is being completely re-implemented in lua in order run 3D CA.

So true! A number of times while working on 3D.lua I found myself thinking "I'm just writing Golly all over again".

Did you consider implementing a 3D neighbourhood natively in Golly in a similar way to LtL?

Yep, 3D.lua might well be the prototype for a new 3D algorithm in Golly. Whether this happens is too early to say, but moving to C++ for generating 3D patterns and OpenGL for rendering them would be a lot faster than doing things via Lua and the overlay.

On the other hand, I wonder how much of this editing and simulation functionality which has been directly implemented in 3D.lua will be reusable by you or someone else wanting to simulate CA on other neighbourhoods not supported natively by Golly.

One of the reasons for writing 3D.lua was to show people how it's possible to completely replace Golly's usual interface and implement any other CA rules you might want to explore (eg. on a Penrose tiling, or real-valued CAs, or more than 256 states). Golly + Lua + the overlay is actually a very nice graphics/game engine (as proven by Chris Rowett's lovely breakout.lua).
User avatar
Andrew
Moderator
 
Posts: 671
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: 3D.lua

Postby Andrew » June 12th, 2018, 8:46 pm

The recently released Golly 3.2b2 has a new version of 3D.lua with many changes. A number of new neighborhoods are now supported:

* The Face neighborhood consists of the 6 cells adjacent to the faces of a cube. Append F to the rule string; eg. 3D0..6/1,3F is that crystal growth rule in the VoC video. (This is the 3D version of the von Neumann neighborhood, so you can also enter V instead of F, but the canonical version uses F.)

* The Corner neighborhood consists of the 8 cells adjacent to the corners of a cube. Append C to the rule string.

* The Edge neighborhood consists of the 12 cells adjacent to the edges of a cube. Append E to the rule string. Here's a diagonal c/2 ship in 3D3/3E:
3D version=1 size=30 pos=14,14,14
x=3 y=3 z=4 rule=3D3/3E
$bo/$obo$bo/obo/bo!

* The Hexahedral neighborhood simulates 12 cells packed around a central sphere. Append H to the rule string. This is the neighborhood discussed by Bays in the 01-5-1.pdf paper. Here are the little and big gliders from figure 2:
3D version=1 size=30 pos=13,12,14
x=5 y=7 z=2 rule=3D3/3H
boboo$bobo4$3boo$3bo/bobo$oboo4$bobo$bboo!

To see the cells in the hexahedral neighborhood, load this pattern containing a single live cell and hit the space bar once:
3D version=1 size=30 pos=15,15,15
x=1 y=1 z=1 rule=3D0..12/1..12H
o!

* Added support for Busy Boxes (see http://www.busyboxes.org). More details are in 3D.lua's help. Note that I've edited the oscar3d.lua script posted above to cope with Busy Boxes (each generation is in one of 6 phases, and in some phases the pattern might not change). The modified script will correctly detect the following oscillator:
3D version=1 size=24 pos=11,12,12
# p3966 oscillator
x=5 y=2 z=2 rule=BusyBoxes
o3bo$3o/oobbo$oo!

* The getevent command now returns a "file filepath" event if the user tries to open a file while a script is running. 3D.lua uses this event to make it a lot easier to open patterns or run 3D scripts. My startup script has these lines:
-- edit the following path to match where you store your 3D patterns and scripts
g.setdir("files", g.getdir("app").."Test-files/3D/")
g.setoption("showfiles", 1)

Now all my 3D patterns and scripts are just a simple click away. 3D.lua will restore the original files directory when it exits.

* User scripts can now call these functions: GetCells, PutCells, Reset (see 3D.lua's help for details).

* The tool bar has a slider for changing the step size. When generating, only generations that are a multiple of the step size will be displayed.

* New keyboard shortcuts:
tab            -- advance pattern to next multiple of step size
=              -- increase step size
-              -- decrease step size
1              -- reset the step size to 1
ctrl-B         -- do a paste using OR mode
shift-ctrl-B   -- do a paste using XOR mode

* The Paste function can paste in Golly's 2D patterns (as long as they aren't empty and fit within the grid).

* If the active plane is visible then clipboard patterns that are 1 cell thick will appear in the middle of the plane, otherwise in the middle of the grid.

* The state parameter in SetCell(x,y,z,s) is optional and defaults to 1.

* Step() has an optional parameter and is equivalent to Step(1).

* Numerous cosmetic improvements.
User avatar
Andrew
Moderator
 
Posts: 671
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: 3D.lua

Postby Andrew » June 12th, 2018, 9:10 pm

This script illustrates the use of Reset:
-- A version of goto.lua for 3D.lua.  The given generation can be an
-- absolute number like 1,000,000 (commas are optional) or a number
-- relative to the current generation like +9 or -6.  If the target
-- generation is less than the current generation then we go back
-- to the starting generation (normally 0) and advance to the target.
-- Author: Andrew Trevorrow, June 2018.

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

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

local function go_to(gen)
    local currgen = GetGeneration()
    local newgen
    if gen:sub(1,1) == '+' then
        newgen = currgen + tonumber(gen:sub(2,-1))
    elseif gen:sub(1,1) == '-' then
        local n = tonumber(gen:sub(2,-1))
        if currgen > n then
            newgen = currgen - n
        else
            newgen = 0
        end
    else
        newgen = tonumber(gen)
    end
   
    if newgen < currgen then
        -- try to go back to starting gen (not necessarily 0) and
        -- then forwards to newgen
        Reset()
        -- current gen might be > 0 if user loaded a pattern file
        -- with gen count > 0
        currgen = GetGeneration()
        if newgen < currgen then
            SetMessage("Can't go back any further; pattern was saved "..
                       "at generation "..currgen..".")
            return
        end
    end
    if newgen == currgen then return end
   
    SetMessage("Hit escape to abort script...")
    local oldsecs = os.clock()
    while currgen < newgen do
        Step()
        currgen = currgen + 1
        if GetPopulation() == 0 then
            SetMessage("Pattern is empty.")
            return
        end
        local newsecs = os.clock()
        if newsecs - oldsecs >= 1.0 then
            -- do an update every sec
            oldsecs = newsecs
            Update()
        end
    end
    SetMessage(nil)
end

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

local function savegen(filename, gen)
    local f = io.open(filename, "w")
    if f then
        f:write(gen)
        f:close()
    else
        g.warn("Can't save gen in filename:\n"..filename)
    end
end

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

local GotoINIFileName = g.getdir("data").."goto3D.ini"
local previousgen = ""
local f = io.open(GotoINIFileName, "r")
if f then
    previousgen = f:read("*l") or ""
    f:close()
end

local gen = g.getstring("Enter the desired generation number,\n"..
                        "or -n/+n to go back/forwards by n:",
                        previousgen, "Go to generation")
if gen == "" then
    -- user entered nothing
elseif gen == '+' or gen == '-' then
    -- clear the default
    savegen(GotoINIFileName, "")
elseif not validint(gen) then
    SetMessage("Sorry, but \""..gen.."\" is not a valid integer.")
else
    -- best to save given gen now in case user aborts script
    savegen(GotoINIFileName, gen)
    go_to(gen:gsub(",",""))
end
User avatar
Andrew
Moderator
 
Posts: 671
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia

Re: 3D.lua

Postby Andrew » July 6th, 2018, 9:21 pm

The following script runs the current pattern and uses selected cells to display its history. The script also allows user interaction while it's running:

- Click-and-drag to rotate the grid.
- Use the mouse wheel to zoom in/out.
- Use keyboard shortcuts.
- Use the slider to adjust the step size.

-- For 3D.lua.  This script runs the current pattern and uses
-- selected cells to show the history of all live cells.
-- Author: Andrew Trevorrow, July 2018.

local g = golly()
local op = require "oplus"
local gp = require "gplus"
local ov = g.overlay
local round = gp.round
local unpack = table.unpack

SetMessage("Hit escape to abort script...")
CancelSelection()
MoveMode()

-- allow dragging mouse to rotate grid
local mousedown = false     -- mouse button is down?
local prevx, prevy          -- previous mouse position

while true do
    local event = op.process(g.getevent())
    if #event == 0 then
        -- might need to resize overlay
        CheckWindowSize()
    elseif event:find("^key") then
        -- handle arrow keys, I for initial view, etc
        HandleKey(event)
    elseif event:find("^oclick") then
        local _, x, y, button, mods = gp.split(event)
        x = tonumber(x)
        y = tonumber(y)
        if y > GetBarHeight() and button == "left" then
            mousedown = true
            prevx = x
            prevy = y
        end
    elseif event:find("^mup") then
        mousedown = false
    elseif event:find("^ozoomout") then
        ZoomOut()
    elseif event:find("^ozoomin") then
        ZoomIn()
    end
    local mousepos = ov("xy")
    if mousedown and #mousepos > 0 then
        local x, y = gp.split(mousepos)
        x = tonumber(x)
        y = tonumber(y)
        if x ~= prevx or y ~= prevy then
            -- mouse has moved so rotate the view
            local deltax = x - prevx
            local deltay = y - prevy
            Rotate(round(-deltay/2.0), round(deltax/2.0), 0)
            Update()
            prevx = x
            prevy = y
        end
    else
        local livecells = GetCells()
        if #livecells == 0 then
            SetMessage("Pattern is empty.")
            break
        end
        -- select all live cells without changing previous selection
        for _, xyz in ipairs(livecells) do
            SelectCell(unpack(xyz))
        end
        Step()
        if GetGeneration() % GetStepSize() == 0 then
            Update()
        end
    end
end
User avatar
Andrew
Moderator
 
Posts: 671
Joined: June 2nd, 2009, 2:08 am
Location: Melbourne, Australia


Return to Scripts

Who is online

Users browsing this forum: No registered users and 2 guests