From a8b3bf55a7bed6924b44c94931d0fc2c71f6aae2 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Tue, 22 Jan 2013 17:18:14 +0100 Subject: [PATCH 1/1] Initial checkin for move to Git (no prior version history available). --- booklook.c | 999 ++++++++++++++++++++++++++++++++++++++++++++++++ remoteglot.pl | 1016 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2015 insertions(+) create mode 100644 booklook.c create mode 100755 remoteglot.pl diff --git a/booklook.c b/booklook.c new file mode 100644 index 0000000..01c7348 --- /dev/null +++ b/booklook.c @@ -0,0 +1,999 @@ +#include +#include +#include +#include +#include + +#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 index 0000000..16416ba --- /dev/null +++ b/remoteglot.pl @@ -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 +# 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 () { + /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 ""; # 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; +} -- 2.39.2