Change to unbuffered reading for the UCI channel.
[remoteglot] / remoteglot.pl
1 #! /usr/bin/perl
2
3 #
4 # remoteglot - Connects an abitrary UCI-speaking engine to ICS for easier post-game
5 #              analysis, or for live analysis of relayed games. (Do not use for
6 #              cheating! Cheating is bad for your karma, and your abuser flag.)
7 #
8 # Copyright 2007 Steinar H. Gunderson <sgunderson@bigfoot.com>
9 # Licensed under the GNU General Public License, version 2.
10 #
11
12 use Net::Telnet;
13 use FileHandle;
14 use IPC::Open2;
15 use Time::HiRes;
16 use strict;
17 use warnings;
18
19 # Configuration
20 my $server = "freechess.org";
21 my $target = "22";
22 # my $engine = "/usr/games/toga2";
23 my $engine = "wine Rybkav2.3.2a.mp.w32.exe";
24 my $telltarget = undef;   # undef to be silent
25 my @tell_intervals = (5, 20, 60, 120, 240, 480, 960);  # after each move
26 my $uci_assume_full_compliance = 1;                    # dangerous :-)
27
28 # Program starts here
29 $SIG{ALRM} = sub { output_screen(); };
30
31 $| = 1;
32
33 open(FICSLOG, ">ficslog.txt")
34         or die "ficslog.txt: $!";
35 print FICSLOG "Log starting.\n";
36 select(FICSLOG);
37 $| = 1;
38
39 open(UCILOG, ">ucilog.txt")
40         or die "ucilog.txt: $!";
41 print UCILOG "Log starting.\n";
42 select(UCILOG);
43 $| = 1;
44 select(STDOUT);
45
46 # open the chess engine
47 my $pid = IPC::Open2::open2(*UCIREAD, *UCIWRITE, $engine);
48 my %uciinfo = ();
49 my %uciid = ();
50 my ($last_move, $last_tell);
51 my $last_text = '';
52 my $last_told_text = '';
53 my ($pos_waiting, $pos_calculating);
54
55 uciprint("uci");
56
57 # gobble the options
58 while (<UCIREAD>) {
59         /uciok/ && last;
60         handle_uci($_);
61 }
62
63 uciprint("setoption name UCI_AnalyseMode value true");
64 # uciprint("setoption name Preserve Analysis value true");
65 uciprint("setoption name NalimovPath value c:\\nalimov");
66 uciprint("setoption name NalimovUsage value Rarely");
67 uciprint("setoption name Hash value 1024");
68 uciprint("setoption name MultiPV value 3");
69 # uciprint("setoption name Contempt value 1000");
70 # uciprint("setoption name Outlook value Ultra Optimistic");
71 uciprint("ucinewgame");
72
73 print "Chess engine ready.\n";
74
75 # now talk to FICS
76 my $t = Net::Telnet->new(Timeout => 10, Prompt => '/fics% /');
77 $t->input_log(\*FICSLOG);
78 $t->open($server);
79 $t->print("guest");
80 $t->waitfor('/Press return to enter the server/');
81 $t->cmd("");
82
83 # set some options
84 $t->cmd("set shout 0");
85 $t->cmd("set seek 0");
86 $t->cmd("set style 12");
87 $t->cmd("observe $target");
88
89 # main loop
90 print "FICS ready.\n";
91 while (1) {
92         my $rin = '';
93         my $rout;
94         vec($rin, fileno(UCIREAD), 1) = 1;
95         vec($rin, fileno($t), 1) = 1;
96
97         my ($nfound, $timeleft) = select($rout=$rin, undef, undef, 5.0);
98         my $sleep = 1.0;
99
100         while (1) {
101                 my $line = $t->getline(Timeout => 0, errmode => 'return');
102                 last if (!defined($line));
103
104                 chomp $line;
105                 $line =~ tr/\r//d;
106                 if ($line =~ /^<12> /) {
107                         my $pos = style12_to_fen($line);
108                         
109                         # if this is already in the queue, ignore it
110                         next if (defined($pos_waiting) && $pos->{'fen'} eq $pos_waiting->{'fen'});
111
112                         # if we're already chewing on this and there's nothing else in the queue,
113                         # also ignore it
114                         next if (!defined($pos_waiting) && defined($pos_calculating) &&
115                                  $pos->{'fen'} eq $pos_calculating->{'fen'});
116
117                         # if we're already thinking on something, stop and wait for the engine
118                         # to approve
119                         if (defined($pos_calculating)) {
120                                 if (!defined($pos_waiting)) {
121                                         uciprint("stop");
122                                 }
123                                 if ($uci_assume_full_compliance) {
124                                         $pos_waiting = $pos;
125                                 } else {
126                                         uciprint("position fen " . $pos->{'fen'});
127                                         uciprint("go infinite");
128                                         $pos_calculating = $pos;
129                                 }
130                         } else {
131                                 # it's wrong just to give the FEN (the move history is useful,
132                                 # and per the UCI spec, we should really have sent "ucinewgame"),
133                                 # but it's easier
134                                 uciprint("position fen " . $pos->{'fen'});
135                                 uciprint("go infinite");
136                                 $pos_calculating = $pos;
137                         }
138
139                         %uciinfo = ();
140                         $last_move = time;
141
142                         # 
143                         # Output a command every move to note that we're
144                         # still paying attention -- this is a good tradeoff,
145                         # since if no move has happened in the last half
146                         # hour, the analysis/relay has most likely stopped
147                         # and we should stop hogging server resources.
148                         #
149                         $t->cmd("date");
150                 }
151                 #print "FICS: [$line]\n";
152                 $sleep = 0;
153         }
154         
155         # any fun on the UCI channel?
156         if ($nfound > 0 && vec($rout, fileno(UCIREAD), 1) == 1) {
157                 # 
158                 # Read until we've got a full line -- if the engine sends part of
159                 # a line and then stops we're pretty much hosed, but that should
160                 # never happen.
161                 #
162                 my $line = '';
163                 while ($line !~ /\n/) {
164                         my $tmp;
165                         my $ret = sysread UCIREAD, $tmp, 1;
166
167                         if (!defined($ret)) {
168                                 next if ($!{EINTR});
169                                 die "error in reading from the UCI engine: $!";
170                         } elsif ($ret == 0) {
171                                 die "EOF from UCI engine";
172                         }
173
174                         $line .= $tmp;
175                 }
176
177                 $line =~ tr/\r\n//d;
178                 handle_uci($line);
179                 $sleep = 0;
180
181                 # don't update too often
182                 Time::HiRes::alarm(0.2);
183         }
184
185         sleep $sleep;
186 }
187
188 sub handle_uci {
189         my ($line) = @_;
190
191         chomp $line;
192         $line =~ tr/\r//d;
193         print UCILOG localtime() . " <= $line\n";
194         if ($line =~ /^info/) {
195                 my (@infos) = split / /, $line;
196                 shift @infos;
197
198                 parse_infos(@infos);
199         }
200         if ($line =~ /^id/) {
201                 my (@ids) = split / /, $line;
202                 shift @ids;
203
204                 parse_ids(@ids);
205         }
206         if ($line =~ /^bestmove/ && $uci_assume_full_compliance) {
207                 if (defined($pos_waiting)) {
208                         uciprint("position fen " . $pos_waiting->{'fen'});
209                         uciprint("go infinite");
210
211                         $pos_calculating = $pos_waiting;
212                         $pos_waiting = undef;
213                 }
214         }
215 }
216
217 sub parse_infos {
218         my (@x) = @_;
219         my $mpv = '';
220
221         while (scalar @x > 0) {
222                 if ($x[0] =~ 'multipv') {
223                         shift @x;
224                         $mpv = shift @x;
225                         next;
226                 }
227                 if ($x[0] =~ /^(currmove|currmovenumber|cpuload)$/) {
228                         my $key = shift @x;
229                         my $value = shift @x;
230                         $uciinfo{$key} = $value;
231                         next;
232                 }
233                 if ($x[0] =~ /^(depth|seldepth|hashfull|time|nodes|nps|tbhits)$/) {
234                         my $key = shift @x;
235                         my $value = shift @x;
236                         $uciinfo{$key . $mpv} = $value;
237                         next;
238                 }
239                 if ($x[0] eq 'score') {
240                         shift @x;
241
242                         delete $uciinfo{'score_cp' . $mpv};
243                         delete $uciinfo{'score_mate' . $mpv};
244
245                         while ($x[0] =~ /^(cp|mate|lowerbound|upperbound)$/) {
246                                 if ($x[0] eq 'cp') {
247                                         shift @x;
248                                         $uciinfo{'score_cp' . $mpv} = shift @x;
249                                 } elsif ($x[0] eq 'mate') {
250                                         shift @x;
251                                         $uciinfo{'score_mate' . $mpv} = shift @x;
252                                 } else {
253                                         shift @x;
254                                 }
255                         }
256                         next;
257                 }
258                 if ($x[0] eq 'pv') {
259                         $uciinfo{'pv' . $mpv} = [ @x[1..$#x] ];
260                         last;
261                 }
262                 if ($x[0] eq 'string' || $x[0] eq 'UCI_AnalyseMode' || $x[0] eq 'setting' || $x[0] eq 'contempt') {
263                         last;
264                 }
265
266                 #print "unknown info '$x[0]', trying to recover...\n";
267                 #shift @x;
268                 die "Unknown info '" . join(',', @x) . "'";
269
270         }
271 }
272
273 sub parse_ids {
274         my (@x) = @_;
275
276         while (scalar @x > 0) {
277                 if ($x[0] =~ /^(name|author)$/) {
278                         my $key = shift @x;
279                         my $value = join(' ', @x);
280                         $uciid{$key} = $value;
281                         last;
282                 }
283
284                 # unknown
285                 shift @x;
286         }
287 }
288
289 sub style12_to_fen {
290         my $str = shift;
291         my %pos = ();
292         my (@x) = split / /, $str;
293         
294         $pos{'board'} = [ @x[1..8] ];
295         $pos{'toplay'} = $x[9];
296         
297         # the board itself
298         my (@board) = @x[1..8];
299         for my $rank (0..7) {
300                 $board[$rank] =~ s/(-+)/length($1)/ge;
301         }
302         my $fen = join('/', @board);
303
304         # white/black to move
305         $fen .= " ";
306         $fen .= lc($x[9]);
307
308         # castling
309         my $castling = "";
310         $castling .= "K" if ($x[11] == 1);
311         $castling .= "Q" if ($x[12] == 1);
312         $castling .= "k" if ($x[13] == 1);
313         $castling .= "q" if ($x[14] == 1);
314         $castling = "-" if ($castling eq "");
315         $fen .= " ";
316         $fen .= $castling;
317
318         # en passant
319         my $ep = "-";
320         if ($x[10] != -1) {
321                 my $col = $x[10];
322                 my $nep = (qw(a b c d e f g h))[$col];
323
324                 if ($x[9] eq 'B') {
325                         $nep .= "3";
326                 } else {
327                         $nep .= "6";
328                 }
329
330                 #
331                 # Showing the en passant square when actually no capture can be made
332                 # seems to confuse at least Rybka. Thus, check if there's actually
333                 # a pawn of the opposite side that can do the en passant move, and if
334                 # not, just lie -- it doesn't matter anyway. I'm unsure what's the
335                 # "right" thing as per the standard, though.
336                 #
337                 if ($x[9] eq 'B') {
338                         $ep = $nep if ($col > 0 && substr($pos{'board'}[4], $col-1, 1) eq 'p');
339                         $ep = $nep if ($col < 7 && substr($pos{'board'}[4], $col+1, 1) eq 'p');
340                 } else {
341                         $ep = $nep if ($col > 0 && substr($pos{'board'}[3], $col-1, 1) eq 'P');
342                         $ep = $nep if ($col < 7 && substr($pos{'board'}[3], $col+1, 1) eq 'P');
343                 }
344         }
345         $fen .= " ";
346         $fen .= $ep;
347
348         # half-move clock
349         $fen .= " ";
350         $fen .= $x[15];
351
352         # full-move clock
353         $fen .= " ";
354         $fen .= $x[26];
355
356         $pos{'fen'} = $fen;
357
358         return \%pos;
359 }
360
361 sub prettyprint_pv {
362         my ($board, @pvs) = @_;
363         
364         if (scalar @pvs == 0 || !defined($pvs[0])) {
365                 return ();
366         }
367         
368         my @nb = @$board;
369
370         my $pv = shift @pvs;
371         my $from_col = ord(substr($pv, 0, 1)) - ord('a');
372         my $from_row = 7 - (ord(substr($pv, 1, 1)) - ord('1'));
373         my $to_col   = ord(substr($pv, 2, 1)) - ord('a');
374         my $to_row   = 7 - (ord(substr($pv, 3, 1)) - ord('1'));
375
376         my $pretty;
377         my $piece = substr($board->[$from_row], $from_col, 1);
378
379         if ($piece eq '-') {
380                 die "Invalid move";
381         }
382
383         # white short castling
384         if ($pv eq 'e1g1' && $piece eq 'K') {
385                 # king
386                 substr($nb[7], 4, 1, '-');
387                 substr($nb[7], 6, 1, $piece);
388                 
389                 # rook
390                 substr($nb[7], 7, 1, '-');
391                 substr($nb[7], 5, 1, 'R');
392                                 
393                 return ('0-0', prettyprint_pv(\@nb, @pvs));
394         }
395
396         # white long castling
397         if ($pv eq 'e1c1' && $piece eq 'K') {
398                 # king
399                 substr($nb[7], 4, 1, '-');
400                 substr($nb[7], 2, 1, $piece);
401                 
402                 # rook
403                 substr($nb[7], 0, 1, '-');
404                 substr($nb[7], 3, 1, 'R');
405                                 
406                 return ('0-0-0', prettyprint_pv(\@nb, @pvs));
407         }
408
409         # black short castling
410         if ($pv eq 'e8g8' && $piece eq 'k') {
411                 # king
412                 substr($nb[0], 4, 1, '-');
413                 substr($nb[0], 6, 1, $piece);
414                 
415                 # rook
416                 substr($nb[0], 7, 1, '-');
417                 substr($nb[0], 5, 1, 'r');
418                                 
419                 return ('0-0', prettyprint_pv(\@nb, @pvs));
420         }
421
422         # black long castling
423         if ($pv eq 'e8c8' && $piece eq 'k') {
424                 # king
425                 substr($nb[0], 4, 1, '-');
426                 substr($nb[0], 2, 1, $piece);
427                 
428                 # rook
429                 substr($nb[0], 0, 1, '-');
430                 substr($nb[0], 3, 1, 'r');
431                                 
432                 return ('0-0-0', prettyprint_pv(\@nb, @pvs));
433         }
434
435         # check if the from-piece is a pawn
436         if (lc($piece) eq 'p') {
437                 # attack?
438                 if ($from_col != $to_col) {
439                         $pretty = substr($pv, 0, 1) . 'x' . substr($pv, 2, 2);
440
441                         # en passant?
442                         if (substr($board->[$to_row], $to_col, 1) eq '-') {
443                                 if ($piece eq 'p') {
444                                         substr($nb[$to_row + 1], $to_col, 1, '-');
445                                 } else {
446                                         substr($nb[$to_row - 1], $to_col, 1, '-');
447                                 }
448                         }
449                 } else {
450                         $pretty = substr($pv, 2, 2);
451
452                         if (length($pv) == 5) {
453                                 # promotion
454                                 $pretty .= "=";
455                                 $pretty .= uc(substr($pv, 4, 1));
456
457                                 if ($piece eq 'p') {
458                                         $piece = substr($pv, 4, 1);
459                                 } else {
460                                         $piece = uc(substr($pv, 4, 1));
461                                 }
462                         }
463                 }
464         } else {
465                 $pretty = uc($piece);
466
467                 # see how many of these pieces could go here, in all
468                 my $num_total = 0;
469                 for my $col (0..7) {
470                         for my $row (0..7) {
471                                 next unless (substr($board->[$row], $col, 1) eq $piece);
472                                 ++$num_total if (can_reach($board, $piece, $row, $col, $to_row, $to_col));
473                         }
474                 }
475
476                 # see how many of these pieces from the given row could go here
477                 my $num_row = 0;
478                 for my $col (0..7) {
479                         next unless (substr($board->[$from_row], $col, 1) eq $piece);
480                         ++$num_row if (can_reach($board, $piece, $from_row, $col, $to_row, $to_col));
481                 }
482                 
483                 # and same for columns
484                 my $num_col = 0;
485                 for my $row (0..7) {
486                         next unless (substr($board->[$row], $from_col, 1) eq $piece);
487                         ++$num_col if (can_reach($board, $piece, $row, $from_col, $to_row, $to_col));
488                 }
489                 
490                 # see if we need to disambiguate
491                 if ($num_total > 1) {
492                         if ($num_col == 1) {
493                                 $pretty .= substr($pv, 0, 1);
494                         } elsif ($num_row == 1) {
495                                 $pretty .= substr($pv, 1, 1);
496                         } else {
497                                 $pretty .= substr($pv, 0, 2);
498                         }
499                 }
500
501                 # attack?
502                 if (substr($board->[$to_row], $to_col, 1) ne '-') {
503                         $pretty .= 'x';
504                 }
505
506                 $pretty .= substr($pv, 2, 2);
507         }
508
509         # update the board
510         substr($nb[$from_row], $from_col, 1, '-');
511         substr($nb[$to_row], $to_col, 1, $piece);
512
513         if (in_mate(\@nb)) {
514                 $pretty .= '#';
515         } elsif (in_check(\@nb) ne 'none') {
516                 $pretty .= '+';
517         }
518
519         return ($pretty, prettyprint_pv(\@nb, @pvs));
520 }
521
522 sub output_screen {
523         #return;
524         
525         return if (!defined($pos_calculating));
526
527         #
528         # Check the PVs first. if they're invalid, just wait, as our data
529         # is most likely out of sync. This isn't a very good solution, as
530         # it can frequently miss stuff, but it's good enough for most users.
531         #
532         eval {
533                 my $dummy;
534                 if (exists($uciinfo{'pv'})) {
535                         $dummy = prettyprint_pv($pos_calculating->{'board'}, @{$uciinfo{'pv'}});
536                 }
537         
538                 my $mpv = 1;
539                 while (exists($uciinfo{'pv' . $mpv})) {
540                         $dummy = prettyprint_pv($pos_calculating->{'board'}, @{$uciinfo{'pv' . $mpv}});
541                         ++$mpv;
542                 }
543         };
544         if ($@) {
545                 %uciinfo = ();
546                 return;
547         }
548
549         my $text = '';
550
551         if (exists($uciid{'name'})) {
552                 $text .= "Analysis by $uciid{'name'}:\n\n";
553         } else {
554                 $text .= "Analysis:\n\n";
555         }
556
557         return unless (exists($pos_calculating->{'board'}));
558                 
559         #
560         # Some programs _always_ report MultiPV, even with only one PV.
561         # In this case, we simply use that data as if MultiPV was never
562         # specified.
563         #
564         if (exists($uciinfo{'pv1'}) && !exists($uciinfo{'pv2'})) {
565                 for my $key qw(pv score_cp score_mate nodes nps depth seldepth tbhits) {
566                         if (exists($uciinfo{$key . '1'}) && !exists($uciinfo{$key})) {
567                                 $uciinfo{$key} = $uciinfo{$key . '1'};
568                         }
569                 }
570         }
571
572         if (exists($uciinfo{'pv1'}) && exists($uciinfo{'pv2'})) {
573                 # multi-PV
574                 my $mpv = 1;
575                 while (exists($uciinfo{'pv' . $mpv})) {
576                         $text .= sprintf "  PV%2u", $mpv;
577                         my $score = short_score(\%uciinfo, $pos_calculating, $mpv);
578                         $text .= "  ($score)" if (defined($score));
579
580                         my $tbhits = '';
581                         if (exists($uciinfo{'tbhits' . $mpv})) {
582                                 $tbhits = sprintf ", %u tbhits", $uciinfo{'tbhits' . $mpv};
583                         }
584
585                         if (exists($uciinfo{'nodes' . $mpv}) && exists($uciinfo{'nps' . $mpv}) && exists($uciinfo{'depth' . $mpv})) {
586                                 $text .= sprintf " (%5u kn, %3u kn/s, %2u ply$tbhits)",
587                                         $uciinfo{'nodes' . $mpv} / 1000, $uciinfo{'nps' . $mpv} / 1000, $uciinfo{'depth' . $mpv};
588                         }
589
590                         $text .= ":\n";
591                         $text .= "  " . join(', ', prettyprint_pv($pos_calculating->{'board'}, @{$uciinfo{'pv' . $mpv}})) . "\n";
592                         $text .= "\n";
593                         ++$mpv;
594                 }
595         } else {
596                 # single-PV
597                 my $score = long_score(\%uciinfo, $pos_calculating, '');
598                 $text .= "  $score\n" if defined($score);
599                 $text .=  "  PV: " . join(', ', prettyprint_pv($pos_calculating->{'board'}, @{$uciinfo{'pv'}}));
600                 $text .=  "\n";
601
602                 if (exists($uciinfo{'nodes'}) && exists($uciinfo{'nps'}) && exists($uciinfo{'depth'})) {
603                         $text .= sprintf "  %u nodes, %7u nodes/sec, depth %u ply",
604                                 $uciinfo{'nodes'}, $uciinfo{'nps'}, $uciinfo{'depth'};
605                 }
606                 if (exists($uciinfo{'tbhits'})) {
607                         $text .= sprintf ", %u Nalimov hits", $uciinfo{'tbhits'};
608                 }
609                 if (exists($uciinfo{'seldepth'})) {
610                         $text .= sprintf " (%u selective)", $uciinfo{'seldepth'};
611                 }
612                 $text .=  "\n\n";
613         }
614
615         if ($last_text ne $text) {
616                 print "\e[H\e[2J"; # clear the screen
617                 print $text;
618                 $last_text = $text;
619         }
620
621         # Now construct the tell text, if any
622         return if (!defined($telltarget));
623
624         my $tell_text = '';
625
626         if (exists($uciid{'name'})) {
627                 $tell_text .= "Analysis by $uciid{'name'} -- see http://analysis.sesse.net/ for more information\n";
628         } else {
629                 $tell_text .= "Computer analysis -- http://analysis.sesse.net/ for more information\n";
630         }
631
632         if (exists($uciinfo{'pv1'}) && exists($uciinfo{'pv2'})) {
633                 # multi-PV
634                 my $mpv = 1;
635                 while (exists($uciinfo{'pv' . $mpv})) {
636                         $tell_text .= sprintf "  PV%2u", $mpv;
637                         my $score = short_score(\%uciinfo, $pos_calculating, $mpv);
638                         $tell_text .= "  ($score)" if (defined($score));
639
640                         if (exists($uciinfo{'depth' . $mpv})) {
641                                 $tell_text .= sprintf " (%2u ply)", $uciinfo{'depth' . $mpv};
642                         }
643
644                         $tell_text .= ": ";
645                         $tell_text .= join(', ', prettyprint_pv($pos_calculating->{'board'}, @{$uciinfo{'pv' . $mpv}}));
646                         $tell_text .= "\n";
647                         ++$mpv;
648                 }
649         } else {
650                 # single-PV
651                 my $score = long_score(\%uciinfo, $pos_calculating, '');
652                 $tell_text .= "  $score\n" if defined($score);
653                 $tell_text .= "  PV: " . join(', ', prettyprint_pv($pos_calculating->{'board'}, @{$uciinfo{'pv'}}));
654                 if (exists($uciinfo{'depth'})) {
655                         $tell_text .= sprintf " (depth %u ply)", $uciinfo{'depth'};
656                 }
657                 $tell_text .=  "\n";
658         }
659
660         # see if a new tell is called for -- it is if the delay has expired _and_
661         # this is not simply a repetition of the last one
662         if ($last_told_text ne $tell_text) {
663                 my $now = time;
664                 for my $iv (@tell_intervals) {
665                         last if ($now - $last_move < $iv);
666                         next if ($last_tell - $last_move >= $iv);
667
668                         for my $line (split /\n/, $tell_text) {
669                                 $t->print("tell $telltarget [$target] $line");
670                         }
671
672                         $last_told_text = $text;
673                         $last_tell = $now;
674
675                         last;
676                 }
677         }
678 }
679
680 sub find_kings {
681         my $board = shift;
682         my ($wkr, $wkc, $bkr, $bkc);
683
684         for my $row (0..7) {
685                 for my $col (0..7) {
686                         my $piece = substr($board->[$row], $col, 1);
687                         if ($piece eq 'K') {
688                                 ($wkr, $wkc) = ($row, $col);
689                         } elsif ($piece eq 'k') {
690                                 ($bkr, $bkc) = ($row, $col);
691                         }
692                 }
693         }
694
695         return ($wkr, $wkc, $bkr, $bkc);
696 }
697
698 sub in_mate {
699         my $board = shift;
700         my $check = in_check($board);
701         return 0 if ($check eq 'none');
702
703         # try all possible moves for the side in check
704         for my $row (0..7) {
705                 for my $col (0..7) {
706                         my $piece = substr($board->[$row], $col, 1);
707                         next if ($piece eq '-');
708
709                         if ($check eq 'white') {
710                                 next if ($piece eq lc($piece));
711                         } else {
712                                 next if ($piece eq uc($piece));
713                         }
714
715                         for my $dest_row (0..7) {
716                                 for my $dest_col (0..7) {
717                                         next if ($row == $dest_row && $col == $dest_col);
718                                         next unless (can_reach($board, $piece, $row, $col, $dest_row, $dest_col));
719
720                                         my @nb = @$board;
721                                         substr($nb[$row], $col, 1, '-');
722                                         substr($nb[$dest_row], $dest_col, 1, $piece);
723
724                                         my $new_check = in_check(\@nb);
725                                         return 0 if ($new_check ne $check && $new_check ne 'both');
726                                 }
727                         }
728                 }
729         }
730
731         # nothing to do; mate
732         return 1;
733 }
734
735 sub in_check {
736         my $board = shift;
737         my ($black_check, $white_check) = (0, 0);
738
739         my ($wkr, $wkc, $bkr, $bkc) = find_kings($board);
740
741         # check all pieces for the possibility of threatening the two kings
742         for my $row (0..7) {
743                 for my $col (0..7) {
744                         my $piece = substr($board->[$row], $col, 1);
745                         next if ($piece eq '-');
746                 
747                         if (uc($piece) eq $piece) {
748                                 # white piece
749                                 $black_check = 1 if (can_reach($board, $piece, $row, $col, $bkr, $bkc));
750                         } else {
751                                 # black piece
752                                 $white_check = 1 if (can_reach($board, $piece, $row, $col, $wkr, $wkc));
753                         }
754                 }
755         }
756
757         if ($black_check && $white_check) {
758                 return 'both';
759         } elsif ($black_check) {
760                 return 'black';
761         } elsif ($white_check) {
762                 return 'white';
763         } else {
764                 return 'none';
765         }
766 }
767
768 sub can_reach {
769         my ($board, $piece, $from_row, $from_col, $to_row, $to_col) = @_;
770         
771         # can't eat your own piece
772         my $dest_piece = substr($board->[$to_row], $to_col, 1);
773         if ($dest_piece ne '-') {
774                 return 0 if (($piece eq lc($piece)) == ($dest_piece eq lc($dest_piece)));
775         }
776
777         if (lc($piece) eq 'k') {
778                 return (abs($from_row - $to_row) <= 1 && abs($from_col - $to_col) <= 1);
779         }
780         if (lc($piece) eq 'r') {
781                 return 0 unless ($from_row == $to_row || $from_col == $to_col);
782
783                 # check that there's a clear passage
784                 if ($from_row == $to_row) {
785                         if ($from_col > $to_col) {
786                                 ($to_col, $from_col) = ($from_col, $to_col);
787                         }
788
789                         for my $c (($from_col+1)..($to_col-1)) {
790                                 my $middle_piece = substr($board->[$to_row], $c, 1);
791                                 return 0 if ($middle_piece ne '-');     
792                         }
793
794                         return 1;
795                 } else {
796                         if ($from_row > $to_row) {
797                                 ($to_row, $from_row) = ($from_row, $to_row);
798                         }
799
800                         for my $r (($from_row+1)..($to_row-1)) {
801                                 my $middle_piece = substr($board->[$r], $to_col, 1);
802                                 return 0 if ($middle_piece ne '-');     
803                         }
804
805                         return 1;
806                 }
807         }
808         if (lc($piece) eq 'b') {
809                 return 0 unless (abs($from_row - $to_row) == abs($from_col - $to_col));
810
811                 my $dr = ($to_row - $from_row) / abs($to_row - $from_row);
812                 my $dc = ($to_col - $from_col) / abs($to_col - $from_col);
813
814                 my $r = $from_row + $dr;
815                 my $c = $from_col + $dc;
816
817                 while ($r != $to_row) {
818                         my $middle_piece = substr($board->[$r], $c, 1);
819                         return 0 if ($middle_piece ne '-');
820                         
821                         $r += $dr;
822                         $c += $dc;
823                 }
824
825                 return 1;
826         }
827         if (lc($piece) eq 'n') {
828                 my $diff_r = abs($from_row - $to_row);
829                 my $diff_c = abs($from_col - $to_col);
830                 return 1 if ($diff_r == 2 && $diff_c == 1);
831                 return 1 if ($diff_r == 1 && $diff_c == 2);
832                 return 0;
833         }
834         if ($piece eq 'q') {
835                 return (can_reach($board, 'r', $from_row, $from_col, $to_row, $to_col) ||
836                         can_reach($board, 'b', $from_row, $from_col, $to_row, $to_col));
837         }
838         if ($piece eq 'Q') {
839                 return (can_reach($board, 'R', $from_row, $from_col, $to_row, $to_col) ||
840                         can_reach($board, 'B', $from_row, $from_col, $to_row, $to_col));
841         }
842         if ($piece eq 'p') {
843                 # black pawn
844                 if ($to_col == $from_col && $to_row == $from_row + 1) {
845                         return ($dest_piece eq '-');
846                 }
847                 if (abs($to_col - $from_col) == 1 && $to_row == $from_row + 1) {
848                         return ($dest_piece ne '-');
849                 }
850                 return 0;
851         }
852         if ($piece eq 'P') {
853                 # white pawn
854                 if ($to_col == $from_col && $to_row == $from_row - 1) {
855                         return ($dest_piece eq '-');
856                 }
857                 if (abs($to_col - $from_col) == 1 && $to_row == $from_row - 1) {
858                         return ($dest_piece ne '-');
859                 }
860                 return 0;
861         }
862         
863         # unknown piece
864         return 0;
865 }
866
867 sub uciprint {
868         my $msg = shift;
869         print UCIWRITE "$msg\n";
870         print UCILOG localtime() . " => $msg\n";
871 }
872
873 sub short_score {
874         my ($uciinfo, $pos, $mpv) = @_;
875
876         if (defined($uciinfo{'score_mate' . $mpv})) {
877                 return sprintf "M%3d", $uciinfo{'score_mate' . $mpv};
878         } else {
879                 if (exists($uciinfo{'score_cp' . $mpv})) {
880                         my $score = $uciinfo{'score_cp' . $mpv} * 0.01;
881                         if ($pos->{'toplay'} eq 'B') {
882                                 $score = -$score;
883                         }
884                         return sprintf "%+5.2f", $score;
885                 }
886         }
887
888         return undef;
889 }
890
891 sub long_score {
892         my ($uciinfo, $pos, $mpv) = @_;
893
894         if (defined($uciinfo{'score_mate' . $mpv})) {
895                 my $mate = $uciinfo{'score_mate' . $mpv};
896                 if ($pos->{'toplay'} eq 'B') {
897                         $mate = -$mate;
898                 }
899                 if ($mate > 0) {
900                         return sprintf "White mates in %u", $mate;
901                 } else {
902                         return sprintf "Black mates in %u", -$mate;
903                 }
904         } else {
905                 if (exists($uciinfo{'score_cp' . $mpv})) {
906                         my $score = $uciinfo{'score_cp' . $mpv} * 0.01;
907                         if ($pos->{'toplay'} eq 'B') {
908                                 $score = -$score;
909                         }
910                         return sprintf "Score: %+5.2f", $score;
911                 }
912         }
913
914         return undef;
915 }