tablebase for explorer: use new api
parent
c3d313b992
commit
cbdcecddf4
|
@ -54,6 +54,8 @@ object Environment
|
|||
|
||||
def explorerEndpoint = apiEnv.ExplorerEndpoint
|
||||
|
||||
def tablebaseEndpoint = apiEnv.TablebaseEndpoint
|
||||
|
||||
def globalCasualOnlyMessage = Env.setup.CasualOnly option {
|
||||
"Due to temporary maintenance on the servers, only casual games are available."
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@ data: @Html(play.api.libs.json.Json.stringify(data)),
|
|||
i18n: @round.jsI18n(),
|
||||
userId: @Html(ctx.userId.fold("null")(id => s""""$id"""")),
|
||||
explorer: {
|
||||
endpoint: "@explorerEndpoint"
|
||||
endpoint: "@explorerEndpoint",
|
||||
tablebaseEndpoint: "@tablebaseEndpoint"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,7 +14,8 @@ lichess.user_analysis = {
|
|||
data: @Html(play.api.libs.json.Json.stringify(data)),
|
||||
i18n: @userAnalysisI18n(),
|
||||
explorer: {
|
||||
endpoint: "@explorerEndpoint"
|
||||
endpoint: "@explorerEndpoint",
|
||||
tablebaseEndpoint: "@tablebaseEndpoint"
|
||||
},
|
||||
canStudy: @isGranted(_.Beta)
|
||||
};
|
||||
|
|
|
@ -11,7 +11,8 @@ chat: @Html(J.stringify(data.chat)),
|
|||
i18n: @board.userAnalysisI18n(),
|
||||
userId: @Html(ctx.userId.fold("null")(id => s""""$id"""")),
|
||||
explorer: {
|
||||
endpoint: "@explorerEndpoint"
|
||||
endpoint: "@explorerEndpoint",
|
||||
tablebaseEndpoint: "@tablebaseEndpoint"
|
||||
},
|
||||
socketUrl: "@routes.Study.websocket(s.id, apiVersion)",
|
||||
socketVersion: @socketVersion
|
||||
|
|
|
@ -400,6 +400,9 @@ explorer {
|
|||
mass_import = {
|
||||
endpoint = "http://expl.lichess.org"
|
||||
}
|
||||
tablebase = {
|
||||
endpoint = "http://expl.lichess.org/tablebase"
|
||||
}
|
||||
}
|
||||
gameSearch {
|
||||
index = game
|
||||
|
|
|
@ -46,6 +46,7 @@ final class Env(
|
|||
val PrismicApiUrl = config getString "prismic.api_url"
|
||||
val EditorAnimationDuration = config duration "editor.animation.duration"
|
||||
val ExplorerEndpoint = config getString "explorer.endpoint"
|
||||
val TablebaseEndpoint = config getString "explorer.tablebase.endpoint"
|
||||
|
||||
object assetVersion {
|
||||
import reactivemongo.bson._
|
||||
|
|
|
@ -11,46 +11,12 @@ var replayable = require('game').game.replayable;
|
|||
function tablebaseRelevant(fen) {
|
||||
var parts = fen.split(/\s/);
|
||||
var pieceCount = parts[0].split(/[nbrqkp]/i).length - 1;
|
||||
return pieceCount <= 7;
|
||||
}
|
||||
|
||||
function tablebasePrepare(moves, fen) {
|
||||
var fenParts = fen.split(/\s/);
|
||||
var stm = fenParts[1];
|
||||
var halfMoves = parseInt(fenParts[4], 10);
|
||||
|
||||
var result = [];
|
||||
|
||||
for (uci in moves) {
|
||||
if (moves.hasOwnProperty(uci)) {
|
||||
var move = moves[uci];
|
||||
if (move.dtz === null) continue;
|
||||
|
||||
move.uci = uci;
|
||||
|
||||
if ((move.dtz < 0 && stm === 'w') || (move.dtz > 0 && stm === 'b')) {
|
||||
move.winner = 'white';
|
||||
} else if ((move.dtz < 0 && stm === 'b') || (move.dtz > 0 && stm === 'w')) {
|
||||
move.winner = 'black';
|
||||
}
|
||||
|
||||
if (move.wdl == -2 && move.dtz - halfMoves <= -100) move.realWdl = -1
|
||||
else if (move.wdl == 2 && move.dtz + halfMoves >= 100) move.realWdl = 1
|
||||
else move.realWdl = move.wdl;
|
||||
|
||||
result.push(move);
|
||||
}
|
||||
}
|
||||
|
||||
result.sort(function (a, b) {
|
||||
if (a.dtm !== null && b.dtm !== null && a.dtm !== b.dtm) return b.dtm - a.dtm;
|
||||
return b.dtz - a.dtz;
|
||||
});
|
||||
|
||||
return result;
|
||||
var castling = parts[2];
|
||||
return pieceCount <= 6 && castling === '-';
|
||||
}
|
||||
|
||||
module.exports = function(root, opts) {
|
||||
|
||||
var enabled = storedProp('explorer.enabled', false);
|
||||
var loading = m.prop(true);
|
||||
var failing = m.prop(false);
|
||||
|
@ -63,43 +29,45 @@ module.exports = function(root, opts) {
|
|||
setNode();
|
||||
}
|
||||
var withGames = synthetic(root.data) || replayable(root.data) || root.data.opponent.ai;
|
||||
var effectiveVariant = root.data.game.variant.key == 'fromPosition' ? 'standard' : root.data.game.variant.key;
|
||||
|
||||
var config = configCtrl(root.data.game.variant, onConfigClose);
|
||||
|
||||
var fetch = throttle(500, false, function() {
|
||||
var fen = root.vm.node.fen;
|
||||
var effectiveVariant = root.data.game.variant.key == 'fromPosition' ? 'standard' : root.data.game.variant.key;
|
||||
if (effectiveVariant === 'standard' && tablebaseRelevant(fen)) {
|
||||
tablebaseXhr(fen).then(function(res) {
|
||||
cache[fen] = {
|
||||
tablebase: true,
|
||||
fen: fen,
|
||||
moves: tablebasePrepare(res.moves, fen)
|
||||
};
|
||||
loading(false);
|
||||
failing(false);
|
||||
m.redraw();
|
||||
}, function (err) {
|
||||
loading(false);
|
||||
failing(true);
|
||||
m.redraw();
|
||||
});
|
||||
} else {
|
||||
openingXhr(opts.endpoint, effectiveVariant, fen, config.data, withGames).then(function(res) {
|
||||
res.opening = true;
|
||||
cache[fen] = res;
|
||||
loading(false);
|
||||
failing(false);
|
||||
m.redraw();
|
||||
}, function(err) {
|
||||
loading(false);
|
||||
failing(true);
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
var handleFetchError = function(err) {
|
||||
loading(false);
|
||||
failing(true);
|
||||
m.redraw();
|
||||
};
|
||||
|
||||
var fetchOpening = throttle(2000, false, function(fen) {
|
||||
openingXhr(opts.endpoint, effectiveVariant, fen, config.data, withGames).then(function(res) {
|
||||
res.opening = true;
|
||||
res.fen = fen;
|
||||
cache[fen] = res;
|
||||
loading(false);
|
||||
failing(false);
|
||||
m.redraw();
|
||||
}, handleFetchError);
|
||||
});
|
||||
|
||||
var fetchTablebase = throttle(500, false, function(fen) {
|
||||
tablebaseXhr(opts.tablebaseEndpoint, root.vm.node.fen).then(function(res) {
|
||||
res.tablebase = true;
|
||||
res.fen = fen;
|
||||
cache[fen] = res;
|
||||
loading(false);
|
||||
failing(false);
|
||||
m.redraw();
|
||||
}, handleFetchError);
|
||||
});
|
||||
|
||||
var fetch = function(fen) {
|
||||
if (effectiveVariant === 'standard' && withGames && tablebaseRelevant(fen)) fetchTablebase(fen);
|
||||
else fetchOpening(fen);
|
||||
};
|
||||
|
||||
var empty = {
|
||||
opening: true,
|
||||
moves: {}
|
||||
};
|
||||
|
||||
|
@ -110,7 +78,10 @@ module.exports = function(root, opts) {
|
|||
if (!cache[node.fen]) {
|
||||
loading(true);
|
||||
fetch(node.fen);
|
||||
} else loading(false);
|
||||
} else {
|
||||
loading(false);
|
||||
failing(false);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -117,6 +117,7 @@ function showGameTable(ctrl, type, games) {
|
|||
}
|
||||
|
||||
function showTablebase(ctrl, title, moves, fen) {
|
||||
var stm = fen.split(/\s/)[1];
|
||||
if (!moves.length) return null;
|
||||
return [
|
||||
m('div.title', title),
|
||||
|
@ -127,30 +128,36 @@ function showTablebase(ctrl, title, moves, fen) {
|
|||
'data-uci': move.uci
|
||||
}, [
|
||||
m('td', move.san),
|
||||
m('td', [showDtm(move), showDtz(move)])
|
||||
m('td', [showDtz(stm, move), showDtm(stm, move)])
|
||||
]);
|
||||
}))
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
function showDtm(move) {
|
||||
var dtm = move.dtm;
|
||||
if (!dtm) return null;
|
||||
var text = 'DTM ' + Math.abs(dtm);
|
||||
var title = 'Depth to mate: ' + Math.abs(dtm) + ' half moves';
|
||||
if (move.winner == 'white') return m('result.white', {title: title}, text);
|
||||
if (move.winner == 'black') return m('result.black', {title: title}, text);
|
||||
function winner(stm, move) {
|
||||
if ((stm[0] == 'w' && move.wdl < 0) || (stm[0] == 'b' && move.wdl > 0))
|
||||
return 'white';
|
||||
else if ((stm[0] == 'b' && move.wdl < 0) || (stm[0] == 'w' && move.wdl > 0))
|
||||
return 'black';
|
||||
}
|
||||
|
||||
function showDtz(move) {
|
||||
var dtz = move.dtz;
|
||||
if (dtz === null) return null;
|
||||
var text = 'DTZ ' + Math.abs(dtz);
|
||||
var title = 'Distance to Zeroing (capture / pawn move): ' + Math.abs(dtz);
|
||||
if (move.winner == 'white') return m('result.white', {title: title}, text);
|
||||
if (move.winner == 'black') return m('result.black', {title: title}, text);
|
||||
return m('result.draws', '½-½');
|
||||
function showDtm(stm, move) {
|
||||
if (move.dtm) return m('result.' + winner(stm, move), {
|
||||
title: 'Depth to mate: ' + Math.abs(move.dtm) + ' half moves'
|
||||
}, 'DTM ' + Math.abs(move.dtm));
|
||||
}
|
||||
|
||||
function showDtz(stm, move) {
|
||||
if (move.checkmate) return m('result.' + winner(stm, move), 'Checkmate');
|
||||
if (move.stalemate) return m('result.draws', 'Stalemate');
|
||||
if (move.insufficient_material) return m('result.draws', 'Insufficient material');
|
||||
if (move.dtz === null) return null;
|
||||
if (move.dtz === 0) return m('result.draws', 'Draw');
|
||||
if (move.zeroing) return m('result.' + winner(stm, move), 'Zeroing');
|
||||
return m('result.' + winner(stm, move), {
|
||||
title: 'Distance to Zeroing (capture / pawn move): ' + Math.abs(move.dtz)
|
||||
}, 'DTZ ' + Math.abs(move.dtz));
|
||||
}
|
||||
|
||||
function showEmpty(ctrl) {
|
||||
|
@ -171,6 +178,19 @@ function showEmpty(ctrl) {
|
|||
]);
|
||||
}
|
||||
|
||||
function showGameEnd(ctrl, title) {
|
||||
return m('div.data.empty', [
|
||||
m('div.title', "Game over"),
|
||||
m('div.message', [
|
||||
m('i[data-icon=]'),
|
||||
m('h3', title),
|
||||
m('button.button.text[data-icon=L]', {
|
||||
onclick: ctrl.explorer.toggle
|
||||
}, 'Close')
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
function show(ctrl) {
|
||||
var data = ctrl.explorer.current();
|
||||
if (data && data.opening) {
|
||||
|
@ -183,12 +203,14 @@ function show(ctrl) {
|
|||
} else if (data && data.tablebase) {
|
||||
var moves = data.moves;
|
||||
if (moves.length) lastShow = m('div.data', [
|
||||
showTablebase(ctrl, 'Winning', moves.filter(function(move) { return move.realWdl === -2; }), data.fen),
|
||||
showTablebase(ctrl, 'Win prevented by 50-move rule', moves.filter(function(move) { return move.realWdl === -1; }), data.fen),
|
||||
showTablebase(ctrl, 'Drawn', moves.filter(function(move) { return move.realWdl === 0; }), data.fen),
|
||||
showTablebase(ctrl, 'Loss saved by 50-move rule', moves.filter(function(move) { return move.realWdl === 1; }), data.fen),
|
||||
showTablebase(ctrl, 'Losing', moves.filter(function(move) { return move.realWdl === 2; }), data.fen)
|
||||
showTablebase(ctrl, 'Winning', moves.filter(function(move) { return move.real_wdl === -2; }), data.fen),
|
||||
showTablebase(ctrl, 'Win prevented by 50-move rule', moves.filter(function(move) { return move.real_wdl === -1; }), data.fen),
|
||||
showTablebase(ctrl, 'Drawn', moves.filter(function(move) { return move.real_wdl === 0; }), data.fen),
|
||||
showTablebase(ctrl, 'Loss saved by 50-move rule', moves.filter(function(move) { return move.real_wdl === 1; }), data.fen),
|
||||
showTablebase(ctrl, 'Losing', moves.filter(function(move) { return move.real_wdl === 2; }), data.fen)
|
||||
])
|
||||
else if (data.checkmate) lastShow = showGameEnd(ctrl, 'Checkmate')
|
||||
else if (data.stalemate) lastShow = showGameEnd(ctrl, 'Stalemate')
|
||||
else lastShow = showEmpty(ctrl);
|
||||
}
|
||||
return lastShow;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
var m = require('mithril');
|
||||
|
||||
module.exports = function(fen) {
|
||||
module.exports = function(endpoint, fen) {
|
||||
return m.request({
|
||||
background: true,
|
||||
method: 'GET',
|
||||
url: 'https://syzygy-tables.info/api/v2',
|
||||
url: endpoint,
|
||||
data: {
|
||||
fen: fen
|
||||
}
|
||||
|
|
|
@ -188,7 +188,7 @@ function buttons(ctrl) {
|
|||
m('div', [
|
||||
ctrl.actionMenu.open ? null : m('button', {
|
||||
id: 'open_explorer',
|
||||
'data-hint': 'Opening explorer',
|
||||
'data-hint': 'Opening explorer & tablebase',
|
||||
'data-act': 'explorer',
|
||||
class: 'button hint--bottom' + (ctrl.explorer.enabled() ? ' active' : '')
|
||||
}, icon(']')),
|
||||
|
|
Loading…
Reference in New Issue