X-Git-Url: https://git.sesse.net/?p=remoteglot;a=blobdiff_plain;f=remoteglot.pl;h=0522e52888fca392a6f0936d337d76719c79e417;hp=c65c7e8a29fdafa8106a6e8610a3126be39c34f2;hb=731dad8cbe2455aefe534bddce20fca85abd3d2e;hpb=b6a17e8068a802500c745d0501e3ebd58ab33929 diff --git a/remoteglot.pl b/remoteglot.pl index c65c7e8..0522e52 100755 --- a/remoteglot.pl +++ b/remoteglot.pl @@ -13,6 +13,7 @@ use Net::Telnet; use FileHandle; use IPC::Open2; use Time::HiRes; +use JSON::XS; use strict; use warnings; @@ -24,8 +25,7 @@ my $engine2_cmdline = "./stockfish_13111119_x64_modern_sse42"; my $telltarget = undef; # undef to be silent my @tell_intervals = (5, 20, 60, 120, 240, 480, 960); # after each move my $uci_assume_full_compliance = 0; # dangerous :-) -my $update_max_interval = 2.0; -my $second_engine_start_depth = 8; +my $update_max_interval = 1.0; my @masters = ( 'Sesse', 'Sessse', @@ -35,7 +35,7 @@ my @masters = ( ); # Program starts here -$SIG{ALRM} = sub { output_screen(); }; +$SIG{ALRM} = sub { output(); }; my $latest_update = undef; $| = 1; @@ -59,8 +59,7 @@ my $engine2 = open_engine($engine2_cmdline, 'E2'); my ($last_move, $last_tell); my $last_text = ''; my $last_told_text = ''; -my ($pos_waiting, $pos_calculating, $move_calculating_second_engine); -my %refutation_moves = (); +my ($pos_waiting, $pos_calculating, $pos_calculating_second_engine); uciprint($engine, "setoption name UCI_AnalyseMode value true"); # uciprint($engine, "setoption name NalimovPath value /srv/tablebase"); @@ -74,7 +73,7 @@ uciprint($engine2, "setoption name UCI_AnalyseMode value true"); uciprint($engine2, "setoption name NalimovUsage value Rarely"); uciprint($engine2, "setoption name Hash value 1024"); uciprint($engine2, "setoption name Threads value 8"); -# uciprint($engine2, "setoption name MultiPV value 2"); +uciprint($engine2, "setoption name MultiPV value 500"); uciprint($engine2, "ucinewgame"); print "Chess engine ready.\n"; @@ -144,12 +143,12 @@ while (1) { $pos_calculating = $pos; } - %refutation_moves = calculate_refutation_moves($pos); - if (defined($move_calculating_second_engine)) { + if (defined($pos_calculating_second_engine)) { uciprint($engine2, "stop"); - $move_calculating_second_engine = undef; } else { - give_new_move_to_second_engine($pos); + uciprint($engine2, "position fen " . $pos->{'fen'}); + uciprint($engine2, "go infinite"); + $pos_calculating_second_engine = $pos; } $engine->{'info'} = {}; @@ -193,7 +192,7 @@ while (1) { } $sleep = 0; - output_screen(); + output(); } if ($nfound > 0 && vec($rout, fileno($engine2->{'read'}), 1) == 1) { my @lines = read_lines($engine2); @@ -203,7 +202,7 @@ while (1) { } $sleep = 0; - output_screen(); + output(); } sleep $sleep; @@ -239,14 +238,11 @@ sub handle_uci { $pos_waiting = undef; } } else { - if (defined($move_calculating_second_engine)) { - my $move = $refutation_moves{$move_calculating_second_engine}; - $move->{'pv'} = $engine->{'info'}{'pv'} // $engine->{'info'}{'pv1'}; - $move->{'score_cp'} = $engine->{'info'}{'score_cp'} // $engine->{'info'}{'score_cp1'} // 0; - $move->{'score_mate'} = $engine->{'info'}{'score_mate'} // $engine->{'info'}{'score_mate1'}; - $move->{'toplay'} = $pos_calculating->{'toplay'}; - } - give_new_move_to_second_engine($pos_waiting // $pos_calculating); + $engine2->{'info'} = {}; + my $pos = $pos_waiting // $pos_calculating; + uciprint($engine2, "position fen " . $pos->{'fen'}); + uciprint($engine2, "go infinite"); + $pos_calculating_second_engine = $pos; } } } @@ -257,10 +253,19 @@ sub parse_infos { my $info = $engine->{'info'}; + # Search for "multipv" first of all, since e.g. Stockfish doesn't put it first. + for my $i (0..$#x - 1) { + if ($x[$i] =~ 'multipv') { + $mpv = $x[$i + 1]; + next; + } + } + while (scalar @x > 0) { if ($x[0] =~ 'multipv') { + # Dealt with above + shift @x; shift @x; - $mpv = shift @x; next; } if ($x[0] =~ /^(currmove|currmovenumber|cpuload)$/) { @@ -339,6 +344,11 @@ sub style12_to_pos { $pos{'black_castle_q'} = $x[14]; $pos{'time_to_100move_rule'} = $x[15]; $pos{'move_num'} = $x[26]; + if ($x[27] =~ /([a-h][1-8])-([a-h][1-8])/) { + $pos{'last_move_uci'} = $1 . $2; + } else { + $pos{'last_move_uci'} = undef; + } $pos{'last_move'} = $x[29]; $pos{'fen'} = make_fen(\%pos); @@ -619,7 +629,7 @@ sub prettyprint_pv { return ($pretty, prettyprint_pv($nb, @pvs)); } -sub output_screen { +sub output { #return; return if (!defined($pos_calculating)); @@ -630,11 +640,22 @@ sub output_screen { Time::HiRes::alarm($update_max_interval + 0.01 - $age); return; } - $latest_update = [Time::HiRes::gettimeofday]; - + my $info = $engine->{'info'}; - my $id = $engine->{'id'}; - + + # + # Some programs _always_ report MultiPV, even with only one PV. + # In this case, we simply use that data as if MultiPV was never + # specified. + # + if (exists($info->{'pv1'}) && !exists($info->{'pv2'})) { + for my $key (qw(pv score_cp score_mate nodes nps depth seldepth tbhits)) { + if (exists($info->{$key . '1'})) { + $info->{$key} = $info->{$key . '1'}; + } + } + } + # # Check the PVs first. if they're invalid, just wait, as our data # is most likely out of sync. This isn't a very good solution, as @@ -657,6 +678,15 @@ sub output_screen { return; } + output_screen(); + output_json(); + $latest_update = [Time::HiRes::gettimeofday]; +} + +sub output_screen { + my $info = $engine->{'info'}; + my $id = $engine->{'id'}; + my $text = 'Analysis'; if ($pos_calculating->{'last_move'} ne 'none') { if ($pos_calculating->{'toplay'} eq 'W') { @@ -677,19 +707,6 @@ sub output_screen { return unless (exists($pos_calculating->{'board'})); - # - # Some programs _always_ report MultiPV, even with only one PV. - # In this case, we simply use that data as if MultiPV was never - # specified. - # - if (exists($info->{'pv1'}) && !exists($info->{'pv2'})) { - for my $key (qw(pv score_cp score_mate nodes nps depth seldepth tbhits)) { - if (exists($info->{$key . '1'})) { - $info->{$key} = $info->{$key . '1'}; - } - } - } - if (exists($info->{'pv1'}) && exists($info->{'pv2'})) { # multi-PV my $mpv = 1; @@ -728,6 +745,9 @@ sub output_screen { $text .= sprintf " %u nodes, %7u nodes/sec, depth %u ply", $info->{'nodes'}, $info->{'nps'}, $info->{'depth'}; } + if (exists($info->{'seldepth'})) { + $text .= sprintf " (%u selective)", $info->{'seldepth'}; + } if (exists($info->{'tbhits'}) && $info->{'tbhits'} > 0) { if ($info->{'tbhits'} == 1) { $text .= ", one Nalimov hit"; @@ -735,31 +755,29 @@ sub output_screen { $text .= sprintf ", %u Nalimov hits", $info->{'tbhits'}; } } - if (exists($info->{'seldepth'})) { - $text .= sprintf " (%u selective)", $info->{'seldepth'}; - } $text .= "\n\n"; } #$text .= book_info($pos_calculating->{'fen'}, $pos_calculating->{'board'}, $pos_calculating->{'toplay'}); my @refutation_lines = (); - for my $move (keys %refutation_moves) { + for (my $mpv = 1; $mpv < 500; ++$mpv) { + my $info = $engine2->{'info'}; + last if (!exists($info->{'pv' . $mpv})); eval { - my $m = $refutation_moves{$move}; - die if ($m->{'depth'} < $second_engine_start_depth); - my $pretty_move = join('', prettyprint_pv($pos_calculating->{'board'}, $move)); - my @pretty_pv = prettyprint_pv($pos_calculating->{'board'}, $move, @{$m->{'pv'}}); + my $pv = $info->{'pv' . $mpv}; + + my $pretty_move = join('', prettyprint_pv($pos_calculating_second_engine->{'board'}, $pv->[0])); + my @pretty_pv = prettyprint_pv($pos_calculating_second_engine->{'board'}, @$pv); if (scalar @pretty_pv > 5) { @pretty_pv = @pretty_pv[0..4]; push @pretty_pv, "..."; } - #my $key = score_sort_key($refutation_moves{$move}, $pos_calculating, '', 1); my $key = $pretty_move; my $line = sprintf(" %-6s %6s %3s %s", $pretty_move, - short_score($refutation_moves{$move}, $pos_calculating, '', 1), - "d" . $m->{'depth'}, + short_score($info, $pos_calculating_second_engine, $mpv, 0), + "d" . $info->{'depth' . $mpv}, join(', ', @pretty_pv)); push @refutation_lines, [ $key, $line ]; }; @@ -838,6 +856,56 @@ sub output_screen { } } +sub output_json { + my $info = $engine->{'info'}; + + my $json = {}; + $json->{'position'} = $pos_calculating; + $json->{'id'} = $engine->{'id'}; + $json->{'score'} = long_score($info, $pos_calculating, ''); + + $json->{'nodes'} = $info->{'nodes'}; + $json->{'nps'} = $info->{'nps'}; + $json->{'depth'} = $info->{'depth'}; + $json->{'tbhits'} = $info->{'tbhits'}; + $json->{'seldepth'} = $info->{'seldepth'}; + + # single-PV only for now + $json->{'pv_uci'} = $info->{'pv'}; + $json->{'pv_pretty'} = [ prettyprint_pv($pos_calculating->{'board'}, @{$info->{'pv'}}) ]; + + my %refutation_lines = (); + my @refutation_lines = (); + for (my $mpv = 1; $mpv < 500; ++$mpv) { + my $info = $engine2->{'info'}; + my $pretty_move = ""; + my @pretty_pv = (); + last if (!exists($info->{'pv' . $mpv})); + + eval { + my $pv = $info->{'pv' . $mpv}; + my $pretty_move = join('', prettyprint_pv($pos_calculating->{'board'}, $pv->[0])); + my @pretty_pv = prettyprint_pv($pos_calculating->{'board'}, @$pv); + $refutation_lines{$pv->[0]} = { + 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_move => $pretty_move, + pv_pretty => \@pretty_pv, + }; + $refutation_lines{$pv->[0]}->{'pv_uci'} = $pv; + }; + } + $json->{'refutation_lines'} = \%refutation_lines; + + open my $fh, ">analysis.json.tmp" + or return; + print $fh JSON::XS::encode_json($json); + close $fh; + rename("analysis.json.tmp", "analysis.json"); +} + sub find_kings { my $board = shift; my ($wkr, $wkc, $bkr, $bkc); @@ -1051,9 +1119,9 @@ sub short_score { if (defined($info->{'score_mate' . $mpv})) { if ($invert) { - return sprintf "M%3d", $info->{'score_mate' . $mpv}; - } else { return sprintf "M%3d", -$info->{'score_mate' . $mpv}; + } else { + return sprintf "M%3d", $info->{'score_mate' . $mpv}; } } else { if (exists($info->{'score_cp' . $mpv})) { @@ -1074,23 +1142,15 @@ sub short_score { sub score_sort_key { my ($info, $pos, $mpv, $invert) = @_; - $invert //= 0; - if ($pos->{'toplay'} eq 'B') { - $invert = !$invert; - } - if (defined($info->{'score_mate' . $mpv})) { if ($invert) { - return -(99999 - $info->{'score_mate' . $mpv}); - } else { return 99999 - $info->{'score_mate' . $mpv}; + } else { + return -(99999 - $info->{'score_mate' . $mpv}); } } else { if (exists($info->{'score_cp' . $mpv})) { my $score = $info->{'score_cp' . $mpv}; - if ($score == 0) { - return " 0.00"; - } if ($invert) { $score = -$score; } @@ -1117,6 +1177,9 @@ sub long_score { } else { if (exists($info->{'score_cp' . $mpv})) { my $score = $info->{'score_cp' . $mpv} * 0.01; + if ($score == 0) { + return "Score: 0.00"; + } if ($pos->{'toplay'} eq 'B') { $score = -$score; } @@ -1237,74 +1300,6 @@ sub read_lines { return @lines; } -# Find all possible legal moves. -sub calculate_refutation_moves { - my $pos = shift; - my $board = $pos->{'board'}; - my %refutation_moves = (); - for my $col (0..7) { - for my $row (0..7) { - my $piece = substr($board->[$row], $col, 1); - - # Check that there's a piece of the right color on this square. - next if ($piece eq '-'); - if ($pos->{'toplay'} eq 'W') { - next if ($piece ne uc($piece)); - } else { - next if ($piece ne lc($piece)); - } - - for my $to_col (0..7) { - for my $to_row (0..7) { - next if ($col == $to_col && $row == $to_row); - next unless (can_reach($board, $piece, $row, $col, $to_row, $to_col)); - - my $promo = ""; # FIXME - my $nb = make_move($board, $row, $col, $to_row, $to_col, $promo); - my $check = in_check($nb); - next if ($check eq 'both'); - if ($pos->{'toplay'} eq 'W') { - next if ($check eq 'white'); - } else { - next if ($check eq 'black'); - } - my $move = move_to_uci_notation($row, $col, $to_row, $to_col, $promo); - $refutation_moves{$move} = { depth => $second_engine_start_depth - 1, score_cp => 0, pv => '' }; - } - } - } - } - return %refutation_moves; -} - -sub give_new_move_to_second_engine { - my $pos = shift; - - # Find the move that's been analyzed the shortest but is most promising. - # Tie-break on UCI move representation. - my $best_move = undef; - for my $move (sort keys %refutation_moves) { - if (!defined($best_move)) { - $best_move = $move; - next; - } - my $best = $refutation_moves{$best_move}; - my $this = $refutation_moves{$move}; - - if ($this->{'depth'} < $best->{'depth'} || - ($this->{'depth'} == $best->{'depth'} && $this->{'score_cp'} < $best->{'score_cp'})) { - $best_move = $move; - next; - } - } - - my $m = $refutation_moves{$best_move}; - ++$m->{'depth'}; - uciprint($engine2, "position fen " . $pos->{'fen'} . " moves " . $best_move); - uciprint($engine2, "go depth " . $m->{'depth'}); - $move_calculating_second_engine = $best_move; -} - sub col_letter_to_num { return ord(shift) - ord('a'); }