diff --git a/.gitignore b/.gitignore index 6887c61..1806165 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ mbb_rook.h tests *.dSYM between_lookup.h +diagonals.h diff --git a/Makefile b/Makefile index 98a241c..9f5e18a 100644 --- a/Makefile +++ b/Makefile @@ -3,14 +3,14 @@ BUILD ?= debug CC := clang CFLAGS.gcc := -std=c23 -Wall -Wextra -Wconversion -Wno-unused-function -CFLAGS.gcc.release := -Ofast +CFLAGS.gcc.release := -Ofast -march=native -DNDEBUG CFLAGS.gcc.debug := -ggdb -O0 -fsanitize=address -CFLAGS.clang := -std=c23 -Wall -Wextra -Wconversion -Wno-unused-function -Wimplicit-int-conversion -CFLAGS.clang.release := -Ofast -march=native -CFLAGS.clang.debug := -ggdb -O0 -fsanitize=address +CFLAGS.clang := -std=c23 -g -Wall -Wextra -Wconversion -Wno-unused-function -Wimplicit-int-conversion +CFLAGS.clang.release := -O3 -ffast-math -march=native # -DNDEBUG +CFLAGS.clang.debug := -g3 -O0 -fsanitize=address CFLAGS.clang.wasm := \ - --target=wasm32-unknown-unknown -O3 -nostdlib \ + --target=wasm32-unknown-unknown -nostdlib -g \ -Wl,--export-all \ -Wl,--no-entry @@ -23,8 +23,8 @@ wasm: chess.wasm codegen: codegen.c $(CC) -o $@ $(CFLAGS) $^ -chess.wasm: wasm-compat.c - $(CC) -DWASM -o $@ wasm-compat.c $(CFLAGS.$(CC)) $(CFLAGS.$(CC).wasm) +chess.wasm: wasm-compat-chatgpt.c mbb_rook.h mbb_bishop.h engine.h + $(CC) -DWASM -o $@ wasm-compat-chatgpt.c $(CFLAGS.$(CC)) $(CFLAGS.$(CC).wasm) mbb_rook.h: codegen ./codegen @@ -33,4 +33,4 @@ mbb_bishop.h: codegen ./codegen tests: tests.c mbb_rook.h mbb_bishop.h engine.h - $(CC) -o $@ $(CFLAGS) tests.c + $(CC) -o $@ $(CFLAGS) -DUSE_PRINTF tests.c diff --git a/board_print.h b/board_print.h index ca51b50..c5cf8a8 100644 --- a/board_print.h +++ b/board_print.h @@ -1,3 +1,4 @@ +#pragma once #include #include @@ -9,15 +10,15 @@ #define INDEX_FMT PRIu8 -static void bitboard_print(bitboard b, FILE* out) +static void bitboard_print(Bb64 b, FILE* out) { setlocale(LC_ALL, ""); fprintf(out, "\n"); - for (index i = 7; i < 8; i--) { + for (Index8 i = 7; i < 8; i--) { fprintf(out, "\033[0m"); /* reset background color */ fprintf(out, "%"INDEX_FMT" ", i+1); - for (index j = 0; j < 8; ++j) { - index const n = INDEX_FROM_RF(i,j); + for (Index8 j = 0; j < 8; ++j) { + Index8 const n = SQ_FROM_RF(i,j); int color; if ((b >> n) & 1) { /* 41: red */ @@ -35,59 +36,6 @@ static void bitboard_print(bitboard b, FILE* out) fprintf(out, " A B C D E F G H \n"); } -static void board_print_threats(const struct pos* pos, FILE* out, struct move* move) -{ - enum player const player = pos->player; - int buf[8][8] = {0}; - - bitboard const threats = all_threats_from_player(pos, player); - - for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; ++player) { - for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) { - bitboard x = pos->pieces[player][piece]; - - for (index i = 7; i < 8; i--) { - for (index j = 0; j < 8; ++j) { - if (x & (1ULL<<(i*8+j))) { - buf[i][j] = piece_unicode[player][piece]; - } - } - } - } - } - - /* see: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors */ - setlocale(LC_ALL, ""); /* needed for unicode symbols */ - for (index i = 7; i < 8; i--) { - fprintf(out, "\033[0m"); /* reset background color */ - fprintf(out, "%"INDEX_FMT" ", i+1); - for (index j = 0; j < 8; ++j) { - index const n = INDEX_FROM_RF(i,j); - - if (move && n == move->from) { - fprintf(out, "\033[%d;%dm", 30, 44); /* 44: blue*/ - } else if (move && n == move->to) { - fprintf(out, "\033[%d;%dm", 30, 44); /* 104: bright blue*/ - } - else if ((threats >> n) & 1) { - fprintf(out, "\033[%d;%dm", 30, 41); /* 41: red */ - } else { - /* 45: magenta, 47: white */ - fprintf(out, "\033[%d;%dm", 30, (i+j) % 2 ? 45 : 47); - } - if (buf[i][j]) { - fprintf(out, "%lc ", buf[i][j]); - } else { - fprintf(out, " "); /* idk why this hack is needed but "%lc " - is not working when buf[i][j] = ' ' */ - } - } - fprintf(out, "\033[0m"); /* reset background color */ - fprintf(out, "\n"); - } - fprintf(out, " A B C D E F G H \n"); -} - static void tt_print_stats(struct tt* tt, FILE* out) { #ifndef NDEBUG @@ -105,6 +53,7 @@ static void tt_print_stats(struct tt* tt, FILE* out) fprintf(out, "tt insertions: %"PRIu64"\n", tt->insertions); fprintf(out, "saturation: %.02lf\n", pct); #else + (void)tt; fprintf(out, "stats not available with NDEBUG\n"); #endif } @@ -113,14 +62,14 @@ static void board_print_fen(struct pos const* pos, FILE* out) { int buf[8][8] = {0}; - for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; ++player) { - for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) { - bitboard x = pos->pieces[player][piece]; + for (Side8 side = SIDE_BEGIN; side < SIDE_COUNT; ++side) { + for (Piece8 piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) { + Bb64 x = pos->pieces[side][piece]; - for (index i = 7; i < 8; i--) { - for (index j = 0; j < 8; ++j) { + for (Index8 i = 7; i < 8; i--) { + for (Index8 j = 0; j < 8; ++j) { if (x & (1ULL<<(i*8+j))) { - buf[i][j] = piece_char[player][piece]; + buf[i][j] = piece_char[side][piece]; } } } @@ -147,22 +96,22 @@ static void board_print_fen(struct pos const* pos, FILE* out) fprintf(out, "/"); } - fprintf(out, " %c ", pos->player == PLAYER_WHITE ? 'w' : 'b'); + fprintf(out, " %c ", pos->moving_side == SIDE_WHITE ? 'w' : 'b'); bool any_castle = false; - if (!pos->castling_illegal[PLAYER_WHITE][CASTLE_KINGSIDE]) { + if (!pos->castling_illegal[SIDE_WHITE][CASTLE_KINGSIDE]) { fprintf(out, "K"); any_castle = true; } - if (!pos->castling_illegal[PLAYER_WHITE][CASTLE_QUEENSIDE]) { + if (!pos->castling_illegal[SIDE_WHITE][CASTLE_QUEENSIDE]) { fprintf(out, "Q"); any_castle = true; } - if (!pos->castling_illegal[PLAYER_BLACK][CASTLE_KINGSIDE]) { + if (!pos->castling_illegal[SIDE_BLACK][CASTLE_KINGSIDE]) { fprintf(out, "k"); any_castle = true; } - if (!pos->castling_illegal[PLAYER_BLACK][CASTLE_QUEENSIDE]) { + if (!pos->castling_illegal[SIDE_BLACK][CASTLE_QUEENSIDE]) { fprintf(out, "q"); any_castle = true; } @@ -172,18 +121,18 @@ static void board_print_fen(struct pos const* pos, FILE* out) if (pos->ep_targets) { /* should be ep target square in algebraic notation */ - enum square_index const sqi = bitboard_lsb(pos->ep_targets); + Sq8 const sqi = bitboard_lsb(pos->ep_targets); - assert(sqi >= SQ_INDEX_BEGIN && sqi < SQ_INDEX_COUNT); + assuming(sqi >= SQ_BEGIN && sqi < SQ_COUNT); - enum file_index const fi = index_to_file(sqi); - enum rank_index const ri = index_to_rank(sqi); + enum file_index const fi = sq_to_file(sqi); + enum rank_index const ri = sq_to_rank(sqi); int const fch = tolower(file_index_char[fi]); int const rch = tolower(rank_index_char[ri]); - assert(fch >= 'a' && fch <= 'h'); - assert(rch >= '1' && rch <= '8'); + assuming(fch >= 'a' && fch <= 'h'); + assuming(rch >= '1' && rch <= '8'); fprintf(out, " %c%c", fch, rch); } else { @@ -196,20 +145,20 @@ static void board_print_fen(struct pos const* pos, FILE* out) fprintf(out, "\n"); } -static void board_print(const struct pos* pos, struct move* move, FILE* out) +static void board_print(struct pos const* pos, struct move* move, FILE* out, bool print_threats) { int buf[8][8] = {0}; int color[8][8] = {0}; - for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; ++player) { - for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) { - bitboard x = pos->pieces[player][piece]; + for (Side8 side = SIDE_BEGIN; side < SIDE_COUNT; ++side) { + for (Piece8 piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) { + Bb64 x = pos->pieces[side][piece]; - for (index i = 7; i < 8; i--) { - for (index j = 0; j < 8; ++j) { + for (Index8 i = 7; i < 8; i--) { + for (Index8 j = 0; j < 8; ++j) { if (x & (1ULL<<(i*8+j))) { - buf[i][j] = piece_unicode[PLAYER_BLACK][piece]; - color[i][j] = (player == PLAYER_WHITE) + buf[i][j] = piece_unicode[SIDE_BLACK][piece]; + color[i][j] = (side == SIDE_WHITE) ? 1 : 2; } @@ -218,16 +167,19 @@ static void board_print(const struct pos* pos, struct move* move, FILE* out) } } + Bb64 const threats = all_threats_from_side(pos, pos->moving_side); + /* see: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors */ setlocale(LC_ALL, ""); - for (index i = 7; i < 8; i--) { + for (Index8 i = 7; i < 8; i--) { fprintf(out, "\033[0m"); /* reset background color */ fprintf(out, "%"INDEX_FMT" ", i+1); - for (index j = 0; j < 8; ++j) { - index const n = INDEX_FROM_RF(i,j); + for (Index8 j = 0; j < 8; ++j) { + Index8 const n = SQ_FROM_RF(i,j); int bg, fg; + /* foreground color */ /**/ if (color[i][j] == 1) { fg = 97; /* bright white */ } @@ -238,14 +190,20 @@ static void board_print(const struct pos* pos, struct move* move, FILE* out) fg = 35; /* magenta (should not happen) */ } - if (move && n == move->from) { - bg = 104; /* blue */ - } else if (move && n == move->to) { + /* background color */ + /**/ if (move && (n == move->to || n == move->from)) { bg = 44; /* bright blue */ + } + else if (print_threats && (threats >> n) & 1) { + bg = 41; } else { /* 45: magenta, - * 47: white */ - bg = (i+j) % 2 ? 45 : 47; + * 43: yellow */ + bg = (i+j) % 2 ? 45 : 43; + } + + if ((pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK]) & MASK_FROM_SQ(n)) { + bg += 60; /* make bright */ } fprintf(out, "\033[%d;%dm", fg, bg); @@ -261,5 +219,9 @@ static void board_print(const struct pos* pos, struct move* move, FILE* out) fprintf(out, "\n"); } fprintf(out, " A B C D E F G H \n"); + fprintf(out, "half moves: %d\n" + "full moves: %d\n", + pos->halfmoves, + pos->fullmoves); } diff --git a/codegen.c b/codegen.c index da8febf..b9eb221 100644 --- a/codegen.c +++ b/codegen.c @@ -5,79 +5,61 @@ #include #include -/* gets the next number with the same bitcount, unless it overflows, in which - * it returns the first number with popcount+1 */ -uint64_t next_magic(uint64_t seed) { - - uint64_t u = seed & -seed; // rightmost 1-bit - uint64_t v = seed + u; // add it - if (v == 0) { - if (seed == ~0ULL) { - return 0; - } - return next_magic((1ULL << (popcount(seed)+1)) - 1); - } - seed = v + (((v ^ seed) / u) >> 2); - - return seed; -} - - int main() { - struct magic rook_magics[SQ_INDEX_COUNT] = {0ULL}; - bitboard rook_attacks[SQ_INDEX_COUNT][1<<12ULL] = {0}; + struct magic rook_magics[SQ_COUNT] = {0ULL}; + Bb64 rook_attacks[SQ_COUNT][1<<12ULL] = {0}; - index rook_relevant_bits[12] = {0}; + Index8 rook_relevant_bits[12] = {0}; size_t rook_relevant_bits_count = 0; - struct magic bishop_magics[SQ_INDEX_COUNT] = {0ULL}; - bitboard bishop_attacks[SQ_INDEX_COUNT][1<<9ULL] = {0}; + struct magic bishop_magics[SQ_COUNT] = {0ULL}; + Bb64 bishop_attacks[SQ_COUNT][1<<9ULL] = {0}; - index bishop_relevant_bits[9] = {0}; + Index8 bishop_relevant_bits[9] = {0}; size_t bishop_relevant_bits_count = 0; - bitboard between_lookup[SQ_INDEX_COUNT][SQ_INDEX_COUNT]; + Bb64 between_lookup[SQ_COUNT][SQ_COUNT]; - bitboard diagonals[SQ_INDEX_COUNT]; + Bb64 diagonals[SQ_COUNT]; - for (enum square_index sq = SQ_INDEX_BEGIN; sq < SQ_INDEX_COUNT; ++sq) { + for (Sq8 sq = SQ_BEGIN; sq < SQ_COUNT; ++sq) { diagonals[sq] = diagonals_from_index(sq); } - for (enum square_index sq_index = SQ_INDEX_BEGIN; sq_index < SQ_INDEX_COUNT; ++sq_index) { - enum file_index file = index_to_file(sq_index); - enum rank_index rank = index_to_rank(sq_index); - fprintf(stderr, "%s ", square_index_display[sq_index]); + for (Sq8 sq_index = SQ_BEGIN; sq_index < SQ_COUNT; ++sq_index) { + enum file_index file = sq_to_file(sq_index); + enum rank_index rank = sq_to_rank(sq_index); + fprintf(stderr, "%s ", sq8_display[sq_index]); /* ROOKS * ======================= */ { /* generate attack mask */ - bitboard atk_mask = 0; + Bb64 atk_mask = 0; { atk_mask |= FILE_MASK(file) & ~(RANK_MASK_1 | RANK_MASK_8); atk_mask |= RANK_MASK(rank) & ~(FILE_MASK_A | FILE_MASK_H); - atk_mask &= ~SQ_MASK_FROM_RF(rank, file); + atk_mask &= ~MASK_FROM_RF(rank, file); /* populate relevant bits array */ rook_relevant_bits_count = 0; - bitboard x = atk_mask; + Bb64 x = atk_mask; while (x) { - const index lsb = bitboard_pop_lsb(&x); + Sq8 const lsb = bitboard_pop_lsb(&x); rook_relevant_bits[rook_relevant_bits_count++] = lsb; } } rook_magics[sq_index].mask = atk_mask; /* brute force the magic number */ - for (;;) { - bitboard magic = my_rand64() & my_rand64() & my_rand64(); + for (;;) { + Bb64 magic = my_rand64() & my_rand64() & my_rand64(); - bitboard atk = 0ULL; - for (bitboard test = 0; test < (1ULL<> i) & 1ULL) << rook_relevant_bits[i]; } @@ -108,32 +90,32 @@ int main() * ====================== */ { /* generate attack mask */ - bitboard atk_mask = 0; + Bb64 atk_mask = 0; { atk_mask |= diagonals_from_index(sq_index); atk_mask &= ~(RANK_MASK_1 | RANK_MASK_8); atk_mask &= ~(FILE_MASK_A | FILE_MASK_H); - atk_mask &= ~SQ_MASK_FROM_RF(rank, file); + atk_mask &= ~MASK_FROM_RF(rank, file); /* populate relevant bits array */ bishop_relevant_bits_count = 0; - bitboard x = atk_mask; + Bb64 x = atk_mask; while (x) { - const index lsb = bitboard_pop_lsb(&x); + Sq8 const lsb = bitboard_pop_lsb(&x); bishop_relevant_bits[bishop_relevant_bits_count++] = lsb; } } bishop_magics[sq_index].mask = atk_mask; /* brute force the magic number */ - bitboard magic = 0ULL; - for (;;) { + Bb64 magic = 0ULL; + for (;;) { magic = my_rand64() & my_rand64() & my_rand64(); - bitboard atk = 0ULL; - for (bitboard test = 0; test < (1ULL<> i) & 1ULL) << bishop_relevant_bits[i]; } @@ -161,8 +143,8 @@ int main() /* BETWEEN TABLE * ===================== */ { - for (enum square_index i = SQ_INDEX_BEGIN; i < SQ_INDEX_COUNT; ++i) { - for (enum square_index j = SQ_INDEX_BEGIN; j < SQ_INDEX_COUNT; ++j) { + for (Sq8 i = SQ_BEGIN; i < SQ_COUNT; ++i) { + for (Sq8 j = SQ_BEGIN; j < SQ_COUNT; ++j) { between_lookup[i][j] = between_mask(i, j); } } @@ -180,10 +162,10 @@ int main() exit(EXIT_FAILURE); } - fprintf(f, "static const bitboard diagonals[SQ_INDEX_COUNT] = {\n"); - for (enum square_index i = SQ_INDEX_BEGIN; i < SQ_INDEX_COUNT; ++i) { + fprintf(f, "static const Bb64 diagonals[SQ_COUNT] = {\n"); + for (Sq8 i = SQ_BEGIN; i < SQ_COUNT; ++i) { fprintf(f, "[%s] = 0x%016"BITBOARD_FMT_X",\n", - square_index_str[i], + sq8_str[i], diagonals[i]); } fprintf(f,"};\n"); @@ -198,15 +180,15 @@ int main() } fprintf(f, "#pragma once\n"); - fprintf(f, "static const struct magic mbb_rook[SQ_INDEX_COUNT] = {\n"); - for (enum square_index i = SQ_INDEX_BEGIN; i < SQ_INDEX_COUNT; i++) { + fprintf(f, "static const struct magic mbb_rook[SQ_COUNT] = {\n"); + for (Sq8 i = SQ_BEGIN; i < SQ_COUNT; i++) { fprintf(f, TAB "[%s] = (struct magic) {" NL TAB " .magic = 0x%016"BITBOARD_FMT_X"ULL," NL TAB " .mask = 0x%016"BITBOARD_FMT_X"ULL," NL TAB "}," NL, - square_index_str[i], + sq8_str[i], rook_magics[i].magic, rook_magics[i].mask); } @@ -214,9 +196,9 @@ int main() fprintf(f,"\n"); - fprintf(f, "static const bitboard rook_attacks[SQ_INDEX_COUNT][1ULL<<12ULL] = {\n"); - for (enum square_index sq = SQ_INDEX_BEGIN; sq < SQ_INDEX_COUNT; ++sq) { - fprintf(f, "[%s] = {\n", square_index_str[sq]); + fprintf(f, "static const Bb64 rook_attacks[SQ_COUNT][1ULL<<12ULL] = {\n"); + for (Sq8 sq = SQ_BEGIN; sq < SQ_COUNT; ++sq) { + fprintf(f, "[%s] = {\n", sq8_str[sq]); for (size_t i = 0; i < sizeof rook_attacks[sq] / sizeof *rook_attacks[sq]; i++) { fprintf(f, "0x%016"BITBOARD_FMT_X"ULL, \n", rook_attacks[sq][i]); } @@ -235,15 +217,15 @@ int main() } fprintf(f, "\n"); - fprintf(f, "static const struct magic mbb_bishop[SQ_INDEX_COUNT] = {\n"); - for (enum square_index i = SQ_INDEX_BEGIN; i < SQ_INDEX_COUNT; i++) { + fprintf(f, "static const struct magic mbb_bishop[SQ_COUNT] = {\n"); + for (Sq8 i = SQ_BEGIN; i < SQ_COUNT; i++) { fprintf(f, TAB "[%s] = (struct magic) {" NL TAB " .magic = 0x%016"BITBOARD_FMT_X"ULL," NL TAB " .mask = 0x%016"BITBOARD_FMT_X"ULL," NL TAB "}," NL, - square_index_str[i], + sq8_str[i], bishop_magics[i].magic, bishop_magics[i].mask); } @@ -251,9 +233,9 @@ int main() fprintf(f,"\n"); - fprintf(f, "static const bitboard bishop_attacks[SQ_INDEX_COUNT][1ULL<<9ULL] = {\n"); - for (enum square_index sq = SQ_INDEX_BEGIN; sq < SQ_INDEX_COUNT; ++sq) { - fprintf(f, "[%s] = {\n", square_index_str[sq]); + fprintf(f, "static const Bb64 bishop_attacks[SQ_COUNT][1ULL<<9ULL] = {\n"); + for (Sq8 sq = SQ_BEGIN; sq < SQ_COUNT; ++sq) { + fprintf(f, "[%s] = {\n", sq8_str[sq]); for (size_t i = 0; i < sizeof bishop_attacks[sq] / sizeof *bishop_attacks[sq]; i++) { fprintf(f, "0x%016"BITBOARD_FMT_X"ULL, \n", bishop_attacks[sq][i]); } @@ -270,10 +252,10 @@ int main() exit(EXIT_FAILURE); } - fprintf(f, "static const bitboard between_lookup[SQ_INDEX_COUNT][SQ_INDEX_COUNT] = {\n"); - for (enum square_index i = SQ_INDEX_BEGIN; i < SQ_INDEX_COUNT; ++i) { - fprintf(f, "[%s] = {\n", square_index_str[i]); - for (enum square_index j = SQ_INDEX_BEGIN; j < SQ_INDEX_COUNT; ++j) { + fprintf(f, "static const Bb64 between_lookup[SQ_COUNT][SQ_COUNT] = {\n"); + for (Sq8 i = SQ_BEGIN; i < SQ_COUNT; ++i) { + fprintf(f, "[%s] = {\n", sq8_str[i]); + for (Sq8 j = SQ_BEGIN; j < SQ_COUNT; ++j) { fprintf(f, "0x%016"BITBOARD_FMT_X"ULL, \n", between_lookup[i][j]); } fprintf(f, "\n},\n"); diff --git a/engine-attack-sets.h b/engine-attack-sets.h new file mode 100644 index 0000000..55119c9 --- /dev/null +++ b/engine-attack-sets.h @@ -0,0 +1,591 @@ +#pragma once + +#include "engine-types.h" +#include "engine-board.h" + +/* `struct magic` is used by magic bitboard lookups, see + * rook_attacks_from_index and bishop_attacks_from_index */ +struct magic { + Bb64 mask; + Bb64 magic; +}; + +static Bb64 cardinals_from_index(Sq8 p) +{ + return (FILE_MASK(sq_to_file(p)) | RANK_MASK(sq_to_rank(p))); +} + +static Bb64 diagonals_from_index(Sq8 sq) +{ +#ifdef CODEGEN + enum rank_index const rank = sq_to_rank(sq); + enum file_index const file = sq_to_file(sq); + + Bb64 mask = 0ULL; + + _Static_assert(((enum rank_index)-1) > 0, "rank_index must be unsigned"); + _Static_assert(((enum file_index)-1) > 0, "file_index must be unsigned"); + + enum rank_index r; + enum file_index f; + for (r = rank+1, f = file+1; + r <= RANK_INDEX_8 && f <= FILE_INDEX_H; + ++r, ++f) + { + mask |= MASK_FROM_RF(r, f); + } + + for (r = rank+1, f = file-1; + r <= RANK_INDEX_8 && f <= FILE_INDEX_H; + ++r, --f) + { + mask |= MASK_FROM_RF(r, f); + } + + for (r = rank-1, f = file+1; + r <= RANK_INDEX_8 && f <= FILE_INDEX_H; + --r, ++f) + { + mask |= MASK_FROM_RF(r, f); + } + + for (r = rank-1, f = file-1; + r <= RANK_INDEX_8 && f <= FILE_INDEX_H; + --r, --f) + { + mask |= MASK_FROM_RF(r, f); + } + return mask; + +#else + #if ! __has_include("diagonals.h") + #error "compile with -DCODEGEN and run once to generate required header files" + #endif + #include "diagonals.h" /* defines static Bb64 diagonals[SQ_COUNT]; */ + return diagonals[sq]; +#endif +} + + + +/* PIECE ATTACKS + * ================== + * + * All piece attack functions rely on the caller masking their own pieces. + * e.g. `knight_attacks(knights) & ~our_occupied` + * */ + +static Bb64 knight_attacks(Bb64 p) +{ + Bb64 const l1 = MASK_SHIFT_EAST_1(p /*& ~FILE_MASK_H*/); + Bb64 const l2 = MASK_SHIFT_EAST_2(p /*& ~(FILE_MASK_G | FILE_MASK_H)*/); + Bb64 const r1 = MASK_SHIFT_WEST_1 (p /*& ~FILE_MASK_A*/); + Bb64 const r2 = MASK_SHIFT_WEST_2 (p /*& ~(FILE_MASK_A | FILE_MASK_B)*/); + Bb64 const h1 = l1 | r1; + Bb64 const h2 = l2 | r2; + return MASK_SHIFT_NORTH_2(h1) + | MASK_SHIFT_SOUTH_2(h1) + | MASK_SHIFT_NORTH_1(h2) + | MASK_SHIFT_SOUTH_1(h2); +} + +static Bb64 knight_attacks_from_index(Sq8 sq) +{ + return knight_attacks(MASK_FROM_SQ(sq)); +} + +static Bb64 rook_attacks_from_index(Sq8 sq, Bb64 occ) +{ +#ifdef CODEGEN + enum rank_index const rank = sq_to_rank(sq); + enum file_index const file = sq_to_file(sq); + + Bb64 atk = 0ULL; + + /* following loops assuming rank and file types are unsigned, which relies on C23 enums */ + _Static_assert(((enum rank_index)-1) > 0, "rank_index must be unsigned"); + _Static_assert(((enum file_index)-1) > 0, "file_index must be unsigned"); + + for (enum rank_index walk_rank = rank+1; walk_rank <= RANK_INDEX_8; ++walk_rank) { + Bb64 const sq = MASK_FROM_RF(walk_rank, file); + atk |= sq; + if (occ & sq) { + break; + } + } + for (enum rank_index walk_rank = rank-1; walk_rank <= RANK_INDEX_8; --walk_rank) { + Bb64 const sq = MASK_FROM_RF(walk_rank, file); + atk |= sq; + if (occ & sq) { + break; + } + } + for (enum file_index walk_file = file+1; walk_file <= FILE_INDEX_H; ++walk_file) { + Bb64 const sq = MASK_FROM_RF(rank, walk_file); + atk |= sq; + if (occ & sq) { + break; + } + } + for (enum file_index walk_file = file-1; walk_file <= FILE_INDEX_H; --walk_file) { + Bb64 const sq = MASK_FROM_RF(rank, walk_file); + atk |= sq; + if (occ & sq) { + break; + } + } + + return atk; + +#else + #if ! __has_include("mbb_rook.h") + #error "compile with -DCODEGEN and run once to generate required header files" + #else + + /* defines + - `mbb_rook[SQ_COUNT]` + - `Bb64 rook_attacks[SQ_COUNT][1<<12ULL] */ + #include "mbb_rook.h" + + struct magic const m = mbb_rook[sq]; + occ &= m.mask; + occ *= m.magic; + occ >>= (64ULL-12ULL); + assuming(rook_attacks[sq][occ] != MASK_FROM_SQ(sq)); + return rook_attacks[sq][occ]; + + #endif +#endif +} + +static Bb64 rook_attacks(Bb64 p, Bb64 occ) +{ + Bb64 b = 0ULL; + while (p) { + Sq8 const lsb = bitboard_pop_lsb(&p); + b |= rook_attacks_from_index(lsb, occ); + } + return b; +} + +static Bb64 bishop_attacks_from_index(Sq8 sq, Bb64 occ) +{ +#ifdef CODEGEN + enum rank_index const rank = sq_to_rank(sq); + enum file_index const file = sq_to_file(sq); + + Bb64 atk = 0ULL; + + /* following loops assuming rank and file types are unsigned, which relies on C23 enums */ + _Static_assert(((enum rank_index)-1) > 0, "rank_index must be unsigned"); + _Static_assert(((enum file_index)-1) > 0, "file_index must be unsigned"); + + enum rank_index walk_rank; + enum file_index walk_file; + for (walk_rank = rank+1, walk_file = file+1; + walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H; + ++walk_rank, ++walk_file) { + Bb64 const sq = MASK_FROM_RF(walk_rank, walk_file); + atk |= sq; + if (occ & sq) { + break; + } + } + for (walk_rank = rank+1, walk_file = file-1; + walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H; + ++walk_rank, --walk_file) { + Bb64 const sq = MASK_FROM_RF(walk_rank, walk_file); + atk |= sq; + if (occ & sq) { + break; + } + } + for (walk_rank = rank-1, walk_file = file+1; + walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H; + --walk_rank, ++walk_file) { + Bb64 const sq = MASK_FROM_RF(walk_rank, walk_file); + atk |= sq; + if (occ & sq) { + break; + } + } + for (walk_rank = rank-1, walk_file = file-1; + walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H; + --walk_rank, --walk_file) { + Bb64 const sq = MASK_FROM_RF(walk_rank, walk_file); + atk |= sq; + if (occ & sq) { + break; + } + } + + return atk; +#else + #if ! __has_include("mbb_bishop.h") + #error "compile with -DCODEGEN and run once to generate required header files" + #else + +/* defines + - `mbb_bishop[SQ_COUNT]` + - `Bb64 bishop_attacks[SQ_COUNT][1<<9ULL] */ +#include "mbb_bishop.h" + assuming(sq < SQ_COUNT); + struct magic const m = mbb_bishop[sq]; + occ &= m.mask; + occ *= m.magic; + occ >>= (64ULL-9ULL); + return bishop_attacks[sq][occ]; + #endif +#endif +} + +static Bb64 bishop_attacks(Bb64 p, Bb64 occ) +{ + Bb64 b = 0ULL; + while (p) { + Sq8 const lsb = bitboard_pop_lsb(&p); + b |= bishop_attacks_from_index(lsb, occ); + } + return b; +} + +static Bb64 queen_attacks_from_index(Sq8 sq, Bb64 occ) +{ + return bishop_attacks_from_index(sq, occ) | rook_attacks_from_index(sq, occ); +} + +static Bb64 queen_attacks(Bb64 p, Bb64 occ) +{ + Bb64 b = 0ULL; + while (p) { + Sq8 const lsb = bitboard_pop_lsb(&p); + b |= queen_attacks_from_index(lsb, occ); + } + return b; +} + +#define DEFINE_PAWN_MOVE_FUNCTIONS(identifier, side_enum, direction, starting_rank, next_rank) \ +static inline Bb64 pawn_moves_##identifier(Bb64 p, Bb64 empty)\ +{\ + Bb64 const single_push = MASK_SHIFT_##direction##_1(p) & empty;\ + Bb64 const double_push =\ + MASK_SHIFT_##direction##_1(single_push & next_rank) & empty;\ + return (single_push | double_push);\ +}\ +\ +static inline Bb64 pawn_moves_##identifier##_from_index(Sq8 sq, Bb64 empty)\ +{\ + return pawn_moves_##identifier(MASK_FROM_SQ(sq), empty);\ +}\ +\ +static inline Bb64 pawn_single_push_##identifier(Bb64 p, Bb64 empty)\ +{\ + return MASK_SHIFT_##direction##_1(p) & empty;\ +}\ +\ +static inline Bb64 pawn_double_push_##identifier(Bb64 p, Bb64 empty)\ +{\ + Bb64 const once = MASK_SHIFT_##direction##_1(p) & empty;\ + Bb64 const twice = MASK_SHIFT_##direction##_1(once) & empty;\ + return twice;\ +}\ +\ +static inline Bb64 pawn_attacks_right_##identifier(Bb64 p)\ +{\ + Bb64 const up = MASK_SHIFT_##direction##_1(p);\ + return MASK_SHIFT_EAST_1(up);\ +}\ +\ +static inline Bb64 pawn_attacks_left_##identifier(Bb64 p)\ +{\ + Bb64 const up = MASK_SHIFT_##direction##_1(p);\ + return MASK_SHIFT_WEST_1(up);\ +}\ +\ +static inline Bb64 pawn_attacks_##identifier(Bb64 p)\ +{\ + Bb64 const up = MASK_SHIFT_##direction##_1(p);\ + Bb64 const captures_right = MASK_SHIFT_EAST_1(up);\ + Bb64 const captures_left = MASK_SHIFT_WEST_1(up);\ +\ + return (captures_right | captures_left);\ +}\ +\ +static inline Bb64 pawn_attacks_##identifier##_from_index(Sq8 sq)\ +{\ + return pawn_attacks_##identifier(MASK_FROM_SQ(sq));\ +} + +DEFINE_PAWN_MOVE_FUNCTIONS(white, SIDE_WHITE, NORTH, RANK_MASK_2, RANK_MASK_3) +DEFINE_PAWN_MOVE_FUNCTIONS(black, SIDE_BLACK, SOUTH, RANK_MASK_7, RANK_MASK_6) + +/* temporary solution, annoying branch */ +static inline Bb64 pawn_moves_dispatch(Bb64 p, Bb64 empty, Side8 attacker) +{ + if (attacker == SIDE_WHITE) { + return pawn_moves_white(p, empty); + } else { + return pawn_moves_black(p, empty); + } +} + +/* temporary solution, annoying branch */ +static inline Bb64 pawn_moves_from_index_dispatch(Sq8 sq, Bb64 empty, Side8 attacker) +{ + if (attacker == SIDE_WHITE) { + return pawn_moves_white_from_index(sq, empty); + } else { + return pawn_moves_black_from_index(sq, empty); + } +} + +/* temporary solution, annoying branch */ +static inline Bb64 pawn_attacks_from_index_dispatch(Sq8 sq, Side8 attacker) +{ + if (attacker == SIDE_WHITE) { + return pawn_attacks_white_from_index(sq); + } else { + return pawn_attacks_black_from_index(sq); + } +} + +static inline Bb64 pawn_attacks_dispatch(Bb64 b, Side8 attacker) +{ + if (attacker == SIDE_WHITE) { + return pawn_attacks_white(b); + } else { + return pawn_attacks_black(b); + } +} + +static Bb64 king_attacks(Bb64 sq) +{ + /* potential untested improvements: + * - lookup table + * - union of three files, three ranks and ~sq */ + Bb64 const n = MASK_SHIFT_NORTH_1(sq); + Bb64 const s = MASK_SHIFT_SOUTH_1(sq); + Bb64 const w = MASK_SHIFT_WEST_1(sq); + Bb64 const e = MASK_SHIFT_EAST_1(sq); + + Bb64 const nw = MASK_SHIFT_WEST_1(n); + Bb64 const ne = MASK_SHIFT_EAST_1(n); + Bb64 const sw = MASK_SHIFT_WEST_1(s); + Bb64 const se = MASK_SHIFT_EAST_1(s); + + return (n | s | w | e | nw | ne | sw | se); +} + +static Bb64 king_attacks_from_index(Sq8 sq) +{ + return king_attacks(MASK_FROM_SQ(sq)); +} + +static Bb64 non_pawn_piece_attacks(Piece8 piece, Bb64 p, Bb64 occ) +{ + assuming(piece != PIECE_PAWN); + + switch (piece) { + case PIECE_KNIGHT: { + return knight_attacks(p); + } break; + + case PIECE_BISHOP: { + return bishop_attacks(p, occ); + } break; + + case PIECE_ROOK: { + return rook_attacks(p, occ); + } break; + + case PIECE_QUEEN: { + return queen_attacks(p, occ); + } break; + + case PIECE_KING: { + return king_attacks(p); + } break; + + default: { + assuming(false); + } break; + } +} + +static Bb64 non_pawn_piece_attacks_from_index(Piece8 piece, Sq8 sq, Bb64 occ) +{ + assuming(piece != PIECE_PAWN); + + switch (piece) { + case PIECE_KNIGHT: { + return knight_attacks_from_index(sq); + } break; + + case PIECE_BISHOP: { + return bishop_attacks_from_index(sq, occ); + } break; + + case PIECE_ROOK: { + return rook_attacks_from_index(sq, occ); + } break; + + case PIECE_QUEEN: { + return queen_attacks_from_index(sq, occ); + } break; + + case PIECE_KING: { + return king_attacks_from_index(sq); + } break; + + default: { + assuming(false); + } break; + } +} + +static Bb64 between_mask(Sq8 a, Sq8 b) +{ +#ifdef CODEGEN + enum rank_index const ra = sq_to_rank(a); + enum file_index const fa = sq_to_file(a); + enum rank_index const rb = sq_to_rank(b); + enum file_index const fb = sq_to_file(b); + + /* directional differences */ + int const dr = (int)rb - (int)ra; + int const df = (int)fb - (int)fa; + + int step_r = 0; + int step_f = 0; + + if (df == 0 && dr != 0) { + step_r = (dr > 0) ? 1 : -1; + step_f = 0; + } + else if (dr == 0 && df != 0) { + step_r = 0; + step_f = (df > 0) ? 1 : -1; + } + else if (dr != 0 && (dr > 0 ? dr : -dr) == (df > 0 ? df : -df)) { + step_r = (dr > 0) ? 1 : -1; + step_f = (df > 0) ? 1 : -1; + } + else { + return 0ULL; + } + + Bb64 mask = 0ULL; + + enum rank_index r = (enum rank_index)((int)ra + step_r); + enum file_index f = (enum file_index)((int)fa + step_f); + + while (r != rb || f != fb) { + mask |= MASK_FROM_RF(r, f); + r = (enum rank_index)((int)r + step_r); + f = (enum file_index)((int)f + step_f); + } + + return mask; +#else + #if ! __has_include("between_lookup.h") + #error "compile codegen.c and run once to generate required header files" + #else + + /* defines static between_lookup[sq8_COUNT][sq8_COUNT] */ + #include "between_lookup.h" + return between_lookup[a][b]; + #endif +#endif +} + +static Bb64 attacks_to(struct pos const* pos, + Bb64 targets, + Bb64 pretend_occupied, + Bb64 pretend_empty) +{ + Bb64 const occ_orig = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK]; + Bb64 const occ = (occ_orig & ~pretend_empty) | pretend_occupied; + + Bb64 const* pw = pos->pieces[SIDE_WHITE]; + Bb64 const* pb = pos->pieces[SIDE_BLACK]; + + Bb64 const wps = pw[PIECE_PAWN]; + + Bb64 const bps = pb[PIECE_PAWN]; + + Bb64 const ns = (pw[PIECE_KNIGHT] + | pb[PIECE_KNIGHT]) + ; + Bb64 const ks = (pw[PIECE_KING] + | pb[PIECE_KING]) + ; + Bb64 const qs = (pw[PIECE_QUEEN] + | pb[PIECE_QUEEN]) + ; + Bb64 const qrs = (pw[PIECE_ROOK] + | pb[PIECE_ROOK] + | qs) + ; + Bb64 const qbs = (pw[PIECE_BISHOP] + | pb[PIECE_BISHOP] + | qs) + ; + + return ((bps & pawn_attacks_white(targets)) + | (wps & pawn_attacks_black(targets)) + | (ns & knight_attacks(targets)) + | (qbs & bishop_attacks(targets, occ)) + | (qrs & rook_attacks(targets, occ)) + | (ks & king_attacks(targets))) + & ~pretend_occupied; +} + +static +Bb64 checkers(struct pos const* pos, Side8 us) +{ + /* TODO: specialize */ + return attacks_to(pos, pos->pieces[us][PIECE_KING], 0ULL, 0ULL) & ~pos->occupied[us]; +} + +static +Bb64 pinning_lines_to_target(struct pos const* pos, Side8 us, Sq8 tsq) +{ + Side8 const them = other_side(us); + Bb64 const our_occ = pos->occupied[us]; + Bb64 const their_occ = pos->occupied[them]; + + Bb64 const* p = pos->pieces[them]; + Bb64 const bqs = p[PIECE_QUEEN] | p[PIECE_BISHOP]; + Bb64 const rqs = p[PIECE_QUEEN] | p[PIECE_ROOK]; + + Bb64 pinners = (bqs & bishop_attacks_from_index(tsq, their_occ)) + | (rqs & rook_attacks_from_index(tsq, their_occ)); + + Bb64 pinned = 0; + while (pinners) { + Sq8 const sq = bitboard_pop_lsb(&pinners); + Bb64 const blockers = between_mask(tsq, sq) & our_occ; + if (!bitboard_more_than_one(blockers)) { + pinned |= blockers; + } + } + + return pinned; +} + +static +Bb64 all_threats_from_side(struct pos const * pos, Side8 who) +{ + Bb64 const occ = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK]; + + Bb64 const* p = pos->pieces[who]; + Bb64 t = 0ULL; + t |= pawn_attacks_dispatch(p[PIECE_PAWN], who); + t |= knight_attacks(p[PIECE_KNIGHT]); + t |= bishop_attacks(p[PIECE_BISHOP], occ); + t |= rook_attacks(p[PIECE_ROOK], occ); + t |= queen_attacks(p[PIECE_QUEEN], occ); + t |= king_attacks(p[PIECE_KING]); + return t; +} + diff --git a/engine-bitboard.h b/engine-bitboard.h new file mode 100644 index 0000000..5e49193 --- /dev/null +++ b/engine-bitboard.h @@ -0,0 +1,143 @@ +#pragma once + +typedef uint64_t Bb64; + +#define BITBOARD( \ + a8,b8,c8,d8,e8,f8,g8,h8, \ + a7,b7,c7,d7,e7,f7,g7,h7, \ + a6,b6,c6,d6,e6,f6,g6,h6, \ + a5,b5,c5,d5,e5,f5,g5,h5, \ + a4,b4,c4,d4,e4,f4,g4,h4, \ + a3,b3,c3,d3,e3,f3,g3,h3, \ + a2,b2,c2,d2,e2,f2,g2,h2, \ + a1,b1,c1,d1,e1,f1,g1,h1) \ +(Bb64)\ +0b##\ +h8##g8##f8##e8##d8##c8##b8##a8##\ +h7##g7##f7##e7##d7##c7##b7##a7##\ +h6##g6##f6##e6##d6##c6##b6##a6##\ +h5##g5##f5##e5##d5##c5##b5##a5##\ +h4##g4##f4##e4##d4##c4##b4##a4##\ +h3##g3##f3##e3##d3##c3##b3##a3##\ +h2##g2##f2##e2##d2##c2##b2##a2##\ +h1##g1##f1##e1##d1##c1##b1##a1##\ +ULL + +#define FILE_MASK(n) MASK_SHIFT_EAST((Bb64)0x0101010101010101ULL, n) +#define RANK_MASK(n) MASK_SHIFT_NORTH((Bb64)0x00000000000000FFULL, n) +#define MASK_FROM_SQ(idx) (((Bb64)1ULL)<<((Index8)idx)) +#define MASK_FROM_RF(rank, file) (MASK_FROM_SQ(SQ_FROM_RF(rank,file))) + +enum : Bb64 { + FILE_MASK_A = FILE_MASK(FILE_INDEX_A), + FILE_MASK_B = FILE_MASK(FILE_INDEX_B), + FILE_MASK_C = FILE_MASK(FILE_INDEX_C), + FILE_MASK_D = FILE_MASK(FILE_INDEX_D), + FILE_MASK_E = FILE_MASK(FILE_INDEX_E), + FILE_MASK_F = FILE_MASK(FILE_INDEX_F), + FILE_MASK_G = FILE_MASK(FILE_INDEX_G), + FILE_MASK_H = FILE_MASK(FILE_INDEX_H), + + RANK_MASK_1 = RANK_MASK(RANK_INDEX_1), + RANK_MASK_2 = RANK_MASK(RANK_INDEX_2), + RANK_MASK_3 = RANK_MASK(RANK_INDEX_3), + RANK_MASK_4 = RANK_MASK(RANK_INDEX_4), + RANK_MASK_5 = RANK_MASK(RANK_INDEX_5), + RANK_MASK_6 = RANK_MASK(RANK_INDEX_6), + RANK_MASK_7 = RANK_MASK(RANK_INDEX_7), + RANK_MASK_8 = RANK_MASK(RANK_INDEX_8), + + MASK_SHIFT_EAST_GUARD_0 = ~0ULL, + MASK_SHIFT_EAST_GUARD_1 = ~FILE_MASK_H, + MASK_SHIFT_EAST_GUARD_2 = ~(FILE_MASK_G | FILE_MASK_H), + MASK_SHIFT_EAST_GUARD_3 = ~(FILE_MASK_F | FILE_MASK_G | FILE_MASK_H), + MASK_SHIFT_EAST_GUARD_4 = ~(FILE_MASK_E | FILE_MASK_F | FILE_MASK_G | FILE_MASK_H), + MASK_SHIFT_EAST_GUARD_5 = ~(FILE_MASK_D | FILE_MASK_E | FILE_MASK_F | FILE_MASK_G | FILE_MASK_H), + MASK_SHIFT_EAST_GUARD_6 = ~(FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F | FILE_MASK_G | FILE_MASK_H), + MASK_SHIFT_EAST_GUARD_7 = ~(FILE_MASK_B | FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F | FILE_MASK_G | FILE_MASK_H), + + MASK_SHIFT_WEST_GUARD_0 = ~0ULL, + MASK_SHIFT_WEST_GUARD_1 = ~FILE_MASK_A, + MASK_SHIFT_WEST_GUARD_2 = ~(FILE_MASK_A | FILE_MASK_B), + MASK_SHIFT_WEST_GUARD_3 = ~(FILE_MASK_A | FILE_MASK_B | FILE_MASK_C), + MASK_SHIFT_WEST_GUARD_4 = ~(FILE_MASK_A | FILE_MASK_B | FILE_MASK_C | FILE_MASK_D), + MASK_SHIFT_WEST_GUARD_5 = ~(FILE_MASK_A | FILE_MASK_B | FILE_MASK_C | FILE_MASK_D | FILE_MASK_E), + MASK_SHIFT_WEST_GUARD_6 = ~(FILE_MASK_A | FILE_MASK_B | FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F), + MASK_SHIFT_WEST_GUARD_7 = ~(FILE_MASK_A | FILE_MASK_B | FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F | FILE_MASK_G), +}; + +#define _MASK_SHIFT_EAST_GUARDED(b, n) \ + (((Bb64)(b) & MASK_SHIFT_EAST_GUARD_##n) << (Index8)(n)) + +#define _MASK_SHIFT_WEST_GUARDED(b, n) \ + (((Bb64)(b) & MASK_SHIFT_WEST_GUARD_##n) >> (Index8)(n)) + +/* MASK_SHIFT_EAST_n shifts the bitboard right by n ranks without wrapping */ +#define MASK_SHIFT_EAST_1(b) _MASK_SHIFT_EAST_GUARDED(b, 1) +#define MASK_SHIFT_EAST_2(b) _MASK_SHIFT_EAST_GUARDED(b, 2) +#define MASK_SHIFT_EAST_3(b) _MASK_SHIFT_EAST_GUARDED(b, 3) +#define MASK_SHIFT_EAST_4(b) _MASK_SHIFT_EAST_GUARDED(b, 4) +#define MASK_SHIFT_EAST_5(b) _MASK_SHIFT_EAST_GUARDED(b, 5) +#define MASK_SHIFT_EAST_6(b) _MASK_SHIFT_EAST_GUARDED(b, 6) +#define MASK_SHIFT_EAST_7(b) _MASK_SHIFT_EAST_GUARDED(b, 7) +#define MASK_SHIFT_EAST_8(b) _MASK_SHIFT_EAST_GUARDED(b, 8) + +/* MASK_SHIFT_WEST_n shifts the bitboard left by n ranks without wrapping */ +#define MASK_SHIFT_WEST_1(b) _MASK_SHIFT_WEST_GUARDED(b, 1) +#define MASK_SHIFT_WEST_2(b) _MASK_SHIFT_WEST_GUARDED(b, 2) +#define MASK_SHIFT_WEST_3(b) _MASK_SHIFT_WEST_GUARDED(b, 3) +#define MASK_SHIFT_WEST_4(b) _MASK_SHIFT_WEST_GUARDED(b, 4) +#define MASK_SHIFT_WEST_5(b) _MASK_SHIFT_WEST_GUARDED(b, 5) +#define MASK_SHIFT_WEST_6(b) _MASK_SHIFT_WEST_GUARDED(b, 6) +#define MASK_SHIFT_WEST_7(b) _MASK_SHIFT_WEST_GUARDED(b, 7) +#define MASK_SHIFT_WEST_8(b) _MASK_SHIFT_WEST_GUARDED(b, 8) + +/* MASK_SHIFT_NORTH_n shifts the bitboard towards rank 8 by n ranks without wrapping */ +#define MASK_SHIFT_NORTH_1(b) MASK_SHIFT_NORTH(b, 1) +#define MASK_SHIFT_NORTH_2(b) MASK_SHIFT_NORTH(b, 2) +#define MASK_SHIFT_NORTH_3(b) MASK_SHIFT_NORTH(b, 3) +#define MASK_SHIFT_NORTH_4(b) MASK_SHIFT_NORTH(b, 4) +#define MASK_SHIFT_NORTH_5(b) MASK_SHIFT_NORTH(b, 5) +#define MASK_SHIFT_NORTH_6(b) MASK_SHIFT_NORTH(b, 6) +#define MASK_SHIFT_NORTH_7(b) MASK_SHIFT_NORTH(b, 7) +#define MASK_SHIFT_NORTH_8(b) MASK_SHIFT_NORTH(b, 8) + +/* MASK_SHIFT_SOUTH_n shifts the bitboard towards rank 1 by n ranks without wrapping */ +#define MASK_SHIFT_SOUTH_1(b) MASK_SHIFT_SOUTH(b, 1) +#define MASK_SHIFT_SOUTH_2(b) MASK_SHIFT_SOUTH(b, 2) +#define MASK_SHIFT_SOUTH_3(b) MASK_SHIFT_SOUTH(b, 3) +#define MASK_SHIFT_SOUTH_4(b) MASK_SHIFT_SOUTH(b, 4) +#define MASK_SHIFT_SOUTH_5(b) MASK_SHIFT_SOUTH(b, 5) +#define MASK_SHIFT_SOUTH_6(b) MASK_SHIFT_SOUTH(b, 6) +#define MASK_SHIFT_SOUTH_7(b) MASK_SHIFT_SOUTH(b, 7) +#define MASK_SHIFT_SOUTH_8(b) MASK_SHIFT_SOUTH(b, 8) + +enum { + #define X(file, rank) SQMASK_##file##rank = MASK_FROM_RF(RANK_INDEX_##rank, FILE_INDEX_##file), + SQUARES_LIST + #undef X +}; + +static Sq8 bitboard_lsb(Bb64 p) +{ + assuming(p != 0); + return (Sq8)__builtin_ffsll((int64_t)p) - 1; +} + +static Sq8 bitboard_pop_lsb(Bb64* p) +{ + Sq8 const lsb = bitboard_lsb(*p); + *p &= ~(1ULL<<(lsb)); + return lsb; +} + +static inline uint64_t bitboard_popcount(Bb64 b) +{ + return (uint64_t)__builtin_popcountll(b); +} + +static inline bool bitboard_more_than_one(Bb64 b) +{ + return b & (b - 1); +} + diff --git a/engine-board.h b/engine-board.h new file mode 100644 index 0000000..ed1d29c --- /dev/null +++ b/engine-board.h @@ -0,0 +1,423 @@ +#pragma once + +#include "engine-types.h" +#include "engine-tt.h" + +#include +#include + +struct board { + struct pos { + Bb64 pieces[SIDE_COUNT][PIECE_COUNT]; + Side8 moving_side; + bool castling_illegal[SIDE_COUNT][CASTLE_COUNT]; + Bb64 ep_targets; + Bb64 occupied[SIDE_COUNT]; + + int halfmoves; /* FIXME: this duplicates the hist.length value */ + int fullmoves; + uint64_t hash; + } pos; + + struct tt tt; + + /* used for repeated board state detection only */ + struct history { + struct pos items[64]; + size_t length; + } hist; + + /* Used only for square->piece lookups, not central to logic. Does not + * store which side owns the square piece, and checking non-occupied + * squares is undefined + * + * TODO: make mailbox smaller, only 3 bits are needed per piece + * */ + Piece8 mailbox[SQ_COUNT]; +}; + +#define BOARD_INIT (struct board) { \ + .pos = { \ + .fullmoves = 1, \ + .pieces = { \ + [SIDE_WHITE] = { \ + [PIECE_PAWN] = RANK_MASK_2, \ + [PIECE_ROOK] = SQMASK_A1 | SQMASK_H1, \ + [PIECE_KNIGHT] = SQMASK_B1 | SQMASK_G1, \ + [PIECE_BISHOP] = SQMASK_C1 | SQMASK_F1, \ + [PIECE_QUEEN] = SQMASK_D1, \ + [PIECE_KING] = SQMASK_E1, \ + }, \ + [SIDE_BLACK] = { \ + [PIECE_PAWN] = RANK_MASK_7, \ + [PIECE_ROOK] = SQMASK_A8 | SQMASK_H8, \ + [PIECE_KNIGHT] = SQMASK_B8 | SQMASK_G8, \ + [PIECE_BISHOP] = SQMASK_C8 | SQMASK_F8, \ + [PIECE_QUEEN] = SQMASK_D8, \ + [PIECE_KING] = SQMASK_E8, \ + } \ + }, \ + .occupied = { \ + [SIDE_WHITE] = \ + RANK_MASK_2 | SQMASK_A1 | SQMASK_H1 | \ + SQMASK_B1 | SQMASK_G1 | SQMASK_C1 | \ + SQMASK_F1 | SQMASK_D1 | SQMASK_E1, \ + [SIDE_BLACK] = \ + RANK_MASK_7 | SQMASK_A8 | SQMASK_H8 | \ + SQMASK_B8 | SQMASK_G8| SQMASK_C8 | \ + SQMASK_F8| SQMASK_D8| SQMASK_E8, \ + }, \ + .hash = ~0ULL, \ + }, \ + .mailbox = { \ + [SQ_A1] = PIECE_ROOK, \ + [SQ_B1] = PIECE_KNIGHT, \ + [SQ_C1] = PIECE_BISHOP, \ + [SQ_D1] = PIECE_QUEEN, \ + [SQ_E1] = PIECE_KING, \ + [SQ_F1] = PIECE_BISHOP, \ + [SQ_G1] = PIECE_KNIGHT, \ + [SQ_H1] = PIECE_ROOK, \ + [SQ_A2] = PIECE_PAWN, \ + [SQ_B2] = PIECE_PAWN, \ + [SQ_C2] = PIECE_PAWN, \ + [SQ_D2] = PIECE_PAWN, \ + [SQ_E2] = PIECE_PAWN, \ + [SQ_F2] = PIECE_PAWN, \ + [SQ_G2] = PIECE_PAWN, \ + [SQ_H2] = PIECE_PAWN, \ + [SQ_A7] = PIECE_PAWN, \ + [SQ_B7] = PIECE_PAWN, \ + [SQ_C7] = PIECE_PAWN, \ + [SQ_D7] = PIECE_PAWN, \ + [SQ_E7] = PIECE_PAWN, \ + [SQ_F7] = PIECE_PAWN, \ + [SQ_G7] = PIECE_PAWN, \ + [SQ_H7] = PIECE_PAWN, \ + [SQ_A8] = PIECE_ROOK, \ + [SQ_B8] = PIECE_KNIGHT, \ + [SQ_C8] = PIECE_BISHOP, \ + [SQ_D8] = PIECE_QUEEN, \ + [SQ_E8] = PIECE_KING, \ + [SQ_F8] = PIECE_BISHOP, \ + [SQ_G8] = PIECE_KNIGHT, \ + [SQ_H8] = PIECE_ROOK, \ + }, \ + .hist = {0}, \ +} + +void board_init(struct board* b) +{ + if (b->pos.fullmoves == 0 && b->pos.hash == 0) { + *b = BOARD_INIT; + } + if (b->tt.entries == NULL) { + b->tt.entries = sys_mmap_anon_shared(TT_ENTRIES * sizeof b->tt.entries[0], + SYS_PROT_READ | SYS_PROT_WRITE, + SYS_MADV_RANDOM); + if (b->tt.entries == NULL) { + __builtin_trap(); + } + } +} + +static bool board_load_fen_unsafe(struct board* b, char const* fen_str) +{ + /* TODO: this function is not tested for malicious/corrupted inputs */ + struct { + char const* buf; + size_t i; + size_t len; + } fen = { + .buf = fen_str, + .i = 0, + .len = strlen(fen_str), + }; +#define BUF_GETCHAR(x) (assuming(x.i < x.len), x.buf[x.i++]) + + my_memset(&b->pos, 0, sizeof b->pos); + b->pos.hash = ~0ULL; + + for (enum rank_index ri = RANK_INDEX_8; ri < RANK_INDEX_COUNT; --ri) { + for (enum file_index fi = FILE_INDEX_BEGIN; fi < FILE_INDEX_COUNT; ++fi) { + char const ch = BUF_GETCHAR(fen); + if (my_isdigit(ch)) { + if (ch == '0') { + __builtin_trap(); + } + fi += ch - '0' - 1; + } else { + struct piece_side const p = piece_and_side_from_char[(uint8_t)ch]; + Bb64 const sq_mask = MASK_FROM_RF(ri, fi); + b->pos.pieces[p.side][p.piece] |= sq_mask; + b->pos.occupied[p.side] |= sq_mask; + b->mailbox[SQ_FROM_RF(ri, fi)] = p.piece; + } + } + (void)BUF_GETCHAR(fen); /* discard '/' or ' ' */ + } + + { /* active color */ + char const ch = BUF_GETCHAR(fen); + if (ch == 'w') { + b->pos.moving_side = SIDE_WHITE; + } else if (ch == 'b') { + b->pos.moving_side = SIDE_BLACK; + } else { + __builtin_trap(); + } + } + + { /* castling */ + char ch = BUF_GETCHAR(fen); + if (ch != ' ') { + __builtin_trap(); + } + b->pos.castling_illegal[SIDE_WHITE][CASTLE_KINGSIDE] = true; + b->pos.castling_illegal[SIDE_WHITE][CASTLE_QUEENSIDE] = true; + b->pos.castling_illegal[SIDE_BLACK][CASTLE_KINGSIDE] = true; + b->pos.castling_illegal[SIDE_BLACK][CASTLE_QUEENSIDE] = true; + do { + ch = BUF_GETCHAR(fen); + switch (ch) { + case 'K': b->pos.castling_illegal[SIDE_WHITE][CASTLE_KINGSIDE] = false; break; + case 'Q': b->pos.castling_illegal[SIDE_WHITE][CASTLE_QUEENSIDE] = false; break; + case 'k': b->pos.castling_illegal[SIDE_BLACK][CASTLE_KINGSIDE] = false; break; + case 'q': b->pos.castling_illegal[SIDE_BLACK][CASTLE_QUEENSIDE] = false; break; + case ' ': break; + case '-': break; + default: { + __builtin_trap(); + } break; + } + } while (ch != ' '); + } + + { /* en passent */ + char const ch = BUF_GETCHAR(fen); + if (ch != '-') { + __builtin_trap(); + } + } + + { /* halfmoves */ + b->pos.halfmoves = 0; + char ch = BUF_GETCHAR(fen); + while (ch != ' ') { + b->pos.halfmoves *= 10; + b->pos.halfmoves += ch-'0'; + ch = BUF_GETCHAR(fen); + } + } + + { /* fullmoves */ + b->pos.fullmoves = 0; + char ch = BUF_GETCHAR(fen); + while (ch != ' ') { + b->pos.fullmoves *= 10; + b->pos.fullmoves += ch-'0'; + ch = BUF_GETCHAR(fen); + } + } +#undef BUF_GETCHAR + + return true; +} + +enum move_result { + MR_NORMAL, + MR_CHECK, + MR_REPEATS, /* means this board state has been observed before */ + MR_STALEMATE, + MR_CHECKMATE, +}; + +/* does not check validity */ +static enum move_result move_piece(struct pos* restrict pos, + struct history* restrict hist, + Piece8 mailbox[restrict static SQ_COUNT], + struct move move) +{ + Side8 const us = pos->moving_side; + Side8 const them = other_side(us); + + Piece8 const from_piece = mailbox[move.from]; + Piece8 const to_piece = mailbox[move.to]; + + Bb64 const from_mask = MASK_FROM_SQ(move.from); + Bb64 const to_mask = MASK_FROM_SQ(move.to); + + Sq8 const krook = (us == SIDE_WHITE) ? SQ_H1 : SQ_H8; + Sq8 const qrook = (us == SIDE_WHITE) ? SQ_A1 : SQ_A8; + Sq8 const krook_to = (us == SIDE_WHITE) ? SQ_F1 : SQ_F8; + Sq8 const qrook_to = (us == SIDE_WHITE) ? SQ_D1 : SQ_D8; + Sq8 const ksq = (us == SIDE_WHITE) ? SQ_E1 : SQ_E8; + Sq8 const kcast_sq = (us == SIDE_WHITE) ? SQ_G1 : SQ_G8; + Sq8 const qcast_sq = (us == SIDE_WHITE) ? SQ_C1 : SQ_C8; + Sq8 const their_krook = (us != SIDE_WHITE) ? SQ_H1 : SQ_H8; + Sq8 const their_qrook = (us != SIDE_WHITE) ? SQ_A1 : SQ_A8; + + #define POS_MOVE(side, piece, from, to) \ + do { \ + Bb64 const x = MASK_FROM_SQ(from) | MASK_FROM_SQ(to); \ + pos->pieces[side][piece] ^= x; \ + pos->occupied[side] ^= x; \ + pos->hash = tt_hash_update(pos->hash, from, side, piece); \ + pos->hash = tt_hash_update(pos->hash, to, side, piece); \ + mailbox[to] = piece; \ + if (piece == PIECE_PAWN) pos->halfmoves = 0; \ + } while (0) + + #define POS_REMOVE(owner, piece, at) \ + do { \ + Bb64 const x = MASK_FROM_SQ(at); \ + pos->pieces[owner][piece] &= ~x; \ + pos->occupied[owner] &= ~x; \ + pos->hash = tt_hash_update(pos->hash, at, owner, piece); \ + hist->length = 0; \ + pos->halfmoves = 0; \ + } while (0) + + #define POS_ADD(owner, piece, at) \ + do { \ + Bb64 const x = MASK_FROM_SQ(at); \ + pos->pieces[owner][piece] |= x; \ + pos->occupied[owner] |= x; \ + pos->hash = tt_hash_update(pos->hash, at, owner, piece); \ + mailbox[at] = piece; \ + pos->halfmoves = 0; \ + hist->length = 0; \ + } while (0) + + Bb64 const ep_targets_now = pos->ep_targets; + if (ep_targets_now) { + pos->hash = tt_hash_update_ep_targets(pos->hash, bitboard_lsb(ep_targets_now)); + } + pos->ep_targets = 0ULL; + + /* castle kingside, legality is checked by the caller */ + /**/ if (from_piece == PIECE_KING && move.from == ksq && move.to == kcast_sq) { + POS_MOVE(us, PIECE_KING, ksq, kcast_sq); + POS_MOVE(us, PIECE_ROOK, krook, krook_to); + } + /* castle queenside, legality is checked by the caller */ + else if (from_piece == PIECE_KING && move.from == ksq && move.to == qcast_sq) { + POS_MOVE(us, PIECE_KING, ksq, qcast_sq); + POS_MOVE(us, PIECE_ROOK, qrook, qrook_to); + } + /* regular move / capture */ + else { + POS_MOVE(us, from_piece, move.from, move.to); + /* capture */ + if (to_mask & pos->occupied[them]) { + POS_REMOVE(them, to_piece, move.to); + } + + /* promote / ep */ + if (from_piece == PIECE_PAWN) { + Bb64 const finishline = (us == SIDE_WHITE ? RANK_MASK_8 : RANK_MASK_1); + + /* en passent */ + /**/ if (to_mask & ep_targets_now) { + Sq8 const ti = + (us == SIDE_WHITE) + ? SQ_SHIFT_SOUTH(move.to, 1) + : SQ_SHIFT_NORTH(move.to, 1); + POS_REMOVE(them, PIECE_PAWN, ti); + } + /* pawn double push -> new ep_target */ + else if (us == SIDE_WHITE && (from_mask & RANK_MASK_2) && (to_mask & RANK_MASK_4)) { + pos->ep_targets |= MASK_SHIFT_NORTH_1(from_mask); + pos->hash = tt_hash_update_ep_targets(pos->hash, SQ_SHIFT_NORTH(move.from, 1)); + } + else if (us == SIDE_BLACK && (from_mask & RANK_MASK_7) && (to_mask & RANK_MASK_5)) { + pos->ep_targets |= MASK_SHIFT_SOUTH_1(from_mask); + pos->hash = tt_hash_update_ep_targets(pos->hash, SQ_SHIFT_SOUTH(move.from, 1)); + } + /* promote */ + else if (to_mask & finishline) { + /* already moved to `move.to` */ + POS_REMOVE(us, PIECE_PAWN, move.to); + POS_ADD(us, PIECE_QUEEN, move.to); + } + } + } + + /* castling rights */ + if (!pos->castling_illegal[us][CASTLE_KINGSIDE]) { + if (move.from == ksq || move.from == krook) { + pos->castling_illegal[us][CASTLE_KINGSIDE] = true; + pos->hash = tt_hash_update_castling_rights(pos->hash, us, CASTLE_KINGSIDE); + } + } + if (!pos->castling_illegal[us][CASTLE_QUEENSIDE]) { + if (move.from == ksq || move.from == qrook) { + pos->castling_illegal[us][CASTLE_QUEENSIDE] = true; + pos->hash = tt_hash_update_castling_rights(pos->hash, us, CASTLE_QUEENSIDE); + } + } + + if (move.to == their_krook) { + if (!pos->castling_illegal[them][CASTLE_KINGSIDE]) { + pos->castling_illegal[them][CASTLE_KINGSIDE] = true; + pos->hash = tt_hash_update_castling_rights(pos->hash, them, CASTLE_KINGSIDE); + } + } + if (move.to == their_qrook) { + if (!pos->castling_illegal[them][CASTLE_QUEENSIDE]) { + pos->castling_illegal[them][CASTLE_QUEENSIDE] = true; + pos->hash = tt_hash_update_castling_rights(pos->hash, them, CASTLE_QUEENSIDE); + } + } + + pos->hash = tt_hash_switch_side(pos->hash); + pos->moving_side = them; + pos->fullmoves += (pos->moving_side == SIDE_BLACK); + pos->halfmoves += 1; + + assuming(hist->length < 64); + int repetitions = 0; + for (size_t i = 0; i < hist->length; ++i) { + _Static_assert(sizeof *pos == sizeof hist->items[i]); + if (!my_memcmp(&hist->items[i].pieces, &pos->pieces, sizeof pos->pieces) + && !my_memcmp(&hist->items[i].castling_illegal, &pos->castling_illegal, sizeof pos->castling_illegal) + && hist->items[i].moving_side == pos->moving_side + && hist->items[i].ep_targets == pos->ep_targets) + { + repetitions += 1; + } + } + + hist->items[hist->length++] = *pos; + + if (repetitions >= 3 || pos->halfmoves > 50) { + return MR_STALEMATE; + } + else if (repetitions > 0) { + return MR_REPEATS; + } + else if (pos->halfmoves > 50) { + return MR_STALEMATE; + } +#if 0 + else if (attacks_to(pos, pos->pieces[them][PIECE_KING], 0ULL, 0ULL) + & ~pos->occupied[them]) { + return MR_CHECK; + } +#endif + else { + return MR_NORMAL; + } + +#undef POS_MOVE +#undef POS_ADD +#undef POS_REMOVE +} + +static enum move_result board_move(struct board* b, struct move move) +{ + return move_piece(&b->pos, + &b->hist, + b->mailbox, + move); +} diff --git a/evaluations.h b/engine-evaluations.h similarity index 79% rename from evaluations.h rename to engine-evaluations.h index ccc4605..79e7a2f 100644 --- a/evaluations.h +++ b/engine-evaluations.h @@ -29,7 +29,7 @@ static enum game_progress endgameness(struct pos const* pos) }; int npm = 0; /* non-pawn material */ - for (enum player pl = PLAYER_BEGIN; pl < PLAYER_COUNT; ++pl) { + for (Side8 pl = SIDE_BEGIN; pl < SIDE_COUNT; ++pl) { npm += piece_value[PIECE_QUEEN] * bitboard_popcount(pos->pieces[pl][PIECE_QUEEN]); npm += piece_value[PIECE_BISHOP] * bitboard_popcount(pos->pieces[pl][PIECE_BISHOP]); npm += piece_value[PIECE_KNIGHT] * bitboard_popcount(pos->pieces[pl][PIECE_KNIGHT]); @@ -39,7 +39,7 @@ static enum game_progress endgameness(struct pos const* pos) /**/ if (npm > 24) { return GP_OPENING; } - else if (npm > 14) { + else if (npm > 16) { return GP_MIDDLE; } else { @@ -47,7 +47,6 @@ static enum game_progress endgameness(struct pos const* pos) } } - #define BITBOARD_WHITE( \ a8,b8,c8,d8,e8,f8,g8,h8, \ a7,b7,c7,d7,e7,f7,g7,h7, \ @@ -57,17 +56,15 @@ static enum game_progress endgameness(struct pos const* pos) a3,b3,c3,d3,e3,f3,g3,h3, \ a2,b2,c2,d2,e2,f2,g2,h2, \ a1,b1,c1,d1,e1,f1,g1,h1) \ -(bitboard)\ -0b##\ -h8##g8##f8##e8##d8##c8##b8##a8##\ -h7##g7##f7##e7##d7##c7##b7##a7##\ -h6##g6##f6##e6##d6##c6##b6##a6##\ -h5##g5##f5##e5##d5##c5##b5##a5##\ -h4##g4##f4##e4##d4##c4##b4##a4##\ -h3##g3##f3##e3##d3##c3##b3##a3##\ -h2##g2##f2##e2##d2##c2##b2##a2##\ -h1##g1##f1##e1##d1##c1##b1##a1##\ -ULL +BITBOARD( \ + a8,b8,c8,d8,e8,f8,g8,h8, \ + a7,b7,c7,d7,e7,f7,g7,h7, \ + a6,b6,c6,d6,e6,f6,g6,h6, \ + a5,b5,c5,d5,e5,f5,g5,h5, \ + a4,b4,c4,d4,e4,f4,g4,h4, \ + a3,b3,c3,d3,e3,f3,g3,h3, \ + a2,b2,c2,d2,e2,f2,g2,h2, \ + a1,b1,c1,d1,e1,f1,g1,h1) #define BITBOARD_BLACK( \ a8,b8,c8,d8,e8,f8,g8,h8, \ @@ -78,17 +75,15 @@ ULL a3,b3,c3,d3,e3,f3,g3,h3, \ a2,b2,c2,d2,e2,f2,g2,h2, \ a1,b1,c1,d1,e1,f1,g1,h1) \ -(bitboard)\ -0b##\ -h1##g1##f1##e1##d1##c1##b1##a1##\ -h2##g2##f2##e2##d2##c2##b2##a2##\ -h3##g3##f3##e3##d3##c3##b3##a3##\ -h4##g4##f4##e4##d4##c4##b4##a4##\ -h5##g5##f5##e5##d5##c5##b5##a5##\ -h6##g6##f6##e6##d6##c6##b6##a6##\ -h7##g7##f7##e7##d7##c7##b7##a7##\ -h8##g8##f8##e8##d8##c8##b8##a8##\ -ULL +BITBOARD( \ + a1,b1,c1,d1,e1,f1,g1,h1, \ + a2,b2,c2,d2,e2,f2,g2,h2, \ + a3,b3,c3,d3,e3,f3,g3,h3, \ + a4,b4,c4,d4,e4,f4,g4,h4, \ + a5,b5,c5,d5,e5,f5,g5,h5, \ + a6,b6,c6,d6,e6,f6,g6,h6, \ + a7,b7,c7,d7,e7,f7,g7,h7, \ + a8,b8,c8,d8,e8,f8,g8,h8) #define REL_RANK_1 \ REL_BITBOARD( \ @@ -179,8 +174,8 @@ ULL 0,0,0,0,0,0,0,0) -#define REL_DIAGONAL_A1_H8 \ - REL_BITBOARD( \ +#define DIAGONAL_A1_H8 \ + BITBOARD( \ 0,0,0,0,0,0,0,1, \ 0,0,0,0,0,0,1,0, \ 0,0,0,0,0,1,0,0, \ @@ -190,8 +185,8 @@ ULL 0,1,0,0,0,0,0,0, \ 1,0,0,0,0,0,0,0) -#define REL_DIAGONAL_A8_H1 \ - REL_BITBOARD( \ +#define DIAGONAL_A8_H1 \ + BITBOARD( \ 1,0,0,0,0,0,0,0, \ 0,1,0,0,0,0,0,0, \ 0,0,1,0,0,0,0,0, \ @@ -261,12 +256,46 @@ ULL 0,0,0,0,0,0,0,0, \ 0,0,0,0,0,0,0,0, \ 1,0,0,0,0,0,0,0, \ - 1,0,0,1,1,0,0,0, \ 1,1,1,1,1,0,0,0, \ - 1,1,1,1,1,0,0,1, \ + 1,1,1,1,1,0,0,0, \ + 1,1,1,1,1,0,0,0, \ 1,1,1,1,1,1,1,1, \ 0,0,0,0,0,0,0,0) +#define REL_PAWN_KINGSIDE \ + REL_BITBOARD( \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,1, \ + 0,0,0,0,0,1,1,0, \ + 0,0,0,0,0,0,0,0) + + +#define REL_UNDEVELOPED_BISHOPS \ + REL_BITBOARD( \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,1,0,0,1,0,0) + +#define REL_UNDEVELOPED_KNIGHTS \ + REL_BITBOARD( \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,1,0,0,0,0,1,0) + #define BOARD_CENTER_6X6 \ ((FILE_MASK_B | FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F | FILE_MASK_G) \ & (RANK_MASK_2 | RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6 | RANK_MASK_7)) @@ -287,9 +316,10 @@ ULL /* piece bonus area*/ \ X(PIECE_PAWN, 0.02, BOARD_CENTER_4X4) \ X(PIECE_KNIGHT, 0.05, BOARD_CENTER_4X4) \ - X(PIECE_BISHOP, 0.05, REL_DIAGONAL_A1_H8 | REL_DIAGONAL_A8_H1) \ + X(PIECE_BISHOP, 0.05, BOARD_CENTER_6X6 & (DIAGONAL_A1_H8 | DIAGONAL_A8_H1)) \ X(PIECE_KING, 0.15, REL_KING_CASTLE_KINGSIDE) \ - X(PIECE_QUEEN, -0.10, RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6) \ + X(PIECE_QUEEN, -0.15, RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6) \ + X(PIECE_ROOK, 0.10, FILE_MASK_D | FILE_MASK_E) \ /**/ #define EARLY_POSITIONAL_BONUS_1 \ @@ -299,14 +329,15 @@ ULL /**/ #define EARLY_POSITIONAL_BONUS_2 \ - /* piece bonus area*/ \ - X(PIECE_PAWN, -0.10, ~REL_EARLY_PAWN_STRUCTURE) \ - X(PIECE_BISHOP, 0.05, CORNERS) \ + /* piece bonus area*/ \ + X(PIECE_PAWN, -0.18, ~REL_EARLY_PAWN_STRUCTURE) \ + X(PIECE_KNIGHT, -0.10, REL_UNDEVELOPED_KNIGHTS) \ + X(PIECE_BISHOP, -0.10, REL_UNDEVELOPED_BISHOPS) \ /**/ #define EARLY_POSITIONAL_BONUS_3 \ /* piece bonus area*/ \ - X(PIECE_BISHOP, 0.02, REL_BISHOP_QUEEN_ATTACK) + /**/ /* ------------------------------ middle game ------------------------------ */ @@ -316,25 +347,26 @@ ULL /* piece bonus area*/ \ X(PIECE_PAWN, 0.02, BOARD_CENTER_4X4) \ X(PIECE_KNIGHT, 0.05, BOARD_CENTER_4X4) \ - X(PIECE_BISHOP, 0.05, REL_DIAGONAL_A1_H8 | REL_DIAGONAL_A8_H1) \ + X(PIECE_BISHOP, 0.05, BOARD_CENTER_6X6 & (DIAGONAL_A1_H8 | DIAGONAL_A8_H1)) \ X(PIECE_KING, 0.15, REL_KING_CASTLE_KINGSIDE) \ /**/ #define MIDDLE_POSITIONAL_BONUS_1 \ /* piece bonus area*/ \ X(PIECE_PAWN, 0.02, BOARD_CENTER_2X2) \ - X(PIECE_BISHOP, 0.05, REL_BISHOP_KING_ATTACK) \ + X(PIECE_BISHOP, 0.07, REL_BISHOP_KING_ATTACK) \ + X(PIECE_QUEEN, 0.07, REL_BISHOP_KING_ATTACK) \ /**/ #define MIDDLE_POSITIONAL_BONUS_2 \ /* piece bonus area*/ \ - X(PIECE_PAWN, -0.10, ~REL_EARLY_PAWN_STRUCTURE) \ - X(PIECE_BISHOP, 0.05, CORNERS) \ + X(PIECE_PAWN, 0.02, REL_PAWN_KINGSIDE) \ /**/ #define MIDDLE_POSITIONAL_BONUS_3 \ /* piece bonus area*/ \ - X(PIECE_BISHOP, 0.02, REL_BISHOP_QUEEN_ATTACK) \ + X(PIECE_BISHOP, 0.05, BOARD_CENTER_6X6) \ + X(PIECE_KNIGHT, 0.05, BOARD_CENTER_6X6) \ /**/ /* ------------------------------- end game -------------------------------- */ @@ -363,17 +395,17 @@ ULL /**/ struct posmod { - bitboard const area; + Bb64 const area; double const val; }; -static inline struct posmod positional_modifier(enum player pl, enum game_progress st, size_t layer, enum piece pz) +static inline struct posmod positional_modifier(Side8 pl, enum game_progress st, size_t layer, Piece8 pz) { static struct posmod const - lookup[PLAYER_COUNT][GP_COUNT][POSITIONAL_MODIFIER_COUNT][PIECE_COUNT] = { + lookup[SIDE_COUNT][GP_COUNT][POSITIONAL_MODIFIER_COUNT][PIECE_COUNT] = { #define X(p, b, a) [p] = {.area = (a), .val = b}, #define REL_BITBOARD BITBOARD_WHITE - [PLAYER_WHITE] = { + [SIDE_WHITE] = { [GP_OPENING] = { {EARLY_POSITIONAL_BONUS_0}, {EARLY_POSITIONAL_BONUS_1}, @@ -394,9 +426,9 @@ static inline struct posmod positional_modifier(enum player pl, enum game_progre }, }, #undef REL_BITBOARD - + #define REL_BITBOARD BITBOARD_BLACK - [PLAYER_BLACK] = { + [SIDE_BLACK] = { [GP_OPENING] = { {EARLY_POSITIONAL_BONUS_0}, {EARLY_POSITIONAL_BONUS_1}, @@ -433,9 +465,9 @@ static inline struct posmod positional_modifier(enum player pl, enum game_progre #undef BOARD_CENTER_2X2 #undef BITBOARD_WHITE #undef BITBOARD_BLACK -#undef REL_DIAGONAL_A1_H8 -#undef REL_DIAGONAL_A8_H1 -#undef REL_BISHOP_KING_ATTACK -#undef REL_BISHOP_QUEEN_ATTACK -#undef REL_KING_CASTLE_KINGSIDE -#undef REL_KING_CASTLE_QUEENSIDE +#undef REL_DIAGONAL_A1_H8 +#undef REL_DIAGONAL_A8_H1 +#undef REL_BISHOP_KING_ATTACK +#undef REL_BISHOP_QUEEN_ATTACK +#undef REL_KING_CASTLE_KINGSIDE +#undef REL_KING_CASTLE_QUEENSIDE diff --git a/engine-macros.h b/engine-macros.h new file mode 100644 index 0000000..83a1527 --- /dev/null +++ b/engine-macros.h @@ -0,0 +1,13 @@ +#pragma once + +#define STR(x) #x + +#if !defined(NDEBUG) && defined(USE_PRINTF) +#define assuming(expr) \ + ((expr) ? 0 : (fprintf(stderr, "assumption <" #expr "> failed on line %d\n", __LINE__), __builtin_trap(), 0)) +#elif !defined(NDEBUG) +#define assuming(expr) \ + ((expr) ? 0 : (__builtin_trap(), 0)) +#else +#define assuming(expr) ((expr) ? 0 : (__builtin_unreachable(), 0)) +#endif diff --git a/engine-move-generation.h b/engine-move-generation.h new file mode 100644 index 0000000..c043556 --- /dev/null +++ b/engine-move-generation.h @@ -0,0 +1,276 @@ +#pragma once + +#define MOVE_CASTLE_KINGSIDE_WHITE (struct move) \ + {.from = SQ_E1, .to = SQ_G1} + +#define MOVE_CASTLE_QUEENSIDE_WHITE (struct move) \ + {.from = SQ_E1, .to = SQ_C1} + +#define MOVE_CASTLE_KINGSIDE_BLACK (struct move) \ + {.from = SQ_E8, .to = SQ_G8} + +#define MOVE_CASTLE_QUEENSIDE_BLACK (struct move) \ + {.from = SQ_E8, .to = SQ_C8} + +static inline +struct move move_make(struct pos const* restrict pos, + Piece8 piece, + Index8 from, + Index8 to, + uint8_t add_attr) +{ + (void)piece; + (void)pos; + (void)add_attr; +#if 0 + Side8 const us = pos->moving_side; + Side8 const them = other_side(us); + Bb64 const their_occ = pos->occupied[them]; + Bb64 const tomask = MASK_FROM_SQ(to); + Bb64 const finishline = (us == SIDE_WHITE ? RANK_MASK_8 : RANK_MASK_1); + + uint8_t attr = 0ULL; +#define MASK_IF8(x) ((~(uint8_t)0U) + (uint8_t)!(x)) + attr |= MATTR_CAPTURE & MASK_IF8(tomask & their_occ); + attr |= MATTR_CAPTURE & MASK_IF8((piece == PIECE_PAWN) && tomask & pos->ep_targets); + attr |= MATTR_PROMOTE & MASK_IF8((piece == PIECE_PAWN) && (tomask & finishline)); + attr |= add_attr; +#undef MASK_IF8 + + return (struct move){.from = from, .to = to, .attr = attr}; +#endif + return (struct move){.from = from, .to = to, .attr = add_attr}; +} +#define MOVE_MAX 128 + +enum move_gen_type { + MG_ALL, + MG_CAPTURES, + MG_CHECKS, + MG_QUIETS, +}; + +static void all_pseudolegal_from_piece(struct pos const* restrict pos, + enum move_gen_type type, + Piece8 piece, + Side8 us, + Bb64 piece_mask, + Bb64 allowed, + size_t* restrict out_count, + struct move out[restrict static MOVE_MAX]) +{ + assuming(piece != PIECE_PAWN); + + Side8 them = other_side(us); + + Bb64 const our_occ = pos->occupied[us]; + Bb64 const all_occ = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK]; + + if (type == MG_CHECKS) { + allowed &= non_pawn_piece_attacks(piece, pos->pieces[them][PIECE_KING], all_occ); + } + + /* pray this gets constprop'ed */ + while (piece_mask) { + Sq8 from = bitboard_pop_lsb(&piece_mask); + Bb64 move_mask = non_pawn_piece_attacks_from_index(piece, from, all_occ) + & ~our_occ + & allowed + ; + while (move_mask) { + Sq8 const to = bitboard_pop_lsb(&move_mask); + assuming(*out_count < MOVE_MAX); \ + out[(*out_count)++] = move_make(pos, piece, from, to, 0); + } + } +} + +#define DEFINE_ALL_PSEUDOLEGAL_PAWN_MOVES(side, opp_side, side_enum, inverse_direction, pawn_rank) \ +static void all_pseudolegal_pawn_moves_##side(struct pos const* restrict pos,\ + enum move_gen_type type,\ + Bb64 piece_mask,\ + Bb64 allowed,\ + size_t* restrict out_count,\ + struct move out[restrict static MOVE_MAX])\ +{\ + Bb64 const all_occ = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK];\ +\ + if (type == MG_CHECKS) {\ + allowed &= pawn_attacks_##opp_side(pos->pieces[other_side(side_enum)][PIECE_KING]);\ + }\ + Bb64 sp = pawn_single_push_##side(piece_mask, ~all_occ)\ + & allowed\ + ;\ + while (sp) {\ + Sq8 const to = bitboard_pop_lsb(&sp);\ + Sq8 const from = SQ_SHIFT_##inverse_direction(to, 1);\ + out[(*out_count)++] = move_make(pos, PIECE_PAWN, from, to, 0);\ + }\ +\ + Bb64 dp = pawn_double_push_##side(piece_mask & pawn_rank, ~all_occ)\ + & allowed\ + ;\ + while (dp) {\ + Sq8 const to = bitboard_pop_lsb(&dp);\ + Sq8 const from = SQ_SHIFT_##inverse_direction(to, 2);\ + out[(*out_count)++] = move_make(pos, PIECE_PAWN, from, to, 0);\ + }\ +} +DEFINE_ALL_PSEUDOLEGAL_PAWN_MOVES(white, black, SIDE_WHITE, SOUTH, RANK_MASK_2) +DEFINE_ALL_PSEUDOLEGAL_PAWN_MOVES(black, white, SIDE_BLACK, NORTH, RANK_MASK_7) + +#define DEFINE_ALL_PSEUDOLEGAL_PAWN_ATTACKS(side, opp_side, side_enum, inverse_direction) \ +static void all_pseudolegal_pawn_attacks_##side(struct pos const* restrict pos,\ + enum move_gen_type type,\ + Bb64 piece_mask,\ + Bb64 allowed,\ + size_t* restrict out_count,\ + struct move out[restrict static MOVE_MAX])\ +{\ + Bb64 const their_occ = pos->occupied[other_side(side_enum)];\ +\ + if (type == MG_CHECKS) {\ + allowed &= pawn_attacks_##opp_side(pos->pieces[other_side(side_enum)][PIECE_KING]);\ + }\ +\ + Bb64 ratk = pawn_attacks_right_##side(piece_mask)\ + & (their_occ | pos->ep_targets)\ + & allowed\ + ;\ + while (ratk) {\ + Sq8 const to = bitboard_pop_lsb(&ratk);\ + Sq8 const from = SQ_SHIFT_WEST(SQ_SHIFT_##inverse_direction(to, 1), 1);\ + assuming(*out_count < MOVE_MAX); \ + out[(*out_count)++] = move_make(pos, PIECE_PAWN, from, to, 0);\ + }\ +\ + Bb64 latk = pawn_attacks_left_##side(piece_mask)\ + & (their_occ | pos->ep_targets)\ + & allowed\ + ;\ + while (latk) {\ + Sq8 const to = bitboard_pop_lsb(&latk);\ + Sq8 const from = SQ_SHIFT_EAST(SQ_SHIFT_##inverse_direction(to, 1), 1);\ + assuming(*out_count < MOVE_MAX); \ + out[(*out_count)++] = move_make(pos, PIECE_PAWN, from, to, 0);\ + }\ +} +DEFINE_ALL_PSEUDOLEGAL_PAWN_ATTACKS(white, black, SIDE_WHITE, SOUTH) +DEFINE_ALL_PSEUDOLEGAL_PAWN_ATTACKS(black, white, SIDE_BLACK, NORTH) + +static void all_pseudolegal_moves(struct pos const* restrict pos, + enum move_gen_type type, + Side8 us, + size_t* restrict out_count, + struct move out[restrict static MOVE_MAX]) +{ + Side8 const them = other_side(us); + if (type == MG_CHECKS && pos->pieces[them][PIECE_KING] == 0ULL) { + return; + } + Bb64 const kmask = pos->pieces[us][PIECE_KING]; + assuming(kmask); + Sq8 const ksq = bitboard_lsb(kmask); + + + Bb64 const chk = checkers(pos, us); + + Bb64 const their_threats = all_threats_from_side(pos, them); + + Bb64 const their_occ = pos->occupied[them]; + Bb64 const all_occ = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK]; + + Bb64 allowed; + if (type == MG_CAPTURES) { + allowed = their_occ; + } else if (type == MG_QUIETS) { + allowed = ~their_occ; + } else if (type == MG_ALL) { + allowed = ~0ULL; + } else { + allowed = ~0ULL; + } + + Bb64 king_allowed = allowed; + + if (chk) { + if (bitboard_more_than_one(chk)) { + if (type == MG_ALL) { + /* no other piece can fix check if the king has two attackers or more */ + all_pseudolegal_from_piece(pos, type, PIECE_KING, us, pos->pieces[us][PIECE_KING], ~their_threats, out_count, out); + } + return; + } + + Sq8 const asq = bitboard_lsb(chk); + Bb64 const blocking_mask = between_mask(ksq, asq); + allowed &= blocking_mask | chk; + } + + if (type == MG_CAPTURES || type == MG_CHECKS || type == MG_ALL) { + Bb64 const x = (type == MG_CHECKS ? pos->ep_targets : 0ULL); + if (us == SIDE_WHITE) { + all_pseudolegal_pawn_attacks_white(pos, type, pos->pieces[us][PIECE_PAWN], allowed | x, out_count, out); + } else { + all_pseudolegal_pawn_attacks_black(pos, type, pos->pieces[us][PIECE_PAWN], allowed | x, out_count, out); + } + } + + if (type == MG_QUIETS || type == MG_CHECKS || type == MG_ALL) { + if (us == SIDE_WHITE) { + all_pseudolegal_pawn_moves_white(pos, type, pos->pieces[us][PIECE_PAWN], allowed, out_count, out); + } else { + all_pseudolegal_pawn_moves_black(pos, type, pos->pieces[us][PIECE_PAWN], allowed, out_count, out); + } + } + + all_pseudolegal_from_piece(pos, type, PIECE_BISHOP, us, pos->pieces[us][PIECE_BISHOP], allowed, out_count, out); + all_pseudolegal_from_piece(pos, type, PIECE_KNIGHT, us, pos->pieces[us][PIECE_KNIGHT], allowed, out_count, out); + all_pseudolegal_from_piece(pos, type, PIECE_ROOK, us, pos->pieces[us][PIECE_ROOK], allowed, out_count, out); + all_pseudolegal_from_piece(pos, type, PIECE_QUEEN, us, pos->pieces[us][PIECE_QUEEN], allowed, out_count, out); + + all_pseudolegal_from_piece(pos, type, PIECE_KING, us, pos->pieces[us][PIECE_KING], ~their_threats & king_allowed, out_count, out); + + if (type == MG_CHECKS) { + Bb64 discovering = pinning_lines_to_target(pos, us, bitboard_lsb(pos->pieces[them][PIECE_KING])); + + all_pseudolegal_from_piece(pos, MG_ALL, PIECE_BISHOP, us, discovering & pos->pieces[us][PIECE_BISHOP], ~discovering, out_count, out); + all_pseudolegal_from_piece(pos, MG_ALL, PIECE_KNIGHT, us, discovering & pos->pieces[us][PIECE_KNIGHT], ~discovering, out_count, out); + all_pseudolegal_from_piece(pos, MG_ALL, PIECE_ROOK, us, discovering & pos->pieces[us][PIECE_ROOK], ~discovering, out_count, out); + all_pseudolegal_from_piece(pos, MG_ALL, PIECE_QUEEN, us, discovering & pos->pieces[us][PIECE_QUEEN], ~discovering, out_count, out); + all_pseudolegal_from_piece(pos, MG_ALL, PIECE_KING, us, discovering & pos->pieces[us][PIECE_KING], ~discovering & ~their_threats, out_count, out); + } + + /* castling */ + if (!chk && type != MG_CAPTURES) { + bool can_castle_kingside, can_castle_queenside; + Bb64 const blocked = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK] | their_threats; + if (us == SIDE_WHITE) { + can_castle_kingside = !(blocked & (SQMASK_F1 | SQMASK_G1)) + && (pos->pieces[us][PIECE_ROOK] & SQMASK_H1) + && !pos->castling_illegal[us][CASTLE_KINGSIDE]; + can_castle_queenside = !(blocked & (SQMASK_C1 | SQMASK_D1)) + && (pos->pieces[us][PIECE_ROOK] & SQMASK_A1) + && !pos->castling_illegal[us][CASTLE_QUEENSIDE]; + } else { + can_castle_kingside = !(blocked & (SQMASK_F8 | SQMASK_G8)) + && (pos->pieces[us][PIECE_ROOK] & SQMASK_H8) + && !pos->castling_illegal[us][CASTLE_KINGSIDE]; + can_castle_queenside = !(blocked & (SQMASK_C8 | SQMASK_D8)) + && (pos->pieces[us][PIECE_ROOK] & SQMASK_A8) + && !pos->castling_illegal[us][CASTLE_QUEENSIDE]; + } + + if (can_castle_kingside) { + out[(*out_count)++] = us == SIDE_WHITE + ? MOVE_CASTLE_KINGSIDE_WHITE + : MOVE_CASTLE_KINGSIDE_BLACK; + } + + if (can_castle_queenside) { + out[(*out_count)++] = us == SIDE_WHITE + ? MOVE_CASTLE_QUEENSIDE_WHITE + : MOVE_CASTLE_QUEENSIDE_BLACK; + } + } +} diff --git a/engine-tt.h b/engine-tt.h new file mode 100644 index 0000000..5690fe8 --- /dev/null +++ b/engine-tt.h @@ -0,0 +1,141 @@ +#pragma once + +struct search_option { + /* TODO: optimize order of fields and size */ + double score; + struct move move; + uint64_t hash; + uint8_t init : 1; + enum tt_flag {TT_EXACT, TT_LOWER, TT_UPPER} flag : 3; + int8_t depth : 4; +}; + +#define TT_ADDRESS_BITS 24 +#define TT_ENTRIES (1ULL<entries[addr]; + +#ifndef NDEBUG + tt->probes += 1; + if (tte.init && tte.hash == hash) { + tt->hits += 1; + } else if (tte.init && tte.hash != hash) { + tt->collisions += 1; + } +#endif + return tte; +} + +static inline void tt_insert(struct tt* tt, uint64_t hash, struct search_option so) +{ + uint64_t const addr = hash % TT_ENTRIES; + so.init = true; + tt->entries[addr] = so; +#ifndef NDEBUG + tt->insertions += 1; +#endif +} + +/* tt_insert_maybe inserts only if heuristics say it's a good idea. There are + * two considerations: + * - higher depth saves more work per probe hit + * - entries closer to the leaves are more likely to be searched multiple time + */ +static inline void tt_insert_maybe(struct tt* tt, uint64_t hash, struct search_option so) +{ + uint64_t const addr = hash % TT_ENTRIES; + +#if 0 + struct search_option* tte = &tt->entries[addr]; + if (so.depth < tte->depth) { + *tte = so; + } +#endif + + so.init = true; + tt->entries[addr] = so; +#ifndef NDEBUG + tt->insertions += 1; +#endif +} + diff --git a/engine-types.h b/engine-types.h new file mode 100644 index 0000000..b37aa2a --- /dev/null +++ b/engine-types.h @@ -0,0 +1,285 @@ +#pragma once + +/* ----------- Index8 ----------- */ +typedef uint8_t Index8; + +#define MASK_SHIFT_NORTH(b, n) ((b) << ((Index8)(n)*8U)) +#define MASK_SHIFT_SOUTH(b, n) ((b) >> ((Index8)(n)*8U)) +#define MASK_SHIFT_WEST(b, n) ((b) >> ((Index8)(n)*1U)) +#define MASK_SHIFT_EAST(b, n) ((b) << ((Index8)(n)*1U)) + +#define SQ_SHIFT_NORTH(i, n) ((i) + ((Index8)(n)*8U)) +#define SQ_SHIFT_SOUTH(i, n) ((i) - ((Index8)(n)*8U)) +#define SQ_SHIFT_WEST(i, n) ((i) - ((Index8)(n)*1U)) +#define SQ_SHIFT_EAST(i, n) ((i) + ((Index8)(n)*1U)) + +#define SQ_FROM_RF(rank, file) ((Index8)(8U*(Index8)(rank) + (Index8)(file))) + +/* ----------- file_index ----------- */ +enum file_index : Index8 { + FILE_INDEX_BEGIN, + FILE_INDEX_A = FILE_INDEX_BEGIN, + FILE_INDEX_B, + FILE_INDEX_C, + FILE_INDEX_D, + FILE_INDEX_E, + FILE_INDEX_F, + FILE_INDEX_G, + FILE_INDEX_H, + FILE_INDEX_COUNT, +}; + +char const file_index_char[FILE_INDEX_COUNT] = { + [FILE_INDEX_A] = 'A', + [FILE_INDEX_B] = 'B', + [FILE_INDEX_C] = 'C', + [FILE_INDEX_D] = 'D', + [FILE_INDEX_E] = 'E', + [FILE_INDEX_F] = 'F', + [FILE_INDEX_G] = 'G', + [FILE_INDEX_H] = 'H', +}; + +/* ----------- rank_index ----------- */ +enum rank_index : Index8 { + RANK_INDEX_BEGIN, + RANK_INDEX_1 = RANK_INDEX_BEGIN, + RANK_INDEX_2, + RANK_INDEX_3, + RANK_INDEX_4, + RANK_INDEX_5, + RANK_INDEX_6, + RANK_INDEX_7, + RANK_INDEX_8, + RANK_INDEX_COUNT, +}; + +char const rank_index_char[RANK_INDEX_COUNT] = { + [RANK_INDEX_1] = '1', + [RANK_INDEX_2] = '2', + [RANK_INDEX_3] = '3', + [RANK_INDEX_4] = '4', + [RANK_INDEX_5] = '5', + [RANK_INDEX_6] = '6', + [RANK_INDEX_7] = '7', + [RANK_INDEX_8] = '8', +}; + +/* ----------- Sq8 ----------- */ + +#define SQMASK_PREFIX SQMASK_ +#define SQUARES_LIST_BEGIN \ + X(A, 1) +#define SQUARES_LIST \ + X(A, 1) \ + X(A, 2) \ + X(A, 3) \ + X(A, 4) \ + X(A, 5) \ + X(A, 6) \ + X(A, 7) \ + X(A, 8) \ + X(B, 1) \ + X(B, 2) \ + X(B, 3) \ + X(B, 4) \ + X(B, 5) \ + X(B, 6) \ + X(B, 7) \ + X(B, 8) \ + X(C, 1) \ + X(C, 2) \ + X(C, 3) \ + X(C, 4) \ + X(C, 5) \ + X(C, 6) \ + X(C, 7) \ + X(C, 8) \ + X(D, 1) \ + X(D, 2) \ + X(D, 3) \ + X(D, 4) \ + X(D, 5) \ + X(D, 6) \ + X(D, 7) \ + X(D, 8) \ + X(E, 1) \ + X(E, 2) \ + X(E, 3) \ + X(E, 4) \ + X(E, 5) \ + X(E, 6) \ + X(E, 7) \ + X(E, 8) \ + X(F, 1) \ + X(F, 2) \ + X(F, 3) \ + X(F, 4) \ + X(F, 5) \ + X(F, 6) \ + X(F, 7) \ + X(F, 8) \ + X(G, 1) \ + X(G, 2) \ + X(G, 3) \ + X(G, 4) \ + X(G, 5) \ + X(G, 6) \ + X(G, 7) \ + X(G, 8) \ + X(H, 1) \ + X(H, 2) \ + X(H, 3) \ + X(H, 4) \ + X(H, 5) \ + X(H, 6) \ + X(H, 7) \ + X(H, 8) + +typedef enum sq8 : uint8_t { + #define X(file, rank) SQ_##file##rank = SQ_FROM_RF(RANK_INDEX_##rank, FILE_INDEX_##file), + SQUARES_LIST + SQ_COUNT, + #undef X + /* define iterator begin enum */ + #define X(file, rank) SQ_BEGIN = SQ_##file##rank, + SQUARES_LIST_BEGIN + #undef X +} Sq8; + +static char* const sq8_display[SQ_COUNT] = { + #define X(file, rank) \ + [SQ_##file##rank] = STR(file##rank), + SQUARES_LIST + #undef X +}; + +static char* const sq8_str[SQ_COUNT] = { + #define X(file, rank) \ + [SQ_##file##rank] = STR(SQ_##file##rank), + SQUARES_LIST + #undef X +}; + +static inline enum file_index sq_to_file(Sq8 p) +{ + return p % 8ULL; +} + +static inline enum rank_index sq_to_rank(Sq8 p) +{ + return p / 8ULL; +} + +/* ----------- Side8 ----------- */ +typedef enum side : uint8_t { + SIDE_WHITE, + SIDE_BLACK, + SIDE_COUNT, + SIDE_BEGIN = SIDE_WHITE, +} Side8; + +static inline Side8 other_side(Side8 p) +{ + return (p == SIDE_WHITE ? SIDE_BLACK : SIDE_WHITE); +} + +static char const* side_str[SIDE_COUNT] = { + [SIDE_WHITE] = "SIDE_WHITE", + [SIDE_BLACK] = "SIDE_BLACK", +}; + +/* ----------- Piece8 ----------- */ + +/* https://en.wikipedia.org/wiki/X_macro */ +/* enum value white char white unicode black char black unicode */ +#define PIECES \ + X(PIECE_PAWN, 1.0, 'P', 0x2659, 'p', 0x265F) \ + X(PIECE_KING, 0.0, 'K', 0x2654, 'k', 0x265A) \ + X(PIECE_QUEEN, 9.0, 'Q', 0x2655, 'q', 0x265B) \ + X(PIECE_BISHOP, 3.0, 'B', 0x2657, 'b', 0x265D) \ + X(PIECE_ROOK, 5.0, 'R', 0x2656, 'r', 0x265C) \ + X(PIECE_KNIGHT, 3.0, 'N', 0x2658, 'n', 0x265E) + +typedef enum piece : uint8_t { +#define X(e, v, wc, wu, bc, bu) e, + PIECES + PIECE_COUNT, + PIECE_BEGIN = 0, +#undef X +} Piece8; + +static double piece_value[PIECE_COUNT] = { +#define X(e, v, wc, wu, bc, bu) [e] = v, + PIECES +#undef X +}; + +static char const* piece_str[PIECE_COUNT] = { +#define X(e, v, wc, wu, bc, bu) [e] = STR(e), + PIECES +#undef X +}; + +struct piece_side {Piece8 piece; Side8 side;} const piece_and_side_from_char[256] = { + #define X(e, v, wc, wu, bc, bu) \ + [(uint8_t)bc] = {.piece = e, .side = SIDE_BLACK}, \ + [(uint8_t)wc] = {.piece = e, .side = SIDE_WHITE}, + PIECES + #undef X +}; + +static char const piece_char[SIDE_COUNT][PIECE_COUNT] = { + [SIDE_WHITE] = { +#define X(e, v, wc, wu, bc, bu) [e] = wc, + PIECES +#undef X + }, + [SIDE_BLACK] = { +#define X(e, v, wc, wu, bc, bu) [e] = bc, + PIECES +#undef X + } +}; + +static int const piece_unicode[SIDE_COUNT][PIECE_COUNT] = { + [SIDE_WHITE] = { +#define X(e, v, wc, wu, bc, bu) [e] = wu, + PIECES +#undef X + }, + [SIDE_BLACK] = { +#define X(e, v, wc, wu, bc, bu) [e] = bu, + PIECES +#undef X + } +}; + + +/* ----------- moves ----------- */ + +enum { + MATTR_PROMOTE = 1<<0, +}; + +struct move { + Sq8 from; + Sq8 to; + uint8_t attr; + #define APPEAL_MAX UINT8_MAX + uint8_t appeal; +}; +_Static_assert(sizeof(struct move) == 4, + "this static assuming is here to check when sizeof(move) changes"); + +#define NULL_MOVE (struct move){0} + +#define IS_NULL_MOVE(m) ((m).from == (m).to) + +/* ----------- castle_direction ----------- */ +enum castle_direction { + CASTLE_BEGIN, + CASTLE_KINGSIDE = CASTLE_BEGIN, + CASTLE_QUEENSIDE, + CASTLE_COUNT, +}; diff --git a/engine.h b/engine.h index 59c4e43..1262793 100644 --- a/engine.h +++ b/engine.h @@ -6,1831 +6,116 @@ #include #include -#ifndef NDEBUG -#define assert(expr) \ - ((expr) ? 0 : (__builtin_trap(), 0)) -#else -#define assert(...) (void)0 -#endif +/* temp performance counter */ +static uint64_t g_ab_node_volume = 0; -/* BIT MANIPULATION - * ================ */ +#include "engine-macros.h" +#include "engine-types.h" +#include "engine-bitboard.h" +#include "engine-board.h" +#include "engine-attack-sets.h" +#include "engine-move-generation.h" +#include "engine-tt.h" +#include "engine-evaluations.h" -static inline uint64_t popcount(uint64_t n) -{ - // TODO: check of popcountll support - return (uint64_t)__builtin_popcountll(n); -} +/* --------------------------- MOVE SEARCH --------------------------------- */ -static inline uint64_t ctz(uint64_t n) -{ - assert(n != 0); - return (uint64_t)__builtin_ctzll(n); -} - -/* BITBOARD AND INDEX DATATYPES AND HELPERS - * ========================================= */ - -typedef uint64_t bitboard; - -#define BITBOARD( \ - a8,b8,c8,d8,e8,f8,g8,h8, \ - a7,b7,c7,d7,e7,f7,g7,h7, \ - a6,b6,c6,d6,e6,f6,g6,h6, \ - a5,b5,c5,d5,e5,f5,g5,h5, \ - a4,b4,c4,d4,e4,f4,g4,h4, \ - a3,b3,c3,d3,e3,f3,g3,h3, \ - a2,b2,c2,d2,e2,f2,g2,h2, \ - a1,b1,c1,d1,e1,f1,g1,h1) \ -(bitboard)\ -0b##\ -h8##g8##f8##e8##d8##c8##b8##a8##\ -h7##g7##f7##e7##d7##c7##b7##a7##\ -h6##g6##f6##e6##d6##c6##b6##a6##\ -h5##g5##f5##e5##d5##c5##b5##a5##\ -h4##g4##f4##e4##d4##c4##b4##a4##\ -h3##g3##f3##e3##d3##c3##b3##a3##\ -h2##g2##f2##e2##d2##c2##b2##a2##\ -h1##g1##f1##e1##d1##c1##b1##a1##\ -ULL - - -typedef uint8_t index; -#define INDEX(n) n##ULL - -#define RANK_SHIFT_UP(b, n) ((b) << ((index)(n)*8U)) -#define RANK_SHIFT_DOWN(b, n) ((b) >> ((index)(n)*8U)) -#define FILE_SHIFT_LEFT(b, n) ((b) >> ((index)(n)*1U)) -#define FILE_SHIFT_RIGHT(b, n) ((b) << ((index)(n)*1U)) - -#define INDEX_SHIFT_UP(i, n) ((i) + ((index)(n)*8U)) -#define INDEX_SHIFT_DOWN(i, n) ((i) - ((index)(n)*8U)) -#define INDEX_SHIFT_LEFT(i, n) ((i) - ((index)(n)*1U)) -#define INDEX_SHIFT_RIGHT(i, n) ((i) + ((index)(n)*1U)) - -#define FILE_MASK(n) FILE_SHIFT_RIGHT((bitboard)0x0101010101010101ULL, n) -#define RANK_MASK(n) RANK_SHIFT_UP((bitboard)0x00000000000000FFULL, n) - -#define INDEX_FROM_RF(rank, file) ((index)(8U*(index)(rank) + (index)(file))) - -#define SQ_MASK_FROM_INDEX(idx) (((bitboard)1ULL)<<((index)idx)) -#define SQ_MASK_FROM_RF(rank, file) (SQ_MASK_FROM_INDEX(INDEX_FROM_RF(rank,file))) - -enum file_index : index { - FILE_INDEX_BEGIN, - FILE_INDEX_A = FILE_INDEX_BEGIN, - FILE_INDEX_B, - FILE_INDEX_C, - FILE_INDEX_D, - FILE_INDEX_E, - FILE_INDEX_F, - FILE_INDEX_G, - FILE_INDEX_H, - FILE_INDEX_COUNT, -}; - -char const file_index_char[FILE_INDEX_COUNT] = { - [FILE_INDEX_A] = 'A', - [FILE_INDEX_B] = 'B', - [FILE_INDEX_C] = 'C', - [FILE_INDEX_D] = 'D', - [FILE_INDEX_E] = 'E', - [FILE_INDEX_F] = 'F', - [FILE_INDEX_G] = 'G', - [FILE_INDEX_H] = 'H', -}; - -enum rank_index : index { - RANK_INDEX_BEGIN, - RANK_INDEX_1 = RANK_INDEX_BEGIN, - RANK_INDEX_2, - RANK_INDEX_3, - RANK_INDEX_4, - RANK_INDEX_5, - RANK_INDEX_6, - RANK_INDEX_7, - RANK_INDEX_8, - RANK_INDEX_COUNT, -}; - -char const rank_index_char[RANK_INDEX_COUNT] = { - [RANK_INDEX_1] = '1', - [RANK_INDEX_2] = '2', - [RANK_INDEX_3] = '3', - [RANK_INDEX_4] = '4', - [RANK_INDEX_5] = '5', - [RANK_INDEX_6] = '6', - [RANK_INDEX_7] = '7', - [RANK_INDEX_8] = '8', -}; - -#define STR(x) #x - -#define SQ_MASK_PREFIX SQ_MASK_ -#define SQUARES_LIST_BEGIN \ - X(A, 1) -#define SQUARES_LIST \ - X(A, 1) \ - X(A, 2) \ - X(A, 3) \ - X(A, 4) \ - X(A, 5) \ - X(A, 6) \ - X(A, 7) \ - X(A, 8) \ - X(B, 1) \ - X(B, 2) \ - X(B, 3) \ - X(B, 4) \ - X(B, 5) \ - X(B, 6) \ - X(B, 7) \ - X(B, 8) \ - X(C, 1) \ - X(C, 2) \ - X(C, 3) \ - X(C, 4) \ - X(C, 5) \ - X(C, 6) \ - X(C, 7) \ - X(C, 8) \ - X(D, 1) \ - X(D, 2) \ - X(D, 3) \ - X(D, 4) \ - X(D, 5) \ - X(D, 6) \ - X(D, 7) \ - X(D, 8) \ - X(E, 1) \ - X(E, 2) \ - X(E, 3) \ - X(E, 4) \ - X(E, 5) \ - X(E, 6) \ - X(E, 7) \ - X(E, 8) \ - X(F, 1) \ - X(F, 2) \ - X(F, 3) \ - X(F, 4) \ - X(F, 5) \ - X(F, 6) \ - X(F, 7) \ - X(F, 8) \ - X(G, 1) \ - X(G, 2) \ - X(G, 3) \ - X(G, 4) \ - X(G, 5) \ - X(G, 6) \ - X(G, 7) \ - X(G, 8) \ - X(H, 1) \ - X(H, 2) \ - X(H, 3) \ - X(H, 4) \ - X(H, 5) \ - X(H, 6) \ - X(H, 7) \ - X(H, 8) - -/* define mask constants: SQ_MASK_A1, SQ_MASK_A2, ..., SQ_MASK_H8 */ -enum : bitboard { - #define X(file, rank) SQ_MASK_##file##rank = SQ_MASK_FROM_RF(RANK_INDEX_##rank, FILE_INDEX_##file), - SQUARES_LIST - #undef X -}; - -enum square_index : index { - #define X(file, rank) SQ_INDEX_##file##rank = INDEX_FROM_RF(RANK_INDEX_##rank, FILE_INDEX_##file), - SQUARES_LIST - SQ_INDEX_COUNT, - #undef X - /* define iterator begin enum */ - #define X(file, rank) SQ_INDEX_BEGIN = SQ_INDEX_##file##rank, - SQUARES_LIST_BEGIN - #undef X -}; - -static char* const square_index_display[SQ_INDEX_COUNT] = { - #define X(file, rank) \ - [SQ_INDEX_##file##rank] = STR(file##rank), - SQUARES_LIST - #undef X -}; - -static char* const square_index_str[SQ_INDEX_COUNT] = { - #define X(file, rank) \ - [SQ_INDEX_##file##rank] = STR(SQ_INDEX_##file##rank), - SQUARES_LIST - #undef X -}; - -enum : bitboard { - FILE_MASK_A = FILE_MASK(FILE_INDEX_A), - FILE_MASK_B = FILE_MASK(FILE_INDEX_B), - FILE_MASK_C = FILE_MASK(FILE_INDEX_C), - FILE_MASK_D = FILE_MASK(FILE_INDEX_D), - FILE_MASK_E = FILE_MASK(FILE_INDEX_E), - FILE_MASK_F = FILE_MASK(FILE_INDEX_F), - FILE_MASK_G = FILE_MASK(FILE_INDEX_G), - FILE_MASK_H = FILE_MASK(FILE_INDEX_H), - - RANK_MASK_1 = RANK_MASK(RANK_INDEX_1), - RANK_MASK_2 = RANK_MASK(RANK_INDEX_2), - RANK_MASK_3 = RANK_MASK(RANK_INDEX_3), - RANK_MASK_4 = RANK_MASK(RANK_INDEX_4), - RANK_MASK_5 = RANK_MASK(RANK_INDEX_5), - RANK_MASK_6 = RANK_MASK(RANK_INDEX_6), - RANK_MASK_7 = RANK_MASK(RANK_INDEX_7), - RANK_MASK_8 = RANK_MASK(RANK_INDEX_8), - - FILE_SHIFT_RIGHT_GUARD_0 = ~0ULL, - FILE_SHIFT_RIGHT_GUARD_1 = ~FILE_MASK_H, - FILE_SHIFT_RIGHT_GUARD_2 = ~(FILE_MASK_G | FILE_MASK_H), - FILE_SHIFT_RIGHT_GUARD_3 = ~(FILE_MASK_F | FILE_MASK_G | FILE_MASK_H), - FILE_SHIFT_RIGHT_GUARD_4 = ~(FILE_MASK_E | FILE_MASK_F | FILE_MASK_G | FILE_MASK_H), - FILE_SHIFT_RIGHT_GUARD_5 = ~(FILE_MASK_D | FILE_MASK_E | FILE_MASK_F | FILE_MASK_G | FILE_MASK_H), - FILE_SHIFT_RIGHT_GUARD_6 = ~(FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F | FILE_MASK_G | FILE_MASK_H), - FILE_SHIFT_RIGHT_GUARD_7 = ~(FILE_MASK_B | FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F | FILE_MASK_G | FILE_MASK_H), - - FILE_SHIFT_LEFT_GUARD_0 = ~0ULL, - FILE_SHIFT_LEFT_GUARD_1 = ~FILE_MASK_A, - FILE_SHIFT_LEFT_GUARD_2 = ~(FILE_MASK_A | FILE_MASK_B), - FILE_SHIFT_LEFT_GUARD_3 = ~(FILE_MASK_A | FILE_MASK_B | FILE_MASK_C), - FILE_SHIFT_LEFT_GUARD_4 = ~(FILE_MASK_A | FILE_MASK_B | FILE_MASK_C | FILE_MASK_D), - FILE_SHIFT_LEFT_GUARD_5 = ~(FILE_MASK_A | FILE_MASK_B | FILE_MASK_C | FILE_MASK_D | FILE_MASK_E), - FILE_SHIFT_LEFT_GUARD_6 = ~(FILE_MASK_A | FILE_MASK_B | FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F), - FILE_SHIFT_LEFT_GUARD_7 = ~(FILE_MASK_A | FILE_MASK_B | FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F | FILE_MASK_G), -}; - - - -#define _FILE_SHIFT_RIGHT_GUARDED(b, n) \ - (((bitboard)(b) & FILE_SHIFT_RIGHT_GUARD_##n) << (index)(n)) - -#define _FILE_SHIFT_LEFT_GUARDED(b, n) \ - (((bitboard)(b) & FILE_SHIFT_LEFT_GUARD_##n) >> (index)(n)) - -/* FILE_SHIFT_RIGHT_n shifts the bitboard right by n ranks without wrapping */ -#define FILE_SHIFT_RIGHT_1(b) _FILE_SHIFT_RIGHT_GUARDED(b, 1) -#define FILE_SHIFT_RIGHT_2(b) _FILE_SHIFT_RIGHT_GUARDED(b, 2) -#define FILE_SHIFT_RIGHT_3(b) _FILE_SHIFT_RIGHT_GUARDED(b, 3) -#define FILE_SHIFT_RIGHT_4(b) _FILE_SHIFT_RIGHT_GUARDED(b, 4) -#define FILE_SHIFT_RIGHT_5(b) _FILE_SHIFT_RIGHT_GUARDED(b, 5) -#define FILE_SHIFT_RIGHT_6(b) _FILE_SHIFT_RIGHT_GUARDED(b, 6) -#define FILE_SHIFT_RIGHT_7(b) _FILE_SHIFT_RIGHT_GUARDED(b, 7) -#define FILE_SHIFT_RIGHT_8(b) _FILE_SHIFT_RIGHT_GUARDED(b, 8) - -/* FILE_SHIFT_LEFT_n shifts the bitboard left by n ranks without wrapping */ -#define FILE_SHIFT_LEFT_1(b) _FILE_SHIFT_LEFT_GUARDED(b, 1) -#define FILE_SHIFT_LEFT_2(b) _FILE_SHIFT_LEFT_GUARDED(b, 2) -#define FILE_SHIFT_LEFT_3(b) _FILE_SHIFT_LEFT_GUARDED(b, 3) -#define FILE_SHIFT_LEFT_4(b) _FILE_SHIFT_LEFT_GUARDED(b, 4) -#define FILE_SHIFT_LEFT_5(b) _FILE_SHIFT_LEFT_GUARDED(b, 5) -#define FILE_SHIFT_LEFT_6(b) _FILE_SHIFT_LEFT_GUARDED(b, 6) -#define FILE_SHIFT_LEFT_7(b) _FILE_SHIFT_LEFT_GUARDED(b, 7) -#define FILE_SHIFT_LEFT_8(b) _FILE_SHIFT_LEFT_GUARDED(b, 8) - -/* RANK_SHIFT_UP_n shifts the bitboard up by n ranks without wrapping */ -#define RANK_SHIFT_UP_1(b) RANK_SHIFT_UP(b, 1) -#define RANK_SHIFT_UP_2(b) RANK_SHIFT_UP(b, 2) -#define RANK_SHIFT_UP_3(b) RANK_SHIFT_UP(b, 3) -#define RANK_SHIFT_UP_4(b) RANK_SHIFT_UP(b, 4) -#define RANK_SHIFT_UP_5(b) RANK_SHIFT_UP(b, 5) -#define RANK_SHIFT_UP_6(b) RANK_SHIFT_UP(b, 6) -#define RANK_SHIFT_UP_7(b) RANK_SHIFT_UP(b, 7) -#define RANK_SHIFT_UP_8(b) RANK_SHIFT_UP(b, 8) - -/* RANK_SHIFT_DOWN_n shifts the bitboard down by n ranks without wrapping */ -#define RANK_SHIFT_DOWN_1(b) RANK_SHIFT_DOWN(b, 1) -#define RANK_SHIFT_DOWN_2(b) RANK_SHIFT_DOWN(b, 2) -#define RANK_SHIFT_DOWN_3(b) RANK_SHIFT_DOWN(b, 3) -#define RANK_SHIFT_DOWN_4(b) RANK_SHIFT_DOWN(b, 4) -#define RANK_SHIFT_DOWN_5(b) RANK_SHIFT_DOWN(b, 5) -#define RANK_SHIFT_DOWN_6(b) RANK_SHIFT_DOWN(b, 6) -#define RANK_SHIFT_DOWN_7(b) RANK_SHIFT_DOWN(b, 7) -#define RANK_SHIFT_DOWN_8(b) RANK_SHIFT_DOWN(b, 8) - -/* - * Player - * =========== - * */ -enum player : uint8_t { - PLAYER_BEGIN, - PLAYER_WHITE = PLAYER_BEGIN, - PLAYER_BLACK, - PLAYER_COUNT, -}; - -static inline enum player opposite_player(enum player p) -{ - return (p == PLAYER_WHITE ? PLAYER_BLACK : PLAYER_WHITE); -} - -static char const* player_str[PLAYER_COUNT] = { - [PLAYER_WHITE] = "PLAYER_WHITE", - [PLAYER_BLACK] = "PLAYER_BLACK", -}; - -/* - * Piece - * =========== - * */ -/* enum value white char white unicode black char black unicode */ -#define PIECES \ - X(PIECE_PAWN, 1.0, 'P', 0x2659, 'p', 0x265F) \ - X(PIECE_KING, 5.0, 'K', 0x2654, 'k', 0x265A) \ - X(PIECE_QUEEN, 9.0, 'Q', 0x2655, 'q', 0x265B) \ - X(PIECE_BISHOP, 3.0, 'B', 0x2657, 'b', 0x265D) \ - X(PIECE_ROOK, 5.0, 'R', 0x2656, 'r', 0x265C) \ - X(PIECE_KNIGHT, 3.0, 'N', 0x2658, 'n', 0x265E) - -enum piece : uint8_t { -#define X(e, v, wc, wu, bc, bu) e, - PIECES - PIECE_COUNT, - PIECE_BEGIN = 0, -#undef X -}; - -static double piece_value[PIECE_COUNT] = { -#define X(e, v, wc, wu, bc, bu) [e] = v, - PIECES -#undef X -}; - -static char const* piece_str[PIECE_COUNT] = { -#define X(e, v, wc, wu, bc, bu) [e] = STR(e), - PIECES -#undef X -}; - -struct piece_player {enum piece piece; enum player player;} const piece_and_player_from_char[256] = { - #define X(e, v, wc, wu, bc, bu) \ - [(uint8_t)bc] = {.piece = e, .player = PLAYER_BLACK}, \ - [(uint8_t)wc] = {.piece = e, .player = PLAYER_WHITE}, - PIECES - #undef X -}; - -static char const piece_char[PLAYER_COUNT][PIECE_COUNT] = { - [PLAYER_WHITE] = { -#define X(e, v, wc, wu, bc, bu) [e] = wc, - PIECES -#undef X - }, - [PLAYER_BLACK] = { -#define X(e, v, wc, wu, bc, bu) [e] = bc, - PIECES -#undef X - } -}; - -static int const piece_unicode[PLAYER_COUNT][PIECE_COUNT] = { - [PLAYER_WHITE] = { -#define X(e, v, wc, wu, bc, bu) [e] = wu, - PIECES -#undef X - }, - [PLAYER_BLACK] = { -#define X(e, v, wc, wu, bc, bu) [e] = bu, - PIECES -#undef X - } -}; - - -/* MISC - * ============*/ -/* TODO: Put these struct definition in a sensible category */ - -/* `struct magic` is used by magic bitboard lookups, see - * rook_attacks_from_index and bishop_attacks_from_index */ -struct magic { - bitboard mask; - bitboard magic; -}; - -/* BITBOARD PRIMITIVES - * ======================== */ - -static enum square_index bitboard_lsb(bitboard p) -{ - return (enum square_index)__builtin_ffsll((int64_t)p) - 1; -} - -static enum square_index bitboard_pop_lsb(bitboard* p) -{ - index const lsb = bitboard_lsb(*p); - *p &= ~(1ULL<<(lsb)); - return lsb; -} - -static uint64_t bitboard_popcount(bitboard b) -{ - return (uint64_t)__builtin_popcountll(b); -} - -static inline enum file_index index_to_file(enum square_index p) -{ - return p % 8ULL; -} - -static inline enum rank_index index_to_rank(enum square_index p) -{ - return p / 8ULL; -} - -static bitboard cardinals_from_index(enum square_index p) -{ - return (FILE_MASK(index_to_file(p)) | RANK_MASK(index_to_rank(p))); -} - -static bitboard diagonals_from_index(enum square_index sq) -{ -#ifdef CODEGEN - enum rank_index const rank = index_to_rank(sq); - enum file_index const file = index_to_file(sq); - - bitboard mask = 0ULL; - - _Static_assert(((enum rank_index)-1) > 0, "rank_index must be unsigned"); - _Static_assert(((enum file_index)-1) > 0, "file_index must be unsigned"); - - enum rank_index r; - enum file_index f; - for (r = rank+1, f = file+1; - r <= RANK_INDEX_8 && f <= FILE_INDEX_H; - ++r, ++f) - { - mask |= SQ_MASK_FROM_RF(r, f); - } - - for (r = rank+1, f = file-1; - r <= RANK_INDEX_8 && f <= FILE_INDEX_H; - ++r, --f) - { - mask |= SQ_MASK_FROM_RF(r, f); - } - - for (r = rank-1, f = file+1; - r <= RANK_INDEX_8 && f <= FILE_INDEX_H; - --r, ++f) - { - mask |= SQ_MASK_FROM_RF(r, f); - } - - for (r = rank-1, f = file-1; - r <= RANK_INDEX_8 && f <= FILE_INDEX_H; - --r, --f) - { - mask |= SQ_MASK_FROM_RF(r, f); - } - return mask; - -#else - #if ! __has_include("diagonals.h") - #error "compile with -DCODEGEN and run once to generate required header files" - #endif - #include "diagonals.h" /* defines static bitboard diagonals[SQ_INDEX_COUNT]; */ - return diagonals[sq]; -#endif -} - - - -/* PIECE ATTACKS - * ================== - * - * All piece attack functions rely on the caller masking their own pieces. - * e.g. `knight_attacks(knights) & ~our_occupied` - * */ - -static bitboard knight_attacks(bitboard p) -{ - bitboard const l1 = FILE_SHIFT_RIGHT_1(p /*& ~FILE_MASK_H*/); - bitboard const l2 = FILE_SHIFT_RIGHT_2(p /*& ~(FILE_MASK_G | FILE_MASK_H)*/); - bitboard const r1 = FILE_SHIFT_LEFT_1 (p /*& ~FILE_MASK_A*/); - bitboard const r2 = FILE_SHIFT_LEFT_2 (p /*& ~(FILE_MASK_A | FILE_MASK_B)*/); - bitboard const h1 = l1 | r1; - bitboard const h2 = l2 | r2; - return RANK_SHIFT_UP_2(h1) - | RANK_SHIFT_DOWN_2(h1) - | RANK_SHIFT_UP_1(h2) - | RANK_SHIFT_DOWN_1(h2); -} - - -static bitboard rook_attacks_from_index(enum square_index sq, bitboard occ) -{ -#ifdef CODEGEN - enum rank_index const rank = index_to_rank(sq); - enum file_index const file = index_to_file(sq); - - bitboard atk = 0ULL; - - /* following loops assume rank and file types are unsigned, which relies on C23 enums */ - _Static_assert(((enum rank_index)-1) > 0, "rank_index must be unsigned"); - _Static_assert(((enum file_index)-1) > 0, "file_index must be unsigned"); - - for (enum rank_index walk_rank = rank+1; walk_rank <= RANK_INDEX_8; ++walk_rank) { - bitboard const sq = SQ_MASK_FROM_RF(walk_rank, file); - atk |= sq; - if (occ & sq) { - break; - } - } - for (enum rank_index walk_rank = rank-1; walk_rank <= RANK_INDEX_8; --walk_rank) { - bitboard const sq = SQ_MASK_FROM_RF(walk_rank, file); - atk |= sq; - if (occ & sq) { - break; - } - } - for (enum file_index walk_file = file+1; walk_file <= FILE_INDEX_H; ++walk_file) { - bitboard const sq = SQ_MASK_FROM_RF(rank, walk_file); - atk |= sq; - if (occ & sq) { - break; - } - } - for (enum file_index walk_file = file-1; walk_file <= FILE_INDEX_H; --walk_file) { - bitboard const sq = SQ_MASK_FROM_RF(rank, walk_file); - atk |= sq; - if (occ & sq) { - break; - } - } - - return atk; - -#else - #if ! __has_include("mbb_rook.h") - #error "compile with -DCODEGEN and run once to generate required header files" - #else - - /* defines - - `mbb_rook[SQ_INDEX_COUNT]` - - `bitboard rook_attacks[SQ_INDEX_COUNT][1<<12ULL] */ - #include "mbb_rook.h" - - struct magic const m = mbb_rook[sq]; - occ &= m.mask; - occ *= m.magic; - occ >>= (64ULL-12ULL); - assert(rook_attacks[sq][occ] != SQ_MASK_FROM_INDEX(sq)); - return rook_attacks[sq][occ]; - - #endif -#endif -} - -static bitboard rook_attacks(bitboard p, bitboard occ) -{ - bitboard b = 0ULL; - while (p) { - index const lsb = bitboard_pop_lsb(&p); - b |= rook_attacks_from_index(lsb, occ); - } - return b; -} - -static bitboard bishop_attacks_from_index(enum square_index sq, bitboard occ) -{ -#ifdef CODEGEN - enum rank_index const rank = index_to_rank(sq); - enum file_index const file = index_to_file(sq); - - bitboard atk = 0ULL; - - /* following loops assume rank and file types are unsigned, which relies on C23 enums */ - _Static_assert(((enum rank_index)-1) > 0, "rank_index must be unsigned"); - _Static_assert(((enum file_index)-1) > 0, "file_index must be unsigned"); - - - enum rank_index walk_rank; - enum file_index walk_file; - for (walk_rank = rank+1, walk_file = file+1; - walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H; - ++walk_rank, ++walk_file) { - bitboard const sq = SQ_MASK_FROM_RF(walk_rank, walk_file); - atk |= sq; - if (occ & sq) { - break; - } - } - for (walk_rank = rank+1, walk_file = file-1; - walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H; - ++walk_rank, --walk_file) { - bitboard const sq = SQ_MASK_FROM_RF(walk_rank, walk_file); - atk |= sq; - if (occ & sq) { - break; - } - } - for (walk_rank = rank-1, walk_file = file+1; - walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H; - --walk_rank, ++walk_file) { - bitboard const sq = SQ_MASK_FROM_RF(walk_rank, walk_file); - atk |= sq; - if (occ & sq) { - break; - } - } - for (walk_rank = rank-1, walk_file = file-1; - walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H; - --walk_rank, --walk_file) { - bitboard const sq = SQ_MASK_FROM_RF(walk_rank, walk_file); - atk |= sq; - if (occ & sq) { - break; - } - } - - return atk; -#else - #if ! __has_include("mbb_bishop.h") - #error "compile with -DCODEGEN and run once to generate required header files" - #else - -/* defines - - `mbb_bishop[SQ_INDEX_COUNT]` - - `bitboard bishop_attacks[SQ_INDEX_COUNT][1<<9ULL] */ -#include "mbb_bishop.h" - assert(sq < SQ_INDEX_COUNT); - struct magic const m = mbb_bishop[sq]; - occ &= m.mask; - occ *= m.magic; - occ >>= (64ULL-9ULL); - return bishop_attacks[sq][occ]; - #endif -#endif -} - -static bitboard bishop_attacks(bitboard p, bitboard occ) -{ - bitboard b = 0ULL; - while (p) { - index const lsb = bitboard_pop_lsb(&p); - b |= bishop_attacks_from_index(lsb, occ); - } - return b; -} - -static bitboard queen_attacks_from_index(enum square_index sq, bitboard occ) -{ - return bishop_attacks_from_index(sq, occ) | rook_attacks_from_index(sq, occ); -} - -static bitboard queen_attacks(bitboard p, bitboard occ) -{ - bitboard b = 0ULL; - while (p) { - index const lsb = bitboard_pop_lsb(&p); - b |= queen_attacks_from_index(lsb, occ); - } - return b; -} - -static inline bitboard pawn_moves_white(bitboard p, bitboard empty) -{ - /* Assumptions: - - empty = ~(black_occupied | white_occupied); - */ - - bitboard const single_push = RANK_SHIFT_UP_1(p) & empty; - - bitboard const double_push = - RANK_SHIFT_UP_1(single_push & RANK_MASK_3) & empty; - - return (single_push | double_push); -} - -static inline bitboard pawn_attacks_white(bitboard p) -{ - bitboard const up = RANK_SHIFT_UP_1(p); - bitboard const captures_right = FILE_SHIFT_RIGHT_1(up); - bitboard const captures_left = FILE_SHIFT_LEFT_1(up); - - return (captures_right | captures_left); -} - -static inline bitboard pawn_moves_black(bitboard p, bitboard empty) -{ - /* Assumptions: - - empty = ~(black_occupied | white_occupied); - */ - - bitboard const single_push = RANK_SHIFT_DOWN_1(p) & empty; - - bitboard const double_push = - RANK_SHIFT_DOWN_1(single_push & RANK_MASK_6) & empty; - - return (single_push | double_push); -} - -static inline bitboard pawn_attacks_black(bitboard p) -{ - bitboard const down = RANK_SHIFT_DOWN_1(p); - bitboard const captures_right = FILE_SHIFT_RIGHT_1(down); - bitboard const captures_left = FILE_SHIFT_LEFT_1(down); - - return (captures_right | captures_left); -} - -/* temporary solution, annoying branch */ -static inline bitboard pawn_moves_dispatch(bitboard p, bitboard empty, enum player attacker) -{ - if (attacker == PLAYER_WHITE) { - return pawn_moves_white(p, empty); - } else { - return pawn_moves_black(p, empty); - } -} - -/* temporary solution, annoying branch */ -static inline bitboard pawn_attacks_dispatch(bitboard p, enum player attacker) -{ - if (attacker == PLAYER_WHITE) { - return pawn_attacks_white(p); - } else { - return pawn_attacks_black(p); - } -} - -static bitboard king_attacks(bitboard sq) -{ - /* potential untested improvements: - * - lookup table - * - union of three files, three ranks and ~sq */ - bitboard const n = RANK_SHIFT_UP_1(sq); - bitboard const s = RANK_SHIFT_DOWN_1(sq); - bitboard const w = FILE_SHIFT_LEFT_1(sq); - bitboard const e = FILE_SHIFT_RIGHT_1(sq); - - bitboard const nw = FILE_SHIFT_LEFT_1(n); - bitboard const ne = FILE_SHIFT_RIGHT_1(n); - bitboard const sw = FILE_SHIFT_LEFT_1(s); - bitboard const se = FILE_SHIFT_RIGHT_1(s); - - return (n | s | w | e | nw | ne | sw | se); -} - -static bitboard piece_attacks(enum piece piece, - enum player player, - bitboard ep_targets, - bitboard p, - bitboard occ, - bitboard their_occ) -{ - switch (piece) { - case PIECE_PAWN: { - return pawn_attacks_dispatch(p, player) & (their_occ | ep_targets); - } break; - - case PIECE_KNIGHT: { - return knight_attacks(p); - } break; - - case PIECE_BISHOP: { - return bishop_attacks(p, occ); - } break; - - case PIECE_ROOK: { - return rook_attacks(p, occ); - } break; - - case PIECE_QUEEN: { - return queen_attacks(p, occ); - } break; - - case PIECE_KING: { - return king_attacks(p); - } break; - -#ifndef NDEBUG - default: { - __builtin_trap(); - __builtin_unreachable(); - } break; -#endif - } -} - -static bitboard piece_moves(enum piece piece, - enum player player, - bitboard ep_targets, - bitboard p, - bitboard occ, - bitboard their_occ) -{ - bitboard x = 0ULL; - if (piece == PIECE_PAWN) { - x |= pawn_moves_dispatch(p, ~occ, player); - } - x |= piece_attacks(piece, player, ep_targets, p, occ, their_occ); - return x; -} - -/* BOARD - * ================= */ -enum castle_direction { - CASTLE_BEGIN, - CASTLE_KINGSIDE = CASTLE_BEGIN, - CASTLE_QUEENSIDE, - CASTLE_COUNT, -}; - -enum { - MATTR_CAPTURE = 1<<0, - MATTR_PROMOTE = 1<<1, - MATTR_CHECK = 1<<2, - MATTR_CASTLE_KINGSIDE = 1<<3, - MATTR_CASTLE_QUEENSIDE = 1<<4, -}; - -struct move { - index from; - index to; - uint8_t attr; - #define APPEAL_MAX UINT8_MAX - uint8_t appeal; -}; -_Static_assert(sizeof(struct move) == 4, - "this static assert is here to check when sizeof(move) changes"); - -struct search_option { - /* TODO: optimize order of fields and size */ - double score; - struct move move; - uint64_t hash; - uint8_t init : 1; - enum tt_flag {TT_EXACT, TT_LOWER, TT_UPPER} flag : 3; - int8_t depth : 4; -}; - -#define TT_ADDRESS_BITS 24 -#define TT_ENTRIES (1ULL<piece lookups, not central to logic. Does not - * store which player owns the square piece, and checking non-occupied - * squares is undefined - * - * TODO: make mailbox smaller, only 3 bits are needed per piece - * */ - enum piece mailbox[SQ_INDEX_COUNT]; -}; +#define SCORE_INF 1e30 +#define SCORE_CHECKMATE 999.0 +/* for initial ordering of moves in alphabeta search */ static void move_compute_appeal(struct move* restrict m, struct pos const* restrict pos, - enum player us, - enum piece mailbox[restrict static SQ_INDEX_COUNT]) + Side8 us, + Piece8 mailbox[restrict static SQ_COUNT]) { /* MVV-LVA: https://www.chessprogramming.org/MVV-LVA */ - enum player them = opposite_player(us); - enum piece const atk = mailbox[m->from]; + Side8 them = other_side(us); + Piece8 const atk = mailbox[m->from]; uint8_t n = 1; - if (SQ_MASK_FROM_INDEX(m->to) & pos->occupied[them]) { + if (MASK_FROM_SQ(m->to) & pos->occupied[them]) { n += (uint8_t)piece_value[mailbox[m->to]]; } uint8_t mmv_lva_bonus = 16*n - (uint8_t)piece_value[atk]; - assert((uint8_t)16*n > (uint8_t)piece_value[atk]); m->appeal = mmv_lva_bonus; } -#define BOARD_INIT (struct board) { \ - .pos = { \ - .fullmoves = 1, \ - .pieces = { \ - [PLAYER_WHITE] = { \ - [PIECE_PAWN] = RANK_MASK_2, \ - [PIECE_ROOK] = SQ_MASK_A1 | SQ_MASK_H1, \ - [PIECE_KNIGHT] = SQ_MASK_B1 | SQ_MASK_G1, \ - [PIECE_BISHOP] = SQ_MASK_C1 | SQ_MASK_F1, \ - [PIECE_QUEEN] = SQ_MASK_D1, \ - [PIECE_KING] = SQ_MASK_E1, \ - }, \ - [PLAYER_BLACK] = { \ - [PIECE_PAWN] = RANK_MASK_7, \ - [PIECE_ROOK] = SQ_MASK_A8 | SQ_MASK_H8, \ - [PIECE_KNIGHT] = SQ_MASK_B8 | SQ_MASK_G8, \ - [PIECE_BISHOP] = SQ_MASK_C8 | SQ_MASK_F8, \ - [PIECE_QUEEN] = SQ_MASK_D8, \ - [PIECE_KING] = SQ_MASK_E8, \ - } \ - }, \ - .occupied = { \ - [PLAYER_WHITE] = \ - RANK_MASK_2 | SQ_MASK_A1 | SQ_MASK_H1 | \ - SQ_MASK_B1 | SQ_MASK_G1 | SQ_MASK_C1 | \ - SQ_MASK_F1 | SQ_MASK_D1 | SQ_MASK_E1, \ - [PLAYER_BLACK] = \ - RANK_MASK_7 | SQ_MASK_A8 | SQ_MASK_H8 | \ - SQ_MASK_B8 | SQ_MASK_G8| SQ_MASK_C8 | \ - SQ_MASK_F8| SQ_MASK_D8| SQ_MASK_E8, \ - }, \ - .hash = ~0ULL, \ - }, \ - .mailbox = { \ - [SQ_INDEX_A1] = PIECE_ROOK, \ - [SQ_INDEX_B1] = PIECE_KNIGHT, \ - [SQ_INDEX_C1] = PIECE_BISHOP, \ - [SQ_INDEX_D1] = PIECE_QUEEN, \ - [SQ_INDEX_E1] = PIECE_KING, \ - [SQ_INDEX_F1] = PIECE_BISHOP, \ - [SQ_INDEX_G1] = PIECE_KNIGHT, \ - [SQ_INDEX_H1] = PIECE_ROOK, \ - [SQ_INDEX_A2] = PIECE_PAWN, \ - [SQ_INDEX_B2] = PIECE_PAWN, \ - [SQ_INDEX_C2] = PIECE_PAWN, \ - [SQ_INDEX_D2] = PIECE_PAWN, \ - [SQ_INDEX_E2] = PIECE_PAWN, \ - [SQ_INDEX_F2] = PIECE_PAWN, \ - [SQ_INDEX_G2] = PIECE_PAWN, \ - [SQ_INDEX_H2] = PIECE_PAWN, \ - [SQ_INDEX_A7] = PIECE_PAWN, \ - [SQ_INDEX_B7] = PIECE_PAWN, \ - [SQ_INDEX_C7] = PIECE_PAWN, \ - [SQ_INDEX_D7] = PIECE_PAWN, \ - [SQ_INDEX_E7] = PIECE_PAWN, \ - [SQ_INDEX_F7] = PIECE_PAWN, \ - [SQ_INDEX_G7] = PIECE_PAWN, \ - [SQ_INDEX_H7] = PIECE_PAWN, \ - [SQ_INDEX_A8] = PIECE_ROOK, \ - [SQ_INDEX_B8] = PIECE_KNIGHT, \ - [SQ_INDEX_C8] = PIECE_BISHOP, \ - [SQ_INDEX_D8] = PIECE_QUEEN, \ - [SQ_INDEX_E8] = PIECE_KING, \ - [SQ_INDEX_F8] = PIECE_BISHOP, \ - [SQ_INDEX_G8] = PIECE_KNIGHT, \ - [SQ_INDEX_H8] = PIECE_ROOK, \ - }, \ - .hist = {0}, \ -} - -void board_init(struct board* b) -{ - if (b->pos.fullmoves == 0 && b->pos.hash == 0) { - *b = BOARD_INIT; - } - if (b->tt.entries == NULL) { - b->tt.entries = sys_mmap_anon_shared(TT_ENTRIES * sizeof b->tt.entries[0], - SYS_PROT_READ | SYS_PROT_WRITE, - SYS_MADV_RANDOM); - if (b->tt.entries == NULL) { - __builtin_trap(); - } - } -} - -static bool board_load_fen_unsafe(struct board* b, char const* fen_str) -{ - /* TODO: this function is not tested for malicious/corrupted inputs */ - struct { - char const* buf; - size_t i; - size_t len; - } fen = { - .buf = fen_str, - .i = 0, - .len = strlen(fen_str), - }; -#define BUF_GETCHAR(x) (assert(x.i < x.len), x.buf[x.i++]) - - my_memset(&b->pos, 0, sizeof b->pos); - b->pos.hash = ~0ULL; - - for (enum rank_index ri = RANK_INDEX_8; ri < RANK_INDEX_COUNT; --ri) { - for (enum file_index fi = FILE_INDEX_BEGIN; fi < FILE_INDEX_COUNT; ++fi) { - char const ch = BUF_GETCHAR(fen); - if (my_isdigit(ch)) { - if (ch == '0') { - __builtin_trap(); - } - fi += ch - '0' - 1; - } else { - struct piece_player const p = piece_and_player_from_char[(uint8_t)ch]; - bitboard const sq_mask = SQ_MASK_FROM_RF(ri, fi); - b->pos.pieces[p.player][p.piece] |= sq_mask; - b->pos.occupied[p.player] |= sq_mask; - b->mailbox[INDEX_FROM_RF(ri, fi)] = p.piece; - } - } - (void)BUF_GETCHAR(fen); /* discard '/' or ' ' */ - } - - { /* active color */ - char const ch = BUF_GETCHAR(fen); - if (ch == 'w') { - b->pos.player = PLAYER_WHITE; - } else if (ch == 'b') { - b->pos.player = PLAYER_BLACK; - } else { - __builtin_trap(); - } - } - - { /* castling */ - char ch = BUF_GETCHAR(fen); - if (ch != ' ') { - __builtin_trap(); - } - b->pos.castling_illegal[PLAYER_WHITE][CASTLE_KINGSIDE] = true; - b->pos.castling_illegal[PLAYER_WHITE][CASTLE_QUEENSIDE] = true; - b->pos.castling_illegal[PLAYER_BLACK][CASTLE_KINGSIDE] = true; - b->pos.castling_illegal[PLAYER_BLACK][CASTLE_QUEENSIDE] = true; - do { - ch = BUF_GETCHAR(fen); - switch (ch) { - case 'K': b->pos.castling_illegal[PLAYER_WHITE][CASTLE_KINGSIDE] = false; break; - case 'Q': b->pos.castling_illegal[PLAYER_WHITE][CASTLE_QUEENSIDE] = false; break; - case 'k': b->pos.castling_illegal[PLAYER_BLACK][CASTLE_KINGSIDE] = false; break; - case 'q': b->pos.castling_illegal[PLAYER_BLACK][CASTLE_QUEENSIDE] = false; break; - case ' ': break; - case '-': break; - default: { - __builtin_trap(); - } break; - } - } while (ch != ' '); - } - - { /* en passent */ - char const ch = BUF_GETCHAR(fen); - if (ch != '-') { - __builtin_trap(); - } - } - - { /* halfmoves */ - b->pos.halfmoves = 0; - char ch = BUF_GETCHAR(fen); - while (ch != ' ') { - b->pos.halfmoves *= 10; - b->pos.halfmoves += ch-'0'; - ch = BUF_GETCHAR(fen); - } - } - - { /* fullmoves */ - b->pos.fullmoves = 0; - char ch = BUF_GETCHAR(fen); - while (ch != ' ') { - b->pos.fullmoves *= 10; - b->pos.fullmoves += ch-'0'; - ch = BUF_GETCHAR(fen); - } - } -#undef BUF_GETCHAR - - return true; -} - -static bitboard all_threats_from_player(struct pos const * pos, enum player player) -{ - bitboard const occ = pos->occupied[PLAYER_WHITE] | pos->occupied[PLAYER_BLACK]; - - bitboard const* p = pos->pieces[player]; - bitboard t = 0ULL; - t |= pawn_attacks_dispatch(p[PIECE_PAWN], player); - t |= knight_attacks(p[PIECE_KNIGHT]); - t |= bishop_attacks(p[PIECE_BISHOP], occ); - t |= rook_attacks(p[PIECE_ROOK], occ); - t |= queen_attacks(p[PIECE_QUEEN], occ); - t |= king_attacks(p[PIECE_KING]); - return t; -} - -static bitboard between_mask(enum square_index a, enum square_index b) -{ -#ifdef CODEGEN - enum rank_index const ra = index_to_rank(a); - enum file_index const fa = index_to_file(a); - enum rank_index const rb = index_to_rank(b); - enum file_index const fb = index_to_file(b); - - /* directional differences */ - int const dr = (int)rb - (int)ra; - int const df = (int)fb - (int)fa; - - int step_r = 0; - int step_f = 0; - - if (df == 0 && dr != 0) { - step_r = (dr > 0) ? 1 : -1; - step_f = 0; - } - else if (dr == 0 && df != 0) { - step_r = 0; - step_f = (df > 0) ? 1 : -1; - } - else if (dr != 0 && (dr > 0 ? dr : -dr) == (df > 0 ? df : -df)) { - step_r = (dr > 0) ? 1 : -1; - step_f = (df > 0) ? 1 : -1; - } - else { - return 0ULL; - } - - bitboard mask = 0ULL; - - enum rank_index r = (enum rank_index)((int)ra + step_r); - enum file_index f = (enum file_index)((int)fa + step_f); - - while (r != rb || f != fb) { - mask |= SQ_MASK_FROM_RF(r, f); - r = (enum rank_index)((int)r + step_r); - f = (enum file_index)((int)f + step_f); - } - - return mask; -#else - #if ! __has_include("between_lookup.h") - #error "compile codegen.c and run once to generate required header files" - #else - - /* defines static between_lookup[SQUARE_INDEX_COUNT][SQUARE_INDEX_COUNT] */ - #include "between_lookup.h" - return between_lookup[a][b]; - #endif -#endif -} - -static bitboard attacks_to(struct pos const* pos, - bitboard target_square, - bitboard pretend_occupied, - bitboard pretend_empty) -{ - bitboard const occ_orig = pos->occupied[PLAYER_WHITE] | pos->occupied[PLAYER_BLACK]; - bitboard const occ = (occ_orig & ~pretend_empty) | pretend_occupied; - - bitboard const* pw = pos->pieces[PLAYER_WHITE]; - bitboard const* pb = pos->pieces[PLAYER_BLACK]; - - bitboard const wps = pw[PIECE_PAWN]; - - bitboard const bps = pb[PIECE_PAWN]; - - bitboard const ns = (pw[PIECE_KNIGHT] - | pb[PIECE_KNIGHT]) - ; - bitboard const ks = (pw[PIECE_KING] - | pb[PIECE_KING]) - ; - bitboard const qs = (pw[PIECE_QUEEN] - | pb[PIECE_QUEEN]) - ; - bitboard const qrs = (pw[PIECE_ROOK] - | pb[PIECE_ROOK] - | qs) - ; - bitboard const qbs = (pw[PIECE_BISHOP] - | pb[PIECE_BISHOP] - | qs) - ; - - enum square_index target_index = bitboard_lsb(target_square); - - return ((bps & pawn_attacks_white(target_square)) - | (wps & pawn_attacks_black(target_square)) - | (ns & knight_attacks(target_square)) - | (qbs & bishop_attacks_from_index(target_index, occ)) - | (qrs & rook_attacks_from_index(target_index, occ)) - | (ks & king_attacks(target_square))) - & ~pretend_occupied; -} - -#define MOVE_CASTLE_KINGSIDE_WHITE (struct move) \ - {.from = SQ_INDEX_E1, .to = SQ_INDEX_G1, .attr = MATTR_CASTLE_KINGSIDE} - -#define MOVE_CASTLE_QUEENSIDE_WHITE \ - (struct move){.from = SQ_INDEX_E1, .to = SQ_INDEX_C1, .attr = MATTR_CASTLE_QUEENSIDE} - -#define MOVE_CASTLE_KINGSIDE_BLACK (struct move) \ - {.from = SQ_INDEX_E8, .to = SQ_INDEX_G8, .attr = MATTR_CASTLE_KINGSIDE} - -#define MOVE_CASTLE_QUEENSIDE_BLACK \ - (struct move){.from = SQ_INDEX_E8, .to = SQ_INDEX_C8, .attr = MATTR_CASTLE_QUEENSIDE} - -struct move move_make(struct pos const* pos, enum piece piece, index from, index to, uint8_t add_attr) -{ - enum player const us = pos->player; - enum player const them = opposite_player(us); - bitboard const their_occ = pos->occupied[them]; - bitboard const tomask = SQ_MASK_FROM_INDEX(to); - bitboard const finishline = (us == PLAYER_WHITE ? RANK_MASK_8 : RANK_MASK_1); - - uint8_t attr = 0ULL; -#define MASK_IF8(x) ((~(uint8_t)0U) + (uint8_t)!(x)) - attr |= MATTR_CAPTURE & MASK_IF8(tomask & their_occ); - attr |= MATTR_CAPTURE & MASK_IF8((piece == PIECE_PAWN) && tomask & pos->ep_targets); - attr |= MATTR_PROMOTE & MASK_IF8((piece == PIECE_PAWN) && (tomask & finishline)); - attr |= add_attr; -#undef MASK_IF8 - - return (struct move){.from = from, .to = to, .attr = attr}; -} - -#define MOVE_MAX 128 - -static void all_moves_from_piece(struct pos const* restrict pos, - enum piece piece, - enum player us, - enum square_index from, - bitboard allowed, - size_t* restrict out_count, - struct move out[restrict static MOVE_MAX]) -{ - /* TODO: this function is terrible, needs major rework - * - * instead of dispatching to a generalized `piece_moves`, this can be specialized for each - * piece - * */ - assert(piece != PIECE_KING); - enum player const them = opposite_player(us); - - bitboard const our_occ = pos->occupied[us]; - bitboard const their_occ = pos->occupied[them]; - bitboard const all_occ = pos->occupied[PLAYER_WHITE] | pos->occupied[PLAYER_BLACK]; - - bitboard const from_mask = SQ_MASK_FROM_INDEX(from); - - bitboard move_mask = - piece_moves(piece, us, pos->ep_targets, from_mask, all_occ, their_occ) - & ~our_occ - /*& allowed*/ - ; - - while (move_mask) { - enum square_index const to = bitboard_pop_lsb(&move_mask); - bitboard const to_mask = SQ_MASK_FROM_INDEX(to); - - bitboard const king_attackers = - attacks_to(pos, pos->pieces[us][PIECE_KING], to_mask, from_mask) & ~(our_occ | to_mask); - - if (king_attackers == 0ULL) { - out[(*out_count)++] = move_make(pos, piece, from, to, 0); - } - } -} - -static void all_moves_from_king(struct pos const* restrict pos, - enum player us, - enum square_index from, - size_t* restrict out_count, - struct move out[restrict static MOVE_MAX]) -{ - enum player const them = opposite_player(us); - bitboard const our_occ = pos->occupied[us]; - bitboard const all_occ = pos->occupied[PLAYER_WHITE] | pos->occupied[PLAYER_BLACK]; - - bitboard const from_mask = SQ_MASK_FROM_INDEX(from); - bitboard const threats = all_threats_from_player(pos, them); - - bitboard move_mask = king_attacks(from_mask) & ~(threats | our_occ); - - /* general moves */ - while (move_mask) { - enum square_index const to = bitboard_pop_lsb(&move_mask); - bitboard const to_mask = SQ_MASK_FROM_INDEX(to); - - bitboard const king_attackers = attacks_to(pos, to_mask, to_mask, from_mask) & ~(our_occ | to_mask); - - if (king_attackers == 0ULL) { - out[(*out_count)++] = move_make(pos, PIECE_KING, from, to, 0); - } - } - - /* castling */ - if (from_mask & ~threats) { - bool kingside_free, queenside_free; - if (us == PLAYER_WHITE) { - kingside_free = !((threats | all_occ) & (SQ_MASK_F1 | SQ_MASK_G1)) - && (pos->pieces[us][PIECE_ROOK] & SQ_MASK_H1); - queenside_free = !((threats | all_occ) & (SQ_MASK_B1 | SQ_MASK_C1 | SQ_MASK_D1)) - && (pos->pieces[us][PIECE_ROOK] & SQ_MASK_A1); - } else { - kingside_free = !((threats | all_occ) & (SQ_MASK_F8 | SQ_MASK_G8)) - && (pos->pieces[us][PIECE_ROOK] & SQ_MASK_H8); - queenside_free = !((threats | all_occ) & (SQ_MASK_B8 | SQ_MASK_C8 | SQ_MASK_D8)) - && (pos->pieces[us][PIECE_ROOK] & SQ_MASK_A8); - } - if (!pos->castling_illegal[us][CASTLE_KINGSIDE] && kingside_free) - { - out[(*out_count)++] = us == PLAYER_WHITE - ? MOVE_CASTLE_KINGSIDE_WHITE - : MOVE_CASTLE_KINGSIDE_BLACK; - } - if (!pos->castling_illegal[us][CASTLE_QUEENSIDE] && queenside_free) - { - out[(*out_count)++] = us == PLAYER_WHITE - ? MOVE_CASTLE_QUEENSIDE_WHITE - : MOVE_CASTLE_QUEENSIDE_BLACK; - } - } -} - -static void all_moves(struct pos const* restrict pos, - enum player us, - size_t* restrict out_count, - struct move out[restrict static MOVE_MAX]) -{ - *out_count = 0ULL; - bitboard const myking = pos->pieces[us][PIECE_KING]; - assert(myking); - - enum square_index const myking_index = bitboard_lsb(myking); - bitboard const attackers = attacks_to(pos, myking, 0ULL, 0ULL) & ~pos->occupied[us]; - - bitboard allowed = ~0ULL; - - uint64_t const natk = bitboard_popcount(attackers); - //if (natk == 1) { - // enum square_index const attacker_index = bitboard_lsb(attackers); - // allowed = between_mask(myking_index, attacker_index) | attackers; - //} else { - // bitboard const pinners = pinning_lines(pos, us, myking_index); - //} - - /* king */ - { - all_moves_from_king(pos, us, myking_index, out_count, out); - } - - if (natk >= 2) { - /* no other piece can fix check if the king has two attackers or more */ - return; - } - - /* pawns */ - { - bitboard ps = pos->pieces[us][PIECE_PAWN]; - while (ps) { - enum square_index const from = bitboard_pop_lsb(&ps); - all_moves_from_piece(pos, PIECE_PAWN, us, from, allowed, out_count, out); - } - } - - /* knights */ - { - bitboard ns = pos->pieces[us][PIECE_KNIGHT]; - while (ns) { - index const from = bitboard_pop_lsb(&ns); - all_moves_from_piece(pos, PIECE_KNIGHT, us, from, allowed, out_count, out); - } - } - - /* bishops */ - { - bitboard bs = pos->pieces[us][PIECE_BISHOP]; - while (bs) { - index const from = bitboard_pop_lsb(&bs); - all_moves_from_piece(pos, PIECE_BISHOP, us, from, allowed, out_count, out); - } - } - - /* rooks */ - { - bitboard rs = pos->pieces[us][PIECE_ROOK]; - while (rs) { - index const from = bitboard_pop_lsb(&rs); - all_moves_from_piece(pos, PIECE_ROOK, us, from, allowed, out_count, out); - } - } - - /* queens */ - { - bitboard qs = pos->pieces[us][PIECE_QUEEN]; - while (qs) { - index const from = bitboard_pop_lsb(&qs); - all_moves_from_piece(pos, PIECE_QUEEN, us, from, allowed, out_count, out); - } - } -} - -struct zobrist { - uint64_t piece_keys[SQ_INDEX_COUNT][PLAYER_COUNT][PIECE_COUNT]; - uint64_t ep_targets[SQ_INDEX_COUNT]; - uint64_t castling_keys[PLAYER_COUNT][CASTLE_COUNT]; - bool init; -}; - -static struct zobrist zobrist; - -static void init_zobrist() -{ - for (enum square_index sq = SQ_INDEX_BEGIN; sq < SQ_INDEX_COUNT; ++sq) { - for (enum player pl = PLAYER_BEGIN; pl < PLAYER_COUNT; ++pl) { - for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) { - zobrist.piece_keys[sq][pl][piece] = my_rand64(); - } - } - zobrist.ep_targets[sq] = my_rand64(); - } - for (enum player pl = PLAYER_BEGIN; pl < PLAYER_COUNT; ++pl) { - for (enum castle_direction d = CASTLE_BEGIN; d < CASTLE_COUNT; ++d) { - zobrist.castling_keys[pl][d] = my_rand64(); - } - } - zobrist.init = true; -} - -static inline uint64_t tt_hash_update(uint64_t hash, - enum square_index sq, - enum player us, - enum piece piece) -{ - if (!zobrist.init) { - init_zobrist(); - } - return hash ^ zobrist.piece_keys[sq][us][piece]; -} - -static inline uint64_t tt_hash_update_castling_rights(uint64_t hash, - enum player us, - enum castle_direction dir) -{ - if (!zobrist.init) { - init_zobrist(); - } - return hash ^ zobrist.castling_keys[us][dir]; -} - -static inline uint64_t tt_hash_update_ep_targets(uint64_t hash, enum square_index sq) -{ - if (!zobrist.init) { - init_zobrist(); - } - assert(sq < SQ_INDEX_COUNT); - return hash ^ zobrist.ep_targets[sq]; -} - -static inline uint64_t tt_hash_switch_player(uint64_t hash) -{ - if (!zobrist.init) { - init_zobrist(); - } - return ~hash; -} - -static inline struct search_option tt_get(struct tt* tt, uint64_t hash) -{ - uint64_t const addr = hash % TT_ENTRIES; - struct search_option tte = tt->entries[addr]; - -#ifndef NDEBUG - tt->probes += 1; - if (tte.init && tte.hash == hash) { - tt->hits += 1; - } else if (tte.init && tte.hash != hash) { - tt->collisions += 1; - } -#endif - return tte; -} - -static inline void tt_insert(struct tt* tt, uint64_t hash, struct search_option so) -{ - uint64_t const addr = hash % TT_ENTRIES; - so.init = true; - tt->entries[addr] = so; -#ifndef NDEBUG - tt->insertions += 1; -#endif -} - -/* tt_insert_maybe inserts only if heuristics say it's a good idea. There are - * two considerations: - * - higher depth saves more work per probe hit - * - entries closer to the leaves are more likely to be searched multiple time - */ -static inline void tt_insert_maybe(struct tt* tt, uint64_t hash, struct search_option so) -{ - uint64_t const addr = hash % TT_ENTRIES; - -#if 0 - struct search_option* tte = &tt->entries[addr]; - if (so.depth < tte->depth) { - *tte = so; - } -#endif - - so.init = true; - tt->entries[addr] = so; -#ifndef NDEBUG - tt->insertions += 1; -#endif -} - -enum move_result { - MR_NORMAL, - MR_CHECK, - MR_REPEATS, /* this board state has been observed before */ - MR_STALEMATE, - MR_CHECKMATE, -}; - -/* does not check validity */ -static enum move_result board_move(struct pos* restrict pos, - struct history* restrict hist, - enum piece mailbox[restrict static SQ_INDEX_COUNT], - struct move move) -{ - enum player const us = pos->player; - enum player const them = opposite_player(us); - - enum piece const from_piece = mailbox[move.from]; - enum piece const to_piece = mailbox[move.to]; - - bitboard const from_mask = SQ_MASK_FROM_INDEX(move.from); - bitboard const to_mask = SQ_MASK_FROM_INDEX(move.to); - - enum square_index const krook = (us == PLAYER_WHITE) ? SQ_INDEX_H1 : SQ_INDEX_H8; - enum square_index const qrook = (us == PLAYER_WHITE) ? SQ_INDEX_A1 : SQ_INDEX_A8; - enum square_index const krook_to = (us == PLAYER_WHITE) ? SQ_INDEX_F1 : SQ_INDEX_F8; - enum square_index const qrook_to = (us == PLAYER_WHITE) ? SQ_INDEX_D1 : SQ_INDEX_D8; - enum square_index const ksq = (us == PLAYER_WHITE) ? SQ_INDEX_E1 : SQ_INDEX_E8; - enum square_index const kcast_sq = (us == PLAYER_WHITE) ? SQ_INDEX_G1 : SQ_INDEX_G8; - enum square_index const qcast_sq = (us == PLAYER_WHITE) ? SQ_INDEX_C1 : SQ_INDEX_C8; - enum square_index const their_krook = (us != PLAYER_WHITE) ? SQ_INDEX_H1 : SQ_INDEX_H8; - enum square_index const their_qrook = (us != PLAYER_WHITE) ? SQ_INDEX_A1 : SQ_INDEX_A8; - - #define POS_MOVE(player, piece, from, to) \ - do { \ - bitboard const x = SQ_MASK_FROM_INDEX(from) | SQ_MASK_FROM_INDEX(to); \ - pos->pieces[player][piece] ^= x; \ - pos->occupied[player] ^= x; \ - pos->hash = tt_hash_update(pos->hash, from, player, piece); \ - pos->hash = tt_hash_update(pos->hash, to, player, piece); \ - mailbox[to] = piece; \ - if (piece == PIECE_PAWN) pos->halfmoves = 0; \ - } while (0) - - #define POS_REMOVE(owner, piece, at) \ - do { \ - bitboard const x = SQ_MASK_FROM_INDEX(at); \ - pos->pieces[owner][piece] &= ~x; \ - pos->occupied[owner] &= ~x; \ - pos->hash = tt_hash_update(pos->hash, at, owner, piece); \ - hist->length = 0; \ - pos->halfmoves = 0; \ - } while (0) - - #define POS_ADD(owner, piece, at) \ - do { \ - bitboard const x = SQ_MASK_FROM_INDEX(at); \ - pos->pieces[owner][piece] |= x; \ - pos->occupied[owner] |= x; \ - pos->hash = tt_hash_update(pos->hash, at, owner, piece); \ - mailbox[at] = piece; \ - pos->halfmoves = 0; \ - hist->length = 0; \ - } while (0) - - bitboard const ep_targets_now = pos->ep_targets; - if (ep_targets_now) { - pos->hash = tt_hash_update_ep_targets(pos->hash, bitboard_lsb(ep_targets_now)); - } - pos->ep_targets = 0ULL; - - /* castle kingside, legality is checked by the caller */ - /**/ if (from_piece == PIECE_KING && move.from == ksq && move.to == kcast_sq) { - POS_MOVE(us, PIECE_KING, ksq, kcast_sq); - POS_MOVE(us, PIECE_ROOK, krook, krook_to); - } - /* castle queenside, legality is checked by the caller */ - else if (from_piece == PIECE_KING && move.from == ksq && move.to == qcast_sq) { - POS_MOVE(us, PIECE_KING, ksq, qcast_sq); - POS_MOVE(us, PIECE_ROOK, qrook, qrook_to); - } - /* regular move / capture */ - else { - POS_MOVE(us, from_piece, move.from, move.to); - /* capture */ - if (to_mask & pos->occupied[them]) { - POS_REMOVE(them, to_piece, move.to); - } - - /* promote / ep */ - if (from_piece == PIECE_PAWN) { - bitboard const finishline = (us == PLAYER_WHITE ? RANK_MASK_8 : RANK_MASK_1); - - /* en passent */ - /**/ if (to_mask & ep_targets_now) { - enum square_index const ti = - (us == PLAYER_WHITE) - ? INDEX_SHIFT_DOWN(move.to, 1) - : INDEX_SHIFT_UP(move.to, 1); - POS_REMOVE(them, PIECE_PAWN, ti); - } - /* pawn double push -> new ep_target */ - else if (us == PLAYER_WHITE && (from_mask & RANK_MASK_2) && (to_mask & RANK_MASK_4)) { - pos->ep_targets |= RANK_SHIFT_UP_1(from_mask); - pos->hash = tt_hash_update_ep_targets(pos->hash, INDEX_SHIFT_UP(move.from, 1)); - } - else if (us == PLAYER_BLACK && (from_mask & RANK_MASK_7) && (to_mask & RANK_MASK_5)) { - pos->ep_targets |= RANK_SHIFT_DOWN_1(from_mask); - pos->hash = tt_hash_update_ep_targets(pos->hash, INDEX_SHIFT_DOWN(move.from, 1)); - } - /* promote */ - else if (to_mask & finishline) { - /* already moved to `move.to` */ - POS_REMOVE(us, PIECE_PAWN, move.to); - POS_ADD(us, PIECE_QUEEN, move.to); - } - } - } - - /* castling rights */ - if (!pos->castling_illegal[us][CASTLE_KINGSIDE]) { - if (move.from == ksq || move.from == krook) { - pos->castling_illegal[us][CASTLE_KINGSIDE] = true; - pos->hash = tt_hash_update_castling_rights(pos->hash, us, CASTLE_KINGSIDE); - } - } - if (!pos->castling_illegal[us][CASTLE_QUEENSIDE]) { - if (move.from == ksq || move.from == qrook) { - pos->castling_illegal[us][CASTLE_QUEENSIDE] = true; - pos->hash = tt_hash_update_castling_rights(pos->hash, us, CASTLE_QUEENSIDE); - } - } - - if (move.to == their_krook) { - if (!pos->castling_illegal[them][CASTLE_KINGSIDE]) { - pos->castling_illegal[them][CASTLE_KINGSIDE] = true; - pos->hash = tt_hash_update_castling_rights(pos->hash, them, CASTLE_KINGSIDE); - } - } - if (move.to == their_qrook) { - if (!pos->castling_illegal[them][CASTLE_QUEENSIDE]) { - pos->castling_illegal[them][CASTLE_QUEENSIDE] = true; - pos->hash = tt_hash_update_castling_rights(pos->hash, them, CASTLE_QUEENSIDE); - } - } - - pos->hash = tt_hash_switch_player(pos->hash); - pos->player = them; - pos->fullmoves += (pos->player == PLAYER_BLACK); - pos->halfmoves += 1; - - assert(hist->length < 64); - int repetitions = 0; - for (size_t i = 0; i < hist->length; ++i) { - _Static_assert(sizeof *pos == sizeof hist->items[i]); - if (!my_memcmp(&hist->items[i].pieces, &pos->pieces, sizeof pos->pieces) - && !my_memcmp(&hist->items[i].castling_illegal, &pos->castling_illegal, sizeof pos->castling_illegal) - && hist->items[i].player == pos->player - && hist->items[i].ep_targets == pos->ep_targets) - { - repetitions += 1; - } - } - - hist->items[hist->length++] = *pos; - - if (repetitions >= 3 || pos->halfmoves > 50) { - return MR_STALEMATE; - } - else if (repetitions > 0) { - return MR_REPEATS; - } - else if (pos->halfmoves > 50) { - return MR_STALEMATE; - } - else if (attacks_to(pos, pos->pieces[them][PIECE_KING], 0ULL, 0ULL) - & ~pos->occupied[them]) { - return MR_CHECK; - } - else { - return MR_NORMAL; - } -} - - - -static enum move_result board_move_2(struct board* b, struct move move) -{ - return board_move(&b->pos, - &b->hist, - b->mailbox, - move); -} - - -/* --------------------------- MOVE SEARCH --------------------------------- */ -#include "evaluations.h" - static double board_score_heuristic(struct pos const* pos) { /* this function always evaluates from white's perspective before - eventually flipping the sign */ + eventually flipping the sign based on `pos` */ double score = 0.0; - /* evaluations.h defines: - - POSITIONAL_MODIFIER_COUNT - - static struct {bitboard const area; double const val} const - positional_modifier[PLAYER_COUNT][POSITIONAL_MODIFIER_COUNT][PIECE_COUNT]; - * */ - bitboard const occw = pos->occupied[PLAYER_WHITE]; - bitboard const occb = pos->occupied[PLAYER_BLACK]; - bitboard const occall = occw | occb; + Bb64 const occw = pos->occupied[SIDE_WHITE]; + Bb64 const occb = pos->occupied[SIDE_BLACK]; + Bb64 const occall = occw | occb; enum game_progress const gp = endgameness(pos); - for (enum piece p = PIECE_BEGIN; p < PIECE_COUNT; ++p) { + if (pos->pieces[SIDE_WHITE][PIECE_KING] == 0) score -= (double)SCORE_CHECKMATE; + if (pos->pieces[SIDE_BLACK][PIECE_KING] == 0) score += (double)SCORE_CHECKMATE; + + for (Piece8 p = PIECE_BEGIN; p < PIECE_COUNT; ++p) { /* raw material value */ score += piece_value[p] * - ((double)bitboard_popcount(pos->pieces[PLAYER_WHITE][p]) - - (double)bitboard_popcount(pos->pieces[PLAYER_BLACK][p])); + ((double)bitboard_popcount(pos->pieces[SIDE_WHITE][p]) - + (double)bitboard_popcount(pos->pieces[SIDE_BLACK][p])); /* very minor advantage for threat projection to break tied moves */ - score += 0.001 * ( - (double)bitboard_popcount(piece_attacks(p, PLAYER_WHITE, 0, p, occall, 0)) - - (double)bitboard_popcount(piece_attacks(p, PLAYER_BLACK, 0, p, occall, 0))); + if (p != PIECE_PAWN) { + score += 0.001 * ( + (double)bitboard_popcount(non_pawn_piece_attacks(p, pos->pieces[SIDE_WHITE][p], occall)) + - (double)bitboard_popcount(non_pawn_piece_attacks(p, pos->pieces[SIDE_BLACK][p], occall))); + } /* positional bonus, see evaluations.h */ for (size_t i = 0; i < POSITIONAL_MODIFIER_COUNT; ++i) { - score += positional_modifier(PLAYER_WHITE, gp, i, p).val * + score += positional_modifier(SIDE_WHITE, gp, i, p).val * ( (double)bitboard_popcount( - pos->pieces[PLAYER_WHITE][p] - & positional_modifier(PLAYER_WHITE, gp, i, p).area) + pos->pieces[SIDE_WHITE][p] + & positional_modifier(SIDE_WHITE, gp, i, p).area) - (double)bitboard_popcount( - pos->pieces[PLAYER_BLACK][p] - & positional_modifier(PLAYER_BLACK, gp, i, p).area) + pos->pieces[SIDE_BLACK][p] + & positional_modifier(SIDE_BLACK, gp, i, p).area) ); } } /* pawns defending pieces are desired */ - score += 0.05 * ( + score += 0.03 * ( (double)bitboard_popcount( - pawn_attacks_white(pos->pieces[PLAYER_WHITE][PIECE_PAWN]) & occw + pawn_attacks_white(pos->pieces[SIDE_WHITE][PIECE_PAWN]) & occw ) - (double)bitboard_popcount( - pawn_attacks_black(pos->pieces[PLAYER_BLACK][PIECE_PAWN]) & occb + pawn_attacks_black(pos->pieces[SIDE_BLACK][PIECE_PAWN]) & occb ) ); /* stacked pawns are bad */ const double k = 0.30; for (enum file_index fi = FILE_INDEX_BEGIN; fi < FILE_INDEX_COUNT; ++fi) { - uint64_t wstk = bitboard_popcount(pos->pieces[PLAYER_WHITE][PIECE_PAWN] & FILE_MASK(fi)); - uint64_t bstk = bitboard_popcount(pos->pieces[PLAYER_BLACK][PIECE_PAWN] & FILE_MASK(fi)); + uint64_t wstk = bitboard_popcount(pos->pieces[SIDE_WHITE][PIECE_PAWN] & FILE_MASK(fi)); + uint64_t bstk = bitboard_popcount(pos->pieces[SIDE_BLACK][PIECE_PAWN] & FILE_MASK(fi)); score -= k * (double)(wstk - (wstk == 1)); score += k * (double)(bstk - (bstk == 1)); } - double sign = (pos->player == PLAYER_WHITE) ? 1.0 : -1.0; + double sign = (pos->moving_side == SIDE_WHITE) ? 1.0 : -1.0; return sign*score; } -static inline struct move moves_linear_search(struct move moves[restrict static MOVE_MAX], +static +struct move moves_linear_search(struct move moves[restrict static MOVE_MAX], size_t* restrict move_count) { size_t best = 0; - assert(*move_count > 0); + assuming(*move_count > 0); for (size_t i = 0; i < *move_count; ++i) { if (moves[i].appeal > moves[best].appeal) { best = i; @@ -1845,14 +130,19 @@ static inline struct move moves_linear_search(struct move moves[restrict st } /* quiescence is a deep search that only considers captures */ +static double quiesce(struct pos const* pos, - enum piece mailbox[restrict static SQ_INDEX_COUNT], - enum player us, + Piece8 mailbox[restrict static SQ_COUNT], + Side8 us, double alpha, double beta, int8_t depth) { - enum player const them = opposite_player(us); + if (pos->pieces[us][PIECE_KING] == 0) { + return -SCORE_CHECKMATE; + } + + Side8 const them = other_side(us); double score = board_score_heuristic(pos); double highscore = score; @@ -1868,7 +158,7 @@ double quiesce(struct pos const* pos, struct move moves[MOVE_MAX]; - all_moves(pos, us, &move_count, moves); + all_pseudolegal_moves(pos, MG_CAPTURES, us, &move_count, moves); if (move_count == 0) { /* TODO: detect stalemate */ return -(999.0 + (double)depth); @@ -1880,19 +170,17 @@ double quiesce(struct pos const* pos, while (move_count) { struct move m = moves_linear_search(moves, &move_count); - if ((m.attr & MATTR_CAPTURE) == 0ULL) { - continue; - } + assuming((pos->occupied[them] | pos->ep_targets) & MASK_FROM_SQ(m.to)); + struct pos poscpy = *pos; - enum piece mailbox_cpy[SQ_INDEX_COUNT]; - my_memcpy(mailbox_cpy, mailbox, sizeof (enum piece[SQ_INDEX_COUNT])); - + Piece8 mailbox_cpy[SQ_COUNT]; + my_memcpy(mailbox_cpy, mailbox, sizeof (Piece8[SQ_COUNT])); /* history is irrelevant when all moves are captures */ static struct history hist; hist.length = 0; - (void)board_move(&poscpy, &hist, mailbox_cpy, m); + (void)move_piece(&poscpy, &hist, mailbox_cpy, m); score = -quiesce(&poscpy, mailbox_cpy, them, -beta, -alpha, depth - 1); @@ -1900,9 +188,11 @@ double quiesce(struct pos const* pos, highscore = score; break; } + if (score > highscore) { highscore = score; } + if (score > alpha) { alpha = score; } @@ -1915,13 +205,26 @@ static struct search_option alphabeta_search(struct pos const* pos, struct history* hist, struct tt* tt, - enum piece mailbox[restrict static SQ_INDEX_COUNT], - enum player us, + Piece8 mailbox[restrict static SQ_COUNT], + Side8 us, int8_t depth, double alpha, double beta, atomic_bool* searching) { + g_ab_node_volume += 1; + + if (pos->pieces[us][PIECE_KING] == 0) { + return (struct search_option) { + .score = -(SCORE_CHECKMATE + (double)depth), + .move = (struct move){0}, + .depth = 0, + .hash = pos->hash, + .init = true, + .flag = TT_EXACT, + }; + } + if (depth <= 0) { double sc = quiesce(pos, mailbox, us, alpha, beta, 0); return (struct search_option){ @@ -1936,100 +239,94 @@ struct search_option alphabeta_search(struct pos const* pos, double const alpha_orig = alpha; - struct search_option tte = tt_get(tt, pos->hash); - if (tte.init && tte.hash == pos->hash && tte.depth >= depth) { - if (tte.flag == TT_EXACT) { - return tte; - } else if (tte.flag == TT_LOWER) { - if (tte.score > alpha) alpha = tte.score; - } else if (tte.flag == TT_UPPER) { - if (tte.score < beta) beta = tte.score; - } - - if (alpha >= beta) { - return tte; - } - } - struct move moves[MOVE_MAX]; size_t move_count = 0; - all_moves(pos, us, &move_count, moves); - /* checkmate or stalemate */ - if (move_count == 0) { - double score = 0.0; - if (attacks_to(pos, - pos->pieces[us][PIECE_KING], - 0ULL, 0ULL) != 0ULL) { - score = -(999.0 + (double)depth); + struct search_option tte = tt_get(tt, pos->hash); + if (tte.init && tte.hash == pos->hash) { + if (tte.depth >= depth) { + if (tte.flag == TT_EXACT) { + return tte; + } else if (tte.flag == TT_LOWER) { + if (tte.score > alpha) alpha = tte.score; + } else if (tte.flag == TT_UPPER) { + if (tte.score < beta) beta = tte.score; + } + + if (alpha >= beta) { + return tte; + } } - return (struct search_option){ - .score = score, - .move = (struct move){0}, - .depth = depth, - .hash = pos->hash, - .init = true, - .flag = TT_EXACT, - }; + moves[move_count] = tte.move; + moves[move_count].appeal = APPEAL_MAX; + ++move_count; } - for (size_t i = 0; i < move_count; ++i) { - move_compute_appeal(&moves[i], pos, us, mailbox); - } + double best_score = -SCORE_INF; + struct move best_move = NULL_MOVE; + + enum move_gen_type const types[] = {MG_CAPTURES, MG_CHECKS, MG_QUIETS}; + for (size_t i = 0; i < sizeof types / sizeof *types; ++i) { + all_pseudolegal_moves(pos, types[i], us, &move_count, moves); - /* put existing TT move first */ - if (tte.init && tte.hash == pos->hash) { for (size_t i = 0; i < move_count; ++i) { - if (moves[i].from == tte.move.from && - moves[i].to == tte.move.to) { - moves[i].appeal = APPEAL_MAX; - break; + move_compute_appeal(&moves[i], pos, us, mailbox); + } + + while (move_count > 0) { + if (!atomic_load_explicit(searching, memory_order_relaxed)) { + return (struct search_option) { .init = false }; + } + struct move m = moves_linear_search(moves, &move_count); + + size_t const old_hist_len = hist->length; + struct pos pos_cpy = *pos; + Piece8 mailbox_cpy[SQ_COUNT]; + my_memcpy(mailbox_cpy, mailbox, sizeof mailbox_cpy); + + enum move_result r = move_piece(&pos_cpy, hist, mailbox_cpy, m); + + double score; + + if (r == MR_STALEMATE || r == MR_REPEATS) { + score = 0.0; + } else { + struct search_option so = alphabeta_search(&pos_cpy, + hist, + tt, + mailbox_cpy, + other_side(us), + depth - 1, + -beta, + -alpha, + searching); + if (!so.init) { + hist->length = old_hist_len; + return (struct search_option){ .init = false, .move = NULL_MOVE }; + } + score = -so.score; + } + + hist->length = old_hist_len; + + if (score > best_score) { + best_score = score; + best_move = m; + } + if (score > alpha) { + alpha = score; + } + if (alpha >= beta) { + goto finish_search; } } } - double best_score = -1e300; - struct move best_move = moves[0]; +finish_search: - while (move_count > 0 && atomic_load_explicit(searching, memory_order_relaxed)) { - struct move m = moves_linear_search(moves, &move_count); - - size_t const old_hist_len = hist->length; - struct pos pos_cpy = *pos; - enum piece mailbox_cpy[SQ_INDEX_COUNT]; - my_memcpy(mailbox_cpy, mailbox, sizeof mailbox_cpy); - - enum move_result r = board_move(&pos_cpy, hist, mailbox_cpy, m); - - double score; - - if (r == MR_STALEMATE || r == MR_REPEATS) { - score = 0.0; - } else { - score = -alphabeta_search(&pos_cpy, - hist, - tt, - mailbox_cpy, - opposite_player(us), - depth - 1, - -beta, - -alpha, - searching).score; - } - - hist->length = old_hist_len; - - if (score > best_score) { - best_score = score; - best_move = m; - } - if (score > alpha) { - alpha = score; - } - if (alpha >= beta) { - break; - } + if (IS_NULL_MOVE(best_move)) { + return (struct search_option){ .init = false, .move = NULL_MOVE }; } enum tt_flag flag = TT_EXACT; @@ -2054,32 +351,42 @@ struct search_option alphabeta_search(struct pos const* pos, return out; } -static struct search_result {struct move move; double score;} -search(struct board* b, enum player us, int8_t max_depth, atomic_bool* searching) +static +struct search_result {struct move move; double score;} + search(struct board* b, Side8 us, int8_t max_depth, atomic_bool* searching) { struct move moves[MOVE_MAX]; size_t move_count = 0; - all_moves(&b->pos, us, &move_count, moves); + all_pseudolegal_moves(&b->pos, MG_ALL, us, &move_count, moves); - assert(move_count); + assuming(move_count); struct move best_move = moves[0]; -#define SCORE_INF 1e300 + g_ab_node_volume = 0; + double score = 0.0; for (int8_t d = 1; d <= max_depth && atomic_load_explicit(searching, memory_order_relaxed); ++d) { - double window = 1000; /* temp debug solution */ + double window = SCORE_INF; /* temp debug solution */ double alpha = score - window; double beta = score + window; - while (atomic_load_explicit(searching, memory_order_relaxed)) { + while (1) { struct search_option so = alphabeta_search(&b->pos, &b->hist, &b->tt, b->mailbox, us, d, alpha, beta, searching); + if (atomic_load_explicit(searching, memory_order_relaxed) == false) { + break; + } + + if (IS_NULL_MOVE(so.move)) { + break; + } + if (so.score > alpha && so.score < beta) { score = so.score; best_move = so.move; @@ -2097,10 +404,17 @@ search(struct board* b, enum player us, int8_t max_depth, atomic_bool* searching } } +#ifdef USE_PRINTF + fprintf(stderr, "depth: %hhd\n", d); +#endif } #undef SCORE_INF +#ifdef USE_PRINTF + fprintf(stderr, "nodes searched: %'llu\n", g_ab_node_volume); +#endif + return (struct search_result){.move = best_move, .score = score}; } diff --git a/tests.c b/tests.c index d73a1e1..2af8b4f 100644 --- a/tests.c +++ b/tests.c @@ -1,4 +1,5 @@ +#define USE_PRINTF #define _XOPEN_SOURCE 500 #include /* usleep */ #include @@ -12,10 +13,12 @@ /* Tests are mostly generated by ChatGPT */ +#define test_expect(expr) if (!(expr)) { fprintf(stderr, "test failed: expected <" #expr ">\n"); exit(EXIT_FAILURE); } + static void print_rook_test(const char *label, - enum square_index sq, - bitboard all_occ, - bitboard own_occ) + Sq8 sq, + Bb64 all_occ, + Bb64 own_occ) { printf("\n%s\n", label); printf("All occ:\n"); @@ -23,7 +26,7 @@ static void print_rook_test(const char *label, printf("Own occ:\n"); bitboard_print(own_occ, stdout); - const bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; + const Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; printf("Rook attacks:\n"); bitboard_print(attacks, stdout); } @@ -31,84 +34,84 @@ static void print_rook_test(const char *label, static void test_rooks() { { - const enum square_index sq = SQ_INDEX_A1; - const bitboard rook = SQ_MASK_A1; - const bitboard all_occ = rook; - const bitboard own_occ = rook; + const Sq8 sq = SQ_A1; + const Bb64 rook = SQMASK_A1; + const Bb64 all_occ = rook; + const Bb64 own_occ = rook; /* Expected: full rank 1 and file A, except A1 */ - bitboard expected = (FILE_MASK_A | RANK_MASK_1) & ~SQ_MASK_A1; + Bb64 expected = (FILE_MASK_A | RANK_MASK_1) & ~SQMASK_A1; print_rook_test("Test 1: Rook at A1, empty board", sq, all_occ, own_occ); - const bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; - assert(attacks == expected); + const Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; + test_expect(attacks == expected); } { - const enum square_index sq = SQ_INDEX_A1; - const bitboard rook = SQ_MASK_A1; - const bitboard own_block = SQ_MASK_A2 | SQ_MASK_B1; - const bitboard all_occ = rook | own_block; - const bitboard own_occ = all_occ; + const Sq8 sq = SQ_A1; + const Bb64 rook = SQMASK_A1; + const Bb64 own_block = SQMASK_A2 | SQMASK_B1; + const Bb64 all_occ = rook | own_block; + const Bb64 own_occ = all_occ; /* Expected: no legal moves (immediately blocked both directions) */ - const bitboard expected = 0ULL; + const Bb64 expected = 0ULL; print_rook_test("Test 2: Rook at A1, own blockers A2, B1", sq, all_occ, own_occ); - const bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; - assert(attacks == expected); + const Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; + test_expect(attacks == expected); } { - const enum square_index sq = SQ_INDEX_A1; - const bitboard rook = SQ_MASK_A1; - const bitboard enemies = SQ_MASK_A3 | SQ_MASK_C1; - const bitboard all_occ = rook | enemies; - const bitboard own_occ = rook; + const Sq8 sq = SQ_A1; + const Bb64 rook = SQMASK_A1; + const Bb64 enemies = SQMASK_A3 | SQMASK_C1; + const Bb64 all_occ = rook | enemies; + const Bb64 own_occ = rook; /* * Expected: * - Along file A: A2, A3 (enemy at A3 is capturable, stop there) * - Along rank 1: B1, C1 (enemy at C1 capturable, stop there) */ - bitboard expected = 0ULL; - expected |= SQ_MASK_A2 | SQ_MASK_A3; - expected |= SQ_MASK_B1 | SQ_MASK_C1; + Bb64 expected = 0ULL; + expected |= SQMASK_A2 | SQMASK_A3; + expected |= SQMASK_B1 | SQMASK_C1; print_rook_test("Test 3: Rook at A1, enemy blockers A3, C1", sq, all_occ, own_occ); - const bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; - assert(attacks == expected); + const Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; + test_expect(attacks == expected); } /* Rook Test 6: center rook on empty board */ { - const enum square_index sq = SQ_INDEX_E5; - const bitboard rook = SQ_MASK_E5; - const bitboard all_occ = rook; - const bitboard own_occ = rook; + const Sq8 sq = SQ_E5; + const Bb64 rook = SQMASK_E5; + const Bb64 all_occ = rook; + const Bb64 own_occ = rook; /* Full rank 5 and file E, except E5 itself */ - bitboard expected = (FILE_MASK_E | RANK_MASK_5) & ~SQ_MASK_E5; + Bb64 expected = (FILE_MASK_E | RANK_MASK_5) & ~SQMASK_E5; print_rook_test("Rook Test 6: E5, empty board", sq, all_occ, own_occ); - bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; - assert(attacks == expected); + Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; + test_expect(attacks == expected); } /* Rook Test 7: center rook, mixed blockers on rays */ { - const enum square_index sq = SQ_INDEX_E5; - const bitboard rook = SQ_MASK_E5; + const Sq8 sq = SQ_E5; + const Bb64 rook = SQMASK_E5; /* Friendly: E7 and C5; Enemy: E3 and H5 */ - const bitboard friends = rook | SQ_MASK_E7 | SQ_MASK_C5; - const bitboard enemies = SQ_MASK_E3 | SQ_MASK_H5; - const bitboard all_occ = friends | enemies; - const bitboard own_occ = friends; + const Bb64 friends = rook | SQMASK_E7 | SQMASK_C5; + const Bb64 enemies = SQMASK_E3 | SQMASK_H5; + const Bb64 all_occ = friends | enemies; + const Bb64 own_occ = friends; /* * From E5: @@ -117,73 +120,73 @@ static void test_rooks() * Left: D5, then friendly C5 (stop before C5) * Right:F5, G5, H5 (enemy, included, then stop) */ - bitboard expected = 0ULL; - expected |= SQ_MASK_E6; - expected |= SQ_MASK_E4 | SQ_MASK_E3; - expected |= SQ_MASK_D5; - expected |= SQ_MASK_F5 | SQ_MASK_G5 | SQ_MASK_H5; + Bb64 expected = 0ULL; + expected |= SQMASK_E6; + expected |= SQMASK_E4 | SQMASK_E3; + expected |= SQMASK_D5; + expected |= SQMASK_F5 | SQMASK_G5 | SQMASK_H5; print_rook_test("Rook Test 7: E5, friends E7/C5, enemies E3/H5", sq, all_occ, own_occ); - bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; - assert(attacks == expected); + Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; + test_expect(attacks == expected); } /* Rook Test 8: edge rook on empty board (top edge, not corner) */ { - const enum square_index sq = SQ_INDEX_C8; - const bitboard rook = SQ_MASK_C8; - const bitboard all_occ = rook; - const bitboard own_occ = rook; + const Sq8 sq = SQ_C8; + const Bb64 rook = SQMASK_C8; + const Bb64 all_occ = rook; + const Bb64 own_occ = rook; /* * From C8: * Down file C: C7..C1 * Across rank 8: A8,B8,D8,E8,F8,G8,H8 */ - bitboard expected = 0ULL; - expected |= (FILE_MASK_C & ~SQ_MASK_C8); - expected |= (RANK_MASK_8 & ~SQ_MASK_C8); + Bb64 expected = 0ULL; + expected |= (FILE_MASK_C & ~SQMASK_C8); + expected |= (RANK_MASK_8 & ~SQMASK_C8); print_rook_test("Rook Test 8: C8, empty board", sq, all_occ, own_occ); - bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; - assert(attacks == expected); + Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; + test_expect(attacks == expected); } /* Rook Test 9: rook completely boxed in by friendly orthogonal neighbors */ { - const enum square_index sq = SQ_INDEX_D4; - const bitboard rook = SQ_MASK_D4; - const bitboard friends = rook | - SQ_MASK_D5 | SQ_MASK_D3 | - SQ_MASK_C4 | SQ_MASK_E4; - const bitboard enemies = 0ULL; - const bitboard all_occ = friends | enemies; - const bitboard own_occ = friends; + const Sq8 sq = SQ_D4; + const Bb64 rook = SQMASK_D4; + const Bb64 friends = rook | + SQMASK_D5 | SQMASK_D3 | + SQMASK_C4 | SQMASK_E4; + const Bb64 enemies = 0ULL; + const Bb64 all_occ = friends | enemies; + const Bb64 own_occ = friends; /* All four rays are immediately blocked by own pieces */ - const bitboard expected = 0ULL; + const Bb64 expected = 0ULL; print_rook_test("Rook Test 9: D4, boxed by own pieces at D5/D3/C4/E4", sq, all_occ, own_occ); - bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; - assert(attacks == expected); + Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; + test_expect(attacks == expected); } /* Rook Test 10: rook on file with non-interfering off-ray pieces */ { - const enum square_index sq = SQ_INDEX_A4; - const bitboard rook = SQ_MASK_A4; + const Sq8 sq = SQ_A4; + const Bb64 rook = SQMASK_A4; /* Pieces placed off the rook's rank/file; they should have no effect */ - const bitboard off_ray = SQ_MASK_C1 | SQ_MASK_F6 | SQ_MASK_H8; + const Bb64 off_ray = SQMASK_C1 | SQMASK_F6 | SQMASK_H8; (void)off_ray; - const bitboard friends = rook | SQ_MASK_C1; - const bitboard enemies = SQ_MASK_F6 | SQ_MASK_H8; - const bitboard all_occ = friends | enemies; - const bitboard own_occ = friends; + const Bb64 friends = rook | SQMASK_C1; + const Bb64 enemies = SQMASK_F6 | SQMASK_H8; + const Bb64 all_occ = friends | enemies; + const Bb64 own_occ = friends; /* * From A4: @@ -191,22 +194,21 @@ static void test_rooks() * Rank 4: B4..H4 * Pieces not on file A or rank 4 must not change attacks. */ - bitboard expected = 0ULL; - expected |= (FILE_MASK_A | RANK_MASK_4) & ~SQ_MASK_A4; + Bb64 expected = 0ULL; + expected |= (FILE_MASK_A | RANK_MASK_4) & ~SQMASK_A4; print_rook_test("Rook Test 10: A4, random off-ray pieces C1/F6/H8", sq, all_occ, own_occ); - bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; - assert(attacks == expected); + Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ; + test_expect(attacks == expected); } } - static void print_bishop_test(const char *label, - enum square_index sq, - bitboard all_occ, - bitboard own_occ) + Sq8 sq, + Bb64 all_occ, + Bb64 own_occ) { fprintf(stderr, "\n%s\n", label); fprintf(stderr, "All occ:\n"); @@ -214,7 +216,7 @@ static void print_bishop_test(const char *label, fprintf(stderr, "Own occ:\n"); bitboard_print(own_occ, stderr); - const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ; + const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ; fprintf(stderr, "Bishop attacks:\n"); bitboard_print(attacks, stderr); } @@ -223,10 +225,10 @@ static void test_bishops(void) { /* Test 1: Bishop at D4 on empty board (only bishop present) */ { - const enum square_index sq = SQ_INDEX_D4; - const bitboard bishop = SQ_MASK_D4; - const bitboard all_occ = bishop; - const bitboard own_occ = bishop; + const Sq8 sq = SQ_D4; + const Bb64 bishop = SQMASK_D4; + const Bb64 all_occ = bishop; + const Bb64 own_occ = bishop; /* * Expected diagonals from D4: @@ -235,24 +237,24 @@ static void test_bishops(void) * SE: E3, F2, G1 * SW: C3, B2, A1 */ - bitboard expected = 0ULL; - expected |= SQ_MASK_E5 | SQ_MASK_F6 | SQ_MASK_G7 | SQ_MASK_H8; - expected |= SQ_MASK_C5 | SQ_MASK_B6 | SQ_MASK_A7; - expected |= SQ_MASK_E3 | SQ_MASK_F2 | SQ_MASK_G1; - expected |= SQ_MASK_C3 | SQ_MASK_B2 | SQ_MASK_A1; + Bb64 expected = 0ULL; + expected |= SQMASK_E5 | SQMASK_F6 | SQMASK_G7 | SQMASK_H8; + expected |= SQMASK_C5 | SQMASK_B6 | SQMASK_A7; + expected |= SQMASK_E3 | SQMASK_F2 | SQMASK_G1; + expected |= SQMASK_C3 | SQMASK_B2 | SQMASK_A1; print_bishop_test("Bishop Test 1: D4, empty board", sq, all_occ, own_occ); - const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ; - assert(attacks == expected); + const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ; + test_expect(attacks == expected); } /* Test 2: Bishop at C1 on empty board (only bishop present) */ { - const enum square_index sq = SQ_INDEX_C1; - const bitboard bishop = SQ_MASK_C1; - const bitboard all_occ = bishop; - const bitboard own_occ = bishop; + const Sq8 sq = SQ_C1; + const Bb64 bishop = SQMASK_C1; + const Bb64 all_occ = bishop; + const Bb64 own_occ = bishop; /* * From C1, diagonals: @@ -260,25 +262,25 @@ static void test_bishops(void) * NW: B2, A3 * SE / SW: none (edge of board) */ - bitboard expected = 0ULL; - expected |= SQ_MASK_D2 | SQ_MASK_E3 | SQ_MASK_F4 | - SQ_MASK_G5 | SQ_MASK_H6; - expected |= SQ_MASK_B2 | SQ_MASK_A3; + Bb64 expected = 0ULL; + expected |= SQMASK_D2 | SQMASK_E3 | SQMASK_F4 | + SQMASK_G5 | SQMASK_H6; + expected |= SQMASK_B2 | SQMASK_A3; print_bishop_test("Bishop Test 2: C1, empty board", sq, all_occ, own_occ); - const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ; - assert(attacks == expected); + const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ; + test_expect(attacks == expected); } /* Test 3: Bishop at D4, friendly blockers at F6 and B2 (no enemies) */ { - const enum square_index sq = SQ_INDEX_D4; - const bitboard bishop = SQ_MASK_D4; - const bitboard friends = bishop | SQ_MASK_F6 | SQ_MASK_B2; - const bitboard enemies = 0ULL; - const bitboard all_occ = friends | enemies; - const bitboard own_occ = friends; + const Sq8 sq = SQ_D4; + const Bb64 bishop = SQMASK_D4; + const Bb64 friends = bishop | SQMASK_F6 | SQMASK_B2; + const Bb64 enemies = 0ULL; + const Bb64 all_occ = friends | enemies; + const Bb64 own_occ = friends; /* * From D4: @@ -287,26 +289,26 @@ static void test_bishops(void) * NW: C5, B6, A7 (no blockers) * SE: E3, F2, G1 (no blockers) */ - bitboard expected = 0ULL; - expected |= SQ_MASK_E5; - expected |= SQ_MASK_C3; - expected |= SQ_MASK_C5 | SQ_MASK_B6 | SQ_MASK_A7; - expected |= SQ_MASK_E3 | SQ_MASK_F2 | SQ_MASK_G1; + Bb64 expected = 0ULL; + expected |= SQMASK_E5; + expected |= SQMASK_C3; + expected |= SQMASK_C5 | SQMASK_B6 | SQMASK_A7; + expected |= SQMASK_E3 | SQMASK_F2 | SQMASK_G1; print_bishop_test("Bishop Test 3: D4, friendly blockers F6, B2", sq, all_occ, own_occ); - const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ; - assert(attacks == expected); + const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ; + test_expect(attacks == expected); } /* Test 4: Bishop at D4, enemy blockers at F6 and B2 (no other friends) */ { - const enum square_index sq = SQ_INDEX_D4; - const bitboard bishop = SQ_MASK_D4; - const bitboard friends = bishop; - const bitboard enemies = SQ_MASK_F6 | SQ_MASK_B2; - const bitboard all_occ = friends | enemies; - const bitboard own_occ = friends; + const Sq8 sq = SQ_D4; + const Bb64 bishop = SQMASK_D4; + const Bb64 friends = bishop; + const Bb64 enemies = SQMASK_F6 | SQMASK_B2; + const Bb64 all_occ = friends | enemies; + const Bb64 own_occ = friends; /* * From D4: @@ -315,27 +317,27 @@ static void test_bishops(void) * NW: C5, B6, A7 * SE: E3, F2, G1 */ - bitboard expected = 0ULL; - expected |= SQ_MASK_E5 | SQ_MASK_F6; - expected |= SQ_MASK_C3 | SQ_MASK_B2; - expected |= SQ_MASK_C5 | SQ_MASK_B6 | SQ_MASK_A7; - expected |= SQ_MASK_E3 | SQ_MASK_F2 | SQ_MASK_G1; + Bb64 expected = 0ULL; + expected |= SQMASK_E5 | SQMASK_F6; + expected |= SQMASK_C3 | SQMASK_B2; + expected |= SQMASK_C5 | SQMASK_B6 | SQMASK_A7; + expected |= SQMASK_E3 | SQMASK_F2 | SQMASK_G1; print_bishop_test("Bishop Test 4: D4, enemy blockers F6, B2", sq, all_occ, own_occ); - const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ; - assert(attacks == expected); + const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ; + test_expect(attacks == expected); } /* Test 5: Bishop at D4, mixed friend/enemy + another friendly bishop elsewhere */ { - const enum square_index sq = SQ_INDEX_D4; - const bitboard bishop1 = SQ_MASK_D4; /* tested bishop */ - const bitboard bishop2 = SQ_MASK_F4; /* another friendly bishop */ - const bitboard friends = bishop1 | bishop2 | SQ_MASK_F6; - const bitboard enemies = SQ_MASK_B2; - const bitboard all_occ = friends | enemies; - const bitboard own_occ = friends; + const Sq8 sq = SQ_D4; + const Bb64 bishop1 = SQMASK_D4; /* tested bishop */ + const Bb64 bishop2 = SQMASK_F4; /* another friendly bishop */ + const Bb64 friends = bishop1 | bishop2 | SQMASK_F6; + const Bb64 enemies = SQMASK_B2; + const Bb64 all_occ = friends | enemies; + const Bb64 own_occ = friends; /* * From D4: @@ -345,24 +347,23 @@ static void test_bishops(void) * SE: E3, F2, G1 * Bishop at F4 is irrelevant; it does not sit on a diagonal from D4. */ - bitboard expected = 0ULL; - expected |= SQ_MASK_E5; - expected |= SQ_MASK_C3 | SQ_MASK_B2; - expected |= SQ_MASK_C5 | SQ_MASK_B6 | SQ_MASK_A7; - expected |= SQ_MASK_E3 | SQ_MASK_F2 | SQ_MASK_G1; + Bb64 expected = 0ULL; + expected |= SQMASK_E5; + expected |= SQMASK_C3 | SQMASK_B2; + expected |= SQMASK_C5 | SQMASK_B6 | SQMASK_A7; + expected |= SQMASK_E3 | SQMASK_F2 | SQMASK_G1; print_bishop_test("Bishop Test 5: D4, mixed friend/enemy + extra bishop F4", sq, all_occ, own_occ); - const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ; - assert(attacks == expected); + const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ; + test_expect(attacks == expected); } /* Test 6: Bishop at H8, no occupancy */ { - const enum square_index sq = SQ_INDEX_H8; - const bitboard enemies = 0ULL; - const bitboard all_occ = 0ULL; - const bitboard own_occ = SQ_MASK_FROM_INDEX(sq); + const Sq8 sq = SQ_H8; + const Bb64 all_occ = 0ULL; + const Bb64 own_occ = MASK_FROM_SQ(sq); /* * From D4: @@ -372,17 +373,17 @@ static void test_bishops(void) * SE: E3, F2, G1 * Bishop at F4 is irrelevant; it does not sit on a diagonal from D4. */ - bitboard expected = 0ULL; - expected = SQ_MASK_G7 | SQ_MASK_F6 | SQ_MASK_E5 | SQ_MASK_D4 | - SQ_MASK_C3 | SQ_MASK_B2 | SQ_MASK_A1; + Bb64 expected = 0ULL; + expected = SQMASK_G7 | SQMASK_F6 | SQMASK_E5 | SQMASK_D4 | + SQMASK_C3 | SQMASK_B2 | SQMASK_A1; print_bishop_test("Bishop Test 6: H8, no occupancy", sq, all_occ, own_occ); - const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ; + const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ; if (attacks != expected) { bitboard_print(attacks, stderr); } - assert(attacks == expected); + test_expect(attacks == expected); } printf("\nAll bishop_attacks_from_index tests passed.\n"); @@ -403,10 +404,13 @@ void* set_after_timeout(void* x) int main() { + bool const print_threats = true; + printf("sizeof pos: %zu\n", sizeof (struct pos)); printf("sizeof tt: %zu\n", sizeof (struct tt)); + printf("sizeof board: %zu\n", sizeof (struct board)); -#if 1 +#if 0 test_rooks(); test_bishops(); #endif @@ -426,35 +430,38 @@ int main() //board_load_fen_unsafe(b, "1n1q1rk1/r1p2P2/1p1pp2p/pB2P3/2P5/PPN5/6b1/3QK1NR b - - 0 1"); //board_load_fen_unsafe(b, "8/8/2kr4/6R1/4K3/6P1/8/8 b - - 0 1"); - //board_load_fen_unsafe(b, "5R2/7k/P7/6pp/3B4/1PPK2bP/4r3/8 b - - 3 57"); + //board_load_fen_unsafe(b, "8/8/5R2/8/2K3PP/1B2k3/8/8 b - - 1 4"); //board_print_fen(b->pos, stdout); - board_print(&b->pos, NULL, stdout); + board_print(&b->pos, NULL, stdout, print_threats); struct move moves[MOVE_MAX]; + size_t move_count; for (int turn = 0; turn < 200; ++turn) { - size_t move_count = 0; - all_moves(&b->pos, b->pos.player, &move_count, moves); + move_count = 0; + all_pseudolegal_moves(&b->pos, MG_ALL, b->pos.moving_side, &move_count, moves); if (move_count == 0) { - printf("no moves for %s, aborting\n", player_str[b->pos.player]); - board_print_threats(&b->pos, stdout, NULL); - b->pos.player = opposite_player(b->pos.player); - board_print_threats(&b->pos, stdout, NULL); + printf("no moves for %s, aborting\n", side_str[b->pos.moving_side]); + board_print(&b->pos, NULL, stdout, print_threats); + b->pos.moving_side = other_side(b->pos.moving_side); + board_print(&b->pos, NULL, stdout, print_threats); break; } pthread_t timer; atomic_bool searching; atomic_init(&searching, true); +#if 1 struct timeout_params timer_params = { .x = &searching, .v = false, - .us = 3*1000*1000, + .us = 2*1000*1000, }; pthread_create(&timer, NULL, set_after_timeout, &timer_params); +#endif - struct search_result sr = search(b, b->pos.player, 5, &searching); + struct search_result sr = search(b, b->pos.moving_side, 25, &searching); struct move move = sr.move; double const score = sr.score; @@ -463,23 +470,23 @@ int main() " .from = %s, (%s)\n" " .to = %s,\n" " .score = %lf,\n" - " .mask = ", + " .mask = " + "", turn, - square_index_display[move.from], + sq8_display[move.from], piece_str[b->mailbox[move.from]], - square_index_display[move.to], + sq8_display[move.to], score ); - if (move.attr & MATTR_CAPTURE) printf("MATTR_CAPTURE "); - if (move.attr & MATTR_PROMOTE) printf("MATTR_PROMOTE "); printf("\n}\n"); - enum move_result const r = board_move_2(b, move); + enum move_result const r = board_move(b, move); #if 1 board_print_fen(&b->pos, stdout); tt_print_stats(&b->tt, stdout); - board_print(&b->pos, &move, stdout); + board_print(&b->pos, &move, stdout, print_threats); + fprintf(stderr, "board hist len: %zu\n", b->hist.length); #endif if (r == MR_STALEMATE) { @@ -487,18 +494,18 @@ int main() break; } - if (b->pos.pieces[PLAYER_WHITE][PIECE_KING] == 0ULL) { + if (b->pos.pieces[SIDE_WHITE][PIECE_KING] == 0ULL) { printf("white king gone!!\n"); exit(1); } - if (b->pos.pieces[PLAYER_BLACK][PIECE_KING] == 0ULL) { + if (b->pos.pieces[SIDE_BLACK][PIECE_KING] == 0ULL) { printf("black king gone!!\n"); exit(1); } //usleep(1000000); } - + return EXIT_SUCCESS; }