2 var arrow_targets = [];
3 var occupied_by_arrows = [];
5 var request_update = function(board, first) {
7 //url: "http://analysis.sesse.net/analysis.pl?first=" + first
8 url: "http://analysis.sesse.net:5000/analysis.pl?first=" + first
9 }).done(function(data) {
10 update_board(board, data);
14 var clear_arrows = function() {
15 for (var i = 0; i < arrows.length; ++i) {
16 jsPlumb.detach(arrows[i]);
20 for (var i = 0; i < arrow_targets.length; ++i) {
21 document.body.removeChild(arrow_targets[i]);
25 occupied_by_arrows = [];
26 for (var y = 0; y < 8; ++y) {
27 occupied_by_arrows.push([false, false, false, false, false, false, false, false]);
31 var sign = function(x) {
41 // See if drawing this arrow on the board would cause unduly amount of confusion.
42 var interfering_arrow = function(from, to) {
43 var from_col = from.charCodeAt(0) - "a1".charCodeAt(0);
44 var from_row = from.charCodeAt(1) - "a1".charCodeAt(1);
45 var to_col = to.charCodeAt(0) - "a1".charCodeAt(0);
46 var to_row = to.charCodeAt(1) - "a1".charCodeAt(1);
48 occupied_by_arrows[from_row][from_col] = true;
50 // Knight move: Just check that we haven't been at the destination before.
51 if ((Math.abs(to_col - from_col) == 2 && Math.abs(to_row - from_row) == 1) ||
52 (Math.abs(to_col - from_col) == 1 && Math.abs(to_row - from_row) == 2)) {
53 return occupied_by_arrows[to_row][to_col];
56 // Sliding piece: Check if anything except the from-square is seen before.
57 var dx = sign(to_col - from_col);
58 var dy = sign(to_row - from_row);
64 if (occupied_by_arrows[y][x]) {
67 occupied_by_arrows[y][x] = true;
68 } while (x != to_col || y != to_row);
73 var add_target = function() {
74 var elem = document.createElement("div");
75 $(elem).addClass("window");
76 elem.id = "target" + arrow_targets.length;
77 document.body.appendChild(elem);
78 arrow_targets.push(elem);
82 var create_arrow = function(from_square, to_square, fg_color, line_width, arrow_size) {
83 var from_col = from_square.charCodeAt(0) - "a1".charCodeAt(0);
84 var from_row = from_square.charCodeAt(1) - "a1".charCodeAt(1);
85 var to_col = to_square.charCodeAt(0) - "a1".charCodeAt(0);
86 var to_row = to_square.charCodeAt(1) - "a1".charCodeAt(1);
88 var from_y = (7 - from_row)*49 + 25;
89 var to_y = (7 - to_row)*49 + 25;
90 var from_x = from_col*49 + 25;
91 var to_x = to_col*49 + 25;
93 var dx = to_x - from_x;
94 var dy = to_y - from_y;
95 var len = Math.sqrt(dx * dx + dy * dy);
100 var s1 = add_target();
101 var d1 = add_target();
102 var s1v = add_target();
103 var d1v = add_target();
104 var pos = $("#board").position();
105 $("#" + s1).css({ top: pos.top + from_y + (0.5 * arrow_size) * dy, left: pos.left + from_x + (0.5 * arrow_size) * dx });
106 $("#" + d1).css({ top: pos.top + to_y - (0.5 * arrow_size) * dy, left: pos.left + to_x - (0.5 * arrow_size) * dx });
107 $("#" + s1v).css({ top: pos.top + from_y - 0 * dy, left: pos.left + from_x - 0 * dx });
108 $("#" + d1v).css({ top: pos.top + to_y + 0 * dy, left: pos.left + to_x + 0 * dx });
109 var connection1 = jsPlumb.connect({
112 connector:["Straight"],
115 endpointClass:"c1Endpoint",
118 lineWidth:line_width,
119 strokeStyle:fg_color,
125 var connection2 = jsPlumb.connect({
128 connector:["Straight"],
131 endpointClass:"c1Endpoint",
135 strokeStyle:fg_color,
143 width: arrow_size, length: arrow_size,
145 lineWidth:line_width,
151 arrows.push(connection1);
152 arrows.push(connection2);
155 // Fake multi-PV using the refutation lines. Find all “relevant” moves,
156 // sorted by quality, descending.
157 var find_nonstupid_moves = function(data, margin) {
158 // First of all, if there are any moves that are more than 0.5 ahead of
159 // the primary move, the refutation lines are probably bunk, so just
161 var best_score = undefined;
162 var pv_score = undefined;
163 for (var move in data.refutation_lines) {
164 var score = data.refutation_lines[move].score_sort_key;
165 if (move == data.pv_uci[0]) {
168 if (best_score === undefined || score > best_score) {
171 if (!(data.refutation_lines[move].depth >= 8)) {
176 if (best_score - pv_score > 50) {
180 // Now find all moves that are within “margin” of the best score.
181 // The PV move will always be first.
183 for (var move in data.refutation_lines) {
184 var score = data.refutation_lines[move].score_sort_key;
185 if (move != data.pv_uci[0] && best_score - score <= margin) {
189 moves = moves.sort(function(a, b) { return data.refutation_lines[b].score_sort_key - data.refutation_lines[a].score_sort_key; });
190 moves.unshift(data.pv_uci[0]);
195 var thousands = function(x) {
196 return String(x).split('').reverse().join('').replace(/(\d{3}\B)/g, '$1,').split('').reverse().join('');
199 var print_pv = function(pretty_pv, move_num, toplay, limit) {
203 pv = move_num + '. … ' + pretty_pv[0];
208 for ( ; i < pretty_pv.length; ++i) {
216 pv += move_num + '. ' + pretty_pv[i];
220 pv += ' ' + pretty_pv[i];
227 var compare_by_sort_key = function(data, a, b) {
228 var ska = data.refutation_lines[a].sort_key;
229 var skb = data.refutation_lines[b].sort_key;
230 if (ska < skb) return -1;
231 if (ska > skb) return 1;
235 var update_board = function(board, data) {
237 var headline = 'Analysis';
238 if (data.position.last_move !== 'none') {
239 headline += ' after ' + data.position.move_num + '. ';
240 if (data.position.toplay == 'W') {
243 headline += data.position.last_move;
246 $("#headline").text(headline);
249 if (data.score !== null) {
250 $("#score").text(data.score);
254 if (data.nodes && data.nps && data.depth) {
255 var stats = thousands(data.nodes) + ' nodes, ' + thousands(data.nps) + ' nodes/sec, depth ' + data.depth + ' ply';
257 stats += ' (' + data.seldepth + ' selective)';
259 if (data.tbhits && data.tbhits > 0) {
260 if (data.tbhits == 1) {
261 stats += ', one Nalimov hit';
263 stats += ', ' + data.tbhits + ' Nalimov hits';
268 $("#searchstats").text(stats);
271 // Update the board itself.
272 board.position(data.position.fen);
274 $("#board").find('.square-55d63').removeClass('nonuglyhighlight');
275 if (data.position.last_move_uci) {
276 var from = data.position.last_move_uci.substr(0, 2);
277 var to = data.position.last_move_uci.substr(2, 4);
278 $("#board").find('.square-' + from).addClass('nonuglyhighlight');
279 $("#board").find('.square-' + to).addClass('nonuglyhighlight');
283 var pv = print_pv(data.pv_pretty, data.position.move_num, data.position.toplay);
286 // Update the PV arrow.
288 if (data.pv_uci.length >= 1) {
289 // draw a continuation arrow as long as it's the same piece
290 for (var i = 0; i < data.pv_uci.length; i += 2) {
291 var from = data.pv_uci[i].substr(0, 2);
292 var to = data.pv_uci[i].substr(2,4);
293 if ((i >= 2 && from != data.pv_uci[i - 2].substr(2, 4)) ||
294 interfering_arrow(from, to)) {
297 create_arrow(from, to, '#f66', 6, 20);
300 var alt_moves = find_nonstupid_moves(data, 30);
301 for (var i = 1; i < alt_moves.length && i < 3; ++i) {
302 create_arrow(alt_moves[i].substr(0, 2),
303 alt_moves[i].substr(2, 4), '#f66', 1, 10);
307 // See if all semi-reasonable moves have only one possible response.
308 if (data.pv_uci.length >= 2) {
309 var nonstupid_moves = find_nonstupid_moves(data, 300);
310 var response = data.pv_uci[1];
311 for (var i = 0; i < nonstupid_moves.length; ++i) {
312 if (nonstupid_moves[i] == data.pv_uci[0]) {
313 // ignore the PV move for refutation lines.
316 if (!data.refutation_lines ||
317 !data.refutation_lines[nonstupid_moves[i]] ||
318 !data.refutation_lines[nonstupid_moves[i]].pv_uci ||
319 data.refutation_lines[nonstupid_moves[i]].pv_uci.length < 1) {
320 // Incomplete PV, abort.
321 response = undefined;
324 var this_response = data.refutation_lines[nonstupid_moves[i]].pv_uci[1];
325 if (response !== this_response) {
326 // Different response depending on lines, abort.
327 response = undefined;
332 if (nonstupid_moves.length > 0 && response !== undefined) {
333 create_arrow(response.substr(0, 2),
334 response.substr(2, 4), '#66f', 6, 20);
338 // Show the refutation lines.
339 var tbl = $("#refutationlines");
343 for (var move in data.refutation_lines) {
346 moves = moves.sort(function(a, b) { return compare_by_sort_key(data, a, b) });
347 for (var i = 0; i < moves.length; ++i) {
348 var line = data.refutation_lines[moves[i]];
350 var tr = document.createElement("tr");
352 var move_td = document.createElement("td");
353 tr.appendChild(move_td);
354 $(move_td).addClass("move");
355 $(move_td).text(line.pretty_move);
357 var score_td = document.createElement("td");
358 tr.appendChild(score_td);
359 $(score_td).addClass("score");
360 $(score_td).text(line.pretty_score);
362 var depth_td = document.createElement("td");
363 tr.appendChild(depth_td);
364 $(depth_td).addClass("depth");
365 $(depth_td).text("d" + line.depth);
367 var pv_td = document.createElement("td");
368 tr.appendChild(pv_td);
369 $(pv_td).addClass("pv");
370 $(pv_td).text(print_pv(line.pv_pretty, data.position.move_num, data.position.toplay, 10));
376 setTimeout(function() { request_update(board, 0); }, 100);
379 var init = function() {
381 var board = new ChessBoard('board', 'start');
383 request_update(board, 1);
386 $(document).ready(init);