-- -------------------------------------------------------------------------------- -- 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 = S(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 gol.formats = {} gol.formats[".gol"] = function (fname) local tmp = {} -- return an array local len -- check for equal line length if fname:sub(-4,-1) ~= ".gol" then fname = fname .. ".gol" end local file = assert(io.open(fname, "r"), "Not a file: " .. fname) local data = file:read("*all") file:close() local cell = R"09" + P"D" local nl = Cs(P"\n") / "" local line = Cs(cell^1) / function (l) if not len then len = string.len(l) end if len ~= string.len(l) then -- inconsistent horizontal sizes; kill off program return nil else table.insert(tmp,l) end end local gol_parser = Cs(line * (nl + line)^0) if gol_parser:match(data) then if context then context.writestatus("simpleslides", "Sucessfully loaded frame from "..fname..".") end return tmp else return false end end gol.formats[".cell"] = function (fname) return false -- stub end gol.formats[".rle"] = function (fname) local parser = require "parsers/rle" return parser(fname) end function gol.extend_area(rows) local tmp = {} local c = mplife.setup.current context.writestatus("simpleslides","Extending area horizontally by "..c.extendx.."cells.") context.writestatus("simpleslides","Extending area vertically by "..c.extendy.."cells.") if c.extendy > 0 then local row = "" for i=1, rows[1]:len(), 1 do -- buildling an empty row row = row.."0" end for i=1, c.extendy, 1 do table.insert(tmp, row) end for _, r in ipairs(rows) do table.insert(tmp, r) end for i=1, c.extendy, 1 do table.insert(tmp, row) end end if c.extendx > 0 then local add = "" for i=1, c.extendx, 1 do -- building an empty pre- and suffix add = add .. "0" end for n, r in ipairs(tmp) do tmp[n] = add .. r .. add end end --for i,j in ipairs(tmp) do print(i,j) end return tmp end function gol.parse_file (fname) local p_suf = P"." * (1-P".")^3 * -P(1) local p_fn = (1-p_suf)^1 * C(p_suf) local suffix = p_fn:match(fname) or ".gol" -- assume .gol format as default print("HERE!!!!!!!!!"..suffix) local tmp = gol.formats[suffix] ( fname ) if mplife and mplife.setup.current and ( mplife.setup.current.extendx > 0 or mplife.setup.current.extendy > 0) then tmp = gol.extend_area(tmp) end 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