From: Steinar H. Gunderson Date: Fri, 15 Jun 2007 22:08:03 +0000 (+0200) Subject: Initial checkin. X-Git-Url: https://git.sesse.net/?p=remoteglot;a=commitdiff_plain;h=758ca7932fa9b9646dcdf1d1d722bdce08f1c71a Initial checkin. --- 758ca7932fa9b9646dcdf1d1d722bdce08f1c71a diff --git a/remoteglot.pl b/remoteglot.pl new file mode 100644 index 0000000..3f84839 --- /dev/null +++ b/remoteglot.pl @@ -0,0 +1,572 @@ +#! /usr/bin/perl + +# +# remoteglot - Connects an abitrary UCI-speaking engine to ICS for easier post-game +# analysis, or for live analysis of relayed games. (Do not use for +# cheating! Cheating is bad for your karma, and your abuser flag.) +# +# Copyright 2007 Steinar H. Gunderson +# Licensed under the GNU General Public License, version 2. +# + +use Net::Telnet; +use FileHandle; +use IPC::Open2; +use Time::HiRes; +use strict; +use warnings; + +$SIG{ALRM} = sub { output_screen(); }; + +$| = 1; + +my $server = "freechess.org"; +my $target = "Sesse"; +# my $engine = "/usr/games/toga2"; +my $engine = "wine rybka22-mpw32.exe"; + +# open the chess engine +my $pid = IPC::Open2::open2(*UCIREAD, *UCIWRITE, $engine); +my %uciinfo = (); +my %ficsinfo = (); +print UCIWRITE "uci\n"; + +# gobble the options +while () { + /uciok/ && last; +} + +print UCIWRITE "setoption name UCI_AnalyseMode value true\n"; +print UCIWRITE "setoption name NalimovPath value c:\\nalimov\n"; +print UCIWRITE "setoption name NalimovUsage value Normally\n"; +print UCIWRITE "ucinewgame\n"; + +print "Chess engine ready.\n"; + +# now talk to FICS +my $t = Net::Telnet->new(Timeout => 10, Prompt => '/fics% /'); +#$t->input_log(\*STDOUT); +$t->open($server); +$t->print("guest"); +$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"); +$t->cmd("observe $target"); + +# main loop +print "FICS ready.\n"; +while (1) { + my $rin = ''; + vec($rin, fileno(UCIREAD), 1) = 1; + vec($rin, fileno($t), 1) = 1; + + my ($nfound, $timeleft) = select($rin, undef, undef, 5.0); + my $sleep = 1.0; + + while (1) { + my $line = $t->getline(Timeout => 0, errmode => 'return'); + last if (!defined($line)); + + chomp $line; + $line =~ tr/\r//d; + if ($line =~ /^<12> /) { + my $fen = style12_to_fen($line); + print UCIWRITE "stop\n"; + print UCIWRITE "position fen $fen\n"; + print UCIWRITE "go infinite\n"; + #print "stop\n"; + #print "position fen $fen\n"; + #print "go infinite\n"; + } + #print "FICS: [$line]\n"; + $sleep = 0; + } + + # any fun on the UCI channel? + if (vec($rin, fileno(UCIREAD), 1)) { + my $line = ; + chomp $line; + $line =~ tr/\r//d; + #print "UCI: $line\n"; + if ($line =~ /^info/) { + my (@infos) = split / /, $line; + shift @infos; + + parse_infos(@infos); + } + $sleep = 0; + + # don't update too often + Time::HiRes::alarm(0.2); + } + + sleep $sleep; +} + +sub parse_infos { + my (@x) = @_; + + while (scalar @x > 0) { + if ($x[0] =~ /^(currmove|currmovenumber|time|nodes|nps|cpuload|hashfull|depth|seldepth|multipv|time|tbhits)$/) { + my $key = shift @x; + my $value = shift @x; + $uciinfo{$key} = $value; + next; + } + if ($x[0] eq 'score') { + shift @x; + + delete $uciinfo{'score_cp'}; + delete $uciinfo{'score_mate'}; + + while ($x[0] =~ /^(cp|mate|lowerbound|upperbound)$/) { + if ($x[0] eq 'cp') { + shift @x; + $uciinfo{'score_cp'} = shift @x; + } elsif ($x[0] eq 'mate') { + shift @x; + $uciinfo{'score_mate'} = shift @x; + } else { + shift @x; + } + } + next; + } + if ($x[0] eq 'pv') { + $uciinfo{'pv'} = [ @x[1..$#x] ]; + last; + } + if ($x[0] eq 'UCI_AnalyseMode' || $x[0] eq 'setting') { + last; + } + + #print "unknown info '$x[0]', trying to recover...\n"; + #shift @x; + die "Unknown info '" . join(',', @x) . "'"; + + } +} + +sub style12_to_fen { + my $str = shift; + my (@x) = split / /, $str; + + $ficsinfo{'board'} = [ @x[1..8] ]; + $ficsinfo{'toplay'} = $x[9]; + + # the board itself + my (@board) = @x[1..8]; + for my $rank (0..7) { + $board[$rank] =~ s/(-+)/length($1)/ge; + } + my $fen = join('/', @board); + + # white/black to move + $fen .= " "; + $fen .= lc($x[9]); + + # castling + my $castling = ""; + $castling .= "K" if ($x[11] == 1); + $castling .= "Q" if ($x[12] == 1); + $castling .= "k" if ($x[13] == 1); + $castling .= "q" if ($x[14] == 1); + $castling = "-" if ($castling eq ""); + $fen .= " "; + $fen .= $castling; + + # en passant + my $ep = "-"; + if ($x[10] != -1) { + $ep = (qw(a b c d e f g h))[$x[10]]; + if ($x[9] eq 'B') { + $ep .= "3"; + } else { + $ep .= "6"; + } + } + $fen .= " "; + $fen .= $ep; + + # half-move clock + $fen .= " "; + $fen .= $x[15]; + + # full-move clock + $fen .= " "; + $fen .= $x[26]; + + return $fen; +} + +sub prettyprint_pv { + my ($board, @pvs) = @_; + + if (scalar @pvs == 0 || !defined($pvs[0])) { + return (); + } + + my $pv = shift @pvs; + my $from_col = ord(substr($pv, 0, 1)) - ord('a'); + my $from_row = 7 - (ord(substr($pv, 1, 1)) - ord('1')); + my $to_col = ord(substr($pv, 2, 1)) - ord('a'); + my $to_row = 7 - (ord(substr($pv, 3, 1)) - ord('1')); + + my $pretty; + my $piece = substr($board->[$from_row], $from_col, 1); + + # white short castling + if ($pv eq 'e1g1' && $piece eq 'K') { + my @nb = @$board; + + # king + substr($nb[7], 4, 1, '-'); + substr($nb[7], 6, 1, $piece); + + # rook + substr($nb[7], 7, 1, '-'); + substr($nb[7], 5, 1, 'R'); + + return ('0-0', prettyprint_pv(\@nb, @pvs)); + } + + # white long castling + if ($pv eq 'e1b1' && $piece eq 'K') { + my @nb = @$board; + + # king + substr($nb[7], 4, 1, '-'); + substr($nb[7], 2, 1, $piece); + + # rook + substr($nb[7], 0, 1, '-'); + substr($nb[7], 2, 1, 'R'); + + return ('0-0-0', prettyprint_pv(\@nb, @pvs)); + } + + # black short castling + if ($pv eq 'e8g8' && $piece eq 'k') { + my @nb = @$board; + + # king + substr($nb[0], 4, 1, '-'); + substr($nb[0], 6, 1, $piece); + + # rook + substr($nb[0], 7, 1, '-'); + substr($nb[0], 5, 1, 'R'); + + return ('0-0', prettyprint_pv(\@nb, @pvs)); + } + + # black long castling + if ($pv eq 'e8b8' && $piece eq 'k') { + my @nb = @$board; + + # king + substr($nb[0], 4, 1, '-'); + substr($nb[0], 2, 1, $piece); + + # rook + substr($nb[0], 0, 1, '-'); + substr($nb[0], 2, 1, 'R'); + + return ('0-0-0', prettyprint_pv(\@nb, @pvs)); + } + + # check if the from-piece is a pawn + if (lc($piece) eq 'p') { + # attack? + if (substr($board->[$to_row], $to_col, 1) ne '-') { + $pretty = substr($pv, 0, 1) . 'x' . substr($pv, 2, 2); + } else { + $pretty = substr($pv, 2, 2); + + if (length($pv) == 5) { + # promotion + $pretty .= "="; + $pretty .= uc(substr($pv, 4, 1)); + + if ($piece eq 'p') { + $piece = substr($pv, 4, 1); + } else { + $piece = uc(substr($pv, 4, 1)); + } + } + } + } else { + $pretty = uc($piece); + + # see how many of these pieces could go here, in all + my $num_total = 0; + for my $col (0..7) { + for my $row (0..7) { + next unless (substr($board->[$row], $col, 1) eq $piece); + ++$num_total if (can_reach($board, $piece, $row, $col, $to_row, $to_col)); + } + } + + # see how many of these pieces from the given row could go here + my $num_row = 0; + for my $col (0..7) { + next unless (substr($board->[$from_row], $col, 1) eq $piece); + ++$num_row if (can_reach($board, $piece, $from_row, $col, $to_row, $to_col)); + } + + # and same for columns + my $num_col = 0; + for my $row (0..7) { + next unless (substr($board->[$row], $from_col, 1) eq $piece); + ++$num_col if (can_reach($board, $piece, $row, $from_col, $to_row, $to_col)); + } + + # see if we need to disambiguate + if ($num_total > 1) { + if ($num_col == 1) { + $pretty .= substr($pv, 0, 1); + } elsif ($num_row == 1) { + $pretty .= substr($pv, 1, 1); + } else { + $pretty .= substr($pv, 0, 2); + } + } + + # attack? + if (substr($board->[$to_row], $to_col, 1) ne '-') { + $pretty .= 'x'; + } + + $pretty .= substr($pv, 2, 2); + } + + # update the board + my @nb = @$board; + substr($nb[$from_row], $from_col, 1, '-'); + substr($nb[$to_row], $to_col, 1, $piece); + + if (in_mate(\@nb)) { + $pretty .= '#'; + } elsif (in_check(\@nb) ne 'none') { + $pretty .= '+'; + } + + return ($pretty, prettyprint_pv(\@nb, @pvs)); +} + +sub output_screen { + #return; + + print "cAnalysis:\n"; + if (defined($uciinfo{'score_mate'})) { + printf " Mate in %d\n", $uciinfo{'score_mate'}; + } else { + my $score = $uciinfo{'score_cp'} * 0.01; + if ($ficsinfo{'toplay'} eq 'B') { + $score = -$score; + } + printf " Score: %+5.2f\n", $score; + } + print " PV: ", join(', ', prettyprint_pv($ficsinfo{'board'}, @{$uciinfo{'pv'}})); + print "\n"; + printf " %u nodes, %7u nodes/sec, depth %u ply", + $uciinfo{'nodes'}, $uciinfo{'nps'}, $uciinfo{'depth'}; + if (exists($uciinfo{'tbhits'})) { + printf ", %u Nalimov hits", $uciinfo{'tbhits'}; + } + if (exists($uciinfo{'seldepth'})) { + printf " (%u selective)", $uciinfo{'seldepth'}; + } + print "\n\n"; +} + +sub find_kings { + my $board = shift; + my ($wkr, $wkc, $bkr, $bkc); + + for my $row (0..7) { + for my $col (0..7) { + my $piece = substr($board->[$row], $col, 1); + if ($piece eq 'K') { + ($wkr, $wkc) = ($row, $col); + } elsif ($piece eq 'k') { + ($bkr, $bkc) = ($row, $col); + } + } + } + + return ($wkr, $wkc, $bkr, $bkc); +} + +sub in_mate { + my $board = shift; + my $check = in_check($board); + return 0 if ($check eq 'none'); + + # try all possible moves for the side in check + for my $row (0..7) { + for my $col (0..7) { + my $piece = substr($board->[$row], $col, 1); + next if ($piece eq '-'); + + if ($check eq 'white') { + next if ($piece eq lc($piece)); + } else { + next if ($piece eq uc($piece)); + } + + for my $dest_row (0..7) { + for my $dest_col (0..7) { + next if ($row == $dest_row && $col == $dest_col); + next unless (can_reach($board, $piece, $row, $col, $dest_row, $dest_col)); + + my @nb = @$board; + substr($nb[$row], $col, 1, '-'); + substr($nb[$dest_row], $dest_col, 1, $piece); + + my $new_check = in_check(\@nb); + return 0 if ($new_check ne $check && $new_check ne 'both'); + } + } + } + } + + # nothing to do; mate + return 1; +} + +sub in_check { + my $board = shift; + my ($black_check, $white_check) = (0, 0); + + my ($wkr, $wkc, $bkr, $bkc) = find_kings($board); + + # check all pieces for the possibility of threatening the two kings + for my $row (0..7) { + for my $col (0..7) { + my $piece = substr($board->[$row], $col, 1); + next if ($piece eq '-' || lc($piece) eq 'k'); + + if (uc($piece) eq $piece) { + # white piece + $black_check = 1 if (can_reach($board, $piece, $row, $col, $bkr, $bkc)); + } else { + # black piece + $white_check = 1 if (can_reach($board, $piece, $row, $col, $wkr, $wkc)); + } + } + } + + if ($black_check && $white_check) { + return 'both'; + } elsif ($black_check) { + return 'black'; + } elsif ($white_check) { + return 'white'; + } else { + return 'none'; + } +} + +sub can_reach { + my ($board, $piece, $from_row, $from_col, $to_row, $to_col) = @_; + + # can't eat your own piece + my $dest_piece = substr($board->[$to_row], $to_col, 1); + if ($dest_piece ne '-') { + return 0 if (($piece eq lc($piece)) == ($dest_piece eq lc($dest_piece))); + } + + if (lc($piece) eq 'k') { + return (abs($from_row - $to_row) <= 1 && abs($from_col - $to_col) <= 1); + } + if (lc($piece) eq 'r') { + return 0 unless ($from_row == $to_row || $from_col == $to_col); + + # check that there's a clear passage + if ($from_row == $to_row) { + if ($from_col > $to_col) { + ($to_col, $from_col) = ($from_col, $to_col); + } + + for my $c (($from_col+1)..($to_col-1)) { + my $middle_piece = substr($board->[$to_row], $c, 1); + return 0 if ($middle_piece ne '-'); + } + + return 1; + } else { + if ($from_row > $to_row) { + ($to_row, $from_row) = ($from_row, $to_row); + } + + for my $r (($from_row+1)..($to_row-1)) { + my $middle_piece = substr($board->[$r], $to_col, 1); + return 0 if ($middle_piece ne '-'); + } + + return 1; + } + } + if (lc($piece) eq 'b') { + return 0 unless (abs($from_row - $to_row) == abs($from_col - $to_col)); + + my $dr = ($to_row - $from_row) / abs($to_row - $from_row); + my $dc = ($to_col - $from_col) / abs($to_col - $from_col); + + my $r = $from_row + $dr; + my $c = $from_col + $dc; + + while ($r != $to_row) { + my $middle_piece = substr($board->[$r], $c, 1); + return 0 if ($middle_piece ne '-'); + + $r += $dr; + $c += $dc; + } + + return 1; + } + if (lc($piece) eq 'n') { + my $diff_r = abs($from_row - $to_row); + my $diff_c = abs($from_col - $to_col); + return 1 if ($diff_r == 2 && $diff_c == 1); + return 1 if ($diff_r == 1 && $diff_c == 2); + return 0; + } + if ($piece eq 'q') { + return (can_reach($board, 'r', $from_row, $from_col, $to_row, $to_col) || + can_reach($board, 'b', $from_row, $from_col, $to_row, $to_col)); + } + if ($piece eq 'Q') { + return (can_reach($board, 'R', $from_row, $from_col, $to_row, $to_col) || + can_reach($board, 'B', $from_row, $from_col, $to_row, $to_col)); + } + if ($piece eq 'p') { + # black pawn + if ($to_col == $from_col && $to_row == $from_row + 1) { + return ($dest_piece eq '-'); + } + if (abs($to_col - $from_col) == 1 && $to_row == $from_row + 1) { + return ($dest_piece ne '-'); + } + return 0; + } + if ($piece eq 'P') { + # white pawn + if ($to_col == $from_col && $to_row == $from_row - 1) { + return ($dest_piece eq '-'); + } + if (abs($to_col - $from_col) == 1 && $to_row == $from_row - 1) { + return ($dest_piece ne '-'); + } + return 0; + } + + # unknown piece + return 0; +}