]> git.sesse.net Git - remoteglot/blobdiff - www/js/remoteglot.js
Fix some more TB short-form issues.
[remoteglot] / www / js / remoteglot.js
index c5ab4379f05d14074e314876fc84cc6116a72521..37f5c7bd1b61a034e56e9471fc4cd74f074be7ce 100644 (file)
@@ -7,7 +7,7 @@
  * @type {Number}
  * @const
  * @private */
-var SCRIPT_VERSION = 2016032202;
+var SCRIPT_VERSION = 2021021300;
 
 /**
  * The current backend URL.
@@ -47,9 +47,10 @@ var displayed_analysis_data = null;
  * @type {?Array.<{
  *      name: string,
  *      url: string,
+ *      hashurl: string,
  *      id: string,
- *      score: =Object,
- *      result: =string,
+ *      score: Object=,
+ *      result: string=,
  * }>}
  * @private
  */
@@ -93,9 +94,6 @@ var toplay = 'W';
 /** @type {number} @private */
 var ims = 0;
 
-/** @type {boolean} @private */
-var sort_refutation_lines_by_score = true;
-
 /** @type {boolean} @private */
 var truncate_display_history = true;
 
@@ -276,13 +274,33 @@ var request_update = function() {
                        location.reload(true);
                }
 
-               possibly_play_sound(current_analysis_data, new_data);
-               current_analysis_data = new_data;
-               update_board();
-               update_num_viewers(num_viewers);
+               // Verify that the PV makes sense.
+               var valid = true;
+               if (new_data['pv']) {
+                       var hiddenboard = new Chess(new_data['position']['fen']);
+                       for (var i = 0; i < new_data['pv'].length; ++i) {
+                               if (hiddenboard.move(new_data['pv'][i]) === null) {
+                                       valid = false;
+                                       break;
+                               }
+                       }
+               }
+
+               var timeout = 100;
+               if (valid) {
+                       possibly_play_sound(current_analysis_data, new_data);
+                       current_analysis_data = new_data;
+                       update_board();
+                       update_num_viewers(num_viewers);
+               } else {
+                       console.log("Received invalid update, waiting five seconds and trying again.");
+                       setTimeout(function() { location.reload(true); }, 5000);
+               }
 
                // Next update.
