3D.lua
3D.lua
Re: 3D.lua
Code: Select all
-- 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
Code: Select all
-- 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)
Re: 3D.lua
- 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:
Code: Select all
-- 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()
Re: 3D.lua
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.
Code: Select all
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!
Re: 3D.lua
Code: Select all
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!
Code: Select all
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!
Code: Select all
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!
Re: 3D.lua
- Copy it to the clipboard
- Switch to Golly
- Run 3D.lua
- Press Shift-R to run the script you copied to the clipboard in step 1
- Now rotate with the mouse...
Code: Select all
-- for 3D.lua
NewPattern()
SetGridSize(100)
SetRule("3D4..9/8..12")
RandomPattern(100, false, true)
Step()
Step()
Re: 3D.lua
Not much further to contribute, but I did find a p30 spaceship in Tom's rule:
Code: Select all
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!
Semi-active here - recovering from a severe case of LWTDS.
Re: 3D.lua
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.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.
Re: 3D.lua
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.
Re: 3D.lua
A quick script based on the cell lists for the two larger ships gives these patterns: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.
Code: Select all
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!
Code: Select all
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!
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):
Code: Select all
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!
Code: Select all
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!
Code: Select all
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!
Code: Select all
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!
Semi-active here - recovering from a severe case of LWTDS.
Re: 3D.lua
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 .
Semi-active here - recovering from a severe case of LWTDS.
Re: 3D.lua
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.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.
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):
Code: Select all
if event == "key , none" then
savedHandler("key . none")
return
elseif event == "key . none" then
savedHandler("key , none")
return
end
Re: 3D.lua
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.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?
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):
Code: Select all
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!
Code: Select all
-- 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::
Re: 3D.lua
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.Andrew wrote:Here is my search-rule.lua script (I use alt-S in my startup script to run it):Code: Select all
<snip code>
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
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.
Semi-active here - recovering from a severe case of LWTDS.
- praosylen
- Posts: 2449
- Joined: September 13th, 2014, 5:36 pm
- Location: Pembina University, Home of the Gliders
- Contact:
Re: 3D.lua
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?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.
praosylen#5847 (Discord)
The only decision I made was made
of flowers, to jump universes to one of springtime in
a land of former winter, where no invisible walls stood,
or could stand for more than a few hours at most...
- gameoflifemaniac
- Posts: 1242
- Joined: January 22nd, 2017, 11:17 am
- Location: There too
Re: 3D.lua
Code: Select all
b4o25bo$o29bo$b3o3b3o2bob2o2bob2o2bo3bobo$4bobo3bob2o2bob2o2bobo3bobo$
4bobo3bobo5bo5bo3bobo$o3bobo3bobo5bo6b4o$b3o3b3o2bo5bo9bobo$24b4o!
Re: 3D.lua
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.)gameoflifemaniac wrote:I can't paste a pattern to a different layer!
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.
Re: 3D.lua
Code: Select all
-- for 3D.lua
NewPattern()
SetGridSize(100)
SetRule("3D10..25/6,7")
RandomPattern(100, false, true)
Code: Select all
-- for 3D.lua
NewPattern()
SetGridSize(100)
SetRule("3D10..23,25/6,8")
RandomPattern(100, false, true)
Re: 3D.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.
Code: Select all
-- 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::
- 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.
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.
Code: Select all
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!
Code: Select all
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!
Semi-active here - recovering from a severe case of LWTDS.
Re: 3D.lua
Thanks for those improvements!wildmyron wrote:An update to Andrew's search-rule.lua...
That seems a rather clumsy way to do it. Better to write your own custom code: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).
Code: Select all
-- 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
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 also checked NewPattern(). This was surprisingly slow for larger grids, once again taking 15-20ms for a size 40 grid.
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.)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.
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!There's no way to manually reset the generation count after calling ClearCells().
It's very easy to save and restore the clipboard using g.getclipstr and g.setclipstr.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.
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 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.
So true! A number of times while working on 3D.lua I found myself thinking "I'm just writing Golly all over again".It feels bizarre that so much Golly functionality is being completely re-implemented in lua in order run 3D CA.
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.Did you consider implementing a 3D neighbourhood natively in Golly in a similar way to LtL?
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).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.
Re: 3D.lua
* 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:
Code: Select all
3D version=1 size=30 pos=14,14,14
x=3 y=3 z=4 rule=3D3/3E
$bo/$obo$bo/obo/bo!
Code: Select all
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!
Code: Select all
3D version=1 size=30 pos=15,15,15
x=1 y=1 z=1 rule=3D0..12/1..12H
o!
Code: Select all
3D version=1 size=24 pos=11,12,12
# p3966 oscillator
x=5 y=2 z=2 rule=BusyBoxes
o3bo$3o/oobbo$oo!
Code: Select all
-- 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)
* 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:
Code: Select all
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
* 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.
Re: 3D.lua
Code: Select all
-- 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
Re: 3D.lua
- 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.
Code: Select all
-- 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
- LaundryPizza03
- Posts: 2336
- Joined: December 15th, 2017, 12:05 am
- Location: Unidentified location "https://en.wikipedia.org/wiki/Texas"
Re: 3D.lua
Code: Select all
x = 4, y = 3, rule = B3-q4z5y/S234k5j
2b2o$b2o$2o!
Re: 3D.lua
Please provide more details when reporting a bug.LaundryPizza03 wrote:I attempted to quit 3D.lua with a saved pattern open by pressing Q. Then Golly froze up.
Which version of Golly? (Upgrade if not 3.2.)
Which operating system?
Can you reproduce the problem?
Which 3D rule were you using?
Please post the saved pattern.