-- 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
-- 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)
-- 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()
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!
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!
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!
-- for 3D.lua
NewPattern()
SetGridSize(100)
SetRule("3D4..9/8..12")
RandomPattern(100, false, true)
Step()
Step()
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!
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.
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.
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!
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!
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 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.
if event == "key , none" then
savedHandler("key . none")
return
elseif event == "key . none" then
savedHandler("key , none")
return
end
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?
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!
-- 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::
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>
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.
gameoflifemaniac wrote:I can't paste a pattern to a different layer!
-- 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)
-- 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::
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!
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 wrote:An update to Andrew's search-rule.lua...
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).
-- 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.
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?
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.
3D version=1 size=30 pos=14,14,14
x=3 y=3 z=4 rule=3D3/3E
$bo/$obo$bo/obo/bo!
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!
3D version=1 size=30 pos=15,15,15
x=1 y=1 z=1 rule=3D0..12/1..12H
o!
3D version=1 size=24 pos=11,12,12
# p3966 oscillator
x=5 y=2 z=2 rule=BusyBoxes
o3bo$3o/oobbo$oo!
-- 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)
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
-- 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
-- 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
Users browsing this forum: No registered users and 3 guests