--- /dev/null
+#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);
+}
+
--- /dev/null
+#! /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;
+}