]> git.sesse.net Git - remoteglot/blobdiff - remoteglot.pl
Support getting PGNs from local files.
[remoteglot] / remoteglot.pl
index 7097306fecfd3795c407392164fcda012c5de1ed..c6815af33932e967e671ee140b139994ef2b8080 100755 (executable)
@@ -67,14 +67,14 @@ select(TBLOG);
 $| = 1;
 
 select(STDOUT);
-umask 0027;  # analysis.json should not be served to users.
+umask 0022;  # analysis.json should not be served to users.
 
 # open the chess engine
 my $engine = open_engine($remoteglotconf::engine_cmdline, 'E1', sub { handle_uci(@_, 1); });
 my $engine2 = open_engine($remoteglotconf::engine2_cmdline, 'E2', sub { handle_uci(@_, 0); });
 my $last_move;
 my $last_text = '';
-my ($pos_waiting, $pos_calculating, $pos_calculating_second_engine);
+my ($pos_calculating, $pos_calculating_second_engine);
 
 uciprint($engine, "setoption name UCI_AnalyseMode value true");
 while (my ($key, $value) = each %remoteglotconf::engine_config) {
@@ -124,7 +124,7 @@ if (defined($remoteglotconf::server)) {
        );
 }
 if (defined($remoteglotconf::target)) {
-       if ($remoteglotconf::target =~ /^https?:/) {
+       if ($remoteglotconf::target =~ /^(?:\/|https?:)/) {
                fetch_pgn($remoteglotconf::target);
        } elsif (defined($t)) {
                $t->cmd("observe $remoteglotconf::target");
@@ -144,6 +144,11 @@ sub handle_uci {
 
        $line =~ s/  / /g;  # Sometimes needed for Zappa Mexico
        print UCILOG localtime() . " $engine->{'tag'} <= $line\n";
+
+       # If we've sent a stop command, gobble up lines until we see bestmove.
+       return if ($engine->{'stopping'} && $line !~ /^bestmove/);
+       $engine->{'stopping'} = 0;
+
        if ($line =~ /^info/) {
                my (@infos) = split / /, $line;
                shift @infos;
@@ -156,24 +161,6 @@ sub handle_uci {
 
                parse_ids($engine, @ids);
        }
-       if ($line =~ /^bestmove/) {
-               if ($primary) {
-                       return if (!$remoteglotconf::uci_assume_full_compliance);
-                       if (defined($pos_waiting)) {
-                               uciprint($engine, "position fen " . $pos_waiting->fen());
-                               uciprint($engine, "go infinite");
-
-                               $pos_calculating = $pos_waiting;
-                               $pos_waiting = undef;
-                       }
-               } else {
-                       $engine2->{'info'} = {};
-                       my $pos = $pos_waiting // $pos_calculating;
-                       uciprint($engine2, "position fen " . $pos->fen());
-                       uciprint($engine2, "go infinite");
-                       $pos_calculating_second_engine = $pos;
-               }
-       }
        output();
 }
 
@@ -189,7 +176,7 @@ sub handle_fics {
                $t->cmd("moves");
        }
        if ($line =~ /^Movelist for game /) {
-               my $pos = $pos_waiting // $pos_calculating;
+               my $pos = $pos_calculating;
                if (defined($pos)) {
                        @uci_movelist = ();
                        @pretty_movelist = ();
@@ -222,10 +209,9 @@ sub handle_fics {
        if ($getting_movelist &&
            $line =~ /^\s+ \{.*\} \s+ (?: \* | 1\/2-1\/2 | 0-1 | 1-0 )/x) {
                # End of movelist.
-               for my $pos ($pos_waiting, $pos_calculating) {
-                       next if (!defined($pos));
-                       if ($pos->fen() eq $pos_for_movelist->fen()) {
-                               $pos->{'history'} = \@pretty_movelist;
+               if (defined($pos_calculating)) {
+                       if ($pos_calculating->fen() eq $pos_for_movelist->fen()) {
+                               $pos_calculating->{'history'} = \@pretty_movelist;
                        }
                }
                $getting_movelist = 0;
@@ -262,9 +248,26 @@ sub handle_fics {
 # Starts periodic fetching of PGNs from the given URL.
 sub fetch_pgn {
        my ($url) = @_;
-       AnyEvent::HTTP::http_get($url, sub {
-               handle_pgn(@_, $url);
-       });
+       if ($url =~ m#^/#) {  # Local file.
+               eval {
+                       local $/ = undef;
+                       open my $fh, "<", $url
+                               or die "$url: $!";
+                       my $pgn = <$fh>;
+                       close $fh;
+                       handle_pgn($pgn, '', $url);
+               };
+               if ($@) {
+                       warn "$url: $@";
+                       $http_timer = AnyEvent->timer(after => 1.0, cb => sub {
+                               fetch_pgn($url);
+                       });
+               }
+       } else {
+               AnyEvent::HTTP::http_get($url, sub {
+                       handle_pgn(@_, $url);
+               });
+       }
 }
 
 my ($last_pgn_white, $last_pgn_black);
@@ -300,7 +303,23 @@ sub handle_pgn {
                        my $black = $pgn->black;
                        $white =~ s/,.*//;  # Remove first name.
                        $black =~ s/,.*//;  # Remove first name.
-                       my $pos = Position->start_pos($white, $black);
+                       my $tags = $pgn->tags();
+                       my $pos;
+                       if (exists($tags->{'FEN'})) {
+                               $pos = Position->from_fen($tags->{'FEN'});
+                               $pos->{'last_move'} = 'none';
+                               $pos->{'player_w'} = $white;
+                               $pos->{'player_b'} = $black;
+                               $pos->{'start_fen'} = $tags->{'FEN'};
+                       } else {
+                               $pos = Position->start_pos($white, $black);
+                       }
+                       if (exists($tags->{'Variant'}) &&
+                           $tags->{'Variant'} =~ /960|fischer/i) {
+                               $pos->{'chess960'} = 1;
+                       } else {
+                               $pos->{'chess960'} = 0;
+                       }
                        my $moves = $pgn->moves;
                        my @uci_moves = ();
                        my @repretty_moves = ();
@@ -356,22 +375,18 @@ sub handle_position {
        my ($pos) = @_;
        find_clock_start($pos, $pos_calculating);
                
-       # if this is already in the queue, ignore it (just update the result)
-       if (defined($pos_waiting) && $pos->fen() eq $pos_waiting->fen()) {
-               $pos_waiting->{'result'} = $pos->{'result'};
-               return;
-       }
-
-       # if we're already chewing on this and there's nothing else in the queue,
-       # also ignore it
-       if (!defined($pos_waiting) && defined($pos_calculating) &&
-           $pos->fen() eq $pos_calculating->fen()) {
+       # If we're already chewing on this and there's nothing else in the queue,
+       # ignore it.
+       if (defined($pos_calculating) && $pos->fen() eq $pos_calculating->fen()) {
                $pos_calculating->{'result'} = $pos->{'result'};
+               for my $key ('white_clock', 'black_clock', 'white_clock_target', 'black_clock_target') {
+                       $pos_calculating->{$key} //= $pos->{$key};
+               }
                return;
        }
 
-       # if we're already thinking on something, stop and wait for the engine
-       # to approve
+       # If we're already thinking on something, stop and wait for the engine
+       # to approve.
        if (defined($pos_calculating)) {
                # Store the final data we have for this position in the history,
                # with the precise clock information we just got from the new
@@ -386,33 +401,35 @@ sub handle_position {
                delete $pos_calculating->{'black_clock_target'};
                output_json(1);
 
-               if (!defined($pos_waiting)) {
-                       uciprint($engine, "stop");
-               }
-               if ($remoteglotconf::uci_assume_full_compliance) {
-                       $pos_waiting = $pos;
-               } else {
-                       uciprint($engine, "position fen " . $pos->fen());
-                       uciprint($engine, "go infinite");
-                       $pos_calculating = $pos;
-               }
-       } else {
-               # it's wrong just to give the FEN (the move history is useful,
-               # and per the UCI spec, we should really have sent "ucinewgame"),
-               # but it's easier
-               uciprint($engine, "position fen " . $pos->fen());
-               uciprint($engine, "go infinite");
-               $pos_calculating = $pos;
+               # Ask the engine to stop; we will throw away its data until it
+               # sends us "bestmove", signaling the end of it.
+               $engine->{'stopping'} = 1;
+               uciprint($engine, "stop");
+       }
+
+       # It's wrong to just give the FEN (the move history is useful,
+       # and per the UCI spec, we should really have sent "ucinewgame"),
+       # but it's easier, and it works around a Stockfish repetition issue.
+       if ($engine->{'chess960'} != $pos->{'chess960'}) {
+               uciprint($engine, "setoption name UCI_Chess960 value " . ($pos->{'chess960'} ? 'true' : 'false'));
+               $engine->{'chess960'} = $pos->{'chess960'};
        }
+       uciprint($engine, "position fen " . $pos->fen());
+       uciprint($engine, "go infinite");
+       $pos_calculating = $pos;
 
        if (defined($engine2)) {
                if (defined($pos_calculating_second_engine)) {
+                       $engine2->{'stopping'} = 1;
                        uciprint($engine2, "stop");
-               } else {
-                       uciprint($engine2, "position fen " . $pos->fen());
-                       uciprint($engine2, "go infinite");
-                       $pos_calculating_second_engine = $pos;
                }
+               if ($engine2->{'chess960'} != $pos->{'chess960'}) {
+                       uciprint($engine2, "setoption name UCI_Chess960 value " . ($pos->{'chess960'} ? 'true' : 'false'));
+                       $engine2->{'chess960'} = $pos->{'chess960'};
+               }
+               uciprint($engine2, "position fen " . $pos->fen());
+               uciprint($engine2, "go infinite");
+               $pos_calculating_second_engine = $pos;
                $engine2->{'info'} = {};
        }
 
@@ -533,7 +550,7 @@ sub prettyprint_pv_no_cache {
 sub prettyprint_pv {
        my ($pos, @pvs) = @_;
 
-       my $cachekey = $pos->{'fen'} . join('', @pvs);
+       my $cachekey = join('', @pvs);
        if (exists($pos->{'prettyprint_cache'}{$cachekey})) {
                return @{$pos->{'prettyprint_cache'}{$cachekey}};
        } else {
@@ -591,6 +608,7 @@ sub complete_using_tbprobe {
 
                # Splice the PV from the tablebase onto what we have so far.
                for my $move (@{$pgn->moves}) {
+                       last if $move eq '#';
                        my $uci_move;
                        ($pos, $uci_move) = $pos->make_pretty_move($move);
                        push @moves, $uci_move;
@@ -901,8 +919,15 @@ sub output_json {
        if (!$historic_json_only && exists($pos_calculating->{'history'})) {
                my %score_history = ();
 
+               local $dbh->{AutoCommit} = 0;
                my $q = $dbh->prepare('SELECT * FROM scores WHERE id=?');
-               my $pos = Position->start_pos('white', 'black');
+               my $pos;
+               if (exists($pos_calculating->{'start_fen'})) {
+                       $pos = Position->from_fen($pos_calculating->{'start_fen'});
+               } else {
+                       $pos = Position->start_pos('white', 'black');
+               }
+               $pos->{'chess960'} = $pos_calculating->{'chess960'};
                my $halfmove_num = 0;
                for my $move (@{$pos_calculating->{'history'}}) {
                        my $id = id_for_pos($pos, $halfmove_num);
@@ -917,6 +942,7 @@ sub output_json {
                        ($pos) = $pos->make_pretty_move($move);
                }
                $q->finish;
+               $dbh->commit;
 
                # If at any point we are missing 10 consecutive moves,
                # truncate the history there. This is so we don't get into
@@ -1296,7 +1322,7 @@ sub find_clock_start {
        }
 
        my $id = id_for_pos($pos);
-       my $clock_info = $dbh->selectrow_hashref('SELECT * FROM clock_info WHERE id=?', undef, $id);
+       my $clock_info = $dbh->selectrow_hashref('SELECT * FROM clock_info WHERE id=? AND COALESCE(white_clock_target, black_clock_target) >= EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - INTERVAL \'1 day\'));', undef, $id);
        if (defined($clock_info)) {
                $pos->{'white_clock'} //= $clock_info->{'white_clock'};
                $pos->{'black_clock'} //= $clock_info->{'black_clock'};
@@ -1348,7 +1374,7 @@ sub find_clock_start {
 
 sub schedule_tb_lookup {
        return if (!defined($remoteglotconf::tb_serial_key));
-       my $pos = $pos_waiting // $pos_calculating;
+       my $pos = $pos_calculating;
        return if (exists($tb_cache{$pos->fen()}));
 
        # If there's more than seven pieces, there's not going to be an answer,
@@ -1360,7 +1386,7 @@ sub schedule_tb_lookup {
        return if ($tb_lookup_running);
 
        $tb_lookup_running = 1;
-       my $url = 'http://158.250.18.203:6904/tasks/addtask?auth.login=' .
+       my $url = 'http://tb7-api.chessok.com:6904/tasks/addtask?auth.login=' .
                $remoteglotconf::tb_serial_key .
                '&auth.password=aquarium&type=0&fen=' . 
                URI::Escape::uri_escape($pos->fen());