]> git.sesse.net Git - remoteglot-book/commitdiff
Initial checkin for move to Git (no prior version history available).
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 22 Jan 2013 16:18:14 +0000 (17:18 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 22 Jan 2013 16:18:14 +0000 (17:18 +0100)
booklook.c [new file with mode: 0644]
remoteglot.pl [new file with mode: 0755]

diff --git a/booklook.c b/booklook.c
new file mode 100644 (file)
index 0000000..01c7348
--- /dev/null
@@ -0,0 +1,999 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+
+#define DUMP_FEN 0
+#define DUMP_ENC 0
+
+int cto_fd, ctg_fd, ctb_fd;
+
+unsigned int tbl2[] = {
+       0x3100d2bf, 0x3118e3de, 0x34ab1372, 0x2807a847,
+       0x1633f566, 0x2143b359, 0x26d56488, 0x3b9e6f59,
+       0x37755656, 0x3089ca7b, 0x18e92d85, 0x0cd0e9d8,
+       0x1a9e3b54, 0x3eaa902f, 0x0d9bfaae, 0x2f32b45b,
+       0x31ed6102, 0x3d3c8398, 0x146660e3, 0x0f8d4b76,
+       0x02c77a5f, 0x146c8799, 0x1c47f51f, 0x249f8f36,
+       0x24772043, 0x1fbc1e4d, 0x1e86b3fa, 0x37df36a6,
+       0x16ed30e4, 0x02c3148e, 0x216e5929, 0x0636b34e,
+       0x317f9f56, 0x15f09d70, 0x131026fb, 0x38c784b1,
+       0x29ac3305, 0x2b485dc5, 0x3c049ddc, 0x35a9fbcd,
+       0x31d5373b, 0x2b246799, 0x0a2923d3, 0x08a96e9d,
+       0x30031a9f, 0x08f525b5, 0x33611c06, 0x2409db98,
+       0x0ca4feb2, 0x1000b71e, 0x30566e32, 0x39447d31,
+       0x194e3752, 0x08233a95, 0x0f38fe36, 0x29c7cd57,
+       0x0f7b3a39, 0x328e8a16, 0x1e7d1388, 0x0fba78f5,
+       0x274c7e7c, 0x1e8be65c, 0x2fa0b0bb, 0x1eb6c371
+};
+
+signed char data[] = {
+       0x36, 0xb6, 0x0f, 0x79, 0x61, 0x1f, 0x50, 0xde, 0x61, 0xb9, 0x52, 0x24, 0xb3, 0xac, 0x6e, 0x5e, 0x0a, 0x69, 0xbd, 0x61, 0x61, 0xc5
+};
+
+void output_stats(char *result, int invert)
+{
+       unsigned char *ptr = result;
+       ptr += *ptr;
+       ptr += 3;
+
+       // wins-draw-loss
+       if (invert) {
+               printf("%u,", (ptr[0] << 16) | (ptr[1] << 8) | ptr[2]);
+               printf("%u,", (ptr[6] << 16) | (ptr[7] << 8) | ptr[8]);
+               printf("%u,", (ptr[3] << 16) | (ptr[4] << 8) | ptr[5]);
+       } else {
+               printf("%u,", (ptr[3] << 16) | (ptr[4] << 8) | ptr[5]);
+               printf("%u,", (ptr[6] << 16) | (ptr[7] << 8) | ptr[8]);
+               printf("%u,", (ptr[0] << 16) | (ptr[1] << 8) | ptr[2]);
+       }
+
+       ptr += 9;
+       ptr += 4;
+       ptr += 7;
+
+       // rating
+       {
+               int rat2_sum, rat2_div;
+               rat2_div = (ptr[0] << 16) | (ptr[1] << 8) | ptr[2];
+               rat2_sum = (ptr[3] << 24) | (ptr[4] << 16) | (ptr[5] << 8) | ptr[6];
+
+               if (rat2_div == 0) {
+                       printf(",0");
+               } else {
+                       printf("%u,%u", rat2_sum / rat2_div, rat2_div);
+               }
+       }
+               
+       printf("\n");
+}
+
+unsigned int gen_hash(signed char *ptr, unsigned len)
+{
+       signed hash = 0;
+       short tmp = 0;
+       int i;
+
+       for (i = 0; i < len; ++i) {
+               signed char ch = *ptr++;
+               tmp += ((0x0f - (ch & 0x0f)) << 2) + 1;
+               hash += tbl2[tmp & 0x3f];
+               tmp += ((0xf0 - (ch & 0xf0)) >> 2) + 1;
+               hash += tbl2[tmp & 0x3f];
+       }
+       return hash;
+}
+
+void decode_fen_board(char *str, char *board)
+{
+       while (*str) {
+               switch (*str) {
+               case 'r':
+               case 'n':
+               case 'b':
+               case 'q':
+               case 'k':
+               case 'p':
+               case 'R':
+               case 'N':
+               case 'B':
+               case 'Q':
+               case 'K':
+               case 'P':
+                       *board++ = *str;
+                       break;
+               case '8':
+                       *board++ = ' ';
+                       // fall through
+               case '7':
+                       *board++ = ' ';
+                       // fall through
+               case '6':
+                       *board++ = ' ';
+                       // fall through
+               case '5':
+                       *board++ = ' ';
+                       // fall through
+               case '4':
+                       *board++ = ' ';
+                       // fall through
+               case '3':
+                       *board++ = ' ';
+                       // fall through
+               case '2':
+                       *board++ = ' ';
+                       // fall through
+               case '1':
+                       *board++ = ' ';
+                       break;
+               case '/':
+                       // ignore
+                       break;
+               default:
+                       fprintf(stderr, "Unknown FEN board character '%c'\n", *str);
+                       exit(1);
+               }
+
+               ++str;
+       }
+}
+
+void invert_board(char *board)
+{
+       int y, x, i;
+
+       // flip the board
+       for (y = 0; y < 4; ++y) {
+               for (x = 0; x < 8; ++x) {
+                       char tmp = board[y * 8 + (x)];
+                       board[y * 8 + (x)] = board[(7-y) * 8 + (x)];
+                       board[(7-y) * 8 + (x)] = tmp;
+               }
+       }
+
+       // invert the colors
+       for (y = 0; y < 8; ++y) {
+               for (x = 0; x < 8; ++x) {
+                       if (board[y * 8 + x] == toupper(board[y * 8 + x])) {
+                               board[y * 8 + x] = tolower(board[y * 8 + x]);
+                       } else {
+                               board[y * 8 + x] = toupper(board[y * 8 + x]);
+                       }
+               }
+       }
+}
+
+int needs_flipping(char *board, char *castling_rights)
+{
+       int y, x;
+
+       // never flip if either side can castle
+       if (strcmp(castling_rights, "-") != 0)
+               return 0;
+
+       for (y = 0; y < 8; ++y) {
+               for (x = 0; x < 4; ++x) {
+                       if (board[y * 8 + x] == 'K')
+                               return 1;
+               }
+       }
+
+       return 0;
+}
+
+// horizontal flip
+void flip_board(char *board, char *eps)
+{
+       int y, x;
+
+       // flip the board
+       for (y = 0; y < 8; ++y) {
+               for (x = 0; x < 4; ++x) {
+                       char tmp = board[y * 8 + x];
+                       board[y * 8 + (x)] = board[y * 8 + (7-x)];
+                       board[y * 8 + (7-x)] = tmp;
+               }
+       }
+
+       // flip the en passant square
+       if (strcmp(eps, "-") != 0) {
+               int epsc = eps[0] - 'a';
+               eps[0] = 'a' + (7 - epsc);
+       }
+}
+
+unsigned char position[32];
+int pos_len;
+int bits_left;
+
+void put_bit(int x)
+{
+       position[pos_len] <<= 1;
+       if (x)
+               position[pos_len] |= 1;
+
+       if (--bits_left == 0) {
+               ++pos_len;
+               bits_left = 8;
+       }
+}
+
+void dump_fen(char *board, int invert, int flip, char *castling_rights, char *ep_square)
+{
+       int y, x;
+       for (y = 0; y < 8; ++y) {
+               int space = 0;
+               for (x = 0; x < 8; ++x) {
+                       int xx = (flip) ? (7-x) : x;
+
+                       if (board[y * 8 + xx] == ' ') {
+                               ++space;
+                       } else {
+                               if (space != 0)
+                                       putchar('0' + space);
+                               putchar(board[y * 8 + xx]);
+                               space = 0;
+                       }
+               }
+               if (space != 0)
+                       putchar('0' + space);
+               if (y != 7)
+                       putchar('/');
+       }
+       putchar(' ');
+
+       if (invert)
+               putchar('b');
+       else
+               putchar('w');
+
+       printf(" %s ", castling_rights);
+       if (flip && strcmp(ep_square, "-") != 0) {
+               printf("%c%c 0 0\n", 'a' + (7 - (ep_square[0] - 'a')), ep_square[1]);
+       } else {
+               printf("%s 0 0\n", ep_square);
+       }
+}
+
+void encode_position(char *board, int invert, char *castling_rights, char *ep_column)
+{
+       int x, y;
+       int ep_any = 0;
+
+       // clear out
+       memset(position, 0, 32);
+
+       // leave some room for the header byte, which will be filled last
+       pos_len = 1;
+       bits_left = 8;
+
+       // slightly unusual ordering
+       for (x = 0; x < 8; ++x) {
+               for (y = 0; y < 8; ++y) {
+                       switch (board[(7-y) * 8 + x]) {
+                       case ' ':
+                               put_bit(0);
+                               break;
+                       case 'p':
+                               put_bit(1);
+                               put_bit(1);
+                               put_bit(1);
+                               break;
+                       case 'P':
+                               put_bit(1);
+                               put_bit(1);
+                               put_bit(0);
+                               break;
+                       case 'r':
+                               put_bit(1);
+                               put_bit(0);
+                               put_bit(1);
+                               put_bit(1);
+                               put_bit(1);
+                               break;
+                       case 'R':
+                               put_bit(1);
+                               put_bit(0);
+                               put_bit(1);
+                               put_bit(1);
+                               put_bit(0);
+                               break;
+                       case 'b':
+                               put_bit(1);
+                               put_bit(0);
+                               put_bit(1);
+                               put_bit(0);
+                               put_bit(1);
+                               break;
+                       case 'B':
+                               put_bit(1);
+                               put_bit(0);
+                               put_bit(1);
+                               put_bit(0);
+                               put_bit(0);
+                               break;
+                       case 'n':
+                               put_bit(1);
+                               put_bit(0);
+                               put_bit(0);
+                               put_bit(1);
+                               put_bit(1);
+                               break;
+                       case 'N':
+                               put_bit(1);
+                               put_bit(0);
+                               put_bit(0);
+                               put_bit(1);
+                               put_bit(0);
+                               break;
+                       case 'q':
+                               put_bit(1);
+                               put_bit(0);
+                               put_bit(0);
+                               put_bit(0);
+                               put_bit(1);
+                               put_bit(1);
+                               break;
+                       case 'Q':
+                               put_bit(1);
+                               put_bit(0);
+                               put_bit(0);
+                               put_bit(0);
+                               put_bit(1);
+                               put_bit(0);
+                               break;
+                       case 'k':
+                               put_bit(1);
+                               put_bit(0);
+                               put_bit(0);
+                               put_bit(0);
+                               put_bit(0);
+                               put_bit(1);
+                               break;
+                       case 'K':
+                               put_bit(1);
+                               put_bit(0);
+                               put_bit(0);
+                               put_bit(0);
+                               put_bit(0);
+                               put_bit(0);
+                               break;
+                       }
+               }
+       }
+               
+       if (strcmp(ep_column, "-") != 0) {
+               int epcn = ep_column[0] - 'a';
+
+               if ((epcn > 0 && board[3*8 + epcn - 1] == 'P') ||
+                   (epcn < 7 && board[3*8 + epcn + 1] == 'P')) {
+                       ep_any = 1;
+               }
+       }
+       
+       // really odd padding
+       {
+               int nb = 0, i;
+
+               // find the right number of bits
+               int right = (ep_any) ? 3 : 8;
+
+               // castling needs four more
+               if (strcmp(castling_rights, "-") != 0) {
+                       right = right + 4;
+                       if (right > 8)
+                               right %= 8;
+               }
+
+               if (bits_left > right)
+                       nb = bits_left - right;
+               else if (bits_left < right)
+                       nb = bits_left + 8 - right;
+
+               if (bits_left == 8 && strcmp(castling_rights, "-") == 0 && !ep_any)
+                       nb = 8;
+
+               for (i = 0; i < nb; ++i) {
+                       put_bit(0);
+               }
+       }
+       
+       // en passant
+       if (ep_any) {
+               int epcn = ep_column[0] - 'a';
+
+               put_bit(epcn & 0x04);
+               put_bit(epcn & 0x02);
+               put_bit(epcn & 0x01);
+       }
+       
+       // castling rights
+       if (strcmp(castling_rights, "-") != 0) {
+               if (invert) {
+                       put_bit(strchr(castling_rights, 'K') != NULL);
+                       put_bit(strchr(castling_rights, 'Q') != NULL);
+                       put_bit(strchr(castling_rights, 'k') != NULL);
+                       put_bit(strchr(castling_rights, 'q') != NULL);
+               } else {
+                       put_bit(strchr(castling_rights, 'k') != NULL);
+                       put_bit(strchr(castling_rights, 'q') != NULL);
+                       put_bit(strchr(castling_rights, 'K') != NULL);
+                       put_bit(strchr(castling_rights, 'Q') != NULL);
+               }
+       }
+
+       // padding stuff
+       if (bits_left == 8) {
+               //++pos_len;
+       } else {
+#if 0
+               ++pos_len;
+#else
+               int i, nd = 8 - bits_left;
+               for (i = 0; i < nd; ++i)
+                       put_bit(0);
+#endif
+       }
+               
+       // and the header byte
+       position[0] = pos_len;
+
+       if (strcmp(castling_rights, "-") != 0)
+               position[0] |= 0x40;
+       if (ep_any)
+               position[0] |= 0x20;
+
+#if DUMP_ENC
+       {
+               int i;
+               for (i = 0; i < pos_len; ++i) {
+                       printf("%02x ", position[i]);
+               }
+               printf("\n");
+       }
+#endif
+}
+               
+int search_pos(unsigned c, char *result)
+{
+       char buf[4];
+       unsigned char pagebuf[4096];
+       unsigned page;
+       unsigned page_len;
+
+       lseek(cto_fd, c * 4 + 16, SEEK_SET);
+       
+       read(cto_fd, buf, 4);
+       page = htonl(*((unsigned *)buf));
+       if (page == -1)
+               return 0;
+
+       lseek(ctg_fd, page * 4096 + 4096, SEEK_SET);
+       read(ctg_fd, pagebuf, 4096);
+
+       // search the page
+       {
+               int pos = 4;
+               int page_end = htons(*((short *)(pagebuf + 2)));
+
+               while (pos < page_end) {
+                       if (pagebuf[pos] != position[0] ||
+                           memcmp(pagebuf + pos, position, pos_len) != 0) {
+                               // no match, skip through
+                               pos += pagebuf[pos] & 0x1f;
+                               pos += pagebuf[pos];
+                               pos += 33;
+                               continue;
+                       }
+                       pos += pagebuf[pos] & 0x1f;
+                       memcpy(result, pagebuf + pos, pagebuf[pos] + 33);
+                       return 1;
+               }
+       }
+
+       return 0;
+}
+
+int lookup_position(char *pos, unsigned len, char *result)
+{
+       int hash = gen_hash(position, pos_len);
+       int n;
+       
+       for (n = 0; n < 0x7fffffff; n = 2 * n + 1) {
+               unsigned c = (hash & n) + n;
+
+               // FIXME: adjust these bounds
+               if (c < 0x80e0)
+                       continue;
+
+               if (search_pos(c, result))
+                       return 1;
+               
+               if (c >= 0x1fd00)
+                       break;
+       }
+
+       return 0;
+}
+
+struct moveenc {
+       char encoding;
+       char piece;
+       int num;
+       int forward, right;
+};
+struct moveenc movetable[] = {
+       0x00, 'P', 5,  1,  1,
+       0x01, 'N', 2, -1, -2,
+       0x03, 'Q', 2,  0,  2,
+       0x04, 'P', 2,  1,  0,
+       0x05, 'Q', 1,  1,  0,
+       0x06, 'P', 4,  1, -1,
+       0x08, 'Q', 2,  0,  4, 
+       0x09, 'B', 2,  6,  6,
+       0x0a, 'K', 1, -1,  0,
+       0x0c, 'P', 1,  1, -1,
+       0x0d, 'B', 1,  3,  3,
+       0x0e, 'R', 2,  0,  3,
+       0x0f, 'N', 1, -1, -2,
+       0x12, 'B', 1,  7,  7,
+       0x13, 'K', 1,  1,  0,
+       0x14, 'P', 8,  1,  1,
+       0x15, 'B', 1,  5,  5,
+       0x18, 'P', 7,  1,  0,
+       0x1a, 'Q', 2,  6,  0,
+       0x1b, 'B', 1,  1, -1,
+       0x1d, 'B', 2,  7,  7,
+       0x21, 'R', 2,  0,  7,
+       0x22, 'B', 2,  2, -2,
+       0x23, 'Q', 2,  6,  6,
+       0x24, 'P', 8,  1, -1,
+       0x26, 'B', 1,  7, -7,
+       0x27, 'P', 3,  1, -1,
+       0x28, 'Q', 1,  5,  5,
+       0x29, 'Q', 1,  0,  6,
+       0x2a, 'N', 2, -2,  1,
+       0x2d, 'P', 6,  1,  1,
+       0x2e, 'B', 1,  1,  1,
+       0x2f, 'Q', 1,  0,  1,
+       0x30, 'N', 2, -2, -1,
+       0x31, 'Q', 1,  0,  3,
+       0x32, 'B', 2,  5,  5,
+       0x34, 'N', 1,  2,  1,
+       0x36, 'N', 1,  1,  2,
+       0x37, 'Q', 1,  4,  0,
+       0x38, 'Q', 2,  4, -4,
+       0x39, 'Q', 1,  0,  5,
+       0x3a, 'B', 1,  6,  6,
+       0x3b, 'Q', 2,  5, -5,
+       0x3c, 'B', 1,  5, -5,
+       0x41, 'Q', 2,  5,  5,
+       0x42, 'Q', 1,  7, -7,
+       0x44, 'K', 1, -1,  1,
+       0x45, 'Q', 1,  3,  3,
+       0x4a, 'P', 8,  2,  0,
+       0x4b, 'Q', 1,  5, -5,
+       0x4c, 'N', 2,  2,  1,
+       0x4d, 'Q', 2,  1,  0,
+       0x50, 'R', 1,  6,  0,
+       0x52, 'R', 1,  0,  6,
+       0x54, 'B', 2,  1, -1,
+       0x55, 'P', 3,  1,  0,
+       0x5c, 'P', 7,  1,  1,
+       0x5f, 'P', 5,  2,  0,
+       0x61, 'Q', 1,  6,  6,
+       0x62, 'P', 2,  2,  0,
+       0x63, 'Q', 2,  7, -7,
+       0x66, 'B', 1,  3, -3,
+       0x67, 'K', 1,  1,  1,
+       0x69, 'R', 2,  7,  0,
+       0x6a, 'B', 1,  4,  4,
+       0x6b, 'K', 1,  0,  2,   /* short castling */
+       0x6e, 'R', 1,  0,  5,
+       0x6f, 'Q', 2,  7,  7,
+       0x72, 'B', 2,  7, -7,
+       0x74, 'Q', 1,  0,  2,
+       0x79, 'B', 2,  6, -6,
+       0x7a, 'R', 1,  3,  0,
+       0x7b, 'R', 2,  6,  0,
+       0x7c, 'P', 3,  1,  1,
+       0x7d, 'R', 2,  1,  0,
+       0x7e, 'Q', 1,  3, -3,
+       0x7f, 'R', 1,  0,  1,
+       0x80, 'Q', 1,  6, -6,
+       0x81, 'R', 1,  1,  0,
+       0x82, 'P', 6,  1, -1,
+       0x85, 'N', 1,  2, -1,
+       0x86, 'R', 1,  0,  7,
+       0x87, 'R', 1,  5,  0,
+       0x8a, 'N', 1, -2,  1,
+       0x8b, 'P', 1,  1,  1,
+       0x8c, 'K', 1, -1, -1,
+       0x8e, 'Q', 2,  2, -2,
+       0x8f, 'Q', 1,  0,  7,
+       0x92, 'Q', 2,  1,  1,
+       0x94, 'Q', 1,  3,  0,
+       0x96, 'P', 2,  1,  1,
+       0x97, 'K', 1,  0, -1,
+       0x98, 'R', 1,  0,  3,
+       0x99, 'R', 1,  4,  0,
+       0x9a, 'Q', 1,  6,  0,
+       0x9b, 'P', 3,  2,  0,
+       0x9d, 'Q', 1,  2,  0,
+       0x9f, 'B', 2,  4, -4,
+       0xa0, 'Q', 2,  3,  0,
+       0xa2, 'Q', 1,  2,  2,
+       0xa3, 'P', 8,  1,  0,
+       0xa5, 'R', 2,  5,  0,
+       0xa9, 'R', 2,  0,  2,
+       0xab, 'Q', 2,  6, -6,
+       0xad, 'R', 2,  0,  4,
+       0xae, 'Q', 2,  3,  3,
+       0xb0, 'Q', 2,  4,  0,
+       0xb1, 'P', 6,  2,  0,
+       0xb2, 'B', 1,  6, -6,
+       0xb5, 'R', 2,  0,  5,
+       0xb7, 'Q', 1,  5,  0,
+       0xb9, 'B', 2,  3,  3,
+       0xbb, 'P', 5,  1,  0,
+       0xbc, 'Q', 2,  0,  5,
+       0xbd, 'Q', 2,  2,  0,
+       0xbe, 'K', 1,  0,  1,
+       0xc1, 'B', 1,  2,  2,
+       0xc2, 'B', 2,  2,  2,
+       0xc3, 'B', 1,  2, -2,
+       0xc4, 'R', 2,  0,  1,
+       0xc5, 'R', 2,  4,  0,
+       0xc6, 'Q', 2,  5,  0,
+       0xc7, 'P', 7,  1, -1,
+       0xc8, 'P', 7,  2,  0,
+       0xc9, 'Q', 2,  7,  0,
+       0xca, 'B', 2,  3, -3,
+       0xcb, 'P', 6,  1,  0,
+       0xcc, 'B', 2,  5, -5,
+       0xcd, 'R', 1,  0,  2,
+       0xcf, 'P', 4,  1,  0,
+       0xd1, 'P', 2,  1, -1,
+       0xd2, 'N', 2,  1,  2,
+       0xd3, 'N', 2,  1, -2,
+       0xd7, 'Q', 1,  1, -1,
+       0xd8, 'R', 2,  0,  6,
+       0xd9, 'Q', 1,  2, -2,
+       0xda, 'N', 1, -2, -1,
+       0xdb, 'P', 1,  2,  0,
+       0xde, 'P', 5,  1, -1,
+       0xdf, 'K', 1,  1, -1,
+       0xe0, 'N', 2, -1,  2,
+       0xe1, 'R', 1,  7,  0,
+       0xe3, 'R', 2,  3,  0,
+       0xe5, 'Q', 1,  0,  4,
+       0xe6, 'P', 4,  2,  0,
+       0xe7, 'Q', 1,  4,  4,
+       0xe8, 'R', 1,  2,  0,
+       0xe9, 'N', 1, -1,  2,
+       0xeb, 'P', 4,  1,  1,
+       0xec, 'P', 1,  1,  0,
+       0xed, 'Q', 1,  7,  7,
+       0xee, 'Q', 2,  1, -1,
+       0xef, 'R', 1,  0,  4,
+       0xf0, 'Q', 2,  0,  7,
+       0xf1, 'Q', 1,  1,  1,
+       0xf3, 'N', 2,  2, -1,
+       0xf4, 'R', 2,  2,  0,
+       0xf5, 'B', 2,  1,  1,
+       0xf6, 'K', 1,  0, -2,   /* long castling */
+       0xf7, 'N', 1,  1, -2,
+       0xf8, 'Q', 2,  0,  1,
+       0xf9, 'Q', 2,  6,  0,
+       0xfa, 'Q', 2,  0,  3,
+       0xfb, 'Q', 2,  2,  2,
+       0xfd, 'Q', 1,  7,  0,
+       0xfe, 'Q', 2,  3, -3
+};
+
+int find_piece(char *board, char piece, int num)
+{
+       int y, x;
+       for (x = 0; x < 8; ++x) {
+               for (y = 0; y < 8; ++y) {
+                       if (board[(7-y) * 8 + x] != piece)
+                               continue;
+                       if (--num == 0)
+                               return (y * 8 + x);
+               }
+       }
+
+       fprintf(stderr, "Couldn't find piece '%c' number %u\n", piece, num);
+       exit(1);
+}
+
+void execute_move(char *board, char *castling_rights, int inverted, char *ep_square, int from_square, int to_square)
+{
+       int black_ks, black_qs, white_ks, white_qs;
+
+       // fudge
+       from_square = (7 - (from_square / 8)) * 8 + (from_square % 8);
+       to_square = (7 - (to_square / 8)) * 8 + (to_square % 8);
+
+       // compute the new castling rights
+       black_ks = (strchr(castling_rights, 'k') != NULL);
+       black_qs = (strchr(castling_rights, 'q') != NULL);
+       white_ks = (strchr(castling_rights, 'K') != NULL);
+       white_qs = (strchr(castling_rights, 'Q') != NULL);
+
+       if (board[from_square] == 'K') {
+               if (inverted)
+                       black_ks = black_qs = 0;
+               else
+                       white_ks = white_qs = 0;
+       }
+       if (board[from_square] == 'R') {
+               if (inverted) {
+                       if (from_square == 56) // h1
+                               black_qs = 0;
+                       else if (from_square == 63) // h8
+                               black_ks = 0;
+               } else {
+                       if (from_square == 56) // a1
+                               white_qs = 0;
+                       else if (from_square == 63) // a8
+                               white_ks = 0;
+               }
+       }
+       if (board[to_square] == 'r') {
+               if (inverted) {
+                       if (to_square == 0) // h1
+                               white_qs = 0;
+                       else if (to_square == 7) // h8
+                               white_ks = 0;
+               } else {
+                       if (to_square == 0) // a1
+                               black_qs = 0;
+                       else if (to_square == 7) // a8
+                               black_ks = 0;
+               }
+       }
+
+       if ((black_ks | black_qs | white_ks | white_qs) == 0) {
+               strcpy(castling_rights, "-");
+       } else {
+               strcpy(castling_rights, "");
+
+               if (white_ks)
+                       strcat(castling_rights, "K");
+               if (white_qs)
+                       strcat(castling_rights, "Q");
+               if (black_ks)
+                       strcat(castling_rights, "k");
+               if (black_qs)
+                       strcat(castling_rights, "q");
+       }
+
+       // now the ep square
+       if (board[from_square] == 'P' && to_square - from_square == -16) {
+               sprintf(ep_square, "%c%u", "abcdefgh"[from_square % 8], from_square / 8);
+       } else {
+               strcpy(ep_square, "-");
+       }
+
+       // is this move an en passant capture?
+       if (board[from_square] == 'P' && board[to_square] == ' ' &&
+           (to_square - from_square == -9 || to_square - from_square == -7)) {
+               board[to_square + 8] = ' ';     
+       }
+
+       // make the move
+       board[to_square] = board[from_square];
+       board[from_square] = ' ';
+
+       // promotion
+       if (board[to_square] == 'P' && to_square < 8)
+               board[to_square] = 'Q';
+
+       if (board[to_square] == 'K' && to_square - from_square == 2) {
+               // short castling
+               board[to_square - 1] = 'R';
+               board[to_square + 1] = ' ';
+       } else if (board[to_square] == 'K' && to_square - from_square == -2) {
+               // long castling
+               board[to_square + 1] = 'R';
+               board[to_square - 2] = ' ';
+       }
+
+#if 0
+       // dump the board
+       {
+               int y, x;
+               printf("\n\n");
+               for (y = 0; y < 8; ++y) {
+                       for (x = 0; x < 8; ++x) {
+                               putchar(board[y * 8 + x]);
+                       }
+                       putchar('\n');
+               }
+       }
+       printf("cr='%s' ep='%s'\n", castling_rights, ep_square);
+#endif
+}
+
+void dump_move(char *board, char *castling_rights, char *ep_col, int invert, int flip, char move, char annotation)
+{
+       int i;
+       char newboard[64], nkr[5], neps[3];
+       for (i = 0; i < sizeof(movetable)/sizeof(movetable[0]); ++i) {
+               int from_square, from_row, from_col;
+               int to_square, to_row, to_col;
+               int ret;
+               char result[256];
+
+               if (move != movetable[i].encoding)
+                       continue;
+
+               from_square = find_piece(board, movetable[i].piece, movetable[i].num);
+               from_row = from_square / 8;
+               from_col = from_square % 8;
+
+               to_row = (from_row + 8 + movetable[i].forward) % 8;
+               to_col = (from_col + 8 + movetable[i].right) % 8;
+               to_square = to_row * 8 + to_col;
+               
+               // do the move, and look up the new position
+               memcpy(newboard, board, 64);
+               strcpy(nkr, castling_rights);
+               execute_move(newboard, nkr, invert, neps, from_square, to_square);
+               invert_board(newboard);
+               
+               if (needs_flipping(newboard, nkr)) {
+                       flip_board(newboard, neps);
+                       flip = !flip;
+               }
+               
+               encode_position(newboard, !invert, nkr, neps);
+               ret = lookup_position(position, pos_len, result);
+               if (!ret) {
+#if DUMP_FEN
+                       if (!invert) 
+                               invert_board(newboard);
+
+                       dump_fen(newboard, !invert, flip, nkr, neps);
+#endif
+                       fprintf(stderr, "Destination move not found in book.\n");
+                       exit(1);
+               }
+
+#if DUMP_FEN
+               // very useful for regression testing (some shell and
+               // you can walk the entire book quite easily)
+               if (!invert) 
+                       invert_board(newboard);
+               dump_fen(newboard, !invert, flip, nkr, neps);
+               return;
+#endif
+
+               // output the move
+               {
+                       int fromcol = from_square % 8;
+                       int fromrow = from_square / 8;
+                       int tocol = to_square % 8;
+                       int torow = to_square / 8;
+
+                       if (invert) {
+                               fromrow = 7 - fromrow;
+                               torow = 7 - torow;
+                       }
+                       if (flip) {
+                               fromcol = 7 - fromcol;
+                               tocol = 7 - tocol;
+                       }
+
+                       printf("%c%u%c%u,",
+                               "abcdefgh"[fromcol], fromrow + 1,
+                               "abcdefgh"[tocol], torow + 1);
+               }
+
+               // annotation
+               switch (annotation) {
+               case 0x00:
+                       break;
+               case 0x01:
+                       printf("!");
+                       break;
+               case 0x02:
+                       printf("?");
+                       break;
+               case 0x03:
+                       printf("!!");
+                       break;
+               case 0x04:
+                       printf("??");
+                       break;
+               case 0x05:
+                       printf("!?");
+                       break;
+               case 0x06:
+                       printf("?!");
+                       break;
+               case 0x08:
+                       printf(" (only move)");
+                       break;
+               case 0x16:
+                       printf(" (zugzwang)");
+                       break;
+               default:
+                       printf(" (unknown status 0x%02x)", annotation);
+               }
+               printf(",");
+
+               output_stats(result, invert);
+               return;
+       }
+
+       fprintf(stderr, "ERROR: Unknown move 0x%02x\n", move);
+       exit(1);
+}
+
+void dump_info(char *board, char *castling_rights, char *ep_col, int invert, int flip, char *result)
+{
+       int book_moves = result[0] >> 1;
+       int i;
+       
+#if !DUMP_FEN
+       printf(",,");   
+       output_stats(result, !invert);
+#endif
+
+       for (i = 0; i < book_moves; ++i) {
+               dump_move(board, castling_rights, ep_col, invert, flip, result[i * 2 + 1], result[i * 2 + 2]);
+       }
+}
+
+int main(int argc, char **argv)
+{
+       // encode the position
+       char board[64], result[256];
+       int invert = 0, flip;
+       int ret;
+       
+       ctg_fd = open("RybkaII.ctg", O_RDONLY);
+       cto_fd = open("RybkaII.cto", O_RDONLY);
+       ctb_fd = open("RybkaII.ctb", O_RDONLY);
+       decode_fen_board(argv[1], board);
+       
+       // always from white's position
+       if (argv[2][0] == 'b') {
+               invert = 1;
+               invert_board(board);
+       }
+       
+       // and the white king is always in the right half
+       flip = needs_flipping(board, argv[3]);
+       if (flip) {
+               flip_board(board, argv[4]);
+       }
+
+
+#if 0
+       // dump the board
+       {
+               int y, x;
+               for (y = 0; y < 8; ++y) {
+                       for (x = 0; x < 8; ++x) {
+                               putchar(board[y * 8 + x]);
+                       }
+                       putchar('\n');
+               }
+       }
+#endif
+
+       encode_position(board, invert, argv[3], argv[4]);
+       ret = lookup_position(position, pos_len, result);
+       if (!ret) {
+               //fprintf(stderr, "Not found in book.\n");
+               exit(1);
+       }
+
+       dump_info(board, argv[3], argv[4], invert, flip, result);
+       exit(0);
+}
+
diff --git a/remoteglot.pl b/remoteglot.pl
new file mode 100755 (executable)
index 0000000..16416ba
--- /dev/null
@@ -0,0 +1,1016 @@
+#! /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 <sgunderson@bigfoot.com>
+# Licensed under the GNU General Public License, version 2.
+#
+
+use Net::Telnet;
+use FileHandle;
+use IPC::Open2;
+use Time::HiRes;
+use strict;
+use warnings;
+
+# Configuration
+my $server = "freechess.org";
+my $target = "GMCarlsen";
+# my $engine = "/usr/games/toga2";
+#my $engine = "wine Rybkav2.3.2a.mp.w32.exe";
+my $engine = "~/microwine-0.5/microwine Rybkav2.3.2a.mp.x64.exe";
+#my $engine = "ssh -t sesse\@84.48.204.209 ./microwine-0.5/microwine ./Rybkav2.3.2a.mp.x64.exe";
+#my $engine = "ssh -t sesse\@cirkus.samfundet.no nice -n 19 ./microwine-0.5/microwine ./microwine-0.5/Rybkav2.3.2a.mp.x64.exe";
+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 @masters = (
+       'Sesse',
+       'Sessse',
+       'Sesssse',
+       'greatestguns',
+       'beuki'
+);
+
+# Program starts here
+$SIG{ALRM} = sub { output_screen(); };
+
+$| = 1;
+
+open(FICSLOG, ">ficslog.txt")
+       or die "ficslog.txt: $!";
+print FICSLOG "Log starting.\n";
+select(FICSLOG);
+$| = 1;
+
+open(UCILOG, ">ucilog.txt")
+       or die "ucilog.txt: $!";
+print UCILOG "Log starting.\n";
+select(UCILOG);
+$| = 1;
+select(STDOUT);
+
+# open the chess engine
+my $pid = IPC::Open2::open2(*UCIREAD, *UCIWRITE, $engine);
+my %uciinfo = ();
+my %uciid = ();
+my ($last_move, $last_tell);
+my $last_text = '';
+my $last_told_text = '';
+my ($pos_waiting, $pos_calculating);
+
+uciprint("uci");
+
+# gobble the options
+while (<UCIREAD>) {
+       /uciok/ && last;
+       handle_uci($_);
+}
+
+uciprint("setoption name UCI_AnalyseMode value true");
+# uciprint("setoption name Preserve Analysis value true");
+# uciprint("setoption name NalimovPath value /srv/tablebase");
+uciprint("setoption name NalimovUsage value Rarely");
+uciprint("setoption name Hash value 1024");
+uciprint("setoption name MultiPV value 2");
+# uciprint("setoption name Contempt value 1000");
+# uciprint("setoption name Outlook value Ultra Optimistic");
+uciprint("ucinewgame");
+
+print "Chess engine ready.\n";
+
+# now talk to FICS
+my $t = Net::Telnet->new(Timeout => 10, Prompt => '/fics% /');
+$t->input_log(\*FICSLOG);
+$t->open($server);
+$t->print("SesseBOT");
+$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 = '';
+       my $rout;
+       vec($rin, fileno(UCIREAD), 1) = 1;
+       vec($rin, fileno($t), 1) = 1;
+
+       my ($nfound, $timeleft) = select($rout=$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 $pos = style12_to_fen($line);
+                       
+                       # if this is already in the queue, ignore it
+                       next if (defined($pos_waiting) && $pos->{'fen'} eq $pos_waiting->{'fen'});
+
+                       # if we're already chewing on this and there's nothing else in the queue,
+                       # also ignore it
+                       next if (!defined($pos_waiting) && defined($pos_calculating) &&
+                                $pos->{'fen'} eq $pos_calculating->{'fen'});
+
+                       # if we're already thinking on something, stop and wait for the engine
+                       # to approve
+                       if (defined($pos_calculating)) {
+                               if (!defined($pos_waiting)) {
+                                       uciprint("stop");
+                               }
+                               if ($uci_assume_full_compliance) {
+                                       $pos_waiting = $pos;
+                               } else {
+                                       uciprint("position fen " . $pos->{'fen'});
+                                       uciprint("go infinite");
+                                       $pos_calculating = $pos;
+                               }
+                       } else {
+                               # it's wrong just to give the FEN (the move history is useful,
+                               # and per the UCI spec, we should really have sent "ucinewgame"),
+                               # but it's easier
+                               uciprint("position fen " . $pos->{'fen'});
+                               uciprint("go infinite");
+                               $pos_calculating = $pos;
+                       }
+
+                       %uciinfo = ();
+                       $last_move = time;
+
+                       # 
+                       # Output a command every move to note that we're
+                       # still paying attention -- this is a good tradeoff,
+                       # since if no move has happened in the last half
+                       # hour, the analysis/relay has most likely stopped
+                       # and we should stop hogging server resources.
+                       #
+                       $t->cmd("date");
+               }
+               if ($line =~ /^([A-Za-z]+)(?:\([A-Z]+\))* tells you: (.*)$/) {
+                       my ($who, $msg) = ($1, $2);
+
+                       next if (grep { $_ eq $who } (@masters) == 0);
+       
+                       if ($msg =~ /^fics (.*?)$/) {
+                               $t->cmd("tell $who Executing '$1' on FICS.");
+                               $t->cmd($1);
+                       } elsif ($msg =~ /^uci (.*?)$/) {
+                               $t->cmd("tell $who Sending '$1' to the engine.");
+                               print UCIWRITE "$1\n";
+                       } else {
+                               $t->cmd("tell $who Couldn't understand '$msg', sorry.");
+                       }
+               }
+               #print "FICS: [$line]\n";
+               $sleep = 0;
+       }
+       
+       # any fun on the UCI channel?
+       if ($nfound > 0 && vec($rout, fileno(UCIREAD), 1) == 1) {
+               # 
+               # 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.
+               #
+               my $line = '';
+               while ($line !~ /\n/) {
+                       my $tmp;
+                       my $ret = sysread UCIREAD, $tmp, 1;
+
+                       if (!defined($ret)) {
+                               next if ($!{EINTR});
+                               die "error in reading from the UCI engine: $!";
+                       } elsif ($ret == 0) {
+                               die "EOF from UCI engine";
+                       }
+
+                       $line .= $tmp;
+               }
+
+               $line =~ tr/\r\n//d;
+               handle_uci($line);
+               $sleep = 0;
+
+               # don't update too often
+               Time::HiRes::alarm(0.2);
+       }
+
+       sleep $sleep;
+}
+
+sub handle_uci {
+       my ($line) = @_;
+
+       chomp $line;
+       $line =~ tr/\r//d;
+       $line =~ s/  / /g;  # Sometimes needed for Zappa Mexico
+       print UCILOG localtime() . " <= $line\n";
+       if ($line =~ /^info/) {
+               my (@infos) = split / /, $line;
+               shift @infos;
+
+               parse_infos(@infos);
+       }
+       if ($line =~ /^id/) {
+               my (@ids) = split / /, $line;
+               shift @ids;
+
+               parse_ids(@ids);
+       }
+       if ($line =~ /^bestmove/ && $uci_assume_full_compliance) {
+               if (defined($pos_waiting)) {
+                       uciprint("position fen " . $pos_waiting->{'fen'});
+                       uciprint("go infinite");
+
+                       $pos_calculating = $pos_waiting;
+                       $pos_waiting = undef;
+               }
+       }
+}
+
+sub parse_infos {
+       my (@x) = @_;
+       my $mpv = '';
+
+       while (scalar @x > 0) {
+               if ($x[0] =~ 'multipv') {
+                       shift @x;
+                       $mpv = shift @x;
+                       next;
+               }
+               if ($x[0] =~ /^(currmove|currmovenumber|cpuload)$/) {
+                       my $key = shift @x;
+                       my $value = shift @x;
+                       $uciinfo{$key} = $value;
+                       next;
+               }
+               if ($x[0] =~ /^(depth|seldepth|hashfull|time|nodes|nps|tbhits)$/) {
+                       my $key = shift @x;
+                       my $value = shift @x;
+                       $uciinfo{$key . $mpv} = $value;
+                       next;
+               }
+               if ($x[0] eq 'score') {
+                       shift @x;
+
+                       delete $uciinfo{'score_cp' . $mpv};
+                       delete $uciinfo{'score_mate' . $mpv};
+
+                       while ($x[0] =~ /^(cp|mate|lowerbound|upperbound)$/) {
+                               if ($x[0] eq 'cp') {
+                                       shift @x;
+                                       $uciinfo{'score_cp' . $mpv} = shift @x;
+                               } elsif ($x[0] eq 'mate') {
+                                       shift @x;
+                                       $uciinfo{'score_mate' . $mpv} = shift @x;
+                               } else {
+                                       shift @x;
+                               }
+                       }
+                       next;
+               }
+               if ($x[0] eq 'pv') {
+                       $uciinfo{'pv' . $mpv} = [ @x[1..$#x] ];
+                       last;
+               }
+               if ($x[0] eq 'string' || $x[0] eq 'UCI_AnalyseMode' || $x[0] eq 'setting' || $x[0] eq 'contempt') {
+                       last;
+               }
+
+               #print "unknown info '$x[0]', trying to recover...\n";
+               #shift @x;
+               die "Unknown info '" . join(',', @x) . "'";
+
+       }
+}
+
+sub parse_ids {
+       my (@x) = @_;
+
+       while (scalar @x > 0) {
+               if ($x[0] =~ /^(name|author)$/) {
+                       my $key = shift @x;
+                       my $value = join(' ', @x);
+                       $uciid{$key} = $value;
+                       last;
+               }
+
+               # unknown
+               shift @x;
+       }
+}
+
+sub style12_to_fen {
+       my $str = shift;
+       my %pos = ();
+       my (@x) = split / /, $str;
+       
+       $pos{'board'} = [ @x[1..8] ];
+       $pos{'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 "");
+       # $castling = "-"; # chess960
+       $fen .= " ";
+       $fen .= $castling;
+
+       # en passant
+       my $ep = "-";
+       if ($x[10] != -1) {
+               my $col = $x[10];
+               my $nep = (qw(a b c d e f g h))[$col];
+
+               if ($x[9] eq 'B') {
+                       $nep .= "3";
+               } else {
+                       $nep .= "6";
+               }
+
+               #
+               # Showing the en passant square when actually no capture can be made
+               # seems to confuse at least Rybka. Thus, check if there's actually
+               # a pawn of the opposite side that can do the en passant move, and if
+               # not, just lie -- it doesn't matter anyway. I'm unsure what's the
+               # "right" thing as per the standard, though.
+               #
+               if ($x[9] eq 'B') {
+                       $ep = $nep if ($col > 0 && substr($pos{'board'}[4], $col-1, 1) eq 'p');
+                       $ep = $nep if ($col < 7 && substr($pos{'board'}[4], $col+1, 1) eq 'p');
+               } else {
+                       $ep = $nep if ($col > 0 && substr($pos{'board'}[3], $col-1, 1) eq 'P');
+                       $ep = $nep if ($col < 7 && substr($pos{'board'}[3], $col+1, 1) eq 'P');
+               }
+       }
+       $fen .= " ";
+       $fen .= $ep;
+
+       # half-move clock
+       $fen .= " ";
+       $fen .= $x[15];
+
+       # full-move clock
+       $fen .= " ";
+       $fen .= $x[26];
+
+       $pos{'fen'} = $fen;
+       $pos{'move_num'} = $x[26];
+       $pos{'last_move'} = $x[29];
+
+       return \%pos;
+}
+
+sub prettyprint_pv {
+       my ($board, @pvs) = @_;
+
+       if (scalar @pvs == 0 || !defined($pvs[0])) {
+               return ();
+       }
+       
+       my @nb = @$board;
+
+       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);
+
+       if ($piece eq '-') {
+               die "Invalid move $pv";
+       }
+
+       # white short castling
+       if ($pv eq 'e1g1' && $piece eq 'K') {
+               # 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 'e1c1' && $piece eq 'K') {
+               # king
+               substr($nb[7], 4, 1, '-');
+               substr($nb[7], 2, 1, $piece);
+               
+               # rook
+               substr($nb[7], 0, 1, '-');
+               substr($nb[7], 3, 1, 'R');
+                               
+               return ('0-0-0', prettyprint_pv(\@nb, @pvs));
+       }
+
+       # black short castling
+       if ($pv eq 'e8g8' && $piece eq 'k') {
+               # 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 'e8c8' && $piece eq 'k') {
+               # king
+               substr($nb[0], 4, 1, '-');
+               substr($nb[0], 2, 1, $piece);
+               
+               # rook
+               substr($nb[0], 0, 1, '-');
+               substr($nb[0], 3, 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 ($from_col != $to_col) {
+                       $pretty = substr($pv, 0, 1) . 'x' . substr($pv, 2, 2);
+
+                       # en passant?
+                       if (substr($board->[$to_row], $to_col, 1) eq '-') {
+                               if ($piece eq 'p') {
+                                       substr($nb[$to_row + 1], $to_col, 1, '-');
+                               } else {
+                                       substr($nb[$to_row - 1], $to_col, 1, '-');
+                               }
+                       }
+               } 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
+       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;
+       
+       return if (!defined($pos_calculating));
+
+       #
+       # 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
+       # it can frequently miss stuff, but it's good enough for most users.
+       #
+       eval {
+               my $dummy;
+               if (exists($uciinfo{'pv'})) {
+                       $dummy = prettyprint_pv($pos_calculating->{'board'}, @{$uciinfo{'pv'}});
+               }
+       
+               my $mpv = 1;
+               while (exists($uciinfo{'pv' . $mpv})) {
+                       $dummy = prettyprint_pv($pos_calculating->{'board'}, @{$uciinfo{'pv' . $mpv}});
+                       ++$mpv;
+               }
+       };
+       if ($@) {
+               %uciinfo = ();
+               return;
+       }
+
+       my $text = 'Analysis';
+       if ($pos_calculating->{'last_move'} ne 'none') {
+               if ($pos_calculating->{'toplay'} eq 'W') {
+                       $text .= sprintf ' after %u. ... %s', ($pos_calculating->{'move_num'}-1), $pos_calculating->{'last_move'};
+               } else {
+                       $text .= sprintf ' after %u. %s', $pos_calculating->{'move_num'}, $pos_calculating->{'last_move'};
+               }
+               if (exists($uciid{'name'})) {
+                       $text .= ',';
+               }
+       }
+
+       if (exists($uciid{'name'})) {
+               $text .= " by $uciid{'name'}:\n\n";
+       } else {
+               $text .= ":\n\n";
+       }
+
+       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($uciinfo{'pv1'}) && !exists($uciinfo{'pv2'})) {
+               for my $key qw(pv score_cp score_mate nodes nps depth seldepth tbhits) {
+                       if (exists($uciinfo{$key . '1'}) && !exists($uciinfo{$key})) {
+                               $uciinfo{$key} = $uciinfo{$key . '1'};
+                       }
+               }
+       }
+
+       if (exists($uciinfo{'pv1'}) && exists($uciinfo{'pv2'})) {
+               # multi-PV
+               my $mpv = 1;
+               while (exists($uciinfo{'pv' . $mpv})) {
+                       $text .= sprintf "  PV%2u", $mpv;
+                       my $score = short_score(\%uciinfo, $pos_calculating, $mpv);
+                       $text .= "  ($score)" if (defined($score));
+
+                       my $tbhits = '';
+                       if (exists($uciinfo{'tbhits' . $mpv}) && $uciinfo{'tbhits' . $mpv} > 0) {
+                               if ($uciinfo{'tbhits' . $mpv} == 1) {
+                                       $tbhits = ", 1 tbhit";
+                               } else {
+                                       $tbhits = sprintf ", %u tbhits", $uciinfo{'tbhits' . $mpv};
+                               }
+                       }
+
+                       if (exists($uciinfo{'nodes' . $mpv}) && exists($uciinfo{'nps' . $mpv}) && exists($uciinfo{'depth' . $mpv})) {
+                               $text .= sprintf " (%5u kn, %3u kn/s, %2u ply$tbhits)",
+                                       $uciinfo{'nodes' . $mpv} / 1000, $uciinfo{'nps' . $mpv} / 1000, $uciinfo{'depth' . $mpv};
+                       }
+
+                       $text .= ":\n";
+                       $text .= "  " . join(', ', prettyprint_pv($pos_calculating->{'board'}, @{$uciinfo{'pv' . $mpv}})) . "\n";
+                       $text .= "\n";
+                       ++$mpv;
+               }
+       } else {
+               # single-PV
+               my $score = long_score(\%uciinfo, $pos_calculating, '');
+               $text .= "  $score\n" if defined($score);
+               $text .=  "  PV: " . join(', ', prettyprint_pv($pos_calculating->{'board'}, @{$uciinfo{'pv'}}));
+               $text .=  "\n";
+
+               if (exists($uciinfo{'nodes'}) && exists($uciinfo{'nps'}) && exists($uciinfo{'depth'})) {
+                       $text .= sprintf "  %u nodes, %7u nodes/sec, depth %u ply",
+                               $uciinfo{'nodes'}, $uciinfo{'nps'}, $uciinfo{'depth'};
+               }
+               if (exists($uciinfo{'tbhits'}) && $uciinfo{'tbhits'} > 0) {
+                       if ($uciinfo{'tbhits'} == 1) {
+                               $text .= ", one Nalimov hit";
+                       } else {
+                               $text .= sprintf ", %u Nalimov hits", $uciinfo{'tbhits'};
+                       }
+               }
+               if (exists($uciinfo{'seldepth'})) {
+                       $text .= sprintf " (%u selective)", $uciinfo{'seldepth'};
+               }
+               $text .=  "\n\n";
+       }
+
+       #$text .= book_info($pos_calculating->{'fen'}, $pos_calculating->{'board'}, $pos_calculating->{'toplay'});
+
+       if ($last_text ne $text) {
+               print "\e[H\e[2J"; # clear the screen
+               print $text;
+               $last_text = $text;
+       }
+
+       # Now construct the tell text, if any
+       return if (!defined($telltarget));
+
+       my $tell_text = '';
+
+       if (exists($uciid{'name'})) {
+               $tell_text .= "Analysis by $uciid{'name'} -- see http://analysis.sesse.net/ for more information\n";
+       } else {
+               $tell_text .= "Computer analysis -- http://analysis.sesse.net/ for more information\n";
+       }
+
+       if (exists($uciinfo{'pv1'}) && exists($uciinfo{'pv2'})) {
+               # multi-PV
+               my $mpv = 1;
+               while (exists($uciinfo{'pv' . $mpv})) {
+                       $tell_text .= sprintf "  PV%2u", $mpv;
+                       my $score = short_score(\%uciinfo, $pos_calculating, $mpv);
+                       $tell_text .= "  ($score)" if (defined($score));
+
+                       if (exists($uciinfo{'depth' . $mpv})) {
+                               $tell_text .= sprintf " (%2u ply)", $uciinfo{'depth' . $mpv};
+                       }
+
+                       $tell_text .= ": ";
+                       $tell_text .= join(', ', prettyprint_pv($pos_calculating->{'board'}, @{$uciinfo{'pv' . $mpv}}));
+                       $tell_text .= "\n";
+                       ++$mpv;
+               }
+       } else {
+               # single-PV
+               my $score = long_score(\%uciinfo, $pos_calculating, '');
+               $tell_text .= "  $score\n" if defined($score);
+               $tell_text .= "  PV: " . join(', ', prettyprint_pv($pos_calculating->{'board'}, @{$uciinfo{'pv'}}));
+               if (exists($uciinfo{'depth'})) {
+                       $tell_text .= sprintf " (depth %u ply)", $uciinfo{'depth'};
+               }
+               $tell_text .=  "\n";
+       }
+
+       # see if a new tell is called for -- it is if the delay has expired _and_
+       # this is not simply a repetition of the last one
+       if ($last_told_text ne $tell_text) {
+               my $now = time;
+               for my $iv (@tell_intervals) {
+                       last if ($now - $last_move < $iv);
+                       next if ($last_tell - $last_move >= $iv);
+
+                       for my $line (split /\n/, $tell_text) {
+                               $t->print("tell $telltarget [$target] $line");
+                       }
+
+                       $last_told_text = $text;
+                       $last_tell = $now;
+
+                       last;
+               }
+       }
+}
+
+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 '-');
+               
+                       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;
+}
+
+sub uciprint {
+       my $msg = shift;
+       print UCIWRITE "$msg\n";
+       print UCILOG localtime() . " => $msg\n";
+}
+
+sub short_score {
+       my ($uciinfo, $pos, $mpv) = @_;
+
+       if (defined($uciinfo{'score_mate' . $mpv})) {
+               return sprintf "M%3d", $uciinfo{'score_mate' . $mpv};
+       } else {
+               if (exists($uciinfo{'score_cp' . $mpv})) {
+                       my $score = $uciinfo{'score_cp' . $mpv} * 0.01;
+                       if ($pos->{'toplay'} eq 'B') {
+                               $score = -$score;
+                       }
+                       return sprintf "%+5.2f", $score;
+               }
+       }
+
+       return undef;
+}
+
+sub long_score {
+       my ($uciinfo, $pos, $mpv) = @_;
+
+       if (defined($uciinfo{'score_mate' . $mpv})) {
+               my $mate = $uciinfo{'score_mate' . $mpv};
+               if ($pos->{'toplay'} eq 'B') {
+                       $mate = -$mate;
+               }
+               if ($mate > 0) {
+                       return sprintf "White mates in %u", $mate;
+               } else {
+                       return sprintf "Black mates in %u", -$mate;
+               }
+       } else {
+               if (exists($uciinfo{'score_cp' . $mpv})) {
+                       my $score = $uciinfo{'score_cp' . $mpv} * 0.01;
+                       if ($pos->{'toplay'} eq 'B') {
+                               $score = -$score;
+                       }
+                       return sprintf "Score: %+5.2f", $score;
+               }
+       }
+
+       return undef;
+}
+
+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;
+}