about summary refs log blame commit diff stats
path: root/life.tlv
blob: 8b3cc6741f5b09a91e53887cf16bd853c98eca5d (plain) (tree)


















                                                                                





                                                                         
                            







                                                
                            


                                
                            






















                                                    



                                                                                        
















                                                                              














                                                                























                                                                 
            





                                















                                                     

                                      



                              


















                                                                        
















                                                                                  
                               


                                                                  


                                  




                                  
                                   
                                                                







                                    
                           





                                          
                      





                                           


                               


                                 





                              
                            








                                      





                              

                      


                           
                                                          



                   
                              


                                                     
                                
                                                                              





















                                                                            


                                                







                                       









                              

                             
                           
                         
                       
                                    












































































                                                                                                                                        
                                          




                                                                                     

                                           



                              
                                                                    














                                                                                
                          
                            
                              



















                                                  
            

                                                 
                                       
                                                                     
                    
                                   
                                      
















                                                                                                        
                              






































                                        




















































                                                                   
           






                                                                                                     
                                       



                    


                           


               












                                                                                                                                                                                
# .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
    >    end
    >    for k, v in pairs(b) do
    >      if a[k] ~= v then
    >        return false
    >      end
    >    end
    >    return true
    >  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
  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