Make it possible for the hash probes to reconcile multiple sources.
[remoteglot] / server / hash-lookup.js
1 var grpc = require('grpc');
2 var Chess = require('../www/js/chess.min.js').Chess;
3
4 var PROTO_PATH = __dirname + '/hashprobe.proto';
5 var hashprobe_proto = grpc.load(PROTO_PATH).hashprobe;
6
7 var board = new Chess();
8
9 var clients = [];
10
11 var init = function(servers) {
12         for (var i = 0; i < servers.length; ++i) {
13                 clients.push(new hashprobe_proto.HashProbe(servers[i], grpc.credentials.createInsecure()));
14         }
15 }
16 exports.init = init;
17
18 var handle_request = function(fen, response) {
19         if (!board.validate_fen(fen).valid) {
20                 response.writeHead(400, {});
21                 response.end();
22                 return;
23         }
24
25         var rpc_status = {
26                 failed: false,
27                 left: clients.length,
28                 responses: [],
29         }
30         for (var i = 0; i < clients.length; ++i) {
31                 clients[i].probe({fen: fen}, function(err, probe_response) {
32                         if (err) {
33                                 rpc_status.failed = true;
34                         } else {
35                                 rpc_status.responses.push(probe_response);
36                         }
37                         if (--rpc_status.left == 0) {
38                                 // All probes have come back.
39                                 if (rpc_status.failed) {
40                                         response.writeHead(500, {});
41                                         response.end();
42                                 } else {
43                                         handle_response(fen, response, rpc_status.responses);
44                                 }
45                         }
46                 });
47         }
48 }
49 exports.handle_request = handle_request;
50
51 var handle_response = function(fen, response, probe_responses) {
52         var probe_response = reconcile_responses(probe_responses);
53         var lines = {};
54
55         var root = translate_line(board, fen, probe_response['root']);
56         for (var i = 0; i < probe_response['line'].length; ++i) {
57                 var line = probe_response['line'][i];
58                 var uci_move = line['move']['from_sq'] + line['move']['to_sq'] + line['move']['promotion'];
59                 lines[uci_move] = translate_line(board, fen, line);
60         }
61
62         var text = JSON.stringify({
63                 root: root,
64                 lines: lines
65         });
66         var headers = {
67                 'Content-Type': 'text/json; charset=utf-8'
68                 //'Content-Length': text.length
69         };
70         response.writeHead(200, headers);
71         response.write(text);
72         response.end();
73 }
74
75 var reconcile_responses = function(probe_responses) {
76         var probe_response = {};
77
78         // Select the root that has searched the deepest, plain and simple.
79         probe_response['root'] = probe_responses[0]['root'];
80         for (var i = 1; i < probe_responses.length; ++i) {
81                 var root = probe_responses[i]['root'];
82                 if (root['depth'] > probe_response['root']['depth']) {
83                         probe_response['root'] = root;
84                 }
85         }
86
87         // Do the same ting for each move, combining on move.
88         var moves = {};
89         for (var i = 0; i < probe_responses.length; ++i) {
90                 for (var j = 0; j < probe_responses[i]['line'].length; ++j) {
91                         var line = probe_responses[i]['line'][j];
92                         var uci_move = line['move']['from_sq'] + line['move']['to_sq'] + line['move']['promotion'];
93
94                         if (!moves[uci_move]) {
95                                 moves[uci_move] = line;
96                         } else {
97                                 moves[uci_move] = reconcile_moves(line, moves[uci_move]);
98                         }
99                 }
100         }
101         probe_response['line'] = [];
102         for (var move in moves) {
103                 probe_response['line'].push(moves[move]);
104         }
105         return probe_response;
106 }
107
108 var reconcile_moves = function(a, b) {
109         // Prefer exact bounds, unless the depth is just so much higher.
110         if (a['bound'] === 'BOUND_EXACT' &&
111             b['bound'] !== 'BOUND_EXACT' &&
112             a['depth'] + 10 >= b['depth']) {
113                 return a;
114         }
115         if (b['bound'] === 'BOUND_EXACT' &&
116             a['bound'] !== 'BOUND_EXACT' &&
117             b['depth'] + 10 >= a['depth']) {
118                 return b;
119         }
120
121         if (a['depth'] > b['depth']) {
122                 return a;
123         } else {
124                 return b;
125         }
126 }       
127
128 var translate_line = function(board, fen, line) {
129         var r = {};
130         board.load(fen);
131         var toplay = board.turn();
132
133         if (line['move'] && line['move']['from_sq']) {
134                 var promo = line['move']['promotion'];
135                 if (promo) {
136                         r['pretty_move'] = board.move({ from: line['move']['from_sq'], to: line['move']['to_sq'], promotion: promo.toLowerCase() }).san;
137                 } else {
138                         r['pretty_move'] = board.move({ from: line['move']['from_sq'], to: line['move']['to_sq'] }).san;
139                 }
140         } else {
141                 r['pretty_move'] = '';
142         }
143         r['sort_key'] = r['pretty_move'];
144         if (!line['found']) {
145                 r['pv_pretty'] = [];
146                 return r;
147         }
148         r['depth'] = line['depth'];
149
150         // Convert the PV.
151         var pv = [];
152         if (r['pretty_move']) {
153                 pv.push(r['pretty_move']);
154         }
155         for (var j = 0; j < line['pv'].length; ++j) {
156                 var move = line['pv'][j];
157                 var decoded = board.move({ from: move['from_sq'], to: move['to_sq'], promotion: move['promotion'] });
158                 if (decoded === null) {
159                         break;
160                 }
161                 pv.push(decoded.san);
162         }
163         r['pv_pretty'] = pv;
164
165         // Convert the score. Use the static eval if no search.
166         var value = line['value'] || line['eval'];
167         var score = null;
168         if (value['score_type'] === 'SCORE_CP') {
169                 score = ['cp', value['score_cp']];
170         } else if (value['score_type'] === 'SCORE_MATE') {
171                 score = ['m', value['score_mate']];
172         }
173         if (score) {
174                 if (line['bound'] === 'BOUND_UPPER') {
175                         score.push('≤');
176                 } else if (line['bound'] === 'BOUND_LOWER') {
177                         score.push('≥');
178                 }
179         }
180
181         r['score'] = score;
182
183         return r;
184 }