+ # If the game is over, the clock is stopped.
+ if (exists($pos->{'result'}) &&
+ ($pos->{'result'} eq '1-0' ||
+ $pos->{'result'} eq '1/2-1/2' ||
+ $pos->{'result'} eq '0-1')) {
+ return;
+ }
+
+ # When we don't have any moves, we assume the clock hasn't started yet.
+ if ($pos->{'move_num'} == 1 && $pos->{'toplay'} eq 'W') {
+ return;
+ }
+
+ # TODO(sesse): Maybe we can get the number of moves somehow else for FICS games.
+ if (!exists($pos->{'pretty_history'})) {
+ return;
+ }
+
+ my $id = id_for_pos($pos);
+ if (exists($clock_target_for_pos{$id})) {
+ if ($pos->{'toplay'} eq 'W') {
+ $pos->{'white_clock_target'} = $clock_target_for_pos{$id};
+ } else {
+ $pos->{'black_clock_target'} = $clock_target_for_pos{$id};
+ }
+ return;
+ }
+
+ # OK, we haven't seen this position before, so we assume the move
+ # happened right now.
+ my $key = ($pos->{'toplay'} eq 'W') ? 'white_clock' : 'black_clock';
+ if (!exists($pos->{$key})) {
+ # No clock information.
+ return;
+ }
+ $pos->{$key} =~ /(\d+):(\d+):(\d+)/;
+ my $time_left = $1 * 3600 + $2 * 60 + $3;
+ $clock_target_for_pos{$id} = time + $time_left;
+ if ($pos->{'toplay'} eq 'W') {
+ $pos->{'white_clock_target'} = $clock_target_for_pos{$id};
+ } else {
+ $pos->{'black_clock_target'} = $clock_target_for_pos{$id};
+ }
+}
+
+sub schedule_tb_lookup {
+ return if (!defined($remoteglotconf::tb_serial_key));
+ my $pos = $pos_waiting // $pos_calculating;
+ return if (exists($tb_cache{$pos->fen()}));
+
+ # If there's more than seven pieces, there's not going to be an answer,
+ # so don't bother.
+ return if ($pos->num_pieces() > 7);
+
+ # Max one at a time. If it's still relevant when it returns,
+ # schedule_tb_lookup() will be called again.
+ return if ($tb_lookup_running);
+
+ $tb_lookup_running = 1;
+ my $url = 'http://158.250.18.203:6904/tasks/addtask?auth.login=' .
+ $remoteglotconf::tb_serial_key .
+ '&auth.password=aquarium&type=0&fen=' .
+ URI::Escape::uri_escape($pos->fen());
+ print TBLOG "Downloading $url...\n";
+ AnyEvent::HTTP::http_get($url, sub {
+ handle_tb_lookup_return(@_, $pos, $pos->fen());
+ });
+}
+
+sub handle_tb_lookup_return {
+ my ($body, $header, $pos, $fen) = @_;
+ print TBLOG "Response for [$fen]:\n";
+ print TBLOG $header . "\n\n";
+ print TBLOG $body . "\n\n";
+ eval {
+ my $response = JSON::XS::decode_json($body);
+ if ($response->{'ErrorCode'} != 0) {
+ die "Unknown tablebase server error: " . $response->{'ErrorDesc'};
+ }
+ my $state = $response->{'Response'}{'StateString'};
+ if ($state eq 'COMPLETE') {
+ my $pgn = Chess::PGN::Parse->new(undef, $response->{'Response'}{'Moves'});
+ if (!defined($pgn) || !$pgn->read_game()) {
+ warn "Error in parsing PGN\n";
+ } else {
+ $pgn->quick_parse_game;
+ my $pvpos = $pos;
+ my $moves = $pgn->moves;
+ my @uci_moves = ();
+ for my $move (@$moves) {
+ my $uci_move;
+ ($pvpos, $uci_move) = $pvpos->make_pretty_move($move);
+ push @uci_moves, $uci_move;
+ }
+ $tb_cache{$fen} = {
+ result => $pgn->result,
+ pv => \@uci_moves,
+ score => $response->{'Response'}{'Score'},
+ };
+ output();
+ }
+ } elsif ($state =~ /QUEUED/ || $state =~ /PROCESSING/) {
+ # Try again in a second. Note that if we have changed
+ # position in the meantime, we might query a completely
+ # different position! But that's fine.
+ } else {
+ die "Unknown response state " . $state;