File: 1D.lua

package info (click to toggle)
golly 3.3-1.1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 20,176 kB
  • sloc: cpp: 72,638; ansic: 25,919; python: 7,921; sh: 4,245; objc: 3,721; java: 2,781; xml: 1,362; makefile: 530; javascript: 279; perl: 69
file content (383 lines) | stat: -rwxr-xr-x 12,982 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
--[[
This script lets you use Golly to explore one-dimensional rules.
It supports all of Stephen Wolfram's 256 elementary rules, as well
as totalistic rules with up to 4 states and a maximum range of 4.
Author: Andrew Trevorrow (andrew@trevorrow.com), May 2019.
--]]

local g = golly()
local gp = require "gplus"
local split = gp.split

require "gplus.NewCA"
SCRIPT_NAME = "1D"
DEFAULT_RULE = "W110"
RULE_HELP = [[
This script lets you explore one-dimensional rules.
Stephen Wolfram's elementary rules are strings of the form Wn
where n is a number from 0 to 255.
<p>
Totalistic rules are strings of the form CcKkRr where c is a code
number from 0 to k^((2r+1)k-2r)-1, k is the number of states (2 to 4),
and r is the range (1 to 4).
<p>
More details can be found at these links:<br>
<a href="http://mathworld.wolfram.com/ElementaryCellularAutomaton.html"
        >http://mathworld.wolfram.com/ElementaryCellularAutomaton.html</a><br>
<a href="http://mathworld.wolfram.com/TotalisticCellularAutomaton.html"
        >http://mathworld.wolfram.com/TotalisticCellularAutomaton.html</a>
]]

-- the following are non-local so a startup script can change them
DEFWD, DEFHT = 500, 500         -- default grid size
aliases = {}                    -- none at the moment

--------------------------------------------------------------------------------

NextPattern = function() end    -- ParseRule sets this to NextElementary or NextTotalistic
local birth = {}                -- set by ParseRule (only if given rule is valid)
local numstates = 2             -- ditto
local range = 1                 -- ditto
local empty_row = false         -- NextPattern created an empty row?

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 elementary = true
    local n, c, k, r
    if prefix:find("^W") then
        n = tonumber( prefix:match("^W(%d+)$") )
        if n == nil or n > 255 then
            return "Rule syntax is Wn where n is from 0 to 255."
        end
    elseif prefix:find("^C") then
        c, k, r = prefix:match("^C(%d+)K([234])R([1234])$")
        c = tonumber(c)
        k = tonumber(k)
        r = tonumber(r)
        if not (c and k and r) then
            return "Rule syntax is CcKkRr where c is the code number,\n"..
                   "k is the number of states (2 to 4), and r is the\n"..
                   "range (1 to 4)."
        end
        local maxcode = math.floor(k^((2*r+1)*k-2*r)) - 1
        if c > maxcode then
            return "Maximum code for K"..k.." and R"..r.." is "..maxcode.."."
        end
        elementary = false
    else
        return "Rule must start with W or C."
    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 > 4000 then wd = 4000 end
    if ht < 10 then ht = 10 elseif ht > 4000 then ht = 4000 end
    
    -- given rule is valid
    
    -- set birth table for use in NextPattern
    birth = {}
    if elementary then
        local bit = 1
        for i = 0, 7 do
            if n & bit > 0 then birth[i] = 1 end
            bit = bit * 2
        end
    else
        -- totalistic
        local code = c
        for i = 0, (2*r+1)*k-2*r-1 do
            if code == 0 then break end
            local s = code % k
            if s > 0 then birth[i] = s end
            code = code // k
        end
    end
    if birth[0] == nil then empty_row = false end

    -- set NextPattern, numstates, range, and create the canonical rule
    local canonrule
    if elementary then
        NextPattern = NextElementary
        numstates = 2
        range = 1
        canonrule = "W"..n..":T"..wd..","..ht
    else
        -- totalistic
        NextPattern = NextTotalistic
        numstates = k
        range = r
        canonrule = "C"..c.."K"..k.."R"..r..":T"..wd..","..ht
    end
    
    return nil, canonrule, wd, ht, numstates
end

--------------------------------------------------------------------------------

function NextElementary(currcells, minx, miny, maxx, maxy)
    -- Create the next elementary pattern.
    
    local newrow = {}       -- cell array for the new row (one-state)
    local newlen = 0        -- length of newrow
    local get = g.getcell
    
    -- find the bottom row of the current pattern
    local bbox = g.getrect()
    local y = bbox[2] + bbox[4] - 1

    if birth[0] then
        -- for odd-numbered rules we need to ensure the grid doesn't become empty
        -- when we wrap to the top row
        if y == maxy then
            -- save bottom row, erase current pattern and put bottom row in top row
            local gridwd = maxx-minx+1
            local bottrow = g.getcells( {minx, maxy, gridwd, 1} )
            g.putcells(currcells, 0, 0, 1, 0, 0, 1, "xor")
            g.putcells(bottrow, 0, -(maxy-miny), 1, 0, 0, 1, "or")
            currcells = g.getcells( {minx, miny, gridwd, 1} )
            y = miny
        elseif empty_row then
            -- if bottom row of current pattern is full then advance y by 1
            local full = true
            for x = minx, maxx do
                if get(x, y) == 0 then full = false ; break end
            end
            if full then y = y + 1 end
        end
    end
    
    local newy = y + 1      -- y coordinates for newrow
    if newy > maxy then
        newy = miny         -- wrap to top row
    end
    
    for x = minx, maxx do
        local xm1 = x-1
        local xp1 = x+1
        -- wrap left and right edges
        if xm1 < minx then xm1 = maxx end
        if xp1 > maxx then xp1 = minx end
        local i = get(xp1, y)
        if get(x,   y) == 1 then i = i + 2 end
        if get(xm1, y) == 1 then i = i + 4 end
        if birth[i] then
            newlen = newlen+1 ; newrow[newlen] = x
            newlen = newlen+1 ; newrow[newlen] = newy
        end
    end
    
    empty_row = newlen == 0     -- for next call (only used if birth[0])
    
    if newy == miny then
        -- erase current pattern and put newrow in top row
        g.putcells(currcells, 0, 0, 1, 0, 0, 1, "xor")
        g.putcells(newrow)
        return newrow
    else
        -- append newrow to currcells (no need to erase current pattern)
        local currlen = #currcells
        for i = 1, newlen do
            currlen = currlen+1 ; currcells[currlen] = newrow[i]
        end
        g.putcells(newrow)
        return currcells
    end
end

--------------------------------------------------------------------------------

function NextTotalistic(currcells, minx, miny, maxx, maxy)
    -- Create the next totalistic pattern.
    
    local newrow = {}       -- cell array for the new row (one-state or multi-state)
    local newlen = 0        -- length of newrow
    local get = g.getcell
    local multistate = numstates > 2
    
    -- find the bottom row of the current pattern
    local bbox = g.getrect()
    local y = bbox[2] + bbox[4] - 1

    if birth[0] then
        -- for odd-numbered rules we need to ensure the grid doesn't become empty
        -- when we wrap to the top row
        if y == maxy then
            -- save bottom row, erase current pattern and put bottom row in top row
            local gridwd = maxx-minx+1
            local bottrow = g.getcells( {minx, maxy, gridwd, 1} )
            g.putcells(currcells, 0, 0, 1, 0, 0, 1, "xor")
            g.putcells(bottrow, 0, -(maxy-miny), 1, 0, 0, 1, "or")
            currcells = g.getcells( {minx, miny, gridwd, 1} )
            y = miny
        elseif empty_row then
            -- if bottom row of current pattern is full then advance y by 1
            local full = true
            for x = minx, maxx do
                if get(x, y) == 0 then full = false ; break end
            end
            if full then y = y + 1 end
        end
    end
    
    local newy = y + 1      -- y coordinates for newrow
    if newy > maxy then
        newy = miny         -- wrap to top row
    end
    
    for x = minx, maxx do
        local total = get(x, y)
        for i = 1, range do
            local xmi = x-i
            local xpi = x+i
            -- wrap left and right edges
            if xmi < minx then xmi = maxx - (minx - xmi - 1) end
            if xpi > maxx then xpi = minx + (xpi - maxx - 1) end
            total = total + get(xmi, y)
            total = total + get(xpi, y)
        end
        if birth[total] then
            newlen = newlen+1 ; newrow[newlen] = x
            newlen = newlen+1 ; newrow[newlen] = newy
            if multistate then
                newlen = newlen+1 ; newrow[newlen] = birth[total]
            end
        end
    end
    
    if newlen > 0 and multistate then
        -- ensure length of newrow is odd
        if newlen & 1 == 0 then newlen = newlen+1 ; newrow[newlen] = 0 end
    end
    
    empty_row = newlen == 0     -- for next call (only used if birth[0])
    
    if newy == miny then
        -- erase current pattern and put newrow in top row
        g.putcells(currcells, 0, 0, 1, 0, 0, 1, "xor")
        g.putcells(newrow)
        return newrow
    else
        -- append newrow to currcells (no need to erase current pattern)
        local currlen = #currcells
        if multistate then
            -- ignore any padding ints in currcells and newrow
            if currlen % 3 > 0 then currlen = currlen - 1 end
            if newlen % 3 > 0 then newlen = newlen - 1 end
        end
        for i = 1, newlen do
            currlen = currlen+1 ; currcells[currlen] = newrow[i]
        end
        if multistate then
            -- ensure length of currcells is odd
            if currlen & 1 == 0 then currcells[currlen+1] = 0 end
        end
        g.putcells(newrow)
        return currcells
    end
end

--------------------------------------------------------------------------------

-- override SetColors to use Wolfram's color scheme in ANKOS
function SetColors()
    g.setcolors{0,255,255,255}  -- state 0 is white
    if g.numstates() == 2 then
        -- state 1 is black
        g.setcolors{1,0,0,0}
    else
        -- live states vary from gray to black
        g.setcolors{128,128,128, 0,0,0}
    end
    g.setcolor("border", 190, 210, 230)     -- light blue border around grid
    g.setcolor(g.getalgo(), 190, 210, 230)  -- ditto for status bar background
end

--------------------------------------------------------------------------------

-- override RandomPattern to create a single row at the top of the grid
function RandomPattern()
    local rand = math.random
    -- avoid flash due to Refresh call in NewPattern
    local savedRefresh = Refresh
    Refresh = function() end
    NewPattern("random")
    Refresh = savedRefresh
    local minx = -(g.getwidth() // 2)
    local miny = -(g.getheight() // 2)
    local maxx = minx + g.getwidth() - 1
    local perc = GetDensity()
    for x = minx, maxx do
        if rand(0,99) < perc then
            g.setcell(x, miny, rand(1,g.numstates()-1))
        end
    end
    FitGrid()   -- calls Refresh
end

--------------------------------------------------------------------------------

-- user's startup script might want to override this
function RandomRule()
    local rand = math.random
    -- create a random totalistic rule
    local k = rand(2,4)
    local r = rand(1,4)
    local c = rand(0,math.floor(k^((2*r+1)*k-2*r)) - 1)
    return "C"..c.."K"..k.."R"..r
end

--------------------------------------------------------------------------------

-- allow alt-R to create a random pattern with a random totalistic 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()