teliva/chesstv.tlv

360 lines
12 KiB
Lua

# .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.