Below is a script based on NewCA.lua in Golly 3.3 that can be used to explore Polystate rules. Hit alt-R (option-R on a Mac) to create a random pattern in a random rule.
Code: Select all
--[[
This script lets you explore Boris Lejkin's Polystate CA using
the rule syntax defined by Apple Bottom here:
http://conwaylife.com/forums/viewtopic.php?f=11&t=3078&start=0
Author: Andrew Trevorrow (andrew@trevorrow.com), Oct 2019.
--]]
local g = golly()
local gp = require "gplus"
local split = gp.split
local rand = math.random
require "gplus.NewCA"
SCRIPT_NAME = "Polystate"
DEFAULT_RULE = "Polystate_B3S23_3"
RULE_HELP = [[
This script lets you explore Boris Lejkin's Polystate CA.
Rule strings are of the form Polystate_BbSs_n where
b is the list of birth counts (1 to 8), s is the list of
survival counts (0 to 8), and n is the number of states (2 to 256).
]]
-- the following are non-local so a startup script can change them
DEFWD, DEFHT = 500, 500 -- default grid size
aliases = {} -- none for now
--------------------------------------------------------------------------------
NextPattern = function() end -- set by ParseRule to NextOneState or NextMultiState
local births, survivals -- set by ParseRule and used in NextPattern
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.
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
local b, s, n = prefix:match("^POLYSTATE_B([1-8]*)S([0-8]*)_(%d+)$")
if not (b and s and n) then
return "The rule syntax must be Polystate_BbSs_n\n"..
"where b lists the birth counts (1 to 8),\n"..
"s lists the survival counts (0 to 8),\n"..
"and n is the number of states (2 to 256)."
end
n = tonumber(n)
if n < 2 or n > 256 then
return "The number of states must be from 2 to 256."
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 < 10 then wd = 10 elseif wd > 2000 then wd = 2000 end
if ht < 10 then ht = 10 elseif ht > 2000 then ht = 2000 end
-- given rule is valid
-- set births and survivals for use in NextPattern
births = {}
survivals = {}
local bc, sc = "", "" -- canonical B and S strings
for i = 0, 8 do
if b:find(tostring(i)) then births[i] = 1; bc = bc..i end
if s:find(tostring(i)) then survivals[i] = 1; sc = sc..i end
end
if n > 2 then
NextPattern = NextMultiState
else
NextPattern = NextOneState
end
-- create the canonical form of the given rule
local canonrule = "Polystate_B"..bc.."S"..sc.."_"..n..":T"..wd..","..ht
return nil, canonrule, wd, ht, n
end
--------------------------------------------------------------------------------
function NextOneState(currcells, minx, miny, maxx, maxy)
-- Create the next pattern where currcells is a one-state cell array.
local newcells = {} -- cell array for the new pattern (one-state)
local newlen = 0 -- length of newcells
local ecount = {} -- keys are x,y coords for empty neighbor counts
local gridwd = maxx-minx+1
local get = g.getcell
for i = 1, #currcells, 2 do
local x = currcells[i]
local y = currcells[i+1]
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
-- calculate offsets needed to create keys for ecount
local xm1off = xm1-minx
local xoff = x -minx
local xp1off = xp1-minx
local ym1off = (ym1-miny) * gridwd
local yoff = (y -miny) * gridwd
local yp1off = (yp1-miny) * gridwd
local xm1_ym1 = xm1off + ym1off
local x___ym1 = xoff + ym1off
local xp1_ym1 = xp1off + ym1off
local xm1_y = xm1off + yoff
local xp1_y = xp1off + yoff
local xm1_yp1 = xm1off + yp1off
local x___yp1 = xoff + yp1off
local xp1_yp1 = xp1off + yp1off
local n = 0
if get(xm1, ym1) > 0 then n = n + 1 else ecount[xm1_ym1] = (ecount[xm1_ym1] or 0) + 1 end
if get(x , ym1) > 0 then n = n + 1 else ecount[x___ym1] = (ecount[x___ym1] or 0) + 1 end
if get(xp1, ym1) > 0 then n = n + 1 else ecount[xp1_ym1] = (ecount[xp1_ym1] or 0) + 1 end
if get(xm1, y ) > 0 then n = n + 1 else ecount[xm1_y ] = (ecount[xm1_y ] or 0) + 1 end
if get(xp1, y ) > 0 then n = n + 1 else ecount[xp1_y ] = (ecount[xp1_y ] or 0) + 1 end
if get(xm1, yp1) > 0 then n = n + 1 else ecount[xm1_yp1] = (ecount[xm1_yp1] or 0) + 1 end
if get(x , yp1) > 0 then n = n + 1 else ecount[x___yp1] = (ecount[x___yp1] or 0) + 1 end
if get(xp1, yp1) > 0 then n = n + 1 else ecount[xp1_yp1] = (ecount[xp1_yp1] or 0) + 1 end
if survivals[n] then
-- this live cell survives
newlen = newlen+1 ; newcells[newlen] = x
newlen = newlen+1 ; newcells[newlen] = y
end
end
-- now add births to newcells using the empty neighbor counts created above
for k,v in pairs(ecount) do
if births[v] then
newlen = newlen+1 ; newcells[newlen] = minx + (k % gridwd)
newlen = newlen+1 ; newcells[newlen] = miny + (k // gridwd)
end
end
-- delete the old pattern and add the new pattern
g.putcells(currcells, 0, 0, 1, 0, 0, 1, "xor")
g.putcells(newcells)
return newcells -- return the new pattern
end
--------------------------------------------------------------------------------
function NextMultiState(currcells, minx, miny, maxx, maxy)
-- Create the next pattern where currcells is a multi-state cell array.
local newcells = {} -- cell array for the new pattern (multi-state)
local newlen = 0 -- length of newcells
local emptycells = {} -- keys are x,y coords for empty cells next to at least one live cell
local gridwd = maxx-minx+1
local maxstate = g.numstates() - 1
local get = g.getcell
local currlen = #currcells -- multi-state so odd number
-- ignore any padding int at end of currcells
if currlen % 3 > 0 then currlen = currlen - 1 end
for i = 1, currlen, 3 do
local x = currcells[i]
local y = currcells[i+1]
local state = currcells[i+2]
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
-- calculate offsets needed to create keys for emptycells
local xm1off = xm1-minx
local xoff = x -minx
local xp1off = xp1-minx
local ym1off = (ym1-miny) * gridwd
local yoff = (y -miny) * gridwd
local yp1off = (yp1-miny) * gridwd
local xm1_ym1 = xm1off + ym1off
local x___ym1 = xoff + ym1off
local xp1_ym1 = xp1off + ym1off
local xm1_y = xm1off + yoff
local xp1_y = xp1off + yoff
local xm1_yp1 = xm1off + yp1off
local x___yp1 = xoff + yp1off
local xp1_yp1 = xp1off + yp1off
local n1 = get(xm1, ym1)
local n2 = get(x , ym1)
local n3 = get(xp1, ym1)
local n4 = get(xm1, y )
local n5 = get(xp1, y )
local n6 = get(xm1, yp1)
local n7 = get(x , yp1)
local n8 = get(xp1, yp1)
-- count the number of neighbors with the same state
local n = 0
if n1 > 0 then if n1 == state then n = n + 1 end else emptycells[xm1_ym1] = 0 end
if n2 > 0 then if n2 == state then n = n + 1 end else emptycells[x___ym1] = 0 end
if n3 > 0 then if n3 == state then n = n + 1 end else emptycells[xp1_ym1] = 0 end
if n4 > 0 then if n4 == state then n = n + 1 end else emptycells[xm1_y ] = 0 end
if n5 > 0 then if n5 == state then n = n + 1 end else emptycells[xp1_y ] = 0 end
if n6 > 0 then if n6 == state then n = n + 1 end else emptycells[xm1_yp1] = 0 end
if n7 > 0 then if n7 == state then n = n + 1 end else emptycells[x___yp1] = 0 end
if n8 > 0 then if n8 == state then n = n + 1 end else emptycells[xp1_yp1] = 0 end
if survivals[n] then
-- this live cell survives
newlen = newlen+1 ; newcells[newlen] = x
newlen = newlen+1 ; newcells[newlen] = y
newlen = newlen+1 ; newcells[newlen] = state
end
end
-- now go thru 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 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 n1 = get(xm1, ym1)
local n2 = get(x , ym1)
local n3 = get(xp1, ym1)
local n4 = get(xm1, y )
local n5 = get(xp1, y )
local n6 = get(xm1, yp1)
local n7 = get(x , yp1)
local n8 = get(xp1, yp1)
local scount = {}
for s = 1, maxstate do
scount[s] = 0
end
if n1 > 0 then scount[n1] = scount[n1] + 1 end
if n2 > 0 then scount[n2] = scount[n2] + 1 end
if n3 > 0 then scount[n3] = scount[n3] + 1 end
if n4 > 0 then scount[n4] = scount[n4] + 1 end
if n5 > 0 then scount[n5] = scount[n5] + 1 end
if n6 > 0 then scount[n6] = scount[n6] + 1 end
if n7 > 0 then scount[n7] = scount[n7] + 1 end
if n8 > 0 then scount[n8] = scount[n8] + 1 end
local possible = {}
local posslen = 0
for s = 1, maxstate do
if births[ scount[s] ] then
-- a possible birth (if it's the only one)
posslen = posslen + 1
if posslen > 1 then break end
possible[posslen] = s
end
end
if posslen == 1 then
-- we have a birth
newlen = newlen+1 ; newcells[newlen] = x
newlen = newlen+1 ; newcells[newlen] = y
newlen = newlen+1 ; newcells[newlen] = possible[posslen]
end
end
if newlen > 0 then
-- ensure length of newcells is odd
if newlen & 1 == 0 then newlen = newlen+1 ; newcells[newlen] = 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)
return newcells -- return the new pattern
end
--------------------------------------------------------------------------------
-- override the SetColors function
function SetColors()
g.setcolors( {0,0,0,0} ) -- state 0 is black
g.setcolors( {255,255,0, 255,0,0 } ) -- live cells go from yellow to red
end
--------------------------------------------------------------------------------
-- startup script can redefine this
function RandomRule()
local rand = math.random
local b = ""
local s = ""
for i = 1, 8 do if rand(0,1) > 0 then b = b..i end end
for i = 0, 8 do if rand(0,1) > 0 then s = s..i end end
return "Polystate_B"..b.."S"..s.."_"..rand(2,256)
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()
else
-- pass the event to the original HandleKey
saveHandleKey(event)
end
end
--------------------------------------------------------------------------------
-- and away we go...
StartNewCA()