Fix a null pointer exception in the frontend.
[remoteglot] / remoteglot.pl
index 11c5d41..9cbf820 100755 (executable)
@@ -67,7 +67,7 @@ select(TBLOG);
 $| = 1;
 
 select(STDOUT);
-umask 0022;
+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); });
@@ -94,40 +94,45 @@ if (defined($engine2)) {
 print "Chess engine ready.\n";
 
 # now talk to FICS
-my $t = Net::Telnet->new(Timeout => 10, Prompt => '/fics% /');
-$t->input_log(\*FICSLOG);
-$t->open($remoteglotconf::server);
-$t->print($remoteglotconf::nick);
-$t->waitfor('/Press return to enter the server/');
-$t->cmd("");
-
-# set some options
-$t->cmd("set shout 0");
-$t->cmd("set seek 0");
-$t->cmd("set style 12");
-
-my $ev1 = AnyEvent->io(
-       fh => fileno($t),
-       poll => 'r',
-       cb => sub {    # what callback to execute
-               while (1) {
-                       my $line = $t->getline(Timeout => 0, errmode => 'return');
-                       return if (!defined($line));
-
-                       chomp $line;
-                       $line =~ tr/\r//d;
-                       handle_fics($line);
+my ($t, $ev1);
+if (defined($remoteglotconf::server)) {
+       $t = Net::Telnet->new(Timeout => 10, Prompt => '/fics% /');
+       $t->input_log(\*FICSLOG);
+       $t->open($remoteglotconf::server);
+       $t->print($remoteglotconf::nick);
+       $t->waitfor('/Press return to enter the server/');
+       $t->cmd("");
+
+       # set some options
+       $t->cmd("set shout 0");
+       $t->cmd("set seek 0");
+       $t->cmd("set style 12");
+
+       $ev1 = AnyEvent->io(
+               fh => fileno($t),
+               poll => 'r',
+               cb => sub {    # what callback to execute
+                       while (1) {
+                               my $line = $t->getline(Timeout => 0, errmode => 'return');
+                               return if (!defined($line));
+
+                               chomp $line;
+                               $line =~ tr/\r//d;
+                               handle_fics($line);
+                       }
                }
-       }
-);
+       );
+}
 if (defined($remoteglotconf::target)) {
-       if ($remoteglotconf::target =~ /^http:/) {
+       if ($remoteglotconf::target =~ /^https?:/) {
                fetch_pgn($remoteglotconf::target);
-       } else {
+       } elsif (defined($t)) {
                $t->cmd("observe $remoteglotconf::target");
        }
 }
-print "FICS ready.\n";
+if (defined($t)) {
+       print "FICS ready.\n";
+}
 
 # Engine events have already been set up by Engine.pm.
 EV::run;
@@ -220,7 +225,7 @@ sub handle_fics {
                for my $pos ($pos_waiting, $pos_calculating) {
                        next if (!defined($pos));
                        if ($pos->fen() eq $pos_for_movelist->fen()) {
-                               $pos->{'pretty_history'} = \@pretty_movelist;
+                               $pos->{'history'} = \@pretty_movelist;
                        }
                }
                $getting_movelist = 0;
@@ -291,7 +296,11 @@ sub handle_pgn {
                        }
 
                        $pgn->parse_game({ save_comments => 'yes' });
-                       my $pos = Position->start_pos($pgn->white, $pgn->black);
+                       my $white = $pgn->white;
+                       my $black = $pgn->black;
+                       $white =~ s/,.*//;  # Remove first name.
+                       $black =~ s/,.*//;  # Remove first name.
+                       my $pos = Position->start_pos($white, $black);
                        my $moves = $pgn->moves;
                        my @uci_moves = ();
                        my @repretty_moves = ();
@@ -305,8 +314,10 @@ sub handle_pgn {
                                push @repretty_moves, $pretty;
                                $pos = $npos;
                        }
-                       $pos->{'result'} = $pgn->result;
-                       $pos->{'pretty_history'} = \@repretty_moves;
+                       if ($pgn->result eq '1-0' || $pgn->result eq '1/2-1/2' || $pgn->result eq '0-1') {
+                               $pos->{'result'} = $pgn->result;
+                       }
+                       $pos->{'history'} = \@repretty_moves;
 
                        extract_clock($pgn, $pos);
 
@@ -417,7 +428,9 @@ sub handle_position {
        # hour, the analysis/relay has most likely stopped
        # and we should stop hogging server resources.
        #
-       $t->cmd("date");
+       if (defined($t)) {
+               $t->cmd("date");
+       }
 }
 
 sub parse_infos {
@@ -520,7 +533,7 @@ sub prettyprint_pv_no_cache {
 sub prettyprint_pv {
        my ($pos, @pvs) = @_;
 
-       my $cachekey = join('', @pvs);
+       my $cachekey = $pos->fen() . join('', @pvs);
        if (exists($pos->{'prettyprint_cache'}{$cachekey})) {
                return @{$pos->{'prettyprint_cache'}{$cachekey}};
        } else {
@@ -530,6 +543,8 @@ sub prettyprint_pv {
        }
 }
 
+my %tbprobe_cache = ();
+
 sub complete_using_tbprobe {
        my ($pos, $info, $mpv) = @_;
 
@@ -547,11 +562,18 @@ sub complete_using_tbprobe {
        # Run through the PV until we are at a 6-man position.
        # TODO: We could in theory only have 5-man data.
        my @pv = @{$info->{'pv' . $mpv}};
-       my $key = join('', @pv);
+       my $key = $pos->fen() . " " . join('', @pv);
        my @moves = ();
-       if (exists($pos->{'tbprobe_cache'}{$key})) {
-               @moves = $pos->{'tbprobe_cache'}{$key};
+       if (exists($tbprobe_cache{$key})) {
+               @moves = @{$tbprobe_cache{$key}};
        } else {
+               if ($mpv ne '') {
+                       # Force doing at least one move of the PV.
+                       my $move = shift @pv;
+                       push @moves, $move;
+                       $pos = $pos->make_move(parse_uci_move($move));
+               }
+
                while ($pos->num_pieces() > 6 && $#pv > -1) {
                        my $move = shift @pv;
                        push @moves, $move;
@@ -573,6 +595,8 @@ sub complete_using_tbprobe {
                        ($pos, $uci_move) = $pos->make_pretty_move($move);
                        push @moves, $uci_move;
                }
+
+               $tbprobe_cache{$key} = \@moves;
        }
 
        $info->{'pv' . $mpv} = \@moves;
@@ -823,6 +847,14 @@ sub output_json {
        if (defined($remoteglotconf::engine_details)) {
                $json->{'engine'}{'details'} = $remoteglotconf::engine_details;
        }
+       my @grpc_backends = ();
+       if (defined($remoteglotconf::engine_grpc_backend)) {
+               push @grpc_backends, $remoteglotconf::engine_grpc_backend;
+       }
+       if (defined($remoteglotconf::engine2_grpc_backend)) {
+               push @grpc_backends, $remoteglotconf::engine2_grpc_backend;
+       }
+       $json->{'internal'}{'grpc_backends'} = \@grpc_backends;
        if (defined($remoteglotconf::move_source)) {
                $json->{'move_source'} = $remoteglotconf::move_source;
        }
@@ -838,9 +870,7 @@ sub output_json {
        $json->{'tbhits'} = $info->{'tbhits'};
        $json->{'seldepth'} = $info->{'seldepth'};
        $json->{'tablebase'} = $info->{'tablebase'};
-
-       $json->{'pv_uci'} = $info->{'pv'};  # Still needs to be there for the JS to calculate arrows; only for the primary PV, though!
-       $json->{'pv_pretty'} = [ prettyprint_pv($pos_calculating, @{$info->{'pv'}}) ];
+       $json->{'pv'} = [ prettyprint_pv($pos_calculating, @{$info->{'pv'}}) ];
 
        my %refutation_lines = ();
        my @refutation_lines = ();
@@ -856,12 +886,11 @@ sub output_json {
                                my $pv = $info->{'pv' . $mpv};
                                my $pretty_move = join('', prettyprint_pv($pos_calculating, $pv->[0]));
                                my @pretty_pv = prettyprint_pv($pos_calculating, @$pv);
-                               $refutation_lines{$pv->[0]} = {
-                                       sort_key => $pretty_move,
+                               $refutation_lines{$pretty_move} = {
                                        depth => $info->{'depth' . $mpv},
                                        score => score_digest($info, $pos_calculating, $mpv),
-                                       pretty_move => $pretty_move,
-                                       pv_pretty => \@pretty_pv,
+                                       move => $pretty_move,
+                                       pv => \@pretty_pv,
                                };
                        };
                }
@@ -869,13 +898,13 @@ sub output_json {
        $json->{'refutation_lines'} = \%refutation_lines;
 
        # Piece together historic score information, to the degree we have it.
-       if (!$historic_json_only && exists($pos_calculating->{'pretty_history'})) {
+       if (!$historic_json_only && exists($pos_calculating->{'history'})) {
                my %score_history = ();
 
                my $q = $dbh->prepare('SELECT * FROM scores WHERE id=?');
                my $pos = Position->start_pos('white', 'black');
                my $halfmove_num = 0;
-               for my $move (@{$pos_calculating->{'pretty_history'}}) {
+               for my $move (@{$pos_calculating->{'history'}}) {
                        my $id = id_for_pos($pos, $halfmove_num);
                        my $ref = $dbh->selectrow_hashref($q, undef, $id);
                        if (defined($ref)) {
@@ -912,6 +941,7 @@ sub output_json {
        }
 
        # Give out a list of other games going on. (Empty is fine.)
+       # TODO: Don't bother reading our own file, the data will be stale anyway.
        if (!$historic_json_only) {
                my @games = ();
 
@@ -926,11 +956,18 @@ sub output_json {
                                my $white = $other_game_json->{'position'}{'player_w'} // die 'Missing white';
                                my $black = $other_game_json->{'position'}{'player_b'} // die 'Missing black';
 
-                               push @games, {
+                               my $game = {
                                        id => $ref->{'id'},
                                        name => "$white–$black",
-                                       url => $ref->{'url'}
+                                       url => $ref->{'url'},
+                                       hashurl => $ref->{'hash_url'},
                                };
+                               if (defined($other_game_json->{'position'}{'result'})) {
+                                       $game->{'result'} = $other_game_json->{'position'}{'result'};
+                               } else {
+                                       $game->{'score'} = $other_game_json->{'score'};
+                               }
+                               push @games, $game;
                        };
                        if ($@) {
                                warn "Could not add external game " . $ref->{'json_path'} . ": $@";
@@ -951,7 +988,7 @@ sub output_json {
                $last_written_json = $encoded;
        }
 
-       if (exists($pos_calculating->{'pretty_history'}) &&
+       if (exists($pos_calculating->{'history'}) &&
            defined($remoteglotconf::json_history_dir)) {
                my $id = id_for_pos($pos_calculating);
                my $filename = $remoteglotconf::json_history_dir . "/" . $id . ".json";
@@ -997,7 +1034,7 @@ sub atomic_set_contents {
 sub id_for_pos {
        my ($pos, $halfmove_num) = @_;
 
-       $halfmove_num //= scalar @{$pos->{'pretty_history'}};
+       $halfmove_num //= scalar @{$pos->{'history'}};
        (my $fen = $pos->fen()) =~ tr,/ ,-_,;
        return "move$halfmove_num-$fen";
 }
@@ -1068,7 +1105,7 @@ sub score_digest {
                        if ($score == 0 && $info->{'tablebase'}) {
                                return ['d', undef];
                        } else {
-                               return ['cp', $score];
+                               return ['cp', int($score)];
                        }
                }
        }
@@ -1254,7 +1291,7 @@ sub find_clock_start {
 
        # TODO(sesse): Maybe we can get the number of moves somehow else for FICS games.
        # The history is needed for id_for_pos.
-       if (!exists($pos->{'pretty_history'})) {
+       if (!exists($pos->{'history'})) {
                return;
        }