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