-- -------------------------------------------------------------------------------- -- FILE: life.lua -- USAGE: ./life.lua -- DESCRIPTION: Conway's Game of Life? -- OPTIONS: --- -- REQUIREMENTS: --- -- BUGS: --- -- NOTES: --- -- AUTHOR: Philipp Gesang (Phg), -- COMPANY: -- VERSION: 1.0 -- CREATED: 05/08/10 12:03:11 CEST -- REVISION: --- -------------------------------------------------------------------------------- -- gol = {} gol.helpers = {} local C, Cs, Ct, P, R, S, match = lpeg.C, lpeg.Cs, lpeg.Ct, lpeg.P, lpeg.R, lpeg.S, lpeg.match -- http://lua-users.org/lists/lua-l/2009-06/msg00343.html gol.helpers.utfchar = R("\000\127") + R("\194\223") * R("\128\191") + R("\224\240") * R("\128\191") * R("\128\191") + R("\241\244") * R("\128\191") * R("\128\191") * R("\128\191") -- by Roberto, captures everything in a table -- http://www.inf.puc-rio.br/~roberto/lpeg/lpeg.html function gol.helpers.split (s, sep) sep = P(sep) local elem = C((1 - sep)^0) local p = Ct(elem * (sep * elem)^0) -- make a table capture return match(p, s) end -- Modified to match left and right of one separator only. function gol.helpers.split_once (s, sep) local utfchar = gol.helpers.utfchar sep = P(sep) local left = C((1 - sep)^0) local right = C((utfchar)^0) local p = Ct(left * sep * right) return p:match(s) end -- Modified stripper from Roberto to support trimming other chars than whitespace function gol.helpers.strip(str, chars) local chars = chars or " \t\v\n" chars = S(chars) local nochars = 1 - chars local stripper = chars^0 * C((chars^0 * nochars^1)^0) return match(stripper,str) or "" end function gol.helpers.dead_row(len) local row = "" local dead = "0" for i=1, len, 1 do row = row .. dead end return row end -- Read rules of type "B3/S23" -- => “birth” of a new cell if exactly 3 adjacent cells are “alive” -- => “staying alive” of cells with two or three adjacent “live” cells function gol.parse_rule (raw_rule) local help = gol.helpers local b_s = help.split_once (raw_rule, "/") local tmp_b = help.strip(b_s[1], "B") local tmp_s = help.strip(b_s[2], "S") local b, s = {}, {} -- single digits express birth/stay conditions local n = 1 repeat table.insert( b, tonumber(tmp_b:sub(n,n)) ) n = n + 1 until n > string.len(tmp_b) n = 1 repeat table.insert( s, tonumber(tmp_s:sub(n,n)) ) n = n + 1 until n > string.len(tmp_s) return { birth = b, stay = s } end function gol.apply_rule (cell, cnt, rule, fade, keep) --local live, dead = "1", "0" --local new = dead local new = 0 local live = "1" local stay = rule.stay local birth = rule.birth -- checking if cnt matches the numbers from the conditions list if cell == live then for _, cond in ipairs(stay) do if cnt == cond then new = "1" break end end else -- ==dead for _, cond in ipairs(birth) do if cnt == cond then new = "1" break end end end if fade then if not (live == new) then local add = tonumber (cell) if not add then -- == "D" add = "D" else if add and add < 9 and add ~= 0 then add = add + 1 elseif keep and add and add == 9 then -- marking dead cells once alive add = "D" else add = 0 end end new = tostring(add) end end return new end function gol.parse_file (fname) local tmp = {} -- return an array local len -- check for equal line length local file = assert(io.open(fname, "r"), "Not a file: " .. fname) for line in file:lines() do if not len then len = string.len(line) end if len ~= string.len(line) then -- inconsistent horizontal sizes; kill off program return nil else table.insert(tmp, line) end end file:close() return tmp end --- Computing single lines and whole frames and intervals thereof function gol.next_line (rows, rule) local live = "1" local fade = false if mplife then fade = mplife.setup.current.fade end local n = 1 local max = string.len(rows[2]) local cell = R("09") + P("D") local nocell = 1-cell local ce = Cs(cell) / function (current) local env = {} local lpos, rpos -- positions left of / right of current -- toroidal, flips over at borders if n == 1 then lpos = max else lpos = n - 1 end if n == max then rpos = 1 else rpos = n + 1 end -- +---+---+---+ -- |nw | n | ne| -- +---+---+---+ -- |w | c | e| env[ironment] of current -- +---+---+---+ -- |sw | s | se| -- +---+---+---+ env.nw = string.sub( rows[1], lpos, lpos ) env.n = string.sub( rows[1], n , n ) env.ne = string.sub( rows[1], rpos, rpos ) env.w = string.sub( rows[2], rpos, rpos ) env.e = string.sub( rows[2], lpos, lpos ) env.sw = string.sub( rows[3], lpos, lpos ) env.s = string.sub( rows[3], n , n ) env.se = string.sub( rows[3], rpos, rpos ) -- counting live cells in the environment local cnt = 0 for _, chr in pairs(env) do if chr == live then cnt = cnt + 1 end end n = n + 1 -- -- adding new cell according to ruleset return gol.apply_rule(current, cnt, rule, fade, true) end local noce = Cs(nocell) / "" local c = Cs(ce * (noce + ce)^0) return c:match(rows[2]) end function gol.next_frame (old, rule) local new = {} local n = 1 repeat local rows = {} rows[2] = old[n] -- current -- toroidal rows[1] = old[n-1] or old[#old] -- last rows[3] = old[n+1] or old[1] -- next new[n] = gol.next_line( rows, rule ) n = n + 1 until n > #old return new end -- get the nth frame starting from init function gol.frame (init, rule, n) local frame = init local gnext = gol.next_frame for i=1, n, 1 do frame = gnext( frame, rule ) end return frame end -- get array of frames until nth [from offset] function gol.frames (init, rule, n, offset) local frames = {} local last local gnext = gol.next_frame -- “fast forward” if offset then last = gol.frame( init, rule, offset ) else last = init end for i=1, n, 1 do frames[i] = last last = gnext( last, rule ) end return frames end --- pretty printing frames (ascii) -- pp a heading: len should exceed length of mark + 4 function gol.pre_section (len, mark) local mark = mark or "Frame" local t = "-" local s = t..t.." "..mark.." " if s:len() < len then for n=1, len - s:len(), 1 do s = s..t end end io.write ("\n\n"..s.."\n") end function gol.pre_frame (frame) local repl = { ["0"] = "·", ["1"] = "O", ["2"] = "2", ["3"] = "3", ["4"] = "4", ["5"] = "5", ["6"] = "6", ["7"] = "7", ["8"] = "8", ["9"] = "9", } local cell = R"09" + P"D" local nocell = 1-cell local ce = Cs(cell) / repl local noce = Cs(nocell) / "" local c = Cs(ce * (noce + ce)^0) for j, row in ipairs(frame) do io.write("\n" .. c:match(row)) end end function gol.pre_movie (frames, section) for i, frame in ipairs(frames) do if section then local l = string.len(frame[1]) gol.pre_section( l, "Nr. " .. tostring(i) ) end gol.pre_frame( frame ) end io.write("\n") end return gol