4 <meta charset="utf-8" />
5 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
6 <title>analysis.sesse.net</title>
8 <link rel="stylesheet" href="css/chessboard-0.3.0.min.css" />
11 font-family: sans-serif;
15 width: 0px; height:0px;
18 .c1 { opacity: 0.75; }
19 .l1arrow { opacity: 1.0; }
20 .hidden { display: none; }
21 .vir path { opacity: 0.0; }
22 .vir path.l1arrow { opacity: 1.0; }
23 #credits { font-size: smaller; }
24 td { vertical-align: top; }
26 #refutationlines { font-size: smaller; }
27 #refutationlines .move { width: 3.5em; }
28 #refutationlines .score { width: 3em; text-align: right; padding-right: 0.5em; }
29 #refutationlines .depth { width: 2em; text-align: right; padding-right: 0.7em; }
30 /* .white-1e1d7 { background-color: #f0d9b5; color: #b58863; }
31 /* .black-3c85d { background-color: #b58863; color: #f0d9b5; } */
32 /* .white-1e1d7.nonuglyhighlight { background-color: #f3e1a4; }
33 .black-3c85d.nonuglyhighlight { background-color: #c7a859; } */
34 .white-1e1d7.nonuglyhighlight { background-color: #cce5cf; }
35 .black-3c85d.nonuglyhighlight { background-color: #9ab6a6; }
39 <h1 id="headline">Analysis</h1>
43 <div id="board" style="width: 400px"></div>
46 <p id="score">Score:</p>
47 <p><strong>PV:</strong> <span id="pv"></span></p>
48 <p id="searchstats"></p>
49 <h3 style="margin-top: 1em; margin-bottom: 0;">Shallow search of all legal moves (multi-PV)</h3>
50 <table id="refutationlines">
55 <h2>Symbol explanation</h2>
57 <li><strong>Score:</strong> 1.00 is the value of one pawn (in the opening). Positive values are better for white.</li>
58 <li><strong>PV:</strong> Principal Variation, the series of moves the engine thinks is the best.</li>
59 <li><strong>Thick red line:</strong> Marks the best move (in the view of the engine). Multiple chained arrows
60 means that the PV starts with multiple successive moves with the same piece, ie., the engine thinks
61 that the piece will execute a maneuver.</li>
62 <li><strong>Thin red lines:</strong> Other good moves, maximum two. Note that even though these are also
63 quality checked, these are less thoroughly analyzed by the engine,
64 and should be taken with a grain of salt.</li>
65 <li><strong>Thick blue line:</strong> Marks the best <em>response</em> move. Note that this is only rarely shown,
66 since usually, the best response move depends on what the first move is. A typical case is when the current move
67 is forced or nearly so.</li>
69 <p id="credits"><a href="http://git.sesse.net/?p=remoteglot;a=summary">remoteglot</a>
70 © 2007-2013 <a href="http://www.sesse.net/">Steinar H. Gunderson</a>.
71 Chess analysis by <a href="http://stockfishchess.org/">Stockfish</a> (main analysis: 12x2.3GHz Sandy Bridge,
72 multi-PV search: 8x2.27GHz Nehalem).
73 Moves provided by <a href="http://www.freechess.org/">FICS</a>.
74 Hosting and main analysis hardware by <a href="http://www.samfundet.no/">Studentersamfundet i Trondhjem</a>.
75 JavaScript chessboard powered by <a href="http://chessboardjs.com/">chessboard.js</a>.
76 Arrows by <a href="http://jsplumbtoolkit.com">jsPlumb</a>—who would think you could
77 draw such beautiful arrows in just 161 kB of JavaScript on top of the
78 323 kB needed for jQuery and jQuery UI? How far technology has come. If you want something
79 more retro, the <a href="/text.pl">text interface</a> is still available.</p>
81 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
82 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"></script>
83 <script type="text/javascript" src="js/jquery.jsPlumb-1.5.3-min.js"></script>
84 <script type="text/javascript" src="js/chessboard-0.3.0.min.js"></script>
87 var arrow_targets = [];
88 var occupied_by_arrows = [];
90 var request_update = function(board, first) {
92 //url: "http://analysis.sesse.net/analysis.pl?first=" + first
93 url: "http://analysis.sesse.net:5000/analysis.pl?first=" + first
94 }).done(function(data) {
95 update_board(board, data);
99 var clear_arrows = function() {
100 for (var i = 0; i < arrows.length; ++i) {
101 jsPlumb.detach(arrows[i]);
105 for (var i = 0; i < arrow_targets.length; ++i) {
106 document.body.removeChild(arrow_targets[i]);
110 occupied_by_arrows = [];
111 for (var y = 0; y < 8; ++y) {
112 occupied_by_arrows.push([false, false, false, false, false, false, false, false]);
116 var sign = function(x) {
126 // See if drawing this arrow on the board would cause unduly amount of confusion.
127 var interfering_arrow = function(from, to) {
128 var from_col = from.charCodeAt(0) - "a1".charCodeAt(0);
129 var from_row = from.charCodeAt(1) - "a1".charCodeAt(1);
130 var to_col = to.charCodeAt(0) - "a1".charCodeAt(0);
131 var to_row = to.charCodeAt(1) - "a1".charCodeAt(1);
133 occupied_by_arrows[from_row][from_col] = true;
135 // Knight move: Just check that we haven't been at the destination before.
136 if ((Math.abs(to_col - from_col) == 2 && Math.abs(to_row - from_row) == 1) ||
137 (Math.abs(to_col - from_col) == 1 && Math.abs(to_row - from_row) == 2)) {
138 return occupied_by_arrows[to_row][to_col];
141 // Sliding piece: Check if anything except the from-square is seen before.
142 var dx = sign(to_col - from_col);
143 var dy = sign(to_row - from_row);
149 if (occupied_by_arrows[y][x]) {
152 occupied_by_arrows[y][x] = true;
153 } while (x != to_col || y != to_row);
158 var add_target = function() {
159 var elem = document.createElement("div");
160 $(elem).addClass("window");
161 elem.id = "target" + arrow_targets.length;
162 document.body.appendChild(elem);
163 arrow_targets.push(elem);
167 var create_arrow = function(from_square, to_square, fg_color, line_width, arrow_size) {
168 var from_col = from_square.charCodeAt(0) - "a1".charCodeAt(0);
169 var from_row = from_square.charCodeAt(1) - "a1".charCodeAt(1);
170 var to_col = to_square.charCodeAt(0) - "a1".charCodeAt(0);
171 var to_row = to_square.charCodeAt(1) - "a1".charCodeAt(1);
173 var from_y = (7 - from_row)*49 + 25;
174 var to_y = (7 - to_row)*49 + 25;
175 var from_x = from_col*49 + 25;
176 var to_x = to_col*49 + 25;
178 var dx = to_x - from_x;
179 var dy = to_y - from_y;
180 var len = Math.sqrt(dx * dx + dy * dy);
185 var s1 = add_target();
186 var d1 = add_target();
187 var s1v = add_target();
188 var d1v = add_target();
189 var pos = $("#board").position();
190 $("#" + s1).css({ top: pos.top + from_y + (0.5 * arrow_size) * dy, left: pos.left + from_x + (0.5 * arrow_size) * dx });
191 $("#" + d1).css({ top: pos.top + to_y - (0.5 * arrow_size) * dy, left: pos.left + to_x - (0.5 * arrow_size) * dx });
192 $("#" + s1v).css({ top: pos.top + from_y - 0 * dy, left: pos.left + from_x - 0 * dx });
193 $("#" + d1v).css({ top: pos.top + to_y + 0 * dy, left: pos.left + to_x + 0 * dx });
194 var connection1 = jsPlumb.connect({
197 connector:["Straight"],
200 endpointClass:"c1Endpoint",
203 lineWidth:line_width,
204 strokeStyle:fg_color,
210 var connection2 = jsPlumb.connect({
213 connector:["Straight"],
216 endpointClass:"c1Endpoint",
220 strokeStyle:fg_color,
228 width: arrow_size, length: arrow_size,
230 lineWidth:line_width,
236 arrows.push(connection1);
237 arrows.push(connection2);
240 // Fake multi-PV using the refutation lines. Find all “relevant” moves,
241 // sorted by quality, descending.
242 var find_nonstupid_moves = function(data, margin) {
243 // First of all, if there are any moves that are more than 0.5 ahead of
244 // the primary move, the refutation lines are probably bunk, so just
246 var best_score = undefined;
247 var pv_score = undefined;
248 for (var move in data.refutation_lines) {
249 var score = data.refutation_lines[move].score_sort_key;
250 if (move == data.pv_uci[0]) {
253 if (best_score === undefined || score > best_score) {
256 if (!(data.refutation_lines[move].depth >= 8)) {
261 if (best_score - pv_score > 50) {
265 // Now find all moves that are within “margin” of the best score.
266 // The PV move will always be first.
268 for (var move in data.refutation_lines) {
269 var score = data.refutation_lines[move].score_sort_key;
270 if (move != data.pv_uci[0] && best_score - score <= margin) {
274 moves = moves.sort(function(a, b) { return data.refutation_lines[b].score_sort_key - data.refutation_lines[a].score_sort_key; });
275 moves.unshift(data.pv_uci[0]);
280 var thousands = function(x) {
281 return String(x).split('').reverse().join('').replace(/(\d{3}\B)/g, '$1,').split('').reverse().join('');
284 var print_pv = function(pretty_pv, move_num, toplay, limit) {
288 pv = move_num + '. … ' + pretty_pv[0];
293 for ( ; i < pretty_pv.length; ++i) {
301 pv += move_num + '. ' + pretty_pv[i];
305 pv += ' ' + pretty_pv[i];
312 var compare_by_sort_key = function(data, a, b) {
313 var ska = data.refutation_lines[a].sort_key;
314 var skb = data.refutation_lines[b].sort_key;
315 if (ska < skb) return -1;
316 if (ska > skb) return 1;
320 var update_board = function(board, data) {
322 var headline = 'Analysis';
323 if (data.position.last_move !== 'none') {
324 headline += ' after ' + data.position.move_num + '. ';
325 if (data.position.toplay == 'W') {
328 headline += data.position.last_move;
335 headline += ' by ' + data.id.name; // + ':';
339 $("#headline").text(headline);
342 if (data.score !== null) {
343 $("#score").text(data.score);
347 if (data.nodes && data.nps && data.depth) {
348 var stats = thousands(data.nodes) + ' nodes, ' + thousands(data.nps) + ' nodes/sec, depth ' + data.depth + ' ply';
350 stats += ' (' + data.seldepth + ' selective)';
352 if (data.tbhits && data.tbhits > 0) {
353 if (data.tbhits == 1) {
354 stats += ', one Nalimov hit';
356 stats += ', ' + data.tbhits + ' Nalimov hits';
361 $("#searchstats").text(stats);
364 // Update the board itself.
365 board.position(data.position.fen);
367 $("#board").find('.square-55d63').removeClass('nonuglyhighlight');
368 if (data.position.last_move_uci) {
369 var from = data.position.last_move_uci.substr(0, 2);
370 var to = data.position.last_move_uci.substr(2, 4);
371 $("#board").find('.square-' + from).addClass('nonuglyhighlight');
372 $("#board").find('.square-' + to).addClass('nonuglyhighlight');
376 var pv = print_pv(data.pv_pretty, data.position.move_num, data.position.toplay);
379 // Update the PV arrow.
381 if (data.pv_uci.length >= 1) {
382 // draw a continuation arrow as long as it's the same piece
383 for (var i = 0; i < data.pv_uci.length; i += 2) {
384 var from = data.pv_uci[i].substr(0, 2);
385 var to = data.pv_uci[i].substr(2,4);
386 if ((i >= 2 && from != data.pv_uci[i - 2].substr(2, 4)) ||
387 interfering_arrow(from, to)) {
390 create_arrow(from, to, '#f66', 6, 20);
393 var alt_moves = find_nonstupid_moves(data, 30);
394 for (var i = 1; i < alt_moves.length && i < 3; ++i) {
395 create_arrow(alt_moves[i].substr(0, 2),
396 alt_moves[i].substr(2, 4), '#f66', 1, 10);
400 // See if all semi-reasonable moves have only one possible response.
401 if (data.pv_uci.length >= 2) {
402 var nonstupid_moves = find_nonstupid_moves(data, 300);
403 var response = data.pv_uci[1];
404 for (var i = 0; i < nonstupid_moves.length; ++i) {
405 if (nonstupid_moves[i] == data.pv_uci[0]) {
406 // ignore the PV move for refutation lines.
409 if (!data.refutation_lines ||
410 !data.refutation_lines[nonstupid_moves[i]] ||
411 !data.refutation_lines[nonstupid_moves[i]].pv_uci ||
412 data.refutation_lines[nonstupid_moves[i]].pv_uci.length < 1) {
413 // Incomplete PV, abort.
414 response = undefined;
417 var this_response = data.refutation_lines[nonstupid_moves[i]].pv_uci[1];
418 if (response !== this_response) {
419 // Different response depending on lines, abort.
420 response = undefined;
425 if (nonstupid_moves.length > 0 && response !== undefined) {
426 create_arrow(response.substr(0, 2),
427 response.substr(2, 4), '#66f', 6, 20);
431 // Show the refutation lines.
432 var tbl = $("#refutationlines");
436 for (var move in data.refutation_lines) {
439 moves = moves.sort(function(a, b) { return compare_by_sort_key(data, a, b) });
440 for (var i = 0; i < moves.length; ++i) {
441 var line = data.refutation_lines[moves[i]];
443 var tr = document.createElement("tr");
445 var move_td = document.createElement("td");
446 tr.appendChild(move_td);
447 $(move_td).addClass("move");
448 $(move_td).text(line.pretty_move);
450 var score_td = document.createElement("td");
451 tr.appendChild(score_td);
452 $(score_td).addClass("score");
453 $(score_td).text(line.pretty_score);
455 var depth_td = document.createElement("td");
456 tr.appendChild(depth_td);
457 $(depth_td).addClass("depth");
458 $(depth_td).text("d" + line.depth);
460 var pv_td = document.createElement("td");
461 tr.appendChild(pv_td);
462 $(pv_td).addClass("pv");
463 $(pv_td).text(print_pv(line.pv_pretty, data.position.move_num, data.position.toplay, 10));
469 setTimeout(function() { request_update(board, 0); }, 100);
472 var init = function() {
474 var board = new ChessBoard('board', 'start');
476 request_update(board, 1);
479 $(document).ready(init);