File: oscar.lua

package info (click to toggle)
golly 3.2-2
  • links: PTS
  • area: main
  • in suites: buster
  • size: 19,516 kB
  • sloc: cpp: 69,819; ansic: 25,894; python: 7,921; sh: 4,267; objc: 3,721; java: 2,781; xml: 1,362; makefile: 530; perl: 69
file content (166 lines) | stat: -rwxr-xr-x 6,094 bytes parent folder | download
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
-- Oscar is an OSCillation AnalyzeR for use with Golly.
-- Author: Andrew Trevorrow (andrew@trevorrow.com), Mar 2016.
-- 
-- This script uses Gabriel Nivasch's "keep minima" algorithm.
-- For each generation, calculate a hash value for the pattern.  Keep all of
-- the record-breaking minimal hashes in a list, with the oldest first.
-- For example, after 5 generations the saved hash values might be:
-- 
--   8 12 16 24 25,
-- 
-- If the next hash goes down to 13 then the list can be shortened:
-- 
--   8 12 13.
-- 
-- If the current hash matches one of the saved hashes, it is highly likely
-- the pattern is oscillating.  By keeping a corresponding list of generation
-- counts we can calculate the period.  We also keep lists of population
-- counts and bounding boxes to reduce the chance of spurious oscillator
-- detection due to hash collisions.  The bounding box info also allows us
-- to detect moving oscillators (spaceships/knightships).

local g = golly()

-- initialize lists
local hashlist = {}     -- for pattern hash values
local genlist = {}      -- corresponding generation counts
local poplist = {}      -- corresponding population counts
local boxlist = {}      -- corresponding bounding boxes

-- check if outer-totalistic rule has B0 but not S8
local r = g.getrule()
r = string.match(r, "^(.+):") or r
local hasB0notS8 = r:find("B0") == 1 and r:find("/") > 1 and r:sub(-1,-1) ~= "8"

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

local function show_spaceship_speed(period, deltax, deltay)
    -- we found a moving oscillator
    if deltax == deltay or deltax == 0 or deltay == 0 then
        local speed = ""
        if deltax == 0 or deltay == 0 then
            -- orthogonal spaceship
            if deltax > 1 or deltay > 1 then
                speed = speed..(deltax + deltay)
            end
        else
            -- diagonal spaceship (deltax == deltay)
            if deltax > 1 then
                speed = speed..deltax
            end
        end
        if period == 1 then
            g.show("Spaceship detected (speed = "..speed.."c)")
        else
            g.show("Spaceship detected (speed = "..speed.."c/"..period..")")
        end
    else
        -- deltax != deltay and both > 0
        local speed = deltay..","..deltax
        if period == 1 then
            g.show("Knightship detected (speed = "..speed.."c)")
        else
            g.show("Knightship detected (speed = "..speed.."c/"..period..")")
        end
    end
end

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

local function oscillating()
    -- return true if the pattern is empty, stable or oscillating
    
    -- first get current pattern's bounding box
    local pbox = g.getrect()
    if #pbox == 0 then
        g.show("The pattern is empty.")
        return true
    end
    
    local h = g.hash(pbox)
    
    -- 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 rect = boxlist[pos]
            if tonumber(g.getpop()) == poplist[pos] and pbox[3] == rect[3] and pbox[4] == rect[4] then
                local period = tonumber(g.getgen()) - genlist[pos]
                
                if hasB0notS8 and (period % 2) > 0 and
                   pbox[1] == rect[1] and pbox[2] == rect[2] and
                   pbox[3] == rect[3] and pbox[4] == rect[4] then
                    -- ignore this hash value because B0-and-not-S8 rules are
                    -- emulated by using different rules for odd and even gens,
                    -- so it's possible to have identical patterns at gen G and
                    -- gen G+p if p is odd
                    return false
                end
                
                if pbox[1] == rect[1] and pbox[2] == rect[2] and
                   pbox[3] == rect[3] and pbox[4] == rect[4] then
                    -- pattern hasn't moved
                    if period == 1 then
                        g.show("The pattern is stable.")
                    else
                        g.show("Oscillator detected (period = "..period..")")
                    end
                else
                    local deltax = math.abs(rect[1] - pbox[1])
                    local deltay = math.abs(rect[2] - pbox[2])
                    show_spaceship_speed(period, deltax, deltay)
                end
                return true
            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, tonumber(g.getgen()))
    table.insert(poplist, pos, tonumber(g.getpop()))
    table.insert(boxlist, pos, pbox)
    
    return false
end

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

local function fit_if_not_visible()
    -- fit pattern in viewport if not empty and not completely visible
    local r = g.getrect()
    if #r > 0 and not g.visrect(r) then g.fit() end
end

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

g.show("Checking for oscillation... (hit escape to abort)")

local oldsecs = os.clock()
while not oscillating() do
    g.run(1)
    local newsecs = os.clock()
    if newsecs - oldsecs >= 1.0 then   -- show pattern every second
        oldsecs = newsecs
        fit_if_not_visible()
        g.update()
    end
end
fit_if_not_visible()