/** Currently displayed refutation lines (on-screen).
* Can either come from the current_analysis_data, displayed_analysis_data,
- * or hash_refutation_lines.
+ * or hash_refutation_lines (choose_displayed_refutation_lines() chooses which one).
*
- * TODO: This is a mess, and we should probably just let update_refutation_lines()
- * decide anew every time instead of storing the state here in multiple global
- * variables.
- *
- * @type {Array.<{
+ * @typedef {{
* score: Array,
* depth: string,
* pv: Array.<string>,
* move: string
- * }>}
+ * }}
* @private
*/
-let refutation_lines = [];
+var RefutationLine;
/** Refutation lines from current hash probe.
- *
* If non-null, will override refutation lines from the base position.
- * Note that these are relative to display_fen, not base_fen.
+ *
+ * @type {Array.<RefutationLine>}
*/
let hash_refutation_lines = null;
/**
- * What FEN refutation_lines is relative to. Will usually be base_fen,
- * but if another move gets loaded while we are looking, they can diverge.
+ * What FEN hash_refutation_lines is relative to.
*/
-let refutation_lines_base_fen = null;
+let hash_refutation_lines_base_fen = null;
/** @type {number} @private */
let ims = 0;
* start_display_move_num: number
* }}
*
- * "start_display_move_num" is the (half-)move number to start displaying the PV at.
- * "score" is also evaluated at this point.
+ * "start_display_move_num" is the (half-)move number to start displaying the PV at,
+ * i.e., the index into pv.
+ *
+ * "score" is also evaluated at this point. scores can be empty and is frequently
+ * sparse; it's generally only really useful for history (we obviously don't know
+ * much about how the score will * move during e.g. a PV, except that a mate/TB
+ * counter might go down).
*/
var DisplayLine;
/** @type {boolean} @private */
let current_display_line_is_history = false;
-/** @type {?number} @private */
+/** @type {?number} @private
+ *
+ * The highlighted/used/shown move in current_display_line.pv, in terms of absolute index
+ * (not relative to e.g. the start FEN).
+ */
let current_display_move = null;
/**
return parseInt(fen.split(' ')[5]);
}
+/** @param {!string} fen
+ * @return {!number}
+ *
+ * Return the half-move clock, starting from 0 (and never resetting).
+ */
+function find_halfmove_num(fen) {
+ let move_num = find_move_num(fen);
+ let toplay = find_toplay(fen);
+ return (move_num - 1) * 2 + (toplay === 'w' ? 0 : 1);
+}
+
/** @param {!number} x
* @return {!number}
*/
function print_pv(line_num, splicepos, opt_limit, opt_showlast) {
let display_line = display_lines[line_num];
let pv = display_line.pv;
- let move_num = find_move_num(display_line.start_fen);
- let toplay = find_toplay(display_line.start_fen);
-
- // Truncate PV at the start if needed.
- let start_display_move_num = display_line.start_display_move_num;
- if (start_display_move_num > 0) {
- pv = pv.slice(start_display_move_num);
- let to_add = start_display_move_num;
- if (toplay === 'b') {
- ++move_num;
- toplay = 'w';
- --to_add;
- }
- if (to_add % 2 == 1) {
- toplay = 'b';
- --to_add;
- }
- move_num += to_add / 2;
- if (splicepos !== null && splicepos > 0) {
- --splicepos;
- }
- }
+ let halfmove_num = find_halfmove_num(display_line.start_fen) + 2; // From two, for simplicity.
+ let start_halfmove_num = halfmove_num;
let ret = document.createDocumentFragment();
- let in_tb = false;
- let i = 0;
- if (opt_limit && opt_showlast && pv.length > opt_limit) {
- // Truncate the PV at the beginning (instead of at the end).
- // We assume here that toplay is 'w'. We also assume that if
- // opt_showlast is set, then it is the history, and thus,
- // the UI should be to expand the history.
+
+ // Truncate PV at the start if needed.
+ let to_skip = display_line.start_display_move_num;
+ if (opt_limit && opt_showlast && pv.length - to_skip > opt_limit) {
+ // Explicit (UI-visible) truncation from the start, for the history.
ret.appendChild(document.createTextNode('('));
let link = document.createElement('a');
link.className = 'move';
link.textContent = '…';
ret.appendChild(link);
ret.appendChild(document.createTextNode(') '));
- i = pv.length - opt_limit;
- if (i % 2 == 1) {
- ++i;
+ to_skip = pv.length - opt_limit;
+ to_skip += to_skip % 2; // Make sure it starts on a white move.
+ }
+ if (to_skip > 0) {
+ pv = pv.slice(to_skip);
+ halfmove_num += to_skip;
+ if (splicepos !== null) {
+ splicepos -= to_skip;
+ if (splicepos < 0) {
+ splicepos = 0;
+ }
}
- move_num += i / 2;
- } else if (toplay == 'b' && pv.length > 0) {
- ret.appendChild(document.createTextNode(move_num + '. … '));
}
- for (; i < pv.length; ++i) {
+
+ // The initial move number needs to go before any (TB: …) marker.
+ if (halfmove_num % 2 == 1) {
+ // Black move.
+ ret.appendChild(document.createTextNode((halfmove_num - 1) / 2 + '. … '));
+ } else {
+ // White move.
+ ret.appendChild(document.createTextNode(halfmove_num / 2 + '. '));
+ }
+ let in_tb = false;
+ for (let i = 0; i < pv.length; ++i, ++halfmove_num) {
let prefix = '';
if (splicepos === i) {
- prefix = '(TB: ';
+ prefix = '(TB:';
in_tb = true;
}
- if (toplay == 'b' && i == 0) {
- ++move_num;
- toplay = 'w';
- } else if (toplay == 'w') {
+ if (halfmove_num % 2 == 0 && i != 0) {
if (i > opt_limit && !opt_showlast) {
if (in_tb) {
prefix += ')';
ret.appendChild(document.createTextNode(prefix + ' (…)'));
return ret;
}
- prefix += ' ' + move_num + '. ';
- ++move_num;
- toplay = 'b';
+ prefix += ' ' + (halfmove_num / 2) + '. ';
} else {
prefix += ' ';
- toplay = 'w';
}
ret.appendChild(document.createTextNode(prefix));
let link = document.createElement('a');
link.className = 'move';
- link.setAttribute('id', 'automove' + line_num + '-' + i);
+ link.setAttribute('id', 'automove' + line_num + '-' + (halfmove_num - start_halfmove_num));
link.textContent = pv[i];
- link.href = 'javascript:show_line(' + line_num + ', ' + i + ');';
+ link.href = 'javascript:show_line(' + line_num + ', ' + (halfmove_num - start_halfmove_num) + ');';
ret.appendChild(link);
}
if (in_tb) {
}
window['collapse_history'] = collapse_history;
-/** Update the HTML display of multi-PV from the global "refutation_lines".
+function choose_displayed_refutation_lines() {
+ if (hash_refutation_lines) {
+ // If we're in hash exploration, that takes precedence.
+ return [hash_refutation_lines, hash_refutation_lines_base_fen];
+ } else {
+ let data = displayed_analysis_data || current_analysis_data;
+ return [data['refutation_lines'], data['position']['fen']];
+ }
+}
+
+/** Update the HTML display of multi-PV.
*
* Also recreates the global "display_lines".
*/
function update_refutation_lines() {
- if (refutation_lines_base_fen === null) {
+ const [refutation_lines, refutation_lines_base_fen] = choose_displayed_refutation_lines();
+ if (!refutation_lines) {
return;
}
if (display_lines.length > 2) {
document.getElementById("refutationlines").replaceChildren();
document.getElementById("whiteclock").replaceChildren();
document.getElementById("blackclock").replaceChildren();
- refutation_lines = [];
- refutation_lines_base_fen = null;
update_refutation_lines();
clear_arrows();
update_displayed_line();
document.getElementById("searchstats").textContent = "";
}
if (admin_password !== null) {
- document.getElementById("searchstats").innerHTML += " | <span style=\"color: red;\">ADMIN MODE (if password is right)</span>";
+ document.getElementById("searchstats").innerHTML += " | <span style=\"color: red;\">ADMIN MODE (if password is right) | <a href=\"javascript:undo_move()\">Undo move</a></span>";
}
// Update the board itself.
}
}
- // Update the refutation lines.
base_fen = data['position']['fen'];
- if (hash_refutation_lines === null) {
- refutation_lines = data['refutation_lines'];
- refutation_lines_base_fen = base_fen;
- }
update_refutation_lines();
// Update the sparkline last, since its size depends on how everything else reflowed.
}
}
-/**
- * @param {boolean} truncate_history
- */
-function set_truncate_history(truncate_history) {
- truncate_display_history = truncate_history;
- update_refutation_lines();
-}
-window['set_truncate_history'] = set_truncate_history;
-
/**
* @param {number} line_num
* @param {number} move_num
current_display_line = null;
current_display_move = null;
hash_refutation_lines = null;
- refutation_lines_base_fen = base_fen;
if (displayed_analysis_data) {
// TODO: Support exiting to history position if we are in an
// analysis line of a history position.
return;
} else {
current_display_line = {...display_lines[line_num]}; // Shallow clone.
- current_display_move = move_num + current_display_line.start_display_move_num;
+ current_display_move = move_num;
}
current_display_line_is_history = (line_num == 0);
display_lines[1].pv = [];
}
- highlighted_move = document.getElementById("automove" + display_line_num + "-" + (current_display_move - current_display_line.start_display_move_num));
+ highlighted_move = document.getElementById("automove" + display_line_num + "-" + current_display_move);
if (highlighted_move !== null) {
highlighted_move.classList.add('highlight');
}
}
current_hash_display_timer = null;
hash_refutation_lines = data['lines'];
- refutation_lines_base_fen = fen;
+ hash_refutation_lines_base_fen = fen;
update_board();
}
let best_move = null;
let best_move_score = null;
+ let refutation_lines = choose_displayed_refutation_lines()[0];
for (let move in refutation_lines) {
let line = refutation_lines[move];
if (!line['score']) {
}
}
+function undo_move() {
+ if (admin_password !== null) {
+ let history = current_analysis_data['position']['history'];
+ history = history.slice(0, history.length - 1);
+
+ let position = current_analysis_data['position']['start_fen'];
+ let hiddenboard = chess_from(position, history, history.length);
+ let fen = hiddenboard.fen();
+
+ let url = '/manual-override.pl';
+ url += '?fen=' + encodeURIComponent(fen);
+ url += '&history=' + encodeURIComponent(JSON.stringify(history));
+ url += '&move=null';
+ url += '&player_w=' + encodeURIComponent(current_analysis_data['position']['player_w']);
+ url += '&player_b=' + encodeURIComponent(current_analysis_data['position']['player_b']);
+ url += '&password=' + encodeURIComponent(admin_password);
+
+ console.log(fen, history);
+ fetch(url); // Ignore the result.
+ }
+}
+window['undo_move'] = undo_move;
+
function onSnapEnd(source, target) {
if (source === target && recommended_move !== null) {
source = recommended_move.from;
}
let line = display_lines[i];
if (line.pv[line.start_display_move_num] === move.san) {
- show_line(i, 0);
+ show_line(i, line.start_display_move_num);
return;
}
}