Very interesting Brian. Have you been able to stabilise the growing end of that puffer / gun pattern - or have you found a more regular gun?
Here is a script to run 2PCA4 rules which can be used with Golly 3.3 (or one of the earlier 3.3 betas with NewCA.lua included). Save it as a file named PCA.lua
It has similar functionality to the RPCA java application. "alt R" to generate a new rule with a random pattern (restricted to reversible rules), "alt V" to change the direction for reversible rules. Like RPCA.jar the generation counter continues to increment even in reverse (this is a limitation of NewCA.lua). It is a fair bit slower than the Java app, but can be sped up a fair bit for low population patterns by increasing the step. Strobing rules and other rules with "birth" on state 0 will run, but there's no built in strobing support like with Andrew's Margolus.lua.
Code: Select all
--[[
This script lets you explore CA rules using the Partitioned Cellular
Automata scheme.
Author: Arie Paap (wildmyron@gmail.com), October 2019.
Adapted from Margolus.lua and other NewCA.lua scripts by Andrew Treverrow
NextVal() based on Java and Golly RuleTree implementation of PCA rules by Brian Prentice
--]]
local g = golly()
local gp = require "gplus"
local split = gp.split
local floor = math.floor
require "gplus.NewCA"
SCRIPT_NAME = "PCA"
DEFAULT_RULE = "2PCA4,0,4,8,3,1,10,6,7,2,9,5,11,12,13,14,15" -- Model 1
RULE_HELP = [[
This script lets you explore Partitioned Cellular Automata (PCA).
It currently only supports 2-state PCA with 4 neighbours (2PCA4)
Rule strings are of the form kPCAn,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s
where 'k' is the number of states (must be 2), 'n' is the number of neighbours
(must be 4) and there must be 16 's' numbers with values from 0 to 15.
<p>
You can enter rules using one of the following aliases (case must match):
<p>
<center>
<table cellspacing=1 border=2 cols=2 width="90%%">
<tr><td align=right> Alias </td><td> Rule </td></tr>
<tr><td align=right> Model_1 / PCA_1 </td><td> 2PCA4,0,4,8,3,1,10,6,7,2,9,5,11,12,13,14,15 </td></tr>
<tr><td align=right> PCA_2 </td><td> 2PCA4,0,2,4,3,8,10,6,7,1,9,5,11,12,13,14,15 </td></tr>
<tr><td align=right> PCA_3 </td><td> 2PCA4,0,8,1,3,2,5,6,7,4,9,10,11,12,13,14,15 </td></tr>
<tr><td align=right> PCA_4 </td><td> 2PCA4,0,2,4,12,8,5,9,7,1,6,10,11,3,13,14,15 </td></tr>
<tr><td align=right> Model_2 / PCA_5 </td><td> 2PCA4,0,4,8,3,1,10,6,11,2,9,5,13,12,14,7,15 </td></tr>
<tr><td colspan=2> PCA_6 - PCA_12: 2PCA4 rules as defined by Brian Prentice at the link below</td></tr>
</table>
</center>
<p>
For more details about 2D Partitioned Cellular Automata see this link:<br>
<a href="http://www.conwaylife.com/forums/viewtopic.php?f=11&t=4098">
http://www.conwaylife.com/forums/viewtopic.php?f=11&t=4098</a>,<br>
and references therein. Model 1 and Model 2 are two reversible PCA rules shown
to be computation universal by Kenichi Morita <i>et al.</i>
]]
-- the following are non-local so a startup script can change them
DEFWD, DEFHT = 500, 500 -- default grid size
aliases = {
Model_1 = "2PCA4,0,4,8,3,1,10,6,7,2,9,5,11,12,13,14,15",
PCA_1 = "2PCA4,0,4,8,3,1,10,6,7,2,9,5,11,12,13,14,15",
PCA_2 = "2PCA4,0,2,4,3,8,10,6,7,1,9,5,11,12,13,14,15",
PCA_3 = "2PCA4,0,8,1,3,2,5,6,7,4,9,10,11,12,13,14,15",
PCA_4 = "2PCA4,0,2,4,12,8,5,9,7,1,6,10,11,3,13,14,15",
Model_2 = "2PCA4,0,4,8,3,1,10,6,11,2,9,5,13,12,14,7,15",
PCA_5 = "2PCA4,0,4,8,3,1,10,6,11,2,9,5,13,12,14,7,15",
PCA_6 = "2PCA4,0,2,4,3,8,10,6,14,1,9,5,7,12,11,13,15",
PCA_7 = "2PCA4,0,2,4,12,8,5,9,14,1,6,10,7,3,11,13,15",
PCA_8 = "2PCA4,0,2,4,12,8,10,9,14,1,6,5,7,3,11,13,15",
PCA_9 = "2PCA4,0,2,4,12,8,10,9,13,1,6,5,14,3,7,11,15",
PCA_10 = "2PCA4,0,1,2,3,4,5,6,13,8,9,10,14,12,7,11,15",
PCA_11 = "2PCA4,0,4,8,3,1,10,6,11,2,9,5,11,13,12,14,15",
PCA_12 = "2PCA4,0,4,2,3,14,6,11,5,8,7,9,13,10,12,1,15",
}
--------------------------------------------------------------------------------
-- Rule variables set by ParseRule and used in NextPattern
NextPattern = function() end -- ParseRule sets this to SlowPattern or FastPattern
local transition = {} -- Transition table used to run PCA rules forwards
local inv_transition = {} -- Lookup table used to run reversible PCA rules backwards
local reversible = false -- True for reversible PCA (RPCA) rules
local direction = 1 -- >0 if running forwards, <0 if running backwards
local numstates = 2 -- Allowed states for each partition
local numneighbors = 4 -- Neighbourhood is von Neumann (ignoring centre cell)
local maxstate = floor(2^4)-1 -- Cell state representing all partitions (S=1, W=2, N=3, E=4)
function ParseRule(newrule)
-- Parse the given rule string.
-- If valid then return nil, the canonical rule string,
-- the width and height of the grid, and the number of states.
-- If not valid then just return an appropriate error message.
-- Default: 2PCA4,0,4,8,3,1,10,6,7,2,9,5,11,12,13,14,15
if #newrule == 0 then
newrule = DEFAULT_RULE -- should be a valid rule!
else
-- check for a known alias
local rule = aliases[newrule]
if rule then
newrule = rule
elseif newrule:find(":") then
-- try without the suffix
local p, s = split(newrule,":")
rule = aliases[p]
if rule then newrule = rule..":"..s end
end
end
local prefix, suffix = split(newrule:upper(),":")
-- check for a valid prefix
if prefix:sub(1,5) ~= "2PCA4" then
return "Rule must start with kPCAn. Only k=2 and n=4 are currently supported."
end
-- fill the transition table for use in NextPattern
transition = {}
local i = 0
for n in string.gmatch(prefix, "[,;_](%d+)") do
if tonumber(n) > maxstate then
return "Bad number: "..n.." (must be from 0 to "..maxstate..")."
end
transition[i] = tonumber(n)
i = i + 1
if i > (maxstate + 1) then break end
end
if i ~= (maxstate + 1) then
return string.format("Rule must specify %d comma-separated numbers from 0 to %d.", maxstate + 1, maxstate)
end
-- invert the transition table for reversible rules
inv_transition = {}
reversible = true
direction = 1
for i = 0, maxstate do
inv_transition[i] = -1
end
for i = 0, maxstate do
inv_transition[transition[i]] = i
end
for i = 0, maxstate do
if inv_transition[i] < 0 then
reversible = false
break
end
end
-- check for a valid suffix like T50 or T50,30
local wd, ht = DEFWD, DEFHT
if suffix then
if suffix:find(",") then
wd, ht = suffix:match("^T(%d+),(%d+)$")
else
wd = suffix:match("^T(%d+)$")
ht = wd
end
wd = tonumber(wd)
ht = tonumber(ht)
if wd == nil or ht == nil then
return "Rule suffix must be Twd,ht or Twd."
end
end
if wd < 4 then wd = 4 elseif wd > 1000 then wd = 1000 end
if ht < 4 then ht = 4 elseif ht > 1000 then ht = 1000 end
-- given rule is valid
-- create the canonical form of the given rule
local canonrule = numstates.."PCA"..numneighbors
for i = 0, 15 do
canonrule = canonrule..","..transition[i]
end
canonrule = canonrule..":T"..wd..","..ht
if transition[0] == 0 then
NextPattern = FastPattern
else
NextPattern = SlowPattern
end
return nil, canonrule, wd, ht, maxstate + 1
end
--------------------------------------------------------------------------------
local function NextVal(x, y, minx, miny, maxx, maxy, emptycells)
-- Calculate the next state of a cell at (x,y)
-- If called with arg <emptycells>, then record coords of empty neighbours
local get = g.getcell
local gridwd = maxx-minx+1
local keepempty = false
if emptycells then keepempty = true end
-- for neighbour coordinates
local xm1 = x-1
local xp1 = x+1
local ym1 = y-1
local yp1 = y+1
-- might need to wrap edges
if xm1 < minx then xm1 = maxx end
if xp1 > maxx then xp1 = minx end
if ym1 < miny then ym1 = maxy end
if yp1 > maxy then yp1 = miny end
local S = get(x, yp1)
local W = get(xm1, y)
local N = get(x, ym1)
local E = get(xp1, y)
if direction < 0 then
S = inv_transition[S]
W = inv_transition[W]
N = inv_transition[N]
E = inv_transition[E]
end
local state = 0
if S == 0 then
if keepempty then emptycells[(yp1 - miny) * gridwd + (x - minx)] = 1 end
elseif (S & 4) > 0 then
state = state + 1
end
if W == 0 then
if keepempty then emptycells[(y - miny) * gridwd + (xm1 - minx)] = 1 end
elseif (W & 8) > 0 then
state = state + 2
end
if N == 0 then
if keepempty then emptycells[(ym1 - miny) * gridwd + (x - minx)] = 1 end
elseif (N & 1) > 0 then
state = state + 4
end
if E == 0 then
if keepempty then emptycells[(y - miny) * gridwd + (xp1 - minx)] = 1 end
elseif (E & 2) > 0 then
state = state + 8
end
if direction > 0 then
state = transition[state]
end
return state
end
--------------------------------------------------------------------------------
function FastPattern(currcells, minx, miny, maxx, maxy)
-- Create the next pattern when transition[0] == 0
-- currcells is a non-empty cell array containing the current pattern.
-- minx, miny, maxx, maxy are the cell coordinates of the grid edges.
-- This function must return the new pattern as a cell array.
local newcells = {} -- cell array for the new pattern (one-state)
local newlen = 0 -- length of newcells
local emptycells = {} -- keys are x,y coords for empty cells
local gridwd = maxx-minx+1
local currlen = #currcells -- currcells is multi-state so currlen is odd
if currlen % 3 > 0 then
currlen = currlen - 1 -- ignore padding int
end
for i = 1, currlen, 3 do
local x = currcells[i]
local y = currcells[i+1]
local newstate = NextVal(x, y, minx, miny, maxx, maxy, emptycells)
if newstate > 0 then
newlen = newlen+1 ; newcells[newlen] = x
newlen = newlen+1 ; newcells[newlen] = y
newlen = newlen+1 ; newcells[newlen] = newstate
end
end
-- now go through emptycells and add any births to newcells
for k,v in pairs(emptycells) do
local x = minx + (k % gridwd)
local y = miny + (k // gridwd)
local newstate = NextVal(x, y, minx, miny, maxx, maxy)
if newstate > 0 then
newlen = newlen + 1 ; newcells[newlen] = x
newlen = newlen + 1 ; newcells[newlen] = y
newlen = newlen + 1 ; newcells[newlen] = newstate
end
end
if newlen > 0 then
-- newcells is a multi-state cell array so ensure length is odd
if newlen & 1 == 0 then newcells[newlen+1] = 0 end
end
-- delete the old pattern and add the new pattern
g.putcells(currcells, 0, 0, 1, 0, 0, 1, "xor")
g.putcells(newcells)
if direction < 0 then
g.show("reverse")
end
return newcells -- return the new pattern
end
--------------------------------------------------------------------------------
function SlowPattern(currcells, minx, miny, maxx, maxy)
-- Create the next pattern when transition[0] > 0 and we have to examine
-- every cell in the grid (so very slow).
-- currcells is a non-empty cell array containing the current pattern.
-- minx, miny, maxx, maxy are the cell coordinates of the grid edges.
-- This function must return the new pattern as a cell array.
-- TODO cache rows to minimise the number of get calls
-- requires completely different NextVal()
local newcells = {} -- cell array for the new pattern (one-state)
local newlen = 0 -- length of newcells
for y = miny, maxy do
for x = minx, maxx do
local newstate = NextVal(x, y, minx, miny, maxx, maxy)
if newstate > 0 then
newlen = newlen+1 ; newcells[newlen] = x
newlen = newlen+1 ; newcells[newlen] = y
newlen = newlen+1 ; newcells[newlen] = newstate
end
end
end
if newlen > 0 then
-- newcells is a multi-state cell array so ensure length is odd
if newlen & 1 == 0 then newcells[newlen+1] = 0 end
end
-- delete the old pattern and add the new pattern
g.putcells(currcells, 0, 0, 1, 0, 0, 1, "xor")
g.putcells(newcells)
if direction < 0 then
g.show("reverse")
end
return newcells -- return the new pattern
end
--------------------------------------------------------------------------------
-- user's startup script might want to override this
function RandomRule(reversible)
local reversible = reversible or true
local rand = math.random
local rule = numstates.."PCA"..numneighbors..",0" -- avoid strobing rules
if reversible then
local states = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
for i = 1, 15 do
rule = rule..","..table.remove(states, rand(1,#states))
end
else
for i = 1, 15 do
rule = rule..","..rand(0,15)
end
end
return rule
end
--------------------------------------------------------------------------------
-- allow alt-R to create a random pattern with a random rule
local saveHandleKey = HandleKey
function HandleKey(event)
local _, key, mods = split(event)
if key == "r" and mods == "alt" then
SetRule(RandomRule())
RandomPattern(nil, 20, 20)
elseif key == "v" and mods == "alt" then
if reversible then
direction = direction * -1
if direction < 0 then
g.show("reverse")
end
else
g.warn("Rule is not reversible.")
end
else
-- pass the event to the original HandleKey
saveHandleKey(event)
end
end
--------------------------------------------------------------------------------
-- and away we go...
StartNewCA()
The following rule can be used to display the icons I posted earlier for PCA_5. This will mean there are some funny icons used for a range 1 LtL rule and other 16 state rules using NewCA.lua, but I don't think it's an issue. Currently there's no built in way for NewCA.lua to change the icons.
: Fix bug in PCA.lua preventing it from starting up.