# .tlv file generated by https://github.com/akkartik/teliva # You may edit it if you are careful; however, you may see cryptic errors if you # violate Teliva's assumptions. # # .tlv files are representations of Teliva programs. Teliva programs consist of # sequences of definitions. Each definition is a table of key/value pairs. Keys # and values are both strings. # # Lines in .tlv files always follow exactly one of the following forms: # - comment lines at the top of the file starting with '#' at column 0 # - beginnings of definitions starting with '- ' at column 0, followed by a # key/value pair # - key/value pairs consisting of ' ' at column 0, containing either a # spaceless value on the same line, or a multi-line value # - multiline values indented by more than 2 spaces, starting with a '>' # # If these constraints are violated, Teliva may unceremoniously crash. Please # report bugs at http://akkartik.name/contact - __teliva_timestamp: original str_helpers: >-- some string helpers from http://lua-users.org/wiki/StringIndexing > >-- index characters using [] >getmetatable('').__index = function(str,i) > if type(i) == 'number' then > return str:sub(i,i) > else > return string[i] > end >end > >-- ranges using (), selected bytes using {} >getmetatable('').__call = function(str,i,j) > if type(i)~='table' then > return str:sub(i,j) > else > local t={} > for k,v in ipairs(i) do > t[k]=str:sub(v,v) > end > return table.concat(t) > end >end > >-- iterate over an ordered sequence >function q(x) > if type(x) == 'string' then > return x:gmatch('.') > else > return ipairs(x) > end >end > >-- insert within string >function string.insert(str1, str2, pos) > return str1:sub(1,pos)..str2..str1:sub(pos+1) >end > >function string.remove(s, pos) > return s:sub(1,pos-1)..s:sub(pos+1) >end > >function string.pos(s, sub) > return string.find(s, sub, 1, true) -- plain=true to disable regular expressions >end > >-- TODO: backport utf-8 support from Lua 5.3 - __teliva_timestamp: original debugy: >debugy = 5 - __teliva_timestamp: original dbg: >-- helper for debug by print; overlay debug information towards the right >-- reset debugy every time you refresh screen >function dbg(window, s) > local oldy = 0 > local oldx = 0 > oldy, oldx = window:getyx() > window:mvaddstr(debugy, 60, s) > debugy = debugy+1 > window:mvaddstr(oldy, oldx, '') >end - __teliva_timestamp: original check: >function check(x, msg) > if x then > Window:addch('.') > else > print('F - '..msg) > print(' '..str(x)..' is false/nil') > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original check_eq: >function check_eq(x, expected, msg) > if eq(x, expected) then > Window:addch('.') > else > print('F - '..msg) > print(' expected '..str(expected)..' but got '..str(x)) > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original eq: >function eq(a, b) > if type(a) ~= type(b) then return false end > if type(a) == 'table' then > if #a ~= #b then return false end > for k, v in pairs(a) do > if b[k] ~= v then > return false > end > return true > end > end > return a == b >end - __teliva_timestamp: original str: >-- smarter tostring >-- slow; used only for debugging >function str(x) > if type(x) == 'table' then > local result = '' > result = result..#x..'{' > for k, v in pairs(x) do > result = result..str(k)..'='..str(v)..', ' > end > result = result..'}' > return result > elseif type(x) == 'string' then > return '"'..x..'"' > end > return tostring(x) >end - __teliva_timestamp: original map: >-- only for arrays >function map(l, f) > result = {} > for _, x in ipairs(l) do > table.insert(result, f(x)) > end > return result >end - __teliva_timestamp: original reduce: >-- only for arrays >function reduce(l, f, init) > result = init > for _, x in ipairs(l) do > result = f(result, x) > end > return result >end - __teliva_timestamp: original filter: >-- only for arrays >function filter(l, f) > result = {} > for _, x in ipairs(l) do > if f(x) then > table.insert(result, x) > end > end > return result >end - __teliva_timestamp: original find_index: >function find_index(arr, x) > for n, y in ipairs(arr) do > if x == y then > return n > end > end >end - __teliva_timestamp: original trim: >function trim(s) > return s:gsub('^%s*', ''):gsub('%s*$', '') >end - __teliva_timestamp: original split: >function split(s, d) > result = {} > for match in (s..d):gmatch("(.-)"..d) do > table.insert(result, match); > end > return result >end - __teliva_timestamp: original sort_letters: >function sort_letters(s) > tmp = {} > for i=1,#s do > table.insert(tmp, s[i]) > end > table.sort(tmp) > local result = '' > for _, c in pairs(tmp) do > result = result..c > end > return result >end > >function test_sort_letters(s) > check_eq(sort_letters(''), '', 'test_sort_letters: empty') > check_eq(sort_letters('ba'), 'ab', 'test_sort_letters: non-empty') > check_eq(sort_letters('abba'), 'aabb', 'test_sort_letters: duplicates') >end - __teliva_timestamp: original count_letters: >function count_letters(s) > local result = {} > for i=1,s:len() do > local c = s[i] > if result[c] == nil then > result[c] = 1 > else > result[c] = result[c] + 1 > end > end > return result >end - __teliva_timestamp: original append: >-- concatenate list 'elems' into 'l', modifying 'l' in the process >function append(l, elems) > for i=1,#elems do > l[#l+1] = elems[i] > end >end - __teliva_timestamp: original menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = {} - __teliva_timestamp: original window: >-- constructor for fake screen and window >-- call it like this: >-- local w = window{ >-- kbd=kbd('abc'), >-- scr=scr{h=5, w=4}, >-- } >-- eventually it'll do everything a real ncurses window can >function window(h) > h.__index = h > setmetatable(h, h) > h.__index = function(table, key) > return rawget(h, key) > end > h.attrset = function(self, x) > self.scr.attrs = x > end > h.attron = function(self, x) > -- currently same as attrset since Lua 5.1 doesn't have bitwise operators > -- doesn't support multiple attrs at once >-- local old = self.scr.attrs >-- self.scr.attrs = old|x > self.scr.attrs = x > end > h.attroff = function(self, x) > -- currently borked since Lua 5.1 doesn't have bitwise operators > -- doesn't support multiple attrs at once >-- local old = self.scr.attrs >-- self.scr.attrs = old & (~x) > self.scr.attrs = curses.A_NORMAL > end > h.getch = function(self) > local c = table.remove(h.kbd, 1) > if c == nil then return c end > return string.byte(c) -- for verisimilitude with ncurses > end > h.addch = function(self, c) > local scr = self.scr > if c == '\n' then > scr.cursy = scr.cursy+1 > scr.cursx = 0 > return > end > if scr.cursy <= scr.h then > scr[scr.cursy][scr.cursx] = {data=c, attrs=scr.attrs} > scr.cursx = scr.cursx+1 > if scr.cursx > scr.w then > scr.cursy = scr.cursy+1 > scr.cursx = 1 > end > end > end > h.addstr = function(self, s) > for i=1,s:len() do > self:addch(s[i]) > end > end > h.mvaddch = function(self, y, x, c) > self.scr.cursy = y > self.scr.cursx = x > self:addch(c) > end > h.mvaddstr = function(self, y, x, s) > self.scr.cursy = y > self.scr.cursx = x > self:addstr(s) > end > h.clear = function(self) > clear_scr(self.scr) > end > h.refresh = function(self) > -- nothing > end > return h >end - __teliva_timestamp: original kbd: >function kbd(keys) > local result = {} > for i=1,keys:len() do > table.insert(result, keys[i]) > end > return result >end - __teliva_timestamp: original scr: >function scr(props) > props.cursx = 1 > props.cursy = 1 > clear_scr(props) > return props >end - __teliva_timestamp: original clear_scr: >function clear_scr(props) > props.cursy = 1 > props.cursx = 1 > for y=1,props.h do > props[y] = {} > for x=1,props.w do > props[y][x] = {data=' ', attrs=curses.A_NORMAL} > end > end > return props >end - __teliva_timestamp: original check_screen: >function check_screen(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > check_eq(window.scr[y][x].data, contents[i], message..'/'..y..','..x) > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end > >-- putting it all together, an example test of both keyboard and screen >function test_check_screen() > local lines = { > c='123', > d='234', > a='345', > b='456', > } > local w = window{ > kbd=kbd('abc'), > scr=scr{h=3, w=5}, > } > local y = 1 > while true do > local b = w:getch() > if b == nil then break end > w:mvaddstr(y, 1, lines[string.char(b)]) > y = y+1 > end > check_screen(w, '345 '.. > '456 '.. > '123 ', > 'test_check_screen') >end - __teliva_timestamp: original grid: >-- main data structure >grid = {} >for i=1,lines*4 do > grid[i] = {} > for j=1,cols*2 do > grid[i][j] = 0 > end >end - __teliva_timestamp: original Window: >Window = curses.stdscr() >-- animation-based app >Window:nodelay(true) >curses.curs_set(0) >lines, cols = Window:getmaxyx() - __teliva_timestamp: original grid_char: >-- grab a 4x2 chunk of grid >function grid_char(line, col) > result = {} > for l, row in ipairs({unpack(grid, (line-1)*4+1, line*4)}) do > result[l] = {unpack(row, (col-1)*2+1, col*2)} > end > return result >end - __teliva_timestamp: original print_grid_char: >function print_grid_char(window, x) > result = {} > for l, row in ipairs(x) do > for c, val in ipairs(row) do > window:mvaddstr(l, c, val) > end > end > return result >end - __teliva_timestamp: original glyph: >-- look up the braille pattern corresponding to a 4x2 chunk of grid >-- https://en.wikipedia.org/wiki/Braille_Patterns >-- not obviously programmatic because Unicode added 4x2 after 3x2 >glyph = { > 0x2800, 0x2801, 0x2802, 0x2803, 0x2804, 0x2805, 0x2806, 0x2807, 0x2840, 0x2841, 0x2842, 0x2843, 0x2844, 0x2845, 0x2846, 0x2847, > 0x2808, 0x2809, 0x280a, 0x280b, 0x280c, 0x280d, 0x280e, 0x280f, 0x2848, 0x2849, 0x284a, 0x284b, 0x284c, 0x284d, 0x284e, 0x284f, > 0x2810, 0x2811, 0x2812, 0x2813, 0x2814, 0x2815, 0x2816, 0x2817, 0x2850, 0x2851, 0x2852, 0x2853, 0x2854, 0x2855, 0x2856, 0x2857, > 0x2818, 0x2819, 0x281a, 0x281b, 0x281c, 0x281d, 0x281e, 0x281f, 0x2858, 0x2859, 0x285a, 0x285b, 0x285c, 0x285d, 0x285e, 0x285f, > 0x2820, 0x2821, 0x2822, 0x2823, 0x2824, 0x2825, 0x2826, 0x2827, 0x2860, 0x2861, 0x2862, 0x2863, 0x2864, 0x2865, 0x2866, 0x2867, > 0x2828, 0x2829, 0x282a, 0x282b, 0x282c, 0x282d, 0x282e, 0x282f, 0x2868, 0x2869, 0x286a, 0x286b, 0x286c, 0x286d, 0x286e, 0x286f, > 0x2830, 0x2831, 0x2832, 0x2833, 0x2834, 0x2835, 0x2836, 0x2837, 0x2870, 0x2871, 0x2872, 0x2873, 0x2874, 0x2875, 0x2876, 0x2877, > 0x2838, 0x2839, 0x283a, 0x283b, 0x283c, 0x283d, 0x283e, 0x283f, 0x2878, 0x2879, 0x287a, 0x287b, 0x287c, 0x287d, 0x287e, 0x287f, > > 0x2880, 0x2881, 0x2882, 0x2883, 0x2884, 0x2885, 0x2886, 0x2887, 0x28c0, 0x28c1, 0x28c2, 0x28c3, 0x28c4, 0x28c5, 0x28c6, 0x28c7, > 0x2888, 0x2889, 0x288a, 0x288b, 0x288c, 0x288d, 0x288e, 0x288f, 0x28c8, 0x28c9, 0x28ca, 0x28cb, 0x28cc, 0x28cd, 0x28ce, 0x28cf, > 0x2890, 0x2891, 0x2892, 0x2893, 0x2894, 0x2895, 0x2896, 0x2897, 0x28d0, 0x28d1, 0x28d2, 0x28d3, 0x28d4, 0x28d5, 0x28d6, 0x28d7, > 0x2898, 0x2899, 0x289a, 0x289b, 0x289c, 0x289d, 0x289e, 0x289f, 0x28d8, 0x28d9, 0x28da, 0x28db, 0x28dc, 0x28dd, 0x28de, 0x28df, > 0x28a0, 0x28a1, 0x28a2, 0x28a3, 0x28a4, 0x28a5, 0x28a6, 0x28a7, 0x28e0, 0x28e1, 0x28e2, 0x28e3, 0x28e4, 0x28e5, 0x28e6, 0x28e7, > 0x28a8, 0x28a9, 0x28aa, 0x28ab, 0x28ac, 0x28ad, 0x28ae, 0x28af, 0x28e8, 0x28e9, 0x28ea, 0x28eb, 0x28ec, 0x28ed, 0x28ee, 0x28ef, > 0x28b0, 0x28b1, 0x28b2, 0x28b3, 0x28b4, 0x28b5, 0x28b6, 0x28b7, 0x28f0, 0x28f1, 0x28f2, 0x28f3, 0x28f4, 0x28f5, 0x28f6, 0x28f7, > 0x28b8, 0x28b9, 0x28ba, 0x28bb, 0x28bc, 0x28bd, 0x28be, 0x28bf, 0x28f8, 0x28f9, 0x28fa, 0x28fb, 0x28fc, 0x28fd, 0x28fe, 0x28ff, >} - __teliva_timestamp: original utf8: >-- https://stackoverflow.com/questions/7983574/how-to-write-a-unicode-symbol-in-lua >function utf8(decimal) > local bytemarkers = { {0x7FF,192}, {0xFFFF,224}, {0x1FFFFF,240} } > if decimal<128 then return string.char(decimal) end > local charbytes = {} > for bytes,vals in ipairs(bytemarkers) do > if decimal<=vals[1] then > for b=bytes+1,2,-1 do > local mod = decimal%64 > decimal = (decimal-mod)/64 > charbytes[b] = string.char(128+mod) > end > charbytes[1] = string.char(vals[2]+decimal) > break > end > end > return table.concat(charbytes) >end - __teliva_timestamp: original grid_char_to_glyph_index: >-- convert a chunk of grid into a number >function grid_char_to_glyph_index(g) > return g[1][1] + g[2][1]*2 + g[3][1]*4 + g[4][1]*8 + > g[1][2]*16 + g[2][2]*32 + g[3][2]*64 + g[4][2]*128 + > 1 -- 1-indexing >end - __teliva_timestamp: original render: >function render(window) > window:clear() > window:attron(curses.color_pair(1)) > for line=1,lines do > for col=1,cols do > window:addstr(utf8(glyph[grid_char_to_glyph_index(grid_char(line, col))])) > end > end > window:attroff(curses.color_pair(1)) > window:refresh() >end - __teliva_timestamp: original state: >function state(line, col) > if line < 1 or line > #grid or col < 1 or col > #grid[1] then > return 0 > end > return grid[line][col] >end - __teliva_timestamp: original num_live_neighbors: >function num_live_neighbors(line, col) > return state(line-1, col-1) + state(line-1, col) + state(line-1, col+1) + > state(line, col-1) + state(line, col+1) + > state(line+1, col-1) + state(line+1, col) + state(line+1, col+1) >end - __teliva_timestamp: original step: >function step() > local new_grid = {} > for line=1,#grid do > new_grid[line] = {} > for col=1,#grid[1] do > local n = num_live_neighbors(line, col) > if n == 3 then > new_grid[line][col] = 1 > elseif n == 2 then > new_grid[line][col] = grid[line][col] > else > new_grid[line][col] = 0 > end > end > end > grid = new_grid >end - __teliva_timestamp: original sleep: >function sleep(a) > local sec = tonumber(os.clock() + a); > while (os.clock() < sec) do > end >end - __teliva_timestamp: original load_file: >function load_file(window, fs, filename) > local infile = start_reading(fs, filename) > if infile == nil then return end > local line_index = lines -- quarter of the way down in pixels > while true do > local line = infile.read() > if line == nil then break end > if line:sub(1,1) ~= '!' then -- comment; plaintext files can't have whitespace before comments > local col_index = cols > for c in line:gmatch(".") do > if c == '\r' then break end -- DOS line ending > if c == '.' then > grid[line_index][col_index] = 0 > else > grid[line_index][col_index] = 1 > end > col_index = col_index+1 > end > line_index = line_index+1 > end > end >end - __teliva_timestamp: original update: >menu = {{"arrow", "pan"}} > >function update(window, c) > if c == curses.KEY_LEFT then > for i=1,lines*4 do > for j=2,cols*2 do > grid[i][j-1] = grid[i][j] > end > grid[i][cols*2] = 0 > end > elseif c == curses.KEY_DOWN then > for i=lines*4-1,1,-1 do > for j=1,cols*2 do > grid[i+1][j] = grid[i][j] > end > end > for j=1,cols*2 do > grid[1][j] = 0 > end > elseif c == curses.KEY_UP then > for i=2,lines*4 do > for j=1,cols*2 do > grid[i-1][j] = grid[i][j] > end > end > for j=1,cols*2 do > grid[lines*4][j] = 0 > end > elseif c == curses.KEY_RIGHT then > for i=1,lines*4 do > for j=cols*2-1,1,-1 do > grid[i][j+1] = grid[i][j] > end > grid[i][1] = 0 > end > end >end - __teliva_timestamp: original main: >function main() > curses.init_pair(1, 22, 189) > > -- initialize grid based on commandline args > if (#arg == 0) then > -- by default, start from a deterministically random state > for i=1,lines*4 do > for j=1,cols*2 do > grid[i][j] = math.random(0, 1) > end > end > elseif arg[1] == "random" then > -- start from a non-deterministically random start state > math.randomseed(os.time()) > for i=1,lines*4 do > for j=1,cols*2 do > grid[i][j] = math.random(0, 1) > end > end > -- shortcuts for some common patterns > elseif arg[1] == "pentomino" then > -- https://www.conwaylife.com/wiki/Pentomino > grid[83][172] = 1 > grid[83][173] = 1 > grid[84][173] = 1 > grid[84][174] = 1 > grid[85][173] = 1 > elseif arg[1] == "glider" then > -- https://www.conwaylife.com/wiki/Glider > grid[5][4] = 1 > grid[6][5] = 1 > grid[7][3] = 1 > grid[7][4] = 1 > grid[7][5] = 1 > elseif arg[1] == "blinker" then > -- https://www.conwaylife.com/wiki/Blinker > grid[7][3] = 1 > grid[7][4] = 1 > grid[7][5] = 1 > elseif arg[1] == "block" then > -- https://www.conwaylife.com/wiki/Block > grid[5][4] = 1 > grid[5][5] = 1 > grid[6][4] = 1 > grid[6][5] = 1 > elseif arg[1] == "loaf" then > -- https://www.conwaylife.com/wiki/Loaf > grid[5][4] = 1 > grid[5][5] = 1 > grid[6][6] = 1 > grid[7][6] = 1 > grid[8][5] = 1 > grid[7][4] = 1 > grid[6][3] = 1 > else > -- Load a file in the standard "plaintext" format: https://www.conwaylife.com/wiki/Plaintext > -- > -- Each pattern page at https://www.conwaylife.com/wiki provides its > -- plaintext representation in a block called "Pattern Files" on the right. > -- > -- For example, check out the list of Important Patterns at > -- https://www.conwaylife.com/wiki/Category:Patterns_with_Catagolue_frequency_class_0 > load_file(Window, nil, arg[1]) > end > > -- main loop > while true do > render(Window) > c = Window:getch() > update(Window, c) > step() > end >end - __teliva_timestamp: >Thu Feb 17 19:58:19 2022 doc:blurb: >Conway's Game of Life > >To get around limitations of text mode we use the braille character set to render 8 cells per character. > >By default it initializes the space with a random state of cells. You can also start it up with .cells files from https://conwaylife.com/wiki, or with a few special names: > $ src/teliva life.tlv block > $ src/teliva life.tlv loaf > $ src/teliva life.tlv blinker > $ src/teliva life.tlv glider > $ src/teliva life.tlv pentomino