no warnings qw(once);
# Program starts here
-$SIG{ALRM} = sub { output(); };
my $latest_update = undef;
+my $output_timer = undef;
my $http_timer = undef;
my $tb_retry_timer = undef;
my %tb_cache = ();
$t->cmd("set shout 0");
$t->cmd("set seek 0");
$t->cmd("set style 12");
-$t->cmd("observe $remoteglotconf::target");
-print "FICS ready.\n";
my $ev1 = AnyEvent->io(
fh => fileno($t),
}
}
);
+if (defined($remoteglotconf::target)) {
+ if ($remoteglotconf::target =~ /^http:/) {
+ fetch_pgn($remoteglotconf::target);
+ } else {
+ $t->cmd("observe $remoteglotconf::target");
+ }
+}
+print "FICS ready.\n";
+
# Engine events have already been set up by Engine.pm.
EV::run;
output();
}
+my $getting_movelist = 0;
+my $pos_for_movelist = undef;
+my @uci_movelist = ();
+my @pretty_movelist = ();
+
sub handle_fics {
my $line = shift;
if ($line =~ /^<12> /) {
handle_position(Position->new($line));
+ $t->cmd("moves");
+ }
+ if ($line =~ /^Movelist for game /) {
+ my $pos = $pos_waiting // $pos_calculating;
+ if (defined($pos)) {
+ @uci_movelist = ();
+ @pretty_movelist = ();
+ $pos_for_movelist = Position->start_pos($pos->{'player_w'}, $pos->{'player_b'});
+ $getting_movelist = 1;
+ }
+ }
+ if ($getting_movelist &&
+ $line =~ /^\s* \d+\. \s+ # move number
+ (\S+) \s+ \( [\d:.]+ \) \s* # first move, then time
+ (?: (\S+) \s+ \( [\d:.]+ \) )? # second move, then time
+ /x) {
+ eval {
+ my $uci_move;
+ ($pos_for_movelist, $uci_move) = $pos_for_movelist->make_pretty_move($1);
+ push @uci_movelist, $uci_move;
+ push @pretty_movelist, $1;
+
+ if (defined($2)) {
+ ($pos_for_movelist, $uci_move) = $pos_for_movelist->make_pretty_move($2);
+ push @uci_movelist, $uci_move;
+ push @pretty_movelist, $2;
+ }
+ };
+ if ($@) {
+ warn "Error when getting FICS move history: $@";
+ $getting_movelist = 0;
+ }
+ }
+ 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'} = \@uci_movelist;
+ $pos->{'pretty_history'} = \@pretty_movelist;
+ }
+ }
+ $getting_movelist = 0;
}
if ($line =~ /^([A-Za-z]+)(?:\([A-Z]+\))* tells you: (.*)$/) {
my ($who, $msg) = ($1, $2);
} elsif ($msg =~ /^pgn (.*?)$/) {
my $url = $1;
$t->cmd("tell $who Starting to poll '$url'.");
- AnyEvent::HTTP::http_get($url, sub {
- handle_pgn(@_, $url);
- });
+ fetch_pgn($url);
} elsif ($msg =~ /^stoppgn$/) {
$t->cmd("tell $who Stopping poll.");
$http_timer = undef;
#print "FICS: [$line]\n";
}
+# Starts periodic fetching of PGNs from the given URL.
+sub fetch_pgn {
+ my ($url) = @_;
+ AnyEvent::HTTP::http_get($url, sub {
+ handle_pgn(@_, $url);
+ });
+}
+
+my ($last_pgn_white, $last_pgn_black);
+my @last_pgn_uci_moves = ();
+my $pgn_hysteresis_counter = 0;
+
sub handle_pgn {
my ($body, $header, $url) = @_;
my $pgn = Chess::PGN::Parse->new(undef, $body);
my $moves = $pgn->moves;
my @uci_moves = ();
for my $move (@$moves) {
- my ($from_row, $from_col, $to_row, $to_col, $promo) = $pos->parse_pretty_move($move);
- push @uci_moves, Board::move_to_uci_notation($from_row, $from_col, $to_row, $to_col, $promo);
- $pos = $pos->make_move($from_row, $from_col, $to_row, $to_col, $promo);
+ my $uci_move;
+ ($pos, $uci_move) = $pos->make_pretty_move($move);
+ push @uci_moves, $uci_move;
}
$pos->{'history'} = \@uci_moves;
$pos->{'pretty_history'} = $moves;
- handle_position($pos);
+
+ # Sometimes, PGNs lose a move or two for a short while,
+ # or people push out new ones non-atomically.
+ # Thus, if we PGN doesn't change names but becomes
+ # shorter, we mistrust it for a few seconds.
+ my $trust_pgn = 1;
+ if (defined($last_pgn_white) && defined($last_pgn_black) &&
+ $last_pgn_white eq $pgn->white &&
+ $last_pgn_black eq $pgn->black &&
+ scalar(@uci_moves) < scalar(@last_pgn_uci_moves)) {
+ if (++$pgn_hysteresis_counter < 3) {
+ $trust_pgn = 0;
+ }
+ }
+ if ($trust_pgn) {
+ $last_pgn_white = $pgn->white;
+ $last_pgn_black = $pgn->black;
+ @last_pgn_uci_moves = @uci_moves;
+ $pgn_hysteresis_counter = 0;
+ handle_position($pos);
+ }
}
$http_timer = AnyEvent->timer(after => 1.0, cb => sub {
- AnyEvent::HTTP::http_get($url, sub {
- handle_pgn(@_, $url);
- });
+ fetch_pgn($url);
});
}
# Don't update too often.
my $age = Time::HiRes::tv_interval($latest_update);
if ($age < $remoteglotconf::update_max_interval) {
- Time::HiRes::alarm($remoteglotconf::update_max_interval + 0.01 - $age);
+ my $wait = $remoteglotconf::update_max_interval + 0.01 - $age;
+ $output_timer = AnyEvent->timer(after => $wait, cb => \&output);
return;
}
my $key = $pretty_move;
my $line = sprintf(" %-6s %6s %3s %s",
$pretty_move,
- short_score($info, $pos_calculating_second_engine, $mpv, 0),
+ short_score($info, $pos_calculating_second_engine, $mpv),
"d" . $info->{'depth' . $mpv},
join(', ', @pretty_pv));
push @refutation_lines, [ $key, $line ];
$json->{'position'} = $pos_calculating->to_json_hash();
$json->{'id'} = $engine->{'id'};
$json->{'score'} = long_score($info, $pos_calculating, '');
+ $json->{'short_score'} = short_score($info, $pos_calculating, '');
$json->{'nodes'} = $info->{'nodes'};
$json->{'nps'} = $info->{'nps'};
sort_key => $pretty_move,
depth => $info->{'depth' . $mpv},
score_sort_key => score_sort_key($info, $pos_calculating, $mpv, 0),
- pretty_score => short_score($info, $pos_calculating, $mpv, 0),
+ pretty_score => short_score($info, $pos_calculating, $mpv),
pretty_move => $pretty_move,
pv_pretty => \@pretty_pv,
};
}
sub short_score {
- my ($info, $pos, $mpv, $invert) = @_;
-
- $invert //= 0;
- if ($pos->{'toplay'} eq 'B') {
- $invert = !$invert;
- }
+ my ($info, $pos, $mpv) = @_;
+ my $invert = ($pos->{'toplay'} eq 'B');
if (defined($info->{'score_mate' . $mpv})) {
if ($invert) {
return sprintf "M%3d", -$info->{'score_mate' . $mpv};
if (exists($info->{'score_cp' . $mpv})) {
my $score = $info->{'score_cp' . $mpv} * 0.01;
if ($score == 0) {
- return " 0.00";
+ if ($info->{'tablebase'}) {
+ return "TB draw";
+ } else {
+ return " 0.00";
+ }
}
if ($invert) {
$score = -$score;
my $score;
if ($mate > 0) {
# Side to move mates
- $mate = 99999 - $mate;
+ $score = 99999 - $mate;
} else {
# Side to move is getting mated (note the double negative for $mate)
- $mate = -99999 - $mate;
+ $score = -99999 - $mate;
}
if ($invert) {
$score = -$score;
my $moves = $pgn->moves;
my @uci_moves = ();
for my $move (@$moves) {
- my ($from_row, $from_col, $to_row, $to_col, $promo) = $pvpos->parse_pretty_move($move);
- push @uci_moves, Board::move_to_uci_notation($from_row, $from_col, $to_row, $to_col, $promo);
- $pvpos = $pvpos->make_move($from_row, $from_col, $to_row, $to_col, $promo);
+ my $uci_move;
+ ($pvpos, $uci_move) = $pvpos->make_pretty_move($move);
+ push @uci_moves, $uci_move;
}
$tb_cache{$fen} = {
result => $pgn->result,
# position in the meantime, we might query a completely
# different position! But that's fine.
} else {
- die "Unknown response state state " . $response->{'Response'}{'StateString'};
+ die "Unknown response state " . $state;
}
# Wait a second before we schedule another one.