tablebase for explorer: use new api

pull/1839/head
Niklas Fiekas 2016-04-25 22:31:34 +02:00
parent c3d313b992
commit cbdcecddf4
10 changed files with 98 additions and 96 deletions

View File

@ -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."
}

View File

@ -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"
}
};
}

View File

@ -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)
};

View File

@ -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

View File

@ -400,6 +400,9 @@ explorer {
mass_import = {
endpoint = "http://expl.lichess.org"
}
tablebase = {
endpoint = "http://expl.lichess.org/tablebase"
}
}
gameSearch {
index = game

View File

@ -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._

View File

@ -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 {

View File

@ -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;

View File

@ -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
}

View File

@ -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(']')),