-               current_analysis_request_timer = setTimeout(function() { request_update(); }, 100);
+               if (!backend_url.match(/history/)) {
+                       current_analysis_request_timer = setTimeout(function() { request_update(); }, timeout);
+               }
        }).fail(function(jqXHR, textStatus, errorThrown) {
                if (textStatus === "abort") {
                        // Aborted because we are switching backends. Abandon and don't retry,
@@ -458,17 +476,25 @@ var position_arrow = function(arrow) {
                return;
        }
 
-       var pos = $(".square-a8").position();
-
        var zoom_factor = $("#board").width() / 400.0;
        var line_width = arrow.line_width * zoom_factor;
        var arrow_size = arrow.arrow_size * zoom_factor;
 
        var square_width = $(".square-a8").width();
-       var from_y = (7 - arrow.from_row + 0.5)*square_width;
-       var to_y = (7 - arrow.to_row + 0.5)*square_width;
-       var from_x = (arrow.from_col + 0.5)*square_width;
-       var to_x = (arrow.to_col + 0.5)*square_width;
+       var pos, from_y, to_y, from_x, to_x;
+       if (board.orientation() === 'black') {
+               pos = $(".square-h1").position();
+               from_y = (arrow.from_row + 0.5)*square_width;
+               to_y = (arrow.to_row + 0.5)*square_width;
+               from_x = (7 - arrow.from_col + 0.5)*square_width;
+               to_x = (7 - arrow.to_col + 0.5)*square_width;
+       } else {
+               pos = $(".square-a8").position();
+               from_y = (7 - arrow.from_row + 0.5)*square_width;
+               to_y = (7 - arrow.to_row + 0.5)*square_width;
+               from_x = (arrow.from_col + 0.5)*square_width;
+               to_x = (arrow.to_col + 0.5)*square_width;
+       }
 
        var SVG_NS = "http://www.w3.org/2000/svg";
        var XHTML_NS = "http://www.w3.org/1999/xhtml";
@@ -550,15 +576,6 @@ var create_arrow = function(from_square, to_square, fg_color, line_width, arrow_
        arrows.push(arrow);
 }
 
-// Note: invert is ignored.
-var compare_by_name = function(refutation_lines, invert, a, b) {
-       var ska = refutation_lines[a]['move'];
-       var skb = refutation_lines[b]['move'];
-       if (ska < skb) return -1;
-       if (ska > skb) return 1;
-       return 0;
-};
-
 var compare_by_score = function(refutation_lines, invert, a, b) {
        var sa = compute_score_sort_key(refutation_lines[b]['score'], refutation_lines[b]['depth'], invert);
        var sb = compute_score_sort_key(refutation_lines[a]['score'], refutation_lines[a]['depth'], invert);
@@ -572,7 +589,7 @@ var compare_by_score = function(refutation_lines, invert, a, b) {
  * @param {!Object} data
  * @param {number} margin The maximum number of centipawns worse than the
  *     best move can be and still be included.
- * @param {boolean} margin Whether black is to play.
+ * @param {boolean} invert Whether black is to play.
  * @return {Array.<string>} The FEN representation (e.g. Ne4) of all
  *     moves, in score order.
  */
@@ -584,7 +601,7 @@ var find_nonstupid_moves = function(data, margin, invert) {
        var pv_score = undefined;
        for (var move in data['refutation_lines']) {
                var line = data['refutation_lines'][move];
-               var score = compute_score_sort_key(line['score'], line['depth'], invert);
+               var score = compute_score_sort_key(line['score'], line['depth'], invert, false);
                if (move == data['pv'][0]) {
                        pv_score = score;
                }
@@ -643,15 +660,24 @@ var add_pv = function(start_fen, pv, move_num, toplay, scores, start_display_mov
                scores: scores,
                start_display_move_num: start_display_move_num
        });
-       return print_pv(display_lines.length - 1, opt_limit, opt_showlast);
+       var splicepos = null;
+       if (scores !== null && scores.length >= 1 &&
+           scores[scores.length - 1].score !== undefined &&
+           scores[scores.length - 1].score !== null &&
+           (scores[scores.length - 1].score[0] === 'T' ||
+            scores[scores.length - 1].score[0] === 't')) {
+               splicepos = scores[scores.length - 1].score[1];
+       }
+       return print_pv(display_lines.length - 1, splicepos, opt_limit, opt_showlast);
 }
 
 /**
  * @param {number} line_num
+ * @param {?number} splicepos If non-null, where the tablebase-spliced portion of the TB starts.
  * @param {number=} opt_limit If set, show at most this number of moves.
  * @param {boolean=} opt_showlast If limit is set, show the last moves instead of the first ones.
  */
-var print_pv = function(line_num, opt_limit, opt_showlast) {
+var print_pv = function(line_num, splicepos, opt_limit, opt_showlast) {
        var display_line = display_lines[line_num];
        var pv = display_line.pv;
        var move_num = display_line.move_num;
@@ -672,10 +698,14 @@ var print_pv = function(line_num, opt_limit, opt_showlast) {
                        --to_add;
                }
                move_num += to_add / 2;
+               if (splicepos !== null && splicepos > 0) {
+                       --splicepos;
+               }
        }
 
        var ret = '';
        var i = 0;
+       var in_tb = false;
        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
@@ -688,7 +718,12 @@ var print_pv = function(line_num, opt_limit, opt_showlast) {
                }
                move_num += i / 2;
        } else if (toplay == 'B' && pv.length > 0) {
-               var move = "<a class=\"move\" id=\"automove" + line_num + "-0\" href=\"javascript:show_line(" + line_num + ", " + 0 + ");\">" + pv[0] + "</a>";
+               var move = "";
+               if (splicepos === 0) {
+                       move += "(TB: ";
+                       in_tb = true;
+               }
+               move += "<a class=\"move\" id=\"automove" + line_num + "-0\" href=\"javascript:show_line(" + line_num + ", " + 0 + ");\">" + pv[0] + "</a>";
                ret = move_num + '. … ' + move;
                toplay = 'W';
                ++i;
@@ -696,9 +731,16 @@ var print_pv = function(line_num, opt_limit, opt_showlast) {
        }
        for ( ; i < pv.length; ++i) {
                var move = "<a class=\"move\" id=\"automove" + line_num + "-" + i + "\" href=\"javascript:show_line(" + line_num + ", " + i + ");\">" + pv[i] + "</a>";
+               if (splicepos === i) {
+                       ret += " (TB: ";
+                       in_tb = true;
+               }
 
                if (toplay == 'W') {
                        if (i > opt_limit && !opt_showlast) {
+                               if (in_tb) {
+                                       ret += ")";
+                               }
                                return ret + ' (…)';
                        }
                        if (ret != '') {
@@ -712,6 +754,9 @@ var print_pv = function(line_num, opt_limit, opt_showlast) {
                        toplay = 'W';
                }
        }
+       if (in_tb) {
+               ret += ")";
+       }
        return ret;
 }
 
@@ -731,11 +776,11 @@ var update_history = function() {
        if (display_lines[0] === null || display_lines[0].pv.length == 0) {
                $("#history").html("No history");
        } else if (truncate_display_history) {
-               $("#history").html(print_pv(0, 8, true));
+               $("#history").html(print_pv(0, null, 8, true));
        } else {
                $("#history").html(
                        '(<a class="move" href="javascript:collapse_history(true)">collapse</a>) ' +
-                       print_pv(0));
+                       print_pv(0, null));
        }
 }
 
@@ -763,6 +808,10 @@ var update_refutation_lines = function() {
        var tbl = $("#refutationlines");
        tbl.empty();
 
+       if (display_lines.length < 2) {
+               return;
+       }
+
        // Find out where the lines start from.
        var base_line = [];
        var base_scores = display_lines[1].scores;
@@ -779,11 +828,10 @@ var update_refutation_lines = function() {
        }
 
        var invert = (toplay === 'B');
-       if (current_display_line && current_display_move % 2 == 0) {
+       if (current_display_line && current_display_move % 2 == 0 && !current_display_line_is_history) {
                invert = !invert;
        }
-       var compare = sort_refutation_lines_by_score ? compare_by_score : compare_by_name;
-       moves = moves.sort(function(a, b) { return compare(refutation_lines, invert, a, b) });
+       moves = moves.sort(function(a, b) { return compare_by_score(refutation_lines, invert, a, b) });
        for (var i = 0; i < moves.length; ++i) {
                var line = refutation_lines[moves[i]];
 
@@ -844,15 +892,6 @@ var update_refutation_lines = function() {
                tbl.append(tr);
        }
 
-       // Make one of the links clickable and the other nonclickable.
-       if (sort_refutation_lines_by_score) {
-               $("#sortbyscore0").html("<a href=\"javascript:resort_refutation_lines(false)\">Move</a>");
-               $("#sortbyscore1").html("<strong>Score</strong>");
-       } else {
-               $("#sortbyscore0").html("<strong>Move</strong>");
-               $("#sortbyscore1").html("<a href=\"javascript:resort_refutation_lines(true)\">Score</a>");
-       }
-
        // Update the move highlight, as we've rewritten all the HTML.
        update_move_highlight();
 }
@@ -867,7 +906,7 @@ var update_refutation_lines = function() {
  */
 var chess_from = function(fen, moves, last_move) {
        var hiddenboard = new Chess();
-       if (fen !== null) {
+       if (fen !== null && fen !== undefined) {
                hiddenboard.load(fen);
        }
        for (var i = 0; i <= last_move; ++i) {
@@ -933,6 +972,18 @@ var update_game_list = function(games) {
  * and switch to it if we're not already displaying it.
  */
 var possibly_switch_game_from_hash = function() {
+       var history_match = window.location.hash.match(/^#history=([a-zA-Z0-9_-]+)/);
+       if (history_match !== null) {
+               var game_id = history_match[1];
+               var fake_game = {
+                       url: '/history/' + game_id + '.json',
+                       hashurl: '',
+                       id: 'history=' + game_id
+               };
+               switch_backend(fake_game);
+               return;
+       }
+
        if (current_games === null) {
                return;
        }
@@ -941,13 +992,39 @@ var possibly_switch_game_from_hash = function() {
        for (var i = 0; i < current_games.length; ++i) {
                if (current_games[i]['id'] === hash) {
                        if (backend_url !== current_games[i]['url']) {
-                               switch_backend(current_games[i]['url'], current_games[i]['hashurl']);
+                               switch_backend(current_games[i]);
                        }
                        return;
                }
        }
 }
 
+/**
+ * If this is a Chess960 castling which doesn't move the king,
+ * move the rook instead.
+*/
+var patch_move = function(move) {
+       if (move === null) return null;
+       if (move.from !== move.to) return move;
+
+       var f = move.rook_sq & 15;
+       var r = move.rook_sq >> 4;
+       var from = ('abcdefgh'.substring(f,f+1) + '87654321'.substring(r,r+1));
+       var to = move.to;
+
+       if (move.to === 'g1') {
+               to = 'f1';
+       } else if (move.to === 'g8') {
+               to = 'f8';
+       } else if (move.to === 'b1') {
+               to = 'c1';
+       } else if (move.to === 'b8') {
+               to = 'c8';
+       }
+
+       return { from: from, to: to };
+}
+
 /** Update all the HTML on the page, based on current global state.
  */
 var update_board = function() {
@@ -960,7 +1037,8 @@ var update_board = function() {
        // unconditionally taken from current_data (we're not interested in
        // historic history).
        if (current_data['position']['history']) {
-               add_pv('start', current_data['position']['history'], 1, 'W', null, 0, 8, true);
+               var start = (current_data['position'] && current_data['position']['start_fen']) ? current_data['position']['start_fen'] : 'start';
+               add_pv(start, current_data['position']['history'], 1, 'W', null, 0, 8, true);
        } else {
                display_lines.push(null);
        }
@@ -1034,15 +1112,27 @@ var update_board = function() {
        if (displayed_analysis_data) {
                // Displaying some non-current position, pick out the last move
                // from the history. This will work even if the fetch failed.
-               last_move = format_halfmove_with_number(
-                       current_display_line.pv[current_display_move],
-                       current_display_move + 1);
-               headline += ' after ' + last_move;
+               if (current_display_move !== -1) {
+                       last_move = format_halfmove_with_number(
+                               current_display_line.pv[current_display_move],
+                               current_display_move);
+                       headline += ' after ' + last_move;
+               }
        } else if (data['position']['last_move'] !== 'none') {
+               // Find the previous move.
+               var previous_move_num, previous_toplay;
+               if (data['position']['toplay'] == 'B') {
+                       previous_move_num = data['position']['move_num'];
+                       previous_toplay = 'W';
+               } else {
+                       previous_move_num = data['position']['move_num'] - 1;
+                       previous_toplay = 'B';
+               }
+
                last_move = format_move_with_number(
                        data['position']['last_move'],
-                       data['position']['move_num'],
-                       data['position']['toplay'] == 'W');
+                       previous_move_num,
+                       previous_toplay == 'W');
                headline += ' after ' + last_move;
        } else {
                last_move = null;
@@ -1070,13 +1160,14 @@ var update_board = function() {
        if (data['position'] && data['position']['last_move_uci']) {
                highlight_from = data['position']['last_move_uci'].substr(0, 2);
                highlight_to = data['position']['last_move_uci'].substr(2, 2);
-       } else if (current_display_line_is_history && current_display_move >= 0) {
+       } else if (current_display_line_is_history && current_display_line && current_display_move >= 0) {
                // We don't have historic analysis for this position, but we
                // can reconstruct what the last move was by just replaying
                // from the start.
-               var hiddenboard = chess_from(null, current_display_line.pv, current_display_move);
+               var position = (data['position'] && data['position']['start_fen']) ? data['position']['start_fen'] : null;
+               var hiddenboard = chess_from(position, current_display_line.pv, current_display_move);
                var moves = hiddenboard.history({ verbose: true });
-               var last_move = moves.pop();
+               last_move = moves.pop();
                highlight_from = last_move.from;
                highlight_to = last_move.to;
        } else {
@@ -1163,21 +1254,24 @@ var update_board = function() {
                // draw a continuation arrow as long as it's the same piece
                var last_to;
                for (var i = 0; i < data['pv'].length; i += 2) {
-                       var move = hiddenboard.move(data['pv'][i]);
+                       var move = patch_move(hiddenboard.move(data['pv'][i]));
+
                        if ((i >= 2 && move.from != last_to) ||
                             interfering_arrow(move.from, move.to)) {
                                break;
                        }
                        create_arrow(move.from, move.to, '#f66', 6, 20);
-                       last_to = move.from;
+                       last_to = move.to;
                        hiddenboard.move(data['pv'][i + 1]);  // To keep continuity.
                }
 
                var alt_moves = find_nonstupid_moves(data, 30, data['position']['toplay'] === 'B');
                for (var i = 1; i < alt_moves.length && i < 3; ++i) {
                        hiddenboard = new Chess(base_fen);
-                       var move = hiddenboard.move(alt_moves[i]);
-                       create_arrow(move.from, move.to, '#f66', 1, 10);
+                       var move = patch_move(hiddenboard.move(alt_moves[i]));
+                       if (move !== null) {
+                               create_arrow(move.from, move.to, '#f66', 1, 10);
+                       }
                }
        }
 
@@ -1198,15 +1292,20 @@ var update_board = function() {
                        if (!data['refutation_lines'] ||
                            !data['refutation_lines'][nonstupid_moves[i]] ||
                            !data['refutation_lines'][nonstupid_moves[i]]['pv'] ||
-                           data['refutation_lines'][nonstupid_moves[i]]['pv'].length < 1) {
+                           data['refutation_lines'][nonstupid_moves[i]]['pv'].length < 2) {
                                // Incomplete PV, abort.
                                response = undefined;
                                break;
                        }
                        var line = data['refutation_lines'][nonstupid_moves[i]];
-                       var hiddenboard = new Chess(base_fen);
+                       hiddenboard = new Chess(base_fen);
                        hiddenboard.move(line['pv'][0]);
                        var this_response = hiddenboard.move(line['pv'][1]);
+                       if (this_response === null) {
+                               console.log("BUG: ", i);
+                               console.log(data);
+                               console.log(line['pv']);
+                       }
                        if (response.from !== this_response.from || response.to !== this_response.to) {
                                // Different response depending on lines, abort.
                                response = undefined;
@@ -1273,13 +1372,24 @@ var update_sparkline = function(data) {
                        }
                        // FIXME: at some widths, calling sparkline() seems to push
                        // #scorecontainer under the board.
+                       $('#scorespark').unbind('sparklineClick');
                        $("#scorespark").sparkline(scores, {
                                type: 'bar',
                                zeroColor: 'gray',
                                chartRangeMin: min_score,
                                chartRangeMax: max_score,
                                tooltipFormatter: function(sparkline, options, fields) {
-                                       return format_tooltip(data, fields[0].offset + first_move_num);
+                                       // score_history contains the Nth _position_, but format_tooltip
+                                       // wants to format the Nth _move_; thus the -1.
+                                       return format_tooltip(data, fields[0].offset + first_move_num - 1);
+                               }
+                       });
+                       $('#scorespark').unbind('sparklineClick');
+                       $('#scorespark').bind('sparklineClick', function(event) {
+                               var sparkline = event.sparklines[0];
+                               var region = sparkline.getCurrentRegionFields();
+                               if (region[0].offset !== undefined) {
+                                       show_line(0, first_move_num + region[0].offset - 1);
                                }
                        });
                } else {
@@ -1294,13 +1404,22 @@ var update_sparkline = function(data) {
  * @param {number} num_viewers
  */
 var update_num_viewers = function(num_viewers) {
+       var text = "";
        if (num_viewers === null) {
-               $("#numviewers").text("");
+               text = "";
        } else if (num_viewers == 1) {
-               $("#numviewers").text("You are the only current viewer");
+               text = "You are the only current viewer";
        } else {
-               $("#numviewers").text(num_viewers + " current viewers");
+               text = num_viewers + " current viewers";
        }
+       if (display_fen !== null) {
+               var counter = Math.floor(display_fen.split(" ")[4] / 2);
+               if (counter >= 20) {
+                       text = text.replace("current ", "");
+                       text += " | 50-move rule: " + counter;
+               }
+       }
+       $("#numviewers").text(text);
 }
 
 var update_clock = function() {
@@ -1336,7 +1455,6 @@ var update_clock = function() {
 
        var white_clock_ms = null;
        var black_clock_ms = null;
-       var show_seconds = false;
 
        // Static clocks.
        if (data['position'] &&
@@ -1377,8 +1495,9 @@ var update_clock = function() {
                return;
        }
 
-       // If either player has ten minutes or less left, add the second counters.
-       var show_seconds = (white_clock_ms < 60 * 10 * 1000 || black_clock_ms < 60 * 10 * 1000);
+       // If either player has twenty minutes or less left, add the second counters.
+       // This matches what DGT clocks do.
+       var show_seconds = (white_clock_ms < 60 * 20 * 1000 || black_clock_ms < 60 * 20 * 1000);
 
        if (color) {
                // See when the clock will change next, and update right after that.
@@ -1434,15 +1553,15 @@ var format_2d = function(x) {
 
 /**
  * @param {string} move
- * @param {Number} move_num
- * @param {boolean} white_to_play
+ * @param {Number} move_num Move number of this move.
+ * @param {boolean} white_to_play Whether white is to play this move.
  */
 var format_move_with_number = function(move, move_num, white_to_play) {
        var ret;
        if (white_to_play) {
-               ret = (move_num - 1) + '… ';
-       } else {
                ret = move_num + '. ';
+       } else {
+               ret = move_num + '… ';
        }
        ret += move;
        return ret;
@@ -1450,7 +1569,8 @@ var format_move_with_number = function(move, move_num, white_to_play) {
 
 /**
  * @param {string} move
- * @param {Number} halfmove_num
+ * @param {Number} halfmove_num Half-move number that is to be played,
+ *   starting from 0.
  */
 var format_halfmove_with_number = function(move, halfmove_num) {
        return format_move_with_number(
@@ -1464,42 +1584,39 @@ var format_halfmove_with_number = function(move, halfmove_num) {
  * @param {Number} halfmove_num
  */
 var format_tooltip = function(data, halfmove_num) {
-       if (data['score_history'][halfmove_num] ||
-           halfmove_num === data['position']['history'].length) {
+       if (data['score_history'][halfmove_num + 1] ||
+           (halfmove_num + 1) === data['position']['history'].length) {
+               // Position is in the history, or it is the current position
+               // (which is implicitly tacked onto the history).
                var move;
                var short_score;
-               if (halfmove_num === data['position']['history'].length) {
+               if ((halfmove_num + 1) === data['position']['history'].length) {
                        move = data['position']['last_move'];
                        short_score = format_short_score(data['score']);
                } else {
                        move = data['position']['history'][halfmove_num];
-                       short_score = format_short_score(data['score_history'][halfmove_num]);
+                       short_score = format_short_score(data['score_history'][halfmove_num + 1]);
+               }
+               if (halfmove_num === -1) {
+                       return "Start position: " + short_score;
+               } else {
+                       var move_with_number = format_halfmove_with_number(move, halfmove_num);
+                       return "After " + move_with_number + ": " + short_score;
                }
-               var move_with_number = format_halfmove_with_number(move, halfmove_num);
-
-               return "After " + move_with_number + ": " + short_score;
        } else {
-               for (var i = halfmove_num; i --> 0; ) {
+               for (var i = halfmove_num; i --> -1; ) {
                        if (data['score_history'][i]) {
                                var move = data['position']['history'][i];
-                               return "[Analysis kept from " + format_halfmove_with_number(move, i) + "]";
+                               if (i === -1) {
+                                       return "[Analysis kept from start position]";
+                               } else {
+                                       return "[Analysis kept from " + format_halfmove_with_number(move, i) + "]";
+                               }
                        }
                }
        }
 }
 
-/**
- * @param {boolean} sort_by_score
- */
-var resort_refutation_lines = function(sort_by_score) {
-       sort_refutation_lines_by_score = sort_by_score;
-       if (supports_html5_storage()) {
-               localStorage['sort_refutation_lines_by_score'] = sort_by_score ? 1 : 0;
-       }
-       update_refutation_lines();
-}
-window['resort_refutation_lines'] = resort_refutation_lines;
-
 /**
  * @param {boolean} truncate_history
  */
@@ -1571,7 +1688,7 @@ var next_game = function() {
                var game = current_games[game_num];
                if (game['url'] === backend_url) {
                        var next_game_num = (game_num + 1) % current_games.length;
-                       switch_backend(current_games[next_game_num]['url'], current_games[next_game_num]['hashurl']);
+                       switch_backend(current_games[next_game_num]);
                        return;
                }
        }
@@ -1589,7 +1706,7 @@ var update_historic_analysis = function() {
        }
 
        // Fetch old analysis for this line if it exists.
-       var hiddenboard = chess_from(null, current_display_line.pv, current_display_move);
+       var hiddenboard = chess_from(current_display_line.start_fen, current_display_line.pv, current_display_move);
        var filename = "/history/move" + (current_display_move + 1) + "-" +
                hiddenboard.fen().replace(/ /g, '_').replace(/\//g, '-') + ".json";
 
@@ -1659,7 +1776,7 @@ var update_move_highlight = function() {
                        $("#pvtitle").text("Exploring:");
                        current_display_line.start_display_move_num = 0;
                        display_lines.push(current_display_line);
-                       $("#pv").html(print_pv(display_lines.length - 1));
+                       $("#pv").html(print_pv(display_lines.length - 1, null));  // FIXME
                        display_line_num = display_lines.length - 1;
 
                        // Clear out the PV, so it's not selected by anything later.
@@ -1974,6 +2091,10 @@ var onSnapEnd = function(source, target) {
        // this move, then select that. Note that this gives us a good priority
        // order (history first, then PV, then multi-PV lines).
        for (var i = 0; i < display_lines.length; ++i) {
+               if (i == 1 && current_display_line) {
+                       // Do not choose PV if not on it.
+                       continue;
+               }
                var line = display_lines[i];
                if (line.pv[line.start_display_move_num] === move.san) {
                        show_line(i, 0);
@@ -2001,14 +2122,25 @@ var format_short_score = function(score) {
        if (!score) {
                return "???";
        }
-       if (score[0] === 'm') {
+       if (score[0] === 'T' || score[0] === 't') {
+               var ret = "TB\u00a0";
+               if (score[2]) {  // Is a bound.
+                       ret = score[2] + "\u00a0TB\u00a0";
+               }
+               if (score[0] === 'T') {
+                       return ret + Math.ceil(score[1] / 2);
+               } else {
+                       return ret + "-" + Math.ceil(score[1] / 2);
+               }
+       } else if (score[0] === 'M' || score[0] === 'm') {
+               var sign = (score[0] === 'm') ? '-' : '';
                if (score[2]) {  // Is a bound.
-                       return score[2] + "\u00a0M " + score[1];
+                       return score[2] + "\u00a0M " + sign + score[1];
                } else {
-                       return "M " + score[1];
+                       return "M " + sign + score[1];
                }
        } else if (score[0] === 'd') {
-               return "TB draw";
+               return "TB =0";
        } else if (score[0] === 'cp') {
                if (score[2]) {  // Is a bound.
                        return score[2] + "\u00a0" + fmt_cp(score[1]);
@@ -2023,11 +2155,29 @@ var format_long_score = function(score) {
        if (!score) {
                return "???";
        }
-       if (score[0] === 'm') {
-               if (score[1] > 0) {
+       if (score[0] === 'T') {
+               if (score[1] == 0) {
+                       return "Won for white (tablebase)";
+               } else {
+                       return "White wins in " + Math.ceil(score[1] / 2);
+               }
+       } else if (score[0] === 't') {
+               if (score[1] == -1) {
+                       return "Won for black (tablebase)";
+               } else {
+                       return "Black wins in " + Math.ceil(score[1] / 2);
+               }
+       } else if (score[0] === 'M') {
+               if (score[1] == 0) {
+                       return "White wins by checkmate";
+               } else {
                        return "White mates in " + score[1];
+               }
+       } else if (score[0] === 'm') {
+               if (score[1] == 0) {
+                       return "Black wins by checkmate";
                } else {
-                       return "Black mates in " + (-score[1]);
+                       return "Black mates in " + score[1];
                }
        } else if (score[0] === 'd') {
                return "Theoretical draw";
@@ -2038,12 +2188,10 @@ var format_long_score = function(score) {
 }
 
 var compute_plot_score = function(score) {
-       if (score[0] === 'm') {
-               if (score[1] > 0) {
-                       return 500;
-               } else {
-                       return -500;
-               }
+       if (score[0] === 'M' || score[0] === 'T') {
+               return 500;
+       } else if (score[0] === 'm' || score[0] === 't') {
+               return -500;
        } else if (score[0] === 'd') {
                return 0;
        } else if (score[0] === 'cp') {
@@ -2062,21 +2210,26 @@ var compute_plot_score = function(score) {
  * @param score The score digest tuple.
  * @param {?number} depth Depth the move has been computed to, or null.
  * @param {boolean} invert Whether black is to play.
+ * @param {boolean=} depth_secondary_key
  * @return {number}
  */
-var compute_score_sort_key = function(score, depth, invert) {
+var compute_score_sort_key = function(score, depth, invert, depth_secondary_key) {
        var s;
        if (!score) {
                return -10000000;
        }
-       if (score[0] === 'm') {
-               if (score[1] > 0) {
-                       // White mates.
-                       s = 99999 - score[1];
-               } else {
-                       // Black mates (note the double negative for score[1]).
-                       s = -99999 - score[1];
-               }
+       if (score[0] === 'T') {
+               // White reaches TB win.
+               s = 89999 - score[1];
+       } else if (score[0] === 't') {
+               // Black reaches TB win.
+               s = -(89999 - score[1]);
+       } else if (score[0] === 'M') {
+               // White mates.
+               s = 99999 - score[1];
+       } else if (score[0] === 'm') {
+               // Black mates.
+               s = -(99999 - score[1]);
        } else if (score[0] === 'd') {
                s = 0;
        } else if (score[0] === 'cp') {
@@ -2084,16 +2237,20 @@ var compute_score_sort_key = function(score, depth, invert) {
        }
        if (s) {
                if (invert) s = -s;
-               return s * 200 + (depth || 0);
+               if (depth_secondary_key) {
+                       return s * 200 + (depth || 0);
+               } else {
+                       return s;
+               }
        } else {
                return null;
        }
 }
 
 /**
- * @param {string} new_backend_url
+ * @param {Object} game
  */
-var switch_backend = function(new_backend_url, new_backend_hash_url) {
+var switch_backend = function(game) {
        // Stop looking at historic data.
        current_display_line = null;
        current_display_move = null;
@@ -2122,14 +2279,17 @@ var switch_backend = function(new_backend_url, new_backend_hash_url) {
        }
 
        // Request an immediate fetch with the new backend.
-       backend_url = new_backend_url;
-       backend_hash_url = new_backend_hash_url;
+       backend_url = game['url'];
+       backend_hash_url = game['hashurl'];
+       window.location.hash = '#' + game['id'];
        current_analysis_data = null;
        ims = 0;
        request_update();
 }
 window['switch_backend'] = switch_backend;
 
+window['flip'] = function() { board.flip(); redraw_arrows(); };
+
 var init = function() {
        unique = get_unique();
 
@@ -2139,11 +2299,6 @@ var init = function() {
        } else {
                set_sound(false);
        }
-       if (supports_html5_storage() && localStorage['sort_refutation_lines_by_score']) {
-               sort_refutation_lines_by_score = parseInt(localStorage['sort_refutation_lines_by_score']);
-       } else {
-               sort_refutation_lines_by_score = true;
-       }
 
        // Create board.
        board = new window.ChessBoard('board', {
@@ -2172,13 +2327,14 @@ var init = function() {
                } else if (event.which >= 49 && event.which <= 57) {  // 1-9.
                        var num = event.which - 49;
                        if (current_games && current_games.length >= num) {
-                               switch_backend(current_games[num]['url'], current_games[num]['hashurl']);
+                               switch_backend(current_games[num]);
                        }
                } else if (event.which == 78) {  // N.
                        next_game();
                }
        });
        window.addEventListener('hashchange', possibly_switch_game_from_hash, false);
+       possibly_switch_game_from_hash();
 };
 $(document).ready(init);