1--[[ 2This script lets you explore rules using the Margolus neighborhood. 3Author: Andrew Trevorrow (andrew@trevorrow.com), May 2019. 4--]] 5 6local g = golly() 7local gp = require "gplus" 8local split = gp.split 9 10require "gplus.NewCA" 11SCRIPT_NAME = "Margolus" 12DEFAULT_RULE = "M0,8,4,3,2,5,9,7,1,6,10,11,12,13,14,15" -- BBM 13RULE_HELP = [[ 14This script lets you explore rules using the Margolus neighborhood. 15Rule strings are of the form Mn,n,n,n,n,n,n,n,n,n,n,n,n,n,n,n 16where there must be 16 numbers with values from 0 to 15. 17<p> 18You can also enter rules using MCell's syntax (MS,Dn;n;n;n...). 19Or you can enter one of the following aliases (case must match): 20<p> 21<center> 22<table cellspacing=1 border=2 cols=2 width="90%%"> 23<tr><td align=right> Alias </td><td> Rule </td></tr> 24<tr><td align=right> bbm </td><td> M0,8,4,3,2,5,9,7,1,6,10,11,12,13,14,15 </td></tr> 25<tr><td align=right> critters </td><td> M15,14,13,3,11,5,6,1,7,9,10,2,12,4,8,0 </td></tr> 26<tr><td align=right> tron </td><td> M15,1,2,3,4,5,6,7,8,9,10,11,12,13,14,0 </td></tr> 27</table> 28</center> 29<p> 30For more details about the Margolus neighborhood see this link:<br> 31<a href="http://www.mirekw.com/ca/rullex_marg.html" 32 >http://www.mirekw.com/ca/rullex_marg.html</a> 33]] 34 35-- the following are non-local so a startup script can change them 36DEFWD, DEFHT = 500, 500 -- default grid size 37aliases = { 38 bbm = "M0,8,4,3,2,5,9,7,1,6,10,11,12,13,14,15", 39 critters = "M15,14,13,3,11,5,6,1,7,9,10,2,12,4,8,0", 40 tron = "M15,1,2,3,4,5,6,7,8,9,10,11,12,13,14,0", 41} 42 43-------------------------------------------------------------------------------- 44 45NextPattern = function() end -- ParseRule sets this to SlowPattern or FastPattern 46local transition = {} -- transition table set by ParseRule and used in NextPattern 47local even_transition = {} -- transition table for even generations 48local odd_transition = {} -- transition table for odd generations 49local alternate_rules = false -- FastPattern uses even_transition and odd_transition? 50 51function ParseRule(newrule) 52 -- Parse the given rule string. 53 -- If valid then return nil, the canonical rule string, 54 -- the width and height of the grid, and the number of states. 55 -- If not valid then just return an appropriate error message. 56 57 if #newrule == 0 then 58 newrule = DEFAULT_RULE -- should be a valid rule! 59 else 60 -- check for a known alias 61 local rule = aliases[newrule] 62 if rule then 63 newrule = rule 64 elseif newrule:find(":") then 65 -- try without the suffix 66 local p, s = split(newrule,":") 67 rule = aliases[p] 68 if rule then newrule = rule..":"..s end 69 end 70 end 71 72 local prefix, suffix = split(newrule:upper(),":") 73 74 -- check for a valid prefix 75 if prefix:sub(1,1) ~= "M" then 76 return "Rule must start with M." 77 end 78 local blocknum = {} 79 local i = 0 80 -- note that we append a comma to prefix to simplify the gmatch pattern 81 for n in string.gmatch(prefix..",", "(%d+)[,;]") do 82 if tonumber(n) > 15 then 83 return "Bad number: "..n.." (must be from 0 to 15)." 84 end 85 blocknum[i] = tonumber(n) 86 i = i + 1 87 if i > 16 then break end 88 end 89 if i ~= 16 then 90 return "Rule must specify 16 comma-separated numbers from 0 to 15." 91 end 92 93 -- check for a valid suffix like T50 or T50,30 94 local wd, ht = DEFWD, DEFHT 95 if suffix then 96 if suffix:find(",") then 97 wd, ht = suffix:match("^T(%d+),(%d+)$") 98 else 99 wd = suffix:match("^T(%d+)$") 100 ht = wd 101 end 102 wd = tonumber(wd) 103 ht = tonumber(ht) 104 if wd == nil or ht == nil then 105 return "Rule suffix must be Twd,ht or Twd." 106 end 107 end 108 if wd < 8 then wd = 8 elseif wd > 1000 then wd = 1000 end 109 if ht < 8 then ht = 8 elseif ht > 1000 then ht = 1000 end 110 111 -- ensure wd and ht are multiples of 4 to simplify NextPattern logic 112 wd = wd - (wd % 4) 113 ht = ht - (ht % 4) 114 115 -- given rule is valid 116 117 -- create the transition table for use in NextPattern 118 transition = {} 119 for i = 0, 15 do transition[i] = blocknum[i] end 120 121 -- create the canonical form of the given rule 122 local canonrule = "M" 123 for i = 0, 15 do 124 canonrule = canonrule..blocknum[i] 125 if i < 15 then canonrule = canonrule.."," end 126 end 127 canonrule = canonrule..":T"..wd..","..ht 128 129 if transition[0] == 0 then 130 NextPattern = FastPattern 131 else 132 NextPattern = SlowPattern 133 end 134 135 alternate_rules = false 136 if transition[0] == 15 and transition[15] == 0 then 137 -- for rules like Critters and Tron we can avoid strobing and use 138 -- FastPattern with alternating transition tables, eg: 139 -- i: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 140 -- Critters: 15,14,13,3, 11,5, 6, 1, 7, 9, 10, 2, 12, 4, 8, 0 141 -- even gens: 0, 1, 2, 12,4, 10,9, 14,8, 6, 5, 13, 3, 11, 7, 15 142 -- odd gens: 0, 8, 4, 12,2, 10,9, 7, 1, 6, 5, 11, 3, 13, 14, 15 143 alternate_rules = true 144 NextPattern = FastPattern 145 even_transition = {} 146 odd_transition = {} 147 for i = 0, 15 do 148 even_transition[i] = 15 - transition[i] -- inverse 149 if i == transition[i] then 150 odd_transition[i] = even_transition[i] 151 elseif even_transition[i] == 0 then odd_transition[i] = 0 152 elseif even_transition[i] == 15 then odd_transition[i] = 15 153 elseif even_transition[i] == 1 then odd_transition[i] = 8 154 elseif even_transition[i] == 8 then odd_transition[i] = 1 155 elseif even_transition[i] == 2 then odd_transition[i] = 4 156 elseif even_transition[i] == 4 then odd_transition[i] = 2 157 elseif even_transition[i] == 3 then odd_transition[i] = 5 158 elseif even_transition[i] == 5 then odd_transition[i] = 3 159 elseif even_transition[i] == 6 then odd_transition[i] = 9 160 elseif even_transition[i] == 9 then odd_transition[i] = 6 161 elseif even_transition[i] == 7 then odd_transition[i] = 14 162 elseif even_transition[i] == 14 then odd_transition[i] = 7 163 elseif even_transition[i] == 10 then odd_transition[i] = 12 164 elseif even_transition[i] == 12 then odd_transition[i] = 10 165 elseif even_transition[i] == 11 then odd_transition[i] = 13 166 elseif even_transition[i] == 13 then odd_transition[i] = 11 167 end 168 end 169 end 170 171 return nil, canonrule, wd, ht, 2 172end 173 174-------------------------------------------------------------------------------- 175 176function FastPattern(currcells, minx, miny, maxx, maxy) 177 -- Create the next pattern when transition[0] == 0 or when using 178 -- alternating rules. 179 180 local newcells = {} -- cell array for the new pattern (one-state) 181 local newlen = 0 -- length of newcells 182 local blocks = {} -- has all 2x2 blocks with at least 1 live cell 183 local gridwd = maxx-minx+1 184 local get = g.getcell 185 local oddgen = GetGenCount() & 1 == 1 186 187 if alternate_rules then 188 if oddgen then 189 transition = odd_transition 190 else 191 transition = even_transition 192 end 193 end 194 195 for i = 1, #currcells, 2 do -- currcells is a one-state cell array 196 local x = currcells[i] 197 local y = currcells[i+1] 198 -- find the top left cell in the 2x2 block to which this live cell belongs 199 local n 200 if oddgen then 201 -- odd-numbered generation so top left cell in block must have odd coords 202 local oddx = x & 1 == 1 203 local oddy = y & 1 == 1 204 if oddx and oddy then 205 n = 1 206 elseif not oddx and oddy then 207 n = 2 208 x = x-1 209 if x < minx then x = maxx end 210 elseif oddx and not oddy then 211 n = 4 212 y = y-1 213 if y < miny then y = maxy end 214 else -- x,y both even 215 n = 8 216 x = x-1 217 y = y-1 218 if x < minx then x = maxx end 219 if y < miny then y = maxy end 220 end 221 else 222 -- even-numbered generation so top left cell in block must have even coords 223 -- (note that ParseRule ensures both grid dimensions are multiples of 4, 224 -- so wrapping won't be needed and minx,miny are always even numbers) 225 local evenx = x & 1 == 0 226 local eveny = y & 1 == 0 227 if evenx and eveny then 228 n = 1 229 elseif not evenx and eveny then 230 n = 2 231 x = x-1 232 elseif evenx and not eveny then 233 n = 4 234 y = y-1 235 else -- x,y both odd 236 n = 8 237 x = x-1 238 y = y-1 239 end 240 end 241 -- x,y is now the top left cell in a non-empty 2x2 block 242 local k = (y-miny) * gridwd + (x-minx) 243 blocks[k] = (blocks[k] or 0) + n 244 end 245 246 -- go thru all the 2x2 blocks created above and put the transitions in newcells 247 for k,v in pairs(blocks) do 248 local x = minx + (k % gridwd) 249 local y = miny + (k // gridwd) 250 -- x,y is the top left cell in this 2x2 block 251 local xp1 = x+1 252 local yp1 = y+1 253 if oddgen then 254 -- may need to wrap right and bottom edges 255 if xp1 > maxx then xp1 = minx end 256 if yp1 > maxy then yp1 = miny end 257 end 258 local n = transition[v] 259 if n > 0 then 260 if n & 1 == 1 then 261 -- n is 1,3,5,7,9,11,13,15 262 newlen = newlen+1 ; newcells[newlen] = x 263 newlen = newlen+1 ; newcells[newlen] = y 264 end 265 if n == 2 or n == 3 or n == 6 or n == 7 or 266 n == 10 or n == 11 or n == 14 or n == 15 then 267 newlen = newlen+1 ; newcells[newlen] = xp1 268 newlen = newlen+1 ; newcells[newlen] = y 269 end 270 if (n >= 4 and n <= 7) or (n >= 12 and n <= 15) then 271 newlen = newlen+1 ; newcells[newlen] = x 272 newlen = newlen+1 ; newcells[newlen] = yp1 273 end 274 if n >= 8 then 275 newlen = newlen+1 ; newcells[newlen] = xp1 276 newlen = newlen+1 ; newcells[newlen] = yp1 277 end 278 end 279 end 280 281 -- delete the old pattern and add the new pattern 282 g.putcells(currcells, 0, 0, 1, 0, 0, 1, "xor") 283 g.putcells(newcells) 284 285 return newcells -- return the new pattern 286end 287 288-------------------------------------------------------------------------------- 289 290function SlowPattern(currcells, minx, miny, maxx, maxy) 291 -- Create the next pattern when transition[0] > 0 and we have to examine 292 -- every cell in the grid (so very slow). 293 294 local newcells = {} -- cell array for the new pattern (one-state) 295 local newlen = 0 -- length of newcells 296 local get = g.getcell 297 local oddgen = GetGenCount() & 1 == 1 298 299 local xfirst, yfirst, xlast, ylast 300 if oddgen then 301 -- odd-numbered generation 302 xfirst = minx+1 303 yfirst = miny+1 304 xlast = maxx 305 ylast = maxy 306 else 307 -- even-numbered generation (note that ParseRule ensures both grid 308 -- dimensions are multiples of 4, so wrapping won't be needed and 309 -- minx,miny are always even numbers) 310 xfirst = minx 311 yfirst = miny 312 xlast = maxx-1 313 ylast = maxy-1 314 end 315 316 for y = yfirst, ylast, 2 do 317 local yp1 = y+1 318 -- may need to wrap bottom edge 319 if oddgen and yp1 > maxy then yp1 = miny end 320 for x = xfirst, xlast, 2 do 321 local xp1 = x+1 322 -- may need to wrap right edge 323 if oddgen and xp1 > maxx then xp1 = minx end 324 local i = get(x, y) 325 i = i + 2 * get(xp1, y) 326 i = i + 4 * get(x, yp1) 327 i = i + 8 * get(xp1, yp1) 328 local n = transition[i] 329 if n > 0 then 330 if n & 1 == 1 then 331 -- n is 1,3,5,7,9,11,13,15 332 newlen = newlen+1 ; newcells[newlen] = x 333 newlen = newlen+1 ; newcells[newlen] = y 334 end 335 if n == 2 or n == 3 or n == 6 or n == 7 or 336 n == 10 or n == 11 or n == 14 or n == 15 then 337 newlen = newlen+1 ; newcells[newlen] = xp1 338 newlen = newlen+1 ; newcells[newlen] = y 339 end 340 if (n >= 4 and n <= 7) or (n >= 12 and n <= 15) then 341 newlen = newlen+1 ; newcells[newlen] = x 342 newlen = newlen+1 ; newcells[newlen] = yp1 343 end 344 if n >= 8 then 345 newlen = newlen+1 ; newcells[newlen] = xp1 346 newlen = newlen+1 ; newcells[newlen] = yp1 347 end 348 end 349 end 350 end 351 352 -- delete the old pattern and add the new pattern 353 g.putcells(currcells, 0, 0, 1, 0, 0, 1, "xor") 354 g.putcells(newcells) 355 356 return newcells -- return the new pattern 357end 358 359-------------------------------------------------------------------------------- 360 361-- override the SetColors function 362function SetColors() 363 g.setcolors( {0,0,0,0} ) -- state 0 is black 364 g.setcolors( {1,255,255,0} ) -- state 1 is yellow 365end 366 367-------------------------------------------------------------------------------- 368 369-- user's startup script might want to override this 370function RandomRule() 371 local rand = math.random 372 local rule = "M0," -- avoid strobing rules 373 for i = 1, 14 do 374 rule = rule..rand(0,15).."," 375 end 376 return rule..rand(0,15) 377end 378 379-------------------------------------------------------------------------------- 380 381-- allow alt-R to create a random pattern with a random rule 382local saveHandleKey = HandleKey 383function HandleKey(event) 384 local _, key, mods = split(event) 385 if key == "r" and mods == "alt" then 386 SetRule(RandomRule()) 387 RandomPattern() 388 else 389 -- pass the event to the original HandleKey 390 saveHandleKey(event) 391 end 392end 393 394-------------------------------------------------------------------------------- 395 396-- and away we go... 397StartNewCA() 398