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" />
9 <link rel="stylesheet" href="css/remoteglot.css" />
12 <h1 id="headline">Analysis</h1>
16 <div id="board" style="width: 400px"></div>
19 <p id="score">Score:</p>
20 <p><strong>PV:</strong> <span id="pv"></span></p>
21 <p id="searchstats"></p>
22 <h3 style="margin-top: 1em; margin-bottom: 0;">Shallow search of all legal moves (multi-PV)</h3>
23 <table id="refutationlines">
28 <h2>Symbol explanation</h2>
30 <li><strong>Score:</strong> 1.00 is the value of one pawn (in the opening). Positive values are better for white.</li>
31 <li><strong>PV:</strong> Principal Variation, the series of moves the engine thinks is the best.</li>
32 <li><strong>Thick red line:</strong> Marks the best move (in the view of the engine). Multiple chained arrows
33 means that the PV starts with multiple successive moves with the same piece, ie., the engine thinks
34 that the piece will execute a maneuver.</li>
35 <li><strong>Thin red lines:</strong> Other good moves, maximum two. Note that even though these are also
36 quality checked, these are less thoroughly analyzed by the engine,
37 and should be taken with a grain of salt.</li>
38 <li><strong>Thick blue line:</strong> Marks the best <em>response</em> move. Note that this is only rarely shown,
39 since usually, the best response move depends on what the first move is. A typical case is when the current move
40 is forced or nearly so.</li>
42 <p id="credits"><a href="http://git.sesse.net/?p=remoteglot;a=summary">remoteglot</a>
43 © 2007-2013 <a href="http://www.sesse.net/">Steinar H. Gunderson</a>.
44 Chess analysis by <a href="http://stockfishchess.org/">Stockfish</a> (main analysis: 12x2.3GHz Sandy Bridge,
45 multi-PV search: 8x2.27GHz Nehalem).
46 Moves provided by <a href="http://www.freechess.org/">FICS</a>.
47 Hosting and main analysis hardware by <a href="http://www.samfundet.no/">Studentersamfundet i Trondhjem</a>.
48 JavaScript chessboard powered by <a href="http://chessboardjs.com/">chessboard.js</a>.
49 Arrows by <a href="http://jsplumbtoolkit.com">jsPlumb</a>—who would think you could
50 draw such beautiful arrows in just 161 kB of JavaScript on top of the
51 323 kB needed for jQuery and jQuery UI? How far technology has come. If you want something
52 more retro, the <a href="/text.pl">text interface</a> is still available.</p>
54 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
55 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"></script>
56 <script type="text/javascript" src="js/jquery.jsPlumb-1.5.3-min.js"></script>
57 <script type="text/javascript" src="js/chessboard-0.3.0.min.js"></script>
60 var arrow_targets = [];
61 var occupied_by_arrows = [];
63 var request_update = function(board, first) {
65 //url: "http://analysis.sesse.net/analysis.pl?first=" + first
66 url: "http://analysis.sesse.net:5000/analysis.pl?first=" + first
67 }).done(function(data) {
68 update_board(board, data);
72 var clear_arrows = function() {
73 for (var i = 0; i < arrows.length; ++i) {
74 jsPlumb.detach(arrows[i]);
78 for (var i = 0; i < arrow_targets.length; ++i) {
79 document.body.removeChild(arrow_targets[i]);
83 occupied_by_arrows = [];
84 for (var y = 0; y < 8; ++y) {
85 occupied_by_arrows.push([false, false, false, false, false, false, false, false]);
89 var sign = function(x) {
99 // See if drawing this arrow on the board would cause unduly amount of confusion.
100 var interfering_arrow = function(from, to) {
101 var from_col = from.charCodeAt(0) - "a1".charCodeAt(0);
102 var from_row = from.charCodeAt(1) - "a1".charCodeAt(1);
103 var to_col = to.charCodeAt(0) - "a1".charCodeAt(0);
104 var to_row = to.charCodeAt(1) - "a1".charCodeAt(1);
106 occupied_by_arrows[from_row][from_col] = true;
108 // Knight move: Just check that we haven't been at the destination before.
109 if ((Math.abs(to_col - from_col) == 2 && Math.abs(to_row - from_row) == 1) ||
110 (Math.abs(to_col - from_col) == 1 && Math.abs(to_row - from_row) == 2)) {
111 return occupied_by_arrows[to_row][to_col];
114 // Sliding piece: Check if anything except the from-square is seen before.
115 var dx = sign(to_col - from_col);
116 var dy = sign(to_row - from_row);
122 if (occupied_by_arrows[y][x]) {
125 occupied_by_arrows[y][x] = true;
126 } while (x != to_col || y != to_row);
131 var add_target = function() {
132 var elem = document.createElement("div");
133 $(elem).addClass("window");
134 elem.id = "target" + arrow_targets.length;
135 document.body.appendChild(elem);
136 arrow_targets.push(elem);
140 var create_arrow = function(from_square, to_square, fg_color, line_width, arrow_size) {
141 var from_col = from_square.charCodeAt(0) - "a1".charCodeAt(0);
142 var from_row = from_square.charCodeAt(1) - "a1".charCodeAt(1);
143 var to_col = to_square.charCodeAt(0) - "a1".charCodeAt(0);
144 var to_row = to_square.charCodeAt(1) - "a1".charCodeAt(1);
146 var from_y = (7 - from_row)*49 + 25;
147 var to_y = (7 - to_row)*49 + 25;
148 var from_x = from_col*49 + 25;
149 var to_x = to_col*49 + 25;
151 var dx = to_x - from_x;
152 var dy = to_y - from_y;
153 var len = Math.sqrt(dx * dx + dy * dy);
158 var s1 = add_target();
159 var d1 = add_target();
160 var s1v = add_target();
161 var d1v = add_target();
162 var pos = $("#board").position();
163 $("#" + s1).css({ top: pos.top + from_y + (0.5 * arrow_size) * dy, left: pos.left + from_x + (0.5 * arrow_size) * dx });
164 $("#" + d1).css({ top: pos.top + to_y - (0.5 * arrow_size) * dy, left: pos.left + to_x - (0.5 * arrow_size) * dx });
165 $("#" + s1v).css({ top: pos.top + from_y - 0 * dy, left: pos.left + from_x - 0 * dx });
166 $("#" + d1v).css({ top: pos.top + to_y + 0 * dy, left: pos.left + to_x + 0 * dx });
167 var connection1 = jsPlumb.connect({
170 connector:["Straight"],
173 endpointClass:"c1Endpoint",
176 lineWidth:line_width,
177 strokeStyle:fg_color,
183 var connection2 = jsPlumb.connect({
186 connector:["Straight"],
189 endpointClass:"c1Endpoint",
193 strokeStyle:fg_color,
201 width: arrow_size, length: arrow_size,
203 lineWidth:line_width,
209 arrows.push(connection1);
210 arrows.push(connection2);
213 // Fake multi-PV using the refutation lines. Find all “relevant” moves,
214 // sorted by quality, descending.
215 var find_nonstupid_moves = function(data, margin) {
216 // First of all, if there are any moves that are more than 0.5 ahead of
217 // the primary move, the refutation lines are probably bunk, so just
219 var best_score = undefined;
220 var pv_score = undefined;
221 for (var move in data.refutation_lines) {
222 var score = data.refutation_lines[move].score_sort_key;
223 if (move == data.pv_uci[0]) {
226 if (best_score === undefined || score > best_score) {
229 if (!(data.refutation_lines[move].depth >= 8)) {
234 if (best_score - pv_score > 50) {
238 // Now find all moves that are within “margin” of the best score.
239 // The PV move will always be first.
241 for (var move in data.refutation_lines) {
242 var score = data.refutation_lines[move].score_sort_key;
243 if (move != data.pv_uci[0] && best_score - score <= margin) {
247 moves = moves.sort(function(a, b) { return data.refutation_lines[b].score_sort_key - data.refutation_lines[a].score_sort_key; });
248 moves.unshift(data.pv_uci[0]);
253 var thousands = function(x) {
254 return String(x).split('').reverse().join('').replace(/(\d{3}\B)/g, '$1,').split('').reverse().join('');
257 var print_pv = function(pretty_pv, move_num, toplay, limit) {
261 pv = move_num + '. … ' + pretty_pv[0];
266 for ( ; i < pretty_pv.length; ++i) {
274 pv += move_num + '. ' + pretty_pv[i];
278 pv += ' ' + pretty_pv[i];
285 var compare_by_sort_key = function(data, a, b) {
286 var ska = data.refutation_lines[a].sort_key;
287 var skb = data.refutation_lines[b].sort_key;
288 if (ska < skb) return -1;
289 if (ska > skb) return 1;
293 var update_board = function(board, data) {
295 var headline = 'Analysis';
296 if (data.position.last_move !== 'none') {
297 headline += ' after ' + data.position.move_num + '. ';
298 if (data.position.toplay == 'W') {
301 headline += data.position.last_move;
308 headline += ' by ' + data.id.name; // + ':';
312 $("#headline").text(headline);
315 if (data.score !== null) {
316 $("#score").text(data.score);
320 if (data.nodes && data.nps && data.depth) {
321 var stats = thousands(data.nodes) + ' nodes, ' + thousands(data.nps) + ' nodes/sec, depth ' + data.depth + ' ply';
323 stats += ' (' + data.seldepth + ' selective)';
325 if (data.tbhits && data.tbhits > 0) {
326 if (data.tbhits == 1) {
327 stats += ', one Nalimov hit';
329 stats += ', ' + data.tbhits + ' Nalimov hits';
334 $("#searchstats").text(stats);
337 // Update the board itself.
338 board.position(data.position.fen);
340 $("#board").find('.square-55d63').removeClass('nonuglyhighlight');
341 if (data.position.last_move_uci) {
342 var from = data.position.last_move_uci.substr(0, 2);
343 var to = data.position.last_move_uci.substr(2, 4);
344 $("#board").find('.square-' + from).addClass('nonuglyhighlight');
345 $("#board").find('.square-' + to).addClass('nonuglyhighlight');
349 var pv = print_pv(data.pv_pretty, data.position.move_num, data.position.toplay);
352 // Update the PV arrow.
354 if (data.pv_uci.length >= 1) {
355 // draw a continuation arrow as long as it's the same piece
356 for (var i = 0; i < data.pv_uci.length; i += 2) {
357 var from = data.pv_uci[i].substr(0, 2);
358 var to = data.pv_uci[i].substr(2,4);
359 if ((i >= 2 && from != data.pv_uci[i - 2].substr(2, 4)) ||
360 interfering_arrow(from, to)) {
363 create_arrow(from, to, '#f66', 6, 20);
366 var alt_moves = find_nonstupid_moves(data, 30);
367 for (var i = 1; i < alt_moves.length && i < 3; ++i) {
368 create_arrow(alt_moves[i].substr(0, 2),
369 alt_moves[i].substr(2, 4), '#f66', 1, 10);
373 // See if all semi-reasonable moves have only one possible response.
374 if (data.pv_uci.length >= 2) {
375 var nonstupid_moves = find_nonstupid_moves(data, 300);
376 var response = data.pv_uci[1];
377 for (var i = 0; i < nonstupid_moves.length; ++i) {
378 if (nonstupid_moves[i] == data.pv_uci[0]) {
379 // ignore the PV move for refutation lines.
382 if (!data.refutation_lines ||
383 !data.refutation_lines[nonstupid_moves[i]] ||
384 !data.refutation_lines[nonstupid_moves[i]].pv_uci ||
385 data.refutation_lines[nonstupid_moves[i]].pv_uci.length < 1) {
386 // Incomplete PV, abort.
387 response = undefined;
390 var this_response = data.refutation_lines[nonstupid_moves[i]].pv_uci[1];
391 if (response !== this_response) {
392 // Different response depending on lines, abort.
393 response = undefined;
398 if (nonstupid_moves.length > 0 && response !== undefined) {
399 create_arrow(response.substr(0, 2),
400 response.substr(2, 4), '#66f', 6, 20);
404 // Show the refutation lines.
405 var tbl = $("#refutationlines");
409 for (var move in data.refutation_lines) {
412 moves = moves.sort(function(a, b) { return compare_by_sort_key(data, a, b) });
413 for (var i = 0; i < moves.length; ++i) {
414 var line = data.refutation_lines[moves[i]];
416 var tr = document.createElement("tr");
418 var move_td = document.createElement("td");
419 tr.appendChild(move_td);
420 $(move_td).addClass("move");
421 $(move_td).text(line.pretty_move);
423 var score_td = document.createElement("td");
424 tr.appendChild(score_td);
425 $(score_td).addClass("score");
426 $(score_td).text(line.pretty_score);
428 var depth_td = document.createElement("td");
429 tr.appendChild(depth_td);
430 $(depth_td).addClass("depth");
431 $(depth_td).text("d" + line.depth);
433 var pv_td = document.createElement("td");
434 tr.appendChild(pv_td);
435 $(pv_td).addClass("pv");
436 $(pv_td).text(print_pv(line.pv_pretty, data.position.move_num, data.position.toplay, 10));
442 setTimeout(function() { request_update(board, 0); }, 100);
445 var init = function() {
447 var board = new ChessBoard('board', 'start');
449 request_update(board, 1);
452 $(document).ready(init);