# .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 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 Window: >Window = curses.stdscr() >-- animation-based app >Window:nodelay(true) >lines, cols = Window:getmaxyx() - __teliva_timestamp: original current_game: >current_game = {} - __teliva_timestamp: original piece_glyph: >piece_glyph = { > -- for legibility, white pieces also use unicode glyphs for black pieces > -- we rely on colors to distinguish them > K = 0x265a, > Q = 0x265b, > R = 0x265c, > B = 0x265d, > N = 0x265e, > P = 0x265f, > k = 0x265a, > q = 0x265b, > r = 0x265c, > b = 0x265d, > n = 0x265e, > p = 0x265f, >} - __teliva_timestamp: original top_player: >function top_player(current_game) > if current_game.players[1].color == "black" then > return current_game.players[1] > end > return current_game.players[2] >end - __teliva_timestamp: original bottom_player: >function bottom_player(current_game) > if current_game.players[1].color == "white" then > return current_game.players[1] > end > return current_game.players[2] >end - __teliva_timestamp: original render_player: >function render_player(y, x, player) > Window:mvaddstr(y, x, player.user.name) > Window:addstr(" ยท ") > Window:addstr(tostring(player.rating)) >end - __teliva_timestamp: original render_square: >function render_square(current_game, rank, file, highlighted_squares) > -- decide whether to highlight > local hl = 0 > if (rank == highlighted_squares.from.rank and file == highlighted_squares.from.file) > or (rank == highlighted_squares.to.rank and file == highlighted_squares.to.file) then > hl = 4 > end > if (rank+file)%2 == 1 then > -- light square > Window:attrset(curses.color_pair(1+hl)) > else > -- dark square > Window:attrset(curses.color_pair(3+hl)) > end > Window:mvaddstr((8 - rank + 1)*3, file*5, " ") > Window:mvaddstr((8 - rank + 1)*3+1, file*5, " ") > Window:mvaddstr((8 - rank + 1)*3+2, file*5, " ") > Window:attrset(curses.A_NORMAL) >end - __teliva_timestamp: original render_fen_rank: >function render_fen_rank(rank, fen_rank, highlighted_squares) > local file = 1 > for x in fen_rank:gmatch(".") do > if x:match("%d") then > file = file + tonumber(x) > else > -- decide whether to highlight > local hl = 0 > if (rank == highlighted_squares.from.rank and file == highlighted_squares.from.file) > or (rank == highlighted_squares.to.rank and file == highlighted_squares.to.file) then > hl = 4 > end > if (rank+file)%2 == 1 then > if x < 'Z' then > -- white piece on light square > Window:attrset(curses.color_pair(1+hl)) > else > -- black piece on light square > Window:attrset(curses.color_pair(2+hl)) > end > else > if x < 'Z' then > -- white piece on dark square > Window:attrset(curses.color_pair(3+hl)) > else > -- black piece on dark square > Window:attrset(curses.color_pair(4+hl)) > end > end > Window:mvaddstr((8 - rank + 1)*3+1, file*5+2, utf8(piece_glyph[x])) > Window:attrset(curses.A_NORMAL) > file = file + 1 > end > end >end - __teliva_timestamp: original render_time: >function render_time(y, x, seconds) > if seconds == nil then return end > Window:mvaddstr(y, x, tostring(math.floor(seconds/60))) > Window:addstr(string.format(":%02d", seconds%60)) >end - __teliva_timestamp: original render_board: >function render_board(current_game) >--? Window:mvaddstr(1, 50, dump(current_game.fen)) >--? Window:mvaddstr(6, 50, dump(current_game.previously_moved_squares)) > render_player(2, 5, top_player(current_game)) > render_time(2, 35, current_game.bc) > for rank=8,1,-1 do > for file=1,8 do > render_square(current_game, rank, file, current_game.previously_moved_squares) > end > render_fen_rank(rank, current_game.fen_rank[8-rank+1], current_game.previously_moved_squares) > end > render_player(27, 5, bottom_player(current_game)) > render_time(27, 35, current_game.wc) >end - __teliva_timestamp: original parse_lm: >function parse_lm(move) >--? Window:mvaddstr(4, 50, move) > local file1 = string.byte(move:sub(1, 1)) - 96 -- 'a'-1 > local rank1 = string.byte(move:sub(2, 2)) - 48 -- '0' > local file2 = string.byte(move:sub(3, 3)) - 96 -- 'a'-1 > local rank2 = string.byte(move:sub(4, 4)) - 48 -- '0' >--? Window:mvaddstr(5, 50, dump({{rank1, file1}, {rank2, file2}})) > return {from={rank=rank1, file=file1}, to={rank=rank2, file=file2}} >end - __teliva_timestamp: original render: >function render(chunk) > local o = json.decode(chunk) > if o.t == "featured" then > current_game = o.d >--? current_game.lm = "__" > current_game.previously_moved_squares = {from={rank=0, file=0}, to={rank=0, file=0}} -- no highlight > else > current_game.fen = o.d.fen > current_game.wc = o.d.wc > current_game.bc = o.d.bc >--? current_game.lm = o.d.lm > current_game.previously_moved_squares = parse_lm(o.d.lm) >--? Window:nodelay(false) >--? Window:mvaddstr(3, 50, "paused") > end > current_game.fen_rank = split(current_game.fen, "%w+") > render_board(current_game) > Window:refresh() >end - __teliva_timestamp: original init_colors: >function init_colors() > -- colors > local light_piece = 1 > local dark_piece = 0 > local light_square = 252 > local dark_square = 242 > local light_last_moved_square = 229 > local dark_last_moved_square = 226 > -- initialize colors > curses.init_pair(1, light_piece, light_square) > curses.init_pair(2, dark_piece, light_square) > curses.init_pair(3, light_piece, dark_square) > curses.init_pair(4, dark_piece, dark_square) > curses.init_pair(5, light_piece, light_last_moved_square) > curses.init_pair(6, dark_piece, light_last_moved_square) > curses.init_pair(7, light_piece, dark_last_moved_square) > curses.init_pair(8, dark_piece, dark_last_moved_square) >end - __teliva_timestamp: original main: >function main() > init_colors() > local request = { > url = "https://lichess.org/api/tv/feed", > sink = function(chunk, err) > if chunk then > Window:clear() > render(chunk) > Window:getch() > end > return 1 > end, > } > http.request(request) >end - __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 split: >function split(s, pat) > result = {} > for x in s:gmatch(pat) do > table.insert(result, x) > end > return result >end - __teliva_timestamp: original dump: >-- https://stackoverflow.com/questions/9168058/how-to-dump-a-table-to-console >function dump(o) > if type(o) == 'table' then > local s = '{ ' > for k,v in pairs(o) do > if type(k) ~= 'number' then k = '"'..k..'"' end > s = s .. '['..k..'] = ' .. dump(v) .. ',' > end > return s .. '} ' > else > return tostring(o) > end >end - __teliva_timestamp: >Sat Feb 12 18:57:02 2022 __teliva_note: >better UX when app isn't permitted to access the network main: >function main() > init_colors() > local request = { > url = "https://lichess.org/api/tv/feed", > sink = function(chunk, err) > if chunk then > Window:clear() > -- main event loop is here > render(chunk) > Window:getch() > end > return 1 > end, > } > http.request(request) > -- degenerate event loop just to show errors in sandboxing, etc. > while true do Window:getch(); end >end - __teliva_timestamp: >Thu Feb 17 20:00:23 2022 doc:blurb: >A demo of Teliva's support for networking: watch the current live chess game from Lichess TV. > >Compare with https://lichess.org/tv on a browser.