+
+my %book_cache = ();
+sub book_info {
+ my ($fen, $board, $toplay) = @_;
+
+ if (exists($book_cache{$fen})) {
+ return $book_cache{$fen};
+ }
+
+ my $ret = `./booklook $fen`;
+ return "" if ($ret =~ /Not found/ || $ret eq '');
+
+ my @moves = ();
+
+ for my $m (split /\n/, $ret) {
+ my ($move, $annotation, $win, $draw, $lose, $rating, $rating_div) = split /,/, $m;
+
+ my $pmove;
+ if ($move eq '') {
+ $pmove = '(current)';
+ } else {
+ ($pmove) = prettyprint_pv($board, $move);
+ $pmove .= $annotation;
+ }
+
+ my $score;
+ if ($toplay eq 'W') {
+ $score = 1.0 * $win + 0.5 * $draw + 0.0 * $lose;
+ } else {
+ $score = 0.0 * $win + 0.5 * $draw + 1.0 * $lose;
+ }
+ my $n = $win + $draw + $lose;
+
+ my $percent;
+ if ($n == 0) {
+ $percent = " ";
+ } else {
+ $percent = sprintf "%4u%%", int(100.0 * $score / $n + 0.5);
+ }
+
+ push @moves, [ $pmove, $n, $percent, $rating ];
+ }
+
+ @moves[1..$#moves] = sort { $b->[2] cmp $a->[2] } @moves[1..$#moves];
+
+ my $text = "Book moves:\n\n Perf. N Rating\n\n";
+ for my $m (@moves) {
+ $text .= sprintf " %-10s %s %6u %4s\n", $m->[0], $m->[2], $m->[1], $m->[3]
+ }
+
+ return $text;
+}
+
+sub open_engine {
+ my ($cmdline, $tag) = @_;
+ my ($uciread, $uciwrite);
+ my $pid = IPC::Open2::open2($uciread, $uciwrite, $cmdline);
+
+ my $engine = {
+ pid => $pid,
+ read => $uciread,
+ readbuf => '',
+ write => $uciwrite,
+ info => {},
+ ids => {},
+ tag => $tag,
+ };
+
+ uciprint($engine, "uci");
+
+ # gobble the options
+ while (<$uciread>) {
+ /uciok/ && last;
+ handle_uci($engine, $_);
+ }
+
+ return $engine;
+}
+
+sub read_lines {
+ my $engine = shift;
+
+ #
+ # Read until we've got a full line -- if the engine sends part of
+ # a line and then stops we're pretty much hosed, but that should
+ # never happen.
+ #
+ while ($engine->{'readbuf'} !~ /\n/) {
+ my $tmp;
+ my $ret = sysread $engine->{'read'}, $tmp, 4096;
+
+ if (!defined($ret)) {
+ next if ($!{EINTR});
+ die "error in reading from the UCI engine: $!";
+ } elsif ($ret == 0) {
+ die "EOF from UCI engine";
+ }
+
+ $engine->{'readbuf'} .= $tmp;
+ }
+
+ # Blah.
+ my @lines = ();
+ while ($engine->{'readbuf'} =~ s/^([^\n]*)\n//) {
+ my $line = $1;
+ $line =~ tr/\r\n//d;
+ push @lines, $line;
+ }
+ return @lines;
+}
+
+sub col_letter_to_num {
+ return ord(shift) - ord('a');
+}
+
+sub row_letter_to_num {
+ return 7 - (ord(shift) - ord('1'));
+}
+
+sub move_to_uci_notation {
+ my ($from_row, $from_col, $to_row, $to_col, $promo) = @_;
+ $promo //= "";
+ return sprintf("%c%d%c%d%s", ord('a') + $from_col, 8 - $from_row, ord('a') + $to_col, 8 - $to_row, $promo);
+}