2021-11-21 22:33:50 +00:00
|
|
|
teliva_program = {
|
2021-11-22 04:47:14 +00:00
|
|
|
window = [==[
|
|
|
|
window = curses.stdscr()
|
|
|
|
-- animation-based app
|
|
|
|
window:nodelay(true)
|
|
|
|
lines, cols = window:getmaxyx()]==],
|
2021-11-22 06:23:32 +00:00
|
|
|
current_game = [==[current_game = {}]==],
|
|
|
|
piece_glyph = [==[
|
|
|
|
piece_glyph = {
|
|
|
|
K = 0x2654,
|
|
|
|
Q = 0x2655,
|
|
|
|
R = 0x2656,
|
|
|
|
B = 0x2657,
|
|
|
|
N = 0x2658,
|
|
|
|
P = 0x2659,
|
|
|
|
k = 0x265a,
|
|
|
|
q = 0x265b,
|
|
|
|
r = 0x265c,
|
|
|
|
b = 0x265d,
|
|
|
|
n = 0x265e,
|
|
|
|
p = 0x265f,
|
|
|
|
}]==],
|
|
|
|
top_player = [==[
|
|
|
|
function top_player(current_game)
|
2021-11-22 07:38:28 +00:00
|
|
|
if current_game.players[1].color == "black" then
|
2021-11-22 06:23:32 +00:00
|
|
|
return current_game.players[1]
|
|
|
|
end
|
|
|
|
return current_game.players[2]
|
|
|
|
end]==],
|
|
|
|
bottom_player = [==[
|
|
|
|
function bottom_player(current_game)
|
2021-11-22 07:38:28 +00:00
|
|
|
if current_game.players[1].color == "white" then
|
2021-11-22 06:23:32 +00:00
|
|
|
return current_game.players[1]
|
|
|
|
end
|
|
|
|
return current_game.players[2]
|
|
|
|
end]==],
|
|
|
|
render_player = [==[
|
|
|
|
function render_player(y, x, player)
|
|
|
|
curses.mvaddstr(y, x, player.user.name)
|
|
|
|
curses.addstr(" · ")
|
|
|
|
curses.addstr(tostring(player.rating))
|
|
|
|
end]==],
|
|
|
|
render_square = [==[
|
2021-11-22 07:38:28 +00:00
|
|
|
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
|
2021-11-22 06:48:32 +00:00
|
|
|
if (rank+file)%2 == 0 then
|
|
|
|
-- light square
|
2021-11-22 07:38:28 +00:00
|
|
|
curses.attrset(curses.color_pair(1+hl))
|
2021-11-22 06:48:32 +00:00
|
|
|
else
|
|
|
|
-- dark square
|
2021-11-22 07:38:28 +00:00
|
|
|
curses.attrset(curses.color_pair(3+hl))
|
2021-11-22 06:23:32 +00:00
|
|
|
end
|
2021-11-22 07:38:28 +00:00
|
|
|
curses.mvaddstr((8 - rank + 1)*3, file*5, " ")
|
|
|
|
curses.mvaddstr((8 - rank + 1)*3+1, file*5, " ")
|
|
|
|
curses.mvaddstr((8 - rank + 1)*3+2, file*5, " ")
|
2021-11-22 06:23:32 +00:00
|
|
|
curses.attrset(curses.A_NORMAL)
|
|
|
|
end]==],
|
|
|
|
render_fen_rank = [==[
|
2021-11-22 07:38:28 +00:00
|
|
|
function render_fen_rank(rank, fen_rank, highlighted_squares)
|
2021-11-22 06:23:32 +00:00
|
|
|
local file = 1
|
|
|
|
for x in fen_rank:gmatch(".") do
|
|
|
|
if x:match("%d") then
|
|
|
|
file = file + tonumber(x)
|
2021-11-22 06:48:32 +00:00
|
|
|
else
|
2021-11-22 07:38:28 +00:00
|
|
|
-- 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
|
2021-11-22 06:48:32 +00:00
|
|
|
if (rank+file)%2 == 0 then
|
|
|
|
if x < 'Z' then
|
|
|
|
-- white piece on light square
|
2021-11-22 07:38:28 +00:00
|
|
|
curses.attrset(curses.color_pair(1+hl))
|
2021-11-22 06:48:32 +00:00
|
|
|
else
|
|
|
|
-- black piece on light square
|
2021-11-22 07:38:28 +00:00
|
|
|
curses.attrset(curses.color_pair(2+hl))
|
2021-11-22 06:48:32 +00:00
|
|
|
end
|
|
|
|
else
|
|
|
|
if x < 'Z' then
|
|
|
|
-- white piece on dark square
|
2021-11-22 07:38:28 +00:00
|
|
|
curses.attrset(curses.color_pair(3+hl))
|
2021-11-22 06:48:32 +00:00
|
|
|
else
|
|
|
|
-- black piece on dark square
|
2021-11-22 07:38:28 +00:00
|
|
|
curses.attrset(curses.color_pair(4+hl))
|
2021-11-22 06:48:32 +00:00
|
|
|
end
|
2021-11-22 06:23:32 +00:00
|
|
|
end
|
2021-11-22 07:38:28 +00:00
|
|
|
curses.mvaddstr((8 - rank + 1)*3+1, file*5+2, utf8(piece_glyph[x]))
|
2021-11-22 06:23:32 +00:00
|
|
|
curses.attrset(curses.A_NORMAL)
|
|
|
|
file = file + 1
|
|
|
|
end
|
|
|
|
end
|
2021-11-22 07:46:08 +00:00
|
|
|
end]==],
|
|
|
|
render_time = [==[
|
|
|
|
function render_time(y, x, seconds)
|
|
|
|
if seconds == nil then return end
|
|
|
|
curses.mvaddstr(y, x, tostring(math.floor(seconds/60)))
|
|
|
|
curses.addstr(string.format(":%02d", seconds%60))
|
2021-11-22 06:23:32 +00:00
|
|
|
end]==],
|
|
|
|
render_board = [==[
|
|
|
|
function render_board(current_game)
|
2021-11-22 07:38:28 +00:00
|
|
|
--? curses.mvaddstr(1, 50, dump(current_game.fen))
|
|
|
|
--? curses.mvaddstr(6, 50, dump(current_game.previously_moved_squares))
|
2021-11-22 06:23:32 +00:00
|
|
|
render_player(2, 5, top_player(current_game))
|
2021-11-22 07:46:08 +00:00
|
|
|
render_time(2, 35, current_game.bc)
|
2021-11-22 07:38:28 +00:00
|
|
|
for rank=8,1,-1 do
|
2021-11-22 06:23:32 +00:00
|
|
|
for file=1,8 do
|
2021-11-22 07:38:28 +00:00
|
|
|
render_square(current_game, rank, file, current_game.previously_moved_squares)
|
2021-11-22 06:23:32 +00:00
|
|
|
end
|
2021-11-22 07:38:28 +00:00
|
|
|
render_fen_rank(rank, current_game.fen_rank[8-rank+1], current_game.previously_moved_squares)
|
2021-11-22 06:23:32 +00:00
|
|
|
end
|
|
|
|
render_player(27, 5, bottom_player(current_game))
|
2021-11-22 07:46:08 +00:00
|
|
|
render_time(27, 35, current_game.wc)
|
2021-11-22 07:38:28 +00:00
|
|
|
end]==],
|
|
|
|
parse_lm = [==[
|
|
|
|
function parse_lm(move)
|
|
|
|
--? curses.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'
|
|
|
|
--? curses.mvaddstr(5, 50, dump({{rank1, file1}, {rank2, file2}}))
|
|
|
|
return {from={rank=rank1, file=file1}, to={rank=rank2, file=file2}}
|
2021-11-22 06:23:32 +00:00
|
|
|
end]==],
|
2021-11-22 04:47:14 +00:00
|
|
|
render = [==[
|
|
|
|
function render(chunk)
|
2021-11-22 06:23:32 +00:00
|
|
|
local o = json.decode(chunk)
|
|
|
|
if o.t == "featured" then
|
|
|
|
current_game = o.d
|
2021-11-22 07:38:28 +00:00
|
|
|
--? current_game.lm = "__"
|
|
|
|
current_game.previously_moved_squares = {from={rank=0, file=0}, to={rank=0, file=0}} -- no highlight
|
2021-11-22 06:23:32 +00:00
|
|
|
else
|
|
|
|
current_game.fen = o.d.fen
|
|
|
|
current_game.wc = o.d.wc
|
|
|
|
current_game.bc = o.d.bc
|
2021-11-22 07:38:28 +00:00
|
|
|
--? current_game.lm = o.d.lm
|
|
|
|
current_game.previously_moved_squares = parse_lm(o.d.lm)
|
|
|
|
--? window:nodelay(false)
|
|
|
|
--? curses.mvaddstr(3, 50, "paused")
|
2021-11-22 06:23:32 +00:00
|
|
|
end
|
|
|
|
current_game.fen_rank = split(current_game.fen, "%w+")
|
|
|
|
render_board(current_game)
|
2021-11-22 04:47:14 +00:00
|
|
|
curses.refresh()
|
|
|
|
end]==],
|
2021-11-21 22:33:50 +00:00
|
|
|
main = [==[
|
|
|
|
function main()
|
2021-11-22 06:48:32 +00:00
|
|
|
-- white piece on light square (assume light background)
|
|
|
|
curses.init_pair(1, -1, -1)
|
|
|
|
-- black piece on light square
|
|
|
|
curses.init_pair(2, 0, -1)
|
|
|
|
-- white piece on dark square
|
|
|
|
curses.init_pair(3, -1, 3)
|
|
|
|
-- black piece on dark square
|
|
|
|
curses.init_pair(4, 0, 3)
|
2021-11-22 07:38:28 +00:00
|
|
|
-- white piece on last-moved light square
|
|
|
|
curses.init_pair(5, -1, 10)
|
|
|
|
-- black piece on last-moved light square
|
|
|
|
curses.init_pair(6, 0, 10)
|
|
|
|
-- white piece on last-moved dark square
|
|
|
|
curses.init_pair(7, -1, 2)
|
|
|
|
-- black piece on last-moved dark square
|
|
|
|
curses.init_pair(8, 0, 2)
|
2021-11-22 04:47:14 +00:00
|
|
|
local request = {
|
|
|
|
url = "https://lichess.org/api/tv/feed",
|
|
|
|
sink = function(chunk, err)
|
|
|
|
if chunk then
|
|
|
|
curses.clear()
|
|
|
|
render(chunk)
|
|
|
|
curses.getch()
|
|
|
|
end
|
|
|
|
return 1
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
http.request(request)
|
2021-11-22 06:23:32 +00:00
|
|
|
end]==],
|
|
|
|
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]==],
|
|
|
|
split = [==[
|
|
|
|
function split(s, pat)
|
|
|
|
result = {}
|
|
|
|
for x in s:gmatch(pat) do
|
|
|
|
table.insert(result, x)
|
|
|
|
end
|
|
|
|
return result
|
2021-11-21 23:07:17 +00:00
|
|
|
end]==],
|
|
|
|
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
|
2021-11-21 22:33:50 +00:00
|
|
|
end]==],
|
|
|
|
}
|