From 2f65828ae24bf13f6a43713298a60ba6c63ce1db Mon Sep 17 00:00:00 2001 From: Ole Morud Date: Mon, 15 Dec 2025 22:51:10 +0100 Subject: [PATCH] almost working now --- Makefile | 23 +- base.h | 1772 +++++++++++++++++++++++++++++++++++++++++------------ codegen.c | 147 +---- tests.c | 74 ++- 4 files changed, 1487 insertions(+), 529 deletions(-) diff --git a/Makefile b/Makefile index 5f19f25..0e644ef 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,27 @@ -CFLAGS := -std=c23 -g +BUILD ?= debug +CC := clang + +CFLAGS.gcc.release := -O3 +CFLAGS.gcc.debug := -ggdb -O0 -fsanitize=address +CFLAGS.gcc := -std=c23 -Wall -Wextra -Wconversion -Wno-unused-function + +CFLAGS.clang.release := -O3 +CFLAGS.clang.debug := -ggdb -O0 +CFLAGS.clang := -std=c23 -Wall -Wextra -Wconversion -Wno-unused-function -Wimplicit-int-conversion + +CFLAGS := $(CFLAGS.$(CC)) $(CFLAGS.$(CC).$(BUILD)) all: tests codegen: codegen.c - $(CC) -o $@ $(CFLAGS) -DCODEGEN $^ + $(CC) -o $@ $(CFLAGS) $^ mbb_rook.h: codegen - #./codegen + ./codegen mbb_bishop.h: codegen - #./codegen + ./codegen -tests: tests.c | mbb_rook.h mbb_bishop.h base.h - $(CC) -o $@ $(CFLAGS) $^ +tests: tests.c mbb_rook.h mbb_bishop.h base.h + $(CC) -o $@ $(CFLAGS) tests.c diff --git a/base.h b/base.h index e8f1c40..38c50ce 100644 --- a/base.h +++ b/base.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include #include @@ -7,6 +9,9 @@ #include #include +#include + + /* BIT MANIPULATION * ================ */ @@ -19,7 +24,24 @@ static inline uint64_t popcount(uint64_t n) static inline uint64_t ctz(uint64_t n) { assert(n != 0); - return __builtin_ctzll(n); + return (uint64_t)__builtin_ctzll(n); +} + +/**/ +static uint64_t rand64() { + union { + uint64_t v; + uint16_t vs[4]; + } x = { + .vs = { + (uint16_t)rand(), + (uint16_t)rand(), + (uint16_t)rand(), + (uint16_t)rand(), + } + }; + + return x.v; } /* BITBOARD AND INDEX DATATYPES AND HELPERS @@ -51,22 +73,22 @@ ULL #define BITBOARD_FMT PRIu64 #define BITBOARD_FMT_X PRIx64 -typedef uint64_t index; +typedef uint8_t index; #define INDEX(n) n##ULL -#define INDEX_FMT PRIu64 +#define INDEX_FMT PRIu8 -#define RANK_SHIFT_UP(b, n) ((b) << (index)(n)*8ULL) -#define RANK_SHIFT_DOWN(b, n) ((b) >> (index)(n)*8ULL) -#define FILE_SHIFT_LEFT(b, n) ((b) >> (index)(n)*1ULL) -#define FILE_SHIFT_RIGHT(b, n) ((b) << (index)(n)*1ULL) +#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 FILE_MASK(n) FILE_SHIFT_RIGHT((index)0x0101010101010101ULL, n) -#define RANK_MASK(n) RANK_SHIFT_UP((index)0x00000000000000FFULL, n) +#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)(8ULL*(index)(rank) + (index)(file))) -#define SQ_MASK_FROM_RF(rank, file) ((index)1ULL << INDEX_FROM_RF(rank,file)) +#define INDEX_FROM_RF(rank, file) ((index)(8U*(index)(rank) + (index)(file))) -#define SQ_MASK_FROM_INDEX(idx) (1ULL<<(idx)) +#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, @@ -172,7 +194,7 @@ enum : bitboard { #undef X }; -enum square_index : bitboard { +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, @@ -183,14 +205,14 @@ enum square_index : bitboard { #undef X }; -const char* square_index_display[SQ_INDEX_COUNT] = { +static char* const square_index_display[SQ_INDEX_COUNT] = { #define X(file, rank) \ [SQ_INDEX_##file##rank] = STR(file##rank), SQUARES_LIST #undef X }; -const char* square_index_str[SQ_INDEX_COUNT] = { +static char* const square_index_str[SQ_INDEX_COUNT] = { #define X(file, rank) \ [SQ_INDEX_##file##rank] = STR(SQ_INDEX_##file##rank), SQUARES_LIST @@ -283,6 +305,95 @@ enum : bitboard { #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 : size_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 */ @@ -297,14 +408,14 @@ struct magic { /* BITBOARD PRIMITIVES * ======================== */ -static index bitboard_lsb(bitboard p) +static enum square_index bitboard_lsb(bitboard p) { - return __builtin_ffsll(p) - 1; + return (enum square_index)__builtin_ffsll((int64_t)p) - 1; } -static index bitboard_pop_lsb(bitboard* p) +static enum square_index bitboard_pop_lsb(bitboard* p) { - const index lsb = bitboard_lsb(*p); + index const lsb = bitboard_lsb(*p); *p &= ~(1ULL<<(lsb)); return lsb; } @@ -314,12 +425,12 @@ static uint64_t bitboard_popcount(bitboard b) return (uint64_t)__builtin_popcountll(b); } -static inline index index_to_file(enum square_index p) +static inline enum file_index index_to_file(enum square_index p) { return p % 8ULL; } -static inline index index_to_rank(enum square_index p) +static inline enum rank_index index_to_rank(enum square_index p) { return p / 8ULL; } @@ -333,14 +444,14 @@ static bitboard cardinals_from_index(enum square_index p) static bitboard diagonals_from_index(enum square_index sq) { - const enum rank_index rank = index_to_rank(sq); - const enum file_index file = index_to_file(sq); + enum rank_index const rank = index_to_rank(sq); + enum file_index const file = index_to_file(sq); bitboard mask = 0ULL; /* Ensure signed-underflow detection rules match your style */ - _Static_assert(((enum rank_index)0) - ((enum rank_index)1) > ((enum rank_index)0)); - _Static_assert(((enum file_index)0) - ((enum file_index)1) > ((enum file_index)0)); + _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; @@ -384,17 +495,17 @@ static bitboard diagonals_from_index(enum square_index sq) * ================== * * All piece attack functions rely on the caller masking their own pieces. - * e.g. `knight_attacks(knights) & ~own_occupied` + * e.g. `knight_attacks(knights) & ~our_occupied` * */ static bitboard knight_attacks(bitboard p) { - const bitboard l1 = FILE_SHIFT_RIGHT_1(p & ~FILE_MASK_H); - const bitboard l2 = FILE_SHIFT_RIGHT_2(p & ~(FILE_MASK_G | FILE_MASK_H)); - const bitboard r1 = FILE_SHIFT_LEFT_1 (p & ~FILE_MASK_A); - const bitboard r2 = FILE_SHIFT_LEFT_2 (p & ~(FILE_MASK_A | FILE_MASK_B)); - const bitboard h1 = l1 | r1; - const bitboard h2 = l2 | r2; + 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) @@ -405,21 +516,61 @@ static bitboard knight_attacks(bitboard p) static bitboard rook_attacks_from_index(enum square_index sq, bitboard occ) { #ifdef CODEGEN - return 0ULL; + 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" + /* defines + - `mbb_rook[SQ_INDEX_COUNT]` + - `bitboard rook_attacks[SQ_INDEX_COUNT][1<<12ULL] */ + #include "mbb_rook.h" - const struct magic m = mbb_rook[sq]; + 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 @@ -430,7 +581,7 @@ static bitboard rook_attacks(bitboard p, bitboard occ) { bitboard b = 0ULL; while (p) { - const index lsb = bitboard_pop_lsb(&p); + index const lsb = bitboard_pop_lsb(&p); b |= rook_attacks_from_index(lsb, occ); } return b; @@ -439,7 +590,56 @@ static bitboard rook_attacks(bitboard p, bitboard occ) static bitboard bishop_attacks_from_index(enum square_index sq, bitboard occ) { #ifdef CODEGEN - return 0ULL; + 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" @@ -449,7 +649,7 @@ static bitboard bishop_attacks_from_index(enum square_index sq, bitboard occ) - `mbb_bishop[SQ_INDEX_COUNT]` - `bitboard bishop_attacks[SQ_INDEX_COUNT][1<<9ULL] */ #include "mbb_bishop.h" - const struct magic m = mbb_bishop[sq]; + struct magic const m = mbb_bishop[sq]; occ &= m.mask; occ *= m.magic; occ >>= (64ULL-9ULL); @@ -462,7 +662,7 @@ static bitboard bishop_attacks(bitboard p, bitboard occ) { bitboard b = 0ULL; while (p) { - const index lsb = bitboard_pop_lsb(&p); + index const lsb = bitboard_pop_lsb(&p); b |= bishop_attacks_from_index(lsb, occ); } return b; @@ -477,7 +677,7 @@ static bitboard queen_attacks(bitboard p, bitboard occ) { bitboard b = 0ULL; while (p) { - const index lsb = bitboard_pop_lsb(&p); + index const lsb = bitboard_pop_lsb(&p); b |= queen_attacks_from_index(lsb, occ); } return b; @@ -489,9 +689,9 @@ static inline bitboard pawn_moves_white(bitboard p, bitboard empty) - empty = ~(black_occupied | white_occupied); */ - const bitboard single_push = RANK_SHIFT_UP_1(p) & empty; + bitboard const single_push = RANK_SHIFT_UP_1(p) & empty; - const bitboard double_push = + bitboard const double_push = RANK_SHIFT_UP_1(single_push & RANK_MASK_3) & empty; return (single_push | double_push); @@ -499,9 +699,9 @@ static inline bitboard pawn_moves_white(bitboard p, bitboard empty) static inline bitboard pawn_attacks_white(bitboard p) { - const bitboard up = RANK_SHIFT_UP_1(p); - const bitboard captures_right = FILE_SHIFT_RIGHT_1(up); - const bitboard captures_left = FILE_SHIFT_LEFT_1(up); + 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); } @@ -512,9 +712,9 @@ static inline bitboard pawn_moves_black(bitboard p, bitboard empty) - empty = ~(black_occupied | white_occupied); */ - const bitboard single_push = RANK_SHIFT_DOWN_1(p) & empty; + bitboard const single_push = RANK_SHIFT_DOWN_1(p) & empty; - const bitboard double_push = + bitboard const double_push = RANK_SHIFT_DOWN_1(single_push & RANK_MASK_6) & empty; return (single_push | double_push); @@ -522,358 +722,1174 @@ static inline bitboard pawn_moves_black(bitboard p, bitboard empty) static inline bitboard pawn_attacks_black(bitboard p) { - const bitboard down = RANK_SHIFT_DOWN_1(p); - const bitboard captures_right = FILE_SHIFT_RIGHT_1(down); - const bitboard captures_left = FILE_SHIFT_LEFT_1(down); + 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); } -static bitboard king_attacks(enum square_index sq) +/* temporary solution, annoying branch */ +static inline bitboard pawn_moves_dispatch(bitboard p, bitboard empty, enum player attacker) { - /* candidate for improvement */ - const bitboard n = RANK_SHIFT_UP_1(sq); - const bitboard s = RANK_SHIFT_DOWN_1(sq); - const bitboard w = FILE_SHIFT_LEFT_1(sq); - const bitboard e = FILE_SHIFT_RIGHT_1(sq); + if (attacker == PLAYER_WHITE) { + return pawn_moves_white(p, empty); + } else { + return pawn_moves_black(p, empty); + } +} - const bitboard nw = FILE_SHIFT_LEFT_1(n); - const bitboard ne = FILE_SHIFT_RIGHT_1(n); - const bitboard sw = FILE_SHIFT_RIGHT_1(s); - const bitboard se = FILE_SHIFT_LEFT_1(s); +/* 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: { + assert(!"unreachable"); + __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 player : index { - PLAYER_BEGIN, - PLAYER_WHITE = PLAYER_BEGIN, - PLAYER_BLACK, - PLAYER_COUNT, +enum castle_direction { + CASTLE_BEGIN, + CASTLE_KINGSIDE = CASTLE_BEGIN, + CASTLE_QUEENSIDE, + CASTLE_COUNT, }; -static inline enum player opposite_player(enum player p) -{ - return (p == PLAYER_WHITE ? PLAYER_BLACK : PLAYER_WHITE); -} -const char* player_str[PLAYER_COUNT] = { - [PLAYER_WHITE] = "PLAYER_WHITE", - [PLAYER_BLACK] = "PLAYER_BLACK", +/* transposition table */ +struct move { + index from : 8; + index to : 8; + unsigned int attr : 8; }; -#define PIECE_BEGIN PIECE_PAWN -/* 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, 'Q', 0x2657, 'q', 0x265D) \ - X(PIECE_ROOK, 5.0, 'R', 0x2656, 'q', 0x265C) \ - X(PIECE_KNIGHT, 3.0, 'N', 0x2658, 'n', 0x265E) - -enum piece : size_t { -#define X(e, v, wc, wu, bc, bu) e, - PIECES - PIECE_COUNT, -#undef X +struct search_option { + double score; + struct move move; + bool set; }; -double piece_value[PIECE_COUNT] = { -#define X(e, v, wc, wu, bc, bu) [e] = v, - PIECES -#undef X +#define TT_ADDRESS_BITS 12 +#define TT_SIZE (1ULL<piece lookups, not central to logic. - * Does not store which player owns the square piece */ + struct tt tt; + + /* used for repeated board state detection only */ + struct history { + struct pos items[4096]; + size_t length; + } hist; + + /* Used only for square->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 BOARD_INITIAL (struct board) { \ - .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, \ - } \ - }, \ - .ep_targets = 0ULL, \ - .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, \ - }, \ - .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, \ - }, \ +#define BOARD_INITIAL (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}, \ } -double board_score_heuristic(const struct board* board, enum player player) +static void board_print_fen(struct board const* b, 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 = b->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_char[player][piece]; + } + } + } + } + } + + for (enum rank_index i = RANK_INDEX_8; i < RANK_INDEX_COUNT; --i) { + int consequtive_empty = 0; + for (enum file_index j = FILE_INDEX_A; j < FILE_INDEX_COUNT; ++j) { + if (buf[i][j]) { + if (consequtive_empty) { + fprintf(out, "%d", consequtive_empty); + } + fprintf(out, "%lc", buf[i][j]); + } else { + consequtive_empty += 1; + } + } + if (consequtive_empty) { + fprintf(out, "%d", consequtive_empty); + } + if (i > 0) + fprintf(out, "/"); + } + + fprintf(out, " %c ", b->pos.player == PLAYER_WHITE ? 'w' : 'b'); + + bool any_castle = false; + if (!b->pos.castling_illegal[PLAYER_WHITE][CASTLE_KINGSIDE]) { + fprintf(out, "K"); + any_castle = true; + } + if (!b->pos.castling_illegal[PLAYER_WHITE][CASTLE_QUEENSIDE]) { + fprintf(out, "Q"); + any_castle = true; + } + if (!b->pos.castling_illegal[PLAYER_BLACK][CASTLE_KINGSIDE]) { + fprintf(out, "k"); + any_castle = true; + } + if (!b->pos.castling_illegal[PLAYER_BLACK][CASTLE_QUEENSIDE]) { + fprintf(out, "q"); + any_castle = true; + } + if (!any_castle) { + fprintf(out, "-"); + } + + if (b->pos.ep_targets) { + /* should be ep target square in algebraic notation */ + fprintf(stderr, "not implemented: fen with en passent squares\n"); + abort(); + } else { + fprintf(out, " -"); + } + + fprintf(out, " %d", b->pos.halfmoves); + fprintf(out, " %d", b->pos.fullmoves); + + fprintf(out, "\n"); +} + +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++]) + + 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 (isdigit(ch)) { + 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 { + abort(); + } + } + + { /* castling */ + char ch = BUF_GETCHAR(fen); + if (ch != ' ') { + abort(); + } + do { + ch = BUF_GETCHAR(fen); + switch (ch) { + case 'K': b->pos.castling_illegal[PLAYER_WHITE][CASTLE_KINGSIDE] = true; break; + case 'Q': b->pos.castling_illegal[PLAYER_WHITE][CASTLE_QUEENSIDE] = true; break; + case 'k': b->pos.castling_illegal[PLAYER_BLACK][CASTLE_KINGSIDE] = true; break; + case 'q': b->pos.castling_illegal[PLAYER_BLACK][CASTLE_QUEENSIDE] = true; break; + case ' ': break; + case '-': { + 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; + } break; + default: { + fprintf(stderr, "unexpected char '%c'\n", ch); + abort(); + } break; + } + } while (ch != ' '); + } + + { /* en passent */ + char const ch = BUF_GETCHAR(fen); + if (ch != '-') { + fprintf(stderr, "ep targets in fen not implemented yet\n"); + abort(); + } + } + + { /* 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; +} + +static bitboard attacks_to_king(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 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))) + & ~pretend_occupied; +} + +enum { + MATTR_CAPTURE = 1<<0, + MATTR_PROMOTE = 1<<1, + /*MATTR_CHECK = 1<<2,*/ + MATTR_CASTLE_KINGSIDE = 1<<3, + MATTR_CASTLE_QUEENSIDE = 1<<4, +}; + +_Static_assert(sizeof(struct move) == 4, + "this static assert is here to check when sizeof(move) changes"); + +#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, uint16_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); + + uint16_t attr = 0ULL; +#define MASK_IF32(x) ((~(uint32_t)0U) + (uint32_t)!(x)) + attr |= MATTR_CAPTURE & MASK_IF32(tomask & their_occ); + attr |= MATTR_PROMOTE & MASK_IF32((piece == PIECE_PAWN) && (tomask & finishline)); + attr |= add_attr; +#undef MASK_IF32 + + return (struct move){.from = from, .to = to, .attr = attr}; +} + +#define MOVE_MAX 256 + +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]; + enum square_index const myking_index = bitboard_lsb(myking); + bitboard const attackers = attacks_to(pos, myking, 0ULL, 0ULL) & ~pos->occupied[us]; + + assert(myking); + + 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) { + // fprintf(stderr, "more than 2 king attackers\n"); + // 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); + } + } +} + +static void init_zobrist(uint64_t zobrist[SQ_INDEX_COUNT][PLAYER_COUNT][PIECE_COUNT]) +{ + 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[sq][pl][piece] = rand64(); + } + } + } +} + +static inline uint64_t zobrist_at(enum square_index sq, enum player pl, enum piece pc) +{ +#if ! __has_include("zobrist_table.h") + /* zobrist hashing: https://en.wikipedia.org/wiki/Zobrist_hashing */ + static uint64_t zobrist[SQ_INDEX_COUNT][PLAYER_COUNT][PIECE_COUNT]; + static bool init; + if (!init) { + init_zobrist(zobrist); + init = true; + } +#else +#include "zobrist_table.h" +#endif /* __has_include("zobrist_table.h") */ + return zobrist[sq][pl][pc]; +} + +/* + * + * + * */ +static inline uint64_t tt_hash_update(uint64_t hash, + enum square_index sq, + enum player us, + enum piece piece) +{ + assert(zobrist_at(sq, us, piece) != 0ULL); + return hash ^ zobrist_at(sq, us, piece); +} + +static inline struct search_option tt_get(struct tt const* tt, uint64_t hash) +{ + /* shitty bloom-filter-eque check */ + uint64_t const addr = hash % TT_SIZE; + return tt->entries[addr]; +} + +static inline void tt_insert(struct tt* tt, uint64_t hash, struct search_option so) +{ + uint64_t const addr = hash % TT_SIZE; + so.set = true; + tt->entries[addr] = so; +} + +/* does not check validity */ +enum move_result { + MR_NORMAL, + MR_STALEMATE, + MR_CHECKMATE, +}; +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); + bool const w = (us == PLAYER_WHITE); + + bitboard const from_mask = SQ_MASK_FROM_INDEX(move.from); + bitboard const to_mask = SQ_MASK_FROM_INDEX(move.to); + + bitboard const rook_home_q_us = w ? SQ_MASK_A1 : SQ_MASK_A8; + bitboard const rook_home_k_us = w ? SQ_MASK_H1 : SQ_MASK_H8; + + bitboard const rook_home_q_them = w ? SQ_MASK_A8 : SQ_MASK_A1; + bitboard const rook_home_k_them = w ? SQ_MASK_H8 : SQ_MASK_H1; + + enum piece const from_piece = mailbox[move.from]; + + bool const is_castle_ks = + (from_piece == PIECE_KING && ((move.from == SQ_INDEX_E1 && move.to == SQ_INDEX_G1) || + (move.from == SQ_INDEX_E8 && move.to == SQ_INDEX_G8))); + bool const is_castle_qs = + (from_piece == PIECE_KING && ((move.from == SQ_INDEX_E1 && move.to == SQ_INDEX_C1) || + (move.from == SQ_INDEX_E8 && move.to == SQ_INDEX_C8))); + bool const is_castle = is_castle_ks || is_castle_qs; + + bool const is_capture = !!(move.to & pos->occupied[them]); + + bitboard const ep_targets = pos->ep_targets; + pos->ep_targets = 0ULL; + + /* our castling rights from moving king/rook */ + if (from_piece == PIECE_KING) { + pos->castling_illegal[us][CASTLE_KINGSIDE] = true; + pos->castling_illegal[us][CASTLE_QUEENSIDE] = true; + } else if (from_mask == rook_home_q_us) { + pos->castling_illegal[us][CASTLE_QUEENSIDE] = true; + } else if (from_mask == rook_home_k_us) { + pos->castling_illegal[us][CASTLE_KINGSIDE] = true; + } + + /* their castling rights: any move landing on their rook home squares kills it */ + if (to_mask == rook_home_q_them) { + pos->castling_illegal[them][CASTLE_QUEENSIDE] = true; + } else if (to_mask == rook_home_k_them) { + pos->castling_illegal[them][CASTLE_KINGSIDE] = true; + } + + pos->pieces[us][from_piece] &= ~from_mask; + pos->hash = tt_hash_update(pos->hash, move.from, us, from_piece); + + /* handle captures without reading mailbox[to] (since empty is undefined) */ + bool const is_pawn = (from_piece == PIECE_PAWN); + bool const is_ep = is_pawn && ((to_mask & ep_targets) != 0ULL); + + if (is_capture) { + if (is_ep) { + /* en passant captured pawn is behind the destination square */ + bitboard const ep_capture_mask = + w ? RANK_SHIFT_DOWN_1(to_mask) : RANK_SHIFT_UP_1(to_mask); + pos->pieces[them][PIECE_PAWN] &= ~ep_capture_mask; + pos->hash = tt_hash_update(pos->hash, bitboard_lsb(ep_capture_mask), them, PIECE_PAWN); + } else { + /* normal capture: clear whatever enemy piece sits on to_mask */ + for (enum piece p = PIECE_BEGIN; p < PIECE_COUNT; ++p) { + if (p & to_mask) { + pos->pieces[them][p] &= ~to_mask; + pos->hash = tt_hash_update(pos->hash, move.to, them, p); + } + } + } + } + + if (is_castle) { + bitboard const king_home = w ? SQ_MASK_E1 : SQ_MASK_E8; + bitboard const king_to = + is_castle_ks ? FILE_SHIFT_RIGHT_2(king_home) : FILE_SHIFT_LEFT_2(king_home); + + bitboard const rook_from = is_castle_ks ? rook_home_k_us : rook_home_q_us; + bitboard const rook_to = + is_castle_ks ? FILE_SHIFT_LEFT_2(rook_home_k_us) : FILE_SHIFT_RIGHT_3(rook_home_q_us); + + pos->pieces[us][PIECE_KING] |= king_to; + mailbox[move.to] = PIECE_KING; + pos->hash = tt_hash_update(pos->hash, bitboard_lsb(king_to), us, PIECE_KING); + + pos->pieces[us][PIECE_ROOK] &= ~rook_from; + pos->hash = tt_hash_update(pos->hash, bitboard_lsb(rook_from), us, PIECE_ROOK); + + const enum square_index rook_to_index = bitboard_lsb(rook_to); + pos->pieces[us][PIECE_ROOK] |= rook_to; + mailbox[rook_to_index] = PIECE_ROOK; + pos->hash = tt_hash_update(pos->hash, rook_to_index, us, PIECE_ROOK); + } else if (is_pawn) { + /* promotion must be based on destination square */ + bitboard const finishline = w ? RANK_MASK_8 : RANK_MASK_1; + + if (to_mask & finishline) { + pos->pieces[us][PIECE_QUEEN] |= to_mask; + mailbox[move.to] = PIECE_QUEEN; + pos->hash = tt_hash_update(pos->hash, move.to, us, PIECE_QUEEN); + } else { + pos->pieces[us][PIECE_PAWN] |= to_mask; + mailbox[move.to] = PIECE_PAWN; + pos->hash = tt_hash_update(pos->hash, move.to, us, PIECE_PAWN); + } + + /* double push -> set ep target */ + bitboard const start = w ? RANK_MASK_2 : RANK_MASK_7; + bitboard const doublepush = w ? RANK_MASK_4 : RANK_MASK_5; + if ((from_mask & start) && (to_mask & doublepush)) { + pos->ep_targets = w ? RANK_SHIFT_UP_1(from_mask) : RANK_SHIFT_DOWN_1(from_mask); + } + } else { + pos->pieces[us][from_piece] |= to_mask; + mailbox[move.to] = from_piece; + pos->hash = tt_hash_update(pos->hash, move.to, us, from_piece); + } + + /* rebuild occupancy */ + pos->occupied[PLAYER_WHITE] = 0ULL; + pos->occupied[PLAYER_BLACK] = 0ULL; + for (enum piece p = PIECE_BEGIN; p < PIECE_COUNT; ++p) { + pos->occupied[PLAYER_WHITE] |= pos->pieces[PLAYER_WHITE][p]; + pos->occupied[PLAYER_BLACK] |= pos->pieces[PLAYER_BLACK][p]; + } + + pos->player = them; + + /* TODO: this stalemates on just one repetition, fine for the engine but not the player */ + assert(hist->length < 4096); + for (size_t i = 0; i < hist->length; ++i) { + _Static_assert(sizeof *pos == sizeof hist->items[i]); + if (memcmp(&hist->items[i], pos, sizeof *pos) == 0) { + return MR_STALEMATE; + } + } + + if (is_capture) { + hist->length = 0; + pos->halfmoves = 0; + } + hist->items[hist->length++] = *pos; + + pos->halfmoves += 1; + + if (pos->halfmoves > 50) { + return MR_STALEMATE; + } + + pos->fullmoves += (pos->player == PLAYER_BLACK); + + 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); +} + +/* + * Placeholder heuristic + * + * */ +static double board_score_heuristic(struct pos const* pos) { double score = 0.0; - enum player opp = opposite_player(player); + enum player const player = pos->player; + enum player const opp = opposite_player(player); - for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) { - score += (double)bitboard_popcount(board->pieces[player][piece]) * piece_value[piece]; - } - for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) { - score -= (double)bitboard_popcount(board->pieces[opp][piece]) * piece_value[piece]; + #define BOARD_CENTER ((FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F) \ + & ~(RANK_MASK_1 | RANK_MASK_2 | RANK_MASK_7 | RANK_MASK_8)) + static const bitboard positional_bonus[PIECE_COUNT] = { + [PIECE_PAWN] = BOARD_CENTER, + [PIECE_KNIGHT] = BOARD_CENTER, + [PIECE_QUEEN] = RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6, + }; + static const double positional_bonus_factor[PIECE_COUNT] = { + [PIECE_PAWN] = 0.20, + [PIECE_KNIGHT] = 0.20, + [PIECE_QUEEN] = -0.30, + }; + + bitboard const our_threats = all_threats_from_player(pos, player); + bitboard const their_threats = all_threats_from_player(pos, opp); + + for (enum piece p = PIECE_BEGIN; p < PIECE_COUNT; ++p) { + score += 0.05 * (double)bitboard_popcount(our_threats & pos->pieces[opp][p]) * piece_value[p]; + score += 1.00 * (double)bitboard_popcount(pos->pieces[player][p]) * piece_value[p]; + score += + positional_bonus_factor[p] * (double)bitboard_popcount(pos->pieces[player][p] & positional_bonus[p]); + + score -= 0.05 * (double)bitboard_popcount(their_threats & pos->pieces[player][p]) * piece_value[p]; + score -= (double)bitboard_popcount(pos->pieces[opp][p]) * piece_value[p]; + score -= + positional_bonus_factor[p] * (double)bitboard_popcount(pos->pieces[opp][p] & positional_bonus[p]); } return score; } -struct move { - index from; - index to; -}; - -struct move move(index from, index to) +struct search_option alphabeta_search(const struct pos* pos, + struct history* hist, + struct tt* tt, + enum piece mailbox[restrict static SQ_INDEX_COUNT], + enum player us, + int depth, + double alpha, + double beta) { - return (struct move){ - .from = from, - .to = to - }; -} - -#define MOVE_MAX 32 - -static size_t all_moves_from(const struct board* board, - enum player player, - enum square_index from, - struct move moves[static MOVE_MAX]) -{ - const enum piece piece = board->mailbox[from]; - - const enum player opp = opposite_player(player); - - const bitboard own_occ = board->occupied[player]; - const bitboard opp_occ = board->occupied[opp]; - const bitboard occ = own_occ | opp_occ; - - const bitboard sqmask = SQ_MASK_FROM_INDEX(from); - - bitboard x = 0ULL; - - switch (piece) { - case PIECE_PAWN: { - if (player == PLAYER_WHITE) { - x |= pawn_moves_white(sqmask, ~occ); - x |= pawn_attacks_white(sqmask) & (board->ep_targets | opp_occ); - } else { - x |= pawn_moves_black(sqmask, ~occ); - x |= pawn_attacks_black(sqmask) & (board->ep_targets | opp_occ); - } - } break; - - case PIECE_KNIGHT: { - x |= knight_attacks(sqmask); - } break; - - case PIECE_BISHOP: { - x |= bishop_attacks_from_index(from, occ); - } break; - - case PIECE_ROOK: { - x |= rook_attacks_from_index(from, occ); - } break; - - case PIECE_QUEEN: { - x |= queen_attacks_from_index(from, occ); - } break; - - case PIECE_KING: { - x |= king_attacks(sqmask); - } break; + if (depth <= 0) { + return (struct search_option){.score = board_score_heuristic(pos)}; } - x &= ~board->occupied[player]; - - size_t move_count = 0; - while (x) { - const index to = bitboard_pop_lsb(&x); - moves[move_count++] = (struct move){.from = from, .to = to}; + struct move moves[MOVE_MAX]; + size_t move_count = 0ULL; + all_moves(pos, us, &move_count, moves); + if (move_count == 0) { + return (struct search_option){.score = -999*depth}; } - return move_count; -} -static size_t all_player_pieces(struct board* board, - enum player player, - enum square_index out[static SQ_INDEX_COUNT]) -{ - size_t count = 0; + double value = alpha; - fprintf(stderr, "%s\n", player_str[player]); - for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) { - bitboard x = board->pieces[player][piece]; - fprintf(stderr, "%s:\t%016"BITBOARD_FMT_X"\n", piece_str[piece], x); - while (x) { - out[count++] = (enum square_index)bitboard_pop_lsb(&x); + for (size_t i = 0; i < move_count; ++i) { + struct pos poscpy = *pos; + enum piece mailbox_cpy[SQ_INDEX_COUNT]; + memcpy(mailbox_cpy, mailbox, sizeof mailbox_cpy); + size_t old_hist_length = hist->length; + enum move_result const r = board_move(&poscpy, hist, mailbox_cpy, moves[i]); + + double x; + + if (r == MR_STALEMATE) { + x = 0.0; + } else { + x = -alphabeta_search(&poscpy, + hist, + tt, + mailbox_cpy, + opposite_player(us), + depth-1, + -beta, + -value).score; + } + + hist->length = old_hist_length; + + if (x > value) { + value = x; + } + if (value >= beta) { + return (struct search_option) {.score = value, .move = moves[i]}; } } - return count; + return (struct search_option){.score = value, .move = moves[0]}; } -/* caller is resposible for checking validity */ -static void board_move_piece(struct board* board, - enum player player, - struct move move) +struct move search(struct board* b, enum player us, int depth) { - const bitboard from_mask = SQ_MASK_FROM_INDEX(move.from); - const bitboard to_mask = SQ_MASK_FROM_INDEX(move.to); + struct move moves[MOVE_MAX]; + size_t move_count = 0ULL; + all_moves(&b->pos, us, &move_count, moves); + if (move_count == 0) { + abort(); + } - const enum player opp = opposite_player(player); - const enum piece from_piece = board->mailbox[move.from]; - const enum piece to_piece = board->mailbox[move.to]; + struct search_option best = { + .score = -INFINITY, + .move = moves[0], + }; - board->pieces[player][from_piece] &= ~from_mask; - board->pieces[player][from_piece] |= to_mask; - board->pieces[opp][to_piece] &= ~to_mask; + memset(&b->tt, 0, sizeof b->tt); - board->occupied[player] &= ~from_mask; - board->occupied[player] |= to_mask; - board->occupied[opp] &= ~to_mask; + for (size_t i = 0; i < move_count; ++i) { + struct pos poscpy = b->pos; + enum piece mailbox_cpy[SQ_INDEX_COUNT]; + memcpy(mailbox_cpy, b->mailbox, sizeof mailbox_cpy); + size_t old_hist_length = b->hist.length; - /* mailboxes are solely used for fast lookups of pieces in already valid - * moves, it's safe to ignore the from index */ - board->mailbox[move.to] = from_piece; + enum move_result const r = board_move(&poscpy, &b->hist, mailbox_cpy, moves[i]); + + double x; + if (r == MR_STALEMATE) { + x = 0.0; + } else { + x = -alphabeta_search(&poscpy, + &b->hist, + &b->tt, + mailbox_cpy, + opposite_player(us), + depth-1, + -INFINITY, + -best.score).score; + } + b->hist.length = old_hist_length; + + x += 1.0 * !!(moves[i].attr & MATTR_CASTLE_KINGSIDE); + x += 0.5 * !!(moves[i].attr & MATTR_CASTLE_QUEENSIDE); + x += -0.5 * !!(b->pos.pieces[us][PIECE_QUEEN] & SQ_MASK_FROM_INDEX(moves[i].from)); + + if (x > best.score) { + best.score = x; + best.move = moves[i]; + } + } + + printf("move %s->%s has score %lf\n", + square_index_str[best.move.from], + square_index_str[best.move.to], + best.score ); + + assert(best.move.from != best.move.to); + + return best.move; } -static bool board_is_check(struct board* b, enum player player) -{ - const bitboard kb = b->pieces[player][PIECE_KING]; - const enum player opp = opposite_player(player); - - const bitboard player_occ = bitboard->occupied[player]; - const bitboard opp_occ = bitboard->occupied[opp]; - const bitboard occ = player_occ | opp_occ; - const bitboard* p = b->pieces[opp]; - - bitboard threats = 0ULL; - if (player == PLAYER_WHITE) { - threats |= pawn_attacks_white(p[PIECE_PAWN]); - } else { - threats |= pawn_attacks_black(p[PIECE_PAWN]); - } - threats |= bishop_attacks(p[PIECE_BISHOP], occ); - threats |= rook_attacks(p[PIECE_ROOK], occ); - threats |= king_attacks(p[PIECE_KING]); - threats |= queen_attacks(p[PIECE_QUEEN], occ); - threats |= knight_attacks(p[PIECE_KNIGHT]); - threats &= ~player_occ; - - if (threats & kb) { - return true; - } - return false; -} - -void board_print(struct board* b, FILE* out) +static void board_print(const struct pos* pos, struct move* move, FILE* out) { int buf[8][8] = {0}; - for (size_t i = 0; i < 8; i++) { - for (size_t i = 0; i < 8; i++) { - buf[i][i] = 0; - } - } - - for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; player++) { - for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; piece++) { - bitboard x = b->pieces[player][piece]; + 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++) { + for (index j = 0; j < 8; ++j) { if (x & (1ULL<<(i*8+j))) { buf[i][j] = piece_unicode[player][piece]; } @@ -887,9 +1903,16 @@ void board_print(struct board* b, FILE* out) 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++) { - /* 45: magenta, 47: white */ - fprintf(out, "\033[%d;%dm", 30, (i+j) % 2 ? 45 : 47); + 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 { + /* 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 { @@ -903,15 +1926,15 @@ void board_print(struct board* b, FILE* out) fprintf(out, " A B C D E F G H \n"); } -void bitboard_print(bitboard b, FILE* out) +static void bitboard_print(bitboard b, FILE* out) { setlocale(LC_ALL, ""); fprintf(out, "\n"); 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++) { - const index n = i*8+j; + for (index j = 0; j < 8; ++j) { + index const n = INDEX_FROM_RF(i,j); int color; if ((b >> n) & 1) { /* 41: red */ @@ -929,46 +1952,19 @@ void bitboard_print(bitboard b, FILE* out) fprintf(out, " A B C D E F G H \n"); } -void board_print_threats(struct board* b, FILE* out, enum player player) +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}; - for (size_t i = 0; i < 8; i++) { - for (size_t i = 0; i < 8; i++) { - buf[i][i] = 0; - } - } + bitboard const threats = all_threats_from_player(pos, player); - enum player opp = opposite_player(player); - bitboard opp_occ = 0ULL, - own_occ = 0ULL; - for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) { - opp_occ |= b->pieces[opp][piece]; - own_occ |= b->pieces[player][piece]; - } - const bitboard occ = opp_occ | own_occ; - - bitboard threats = 0ULL; - - const bitboard* p = b->pieces[player]; - if (player == PLAYER_WHITE) { - threats |= pawn_attacks_white(p[PIECE_PAWN]); - } else { - threats |= pawn_attacks_black(p[PIECE_PAWN]); - } - threats |= bishop_attacks(p[PIECE_BISHOP], occ); - threats |= rook_attacks(p[PIECE_ROOK], occ); - threats |= king_attacks(p[PIECE_KING]); - threats |= queen_attacks(p[PIECE_QUEEN], occ); - threats |= knight_attacks(p[PIECE_KNIGHT]); - threats &= ~own_occ; - - for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; player++) { - for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; piece++) { - bitboard x = b->pieces[player][piece]; + 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++) { + for (index j = 0; j < 8; ++j) { if (x & (1ULL<<(i*8+j))) { buf[i][j] = piece_unicode[player][piece]; } @@ -978,14 +1974,20 @@ void board_print_threats(struct board* b, FILE* out, enum player player) } /* see: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors */ - setlocale(LC_ALL, ""); + 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++) { - const index n = i*8+j; - if ((threats >> n) & 1) { - fprintf(out, "\033[%d;%dm", 30, 41); + 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); diff --git a/codegen.c b/codegen.c index 5ef3daf..8859586 100644 --- a/codegen.c +++ b/codegen.c @@ -1,104 +1,8 @@ - +#define CODEGEN #include "base.h" #include #include -static bitboard bishop_attacks_from_index_slow(enum square_index sq, bitboard occ) -{ - const enum rank_index rank = index_to_rank(sq); - const enum file_index 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)0) - ((enum rank_index)1) > ((enum rank_index)0)); - _Static_assert(((enum file_index)0) - ((enum file_index)1) > ((enum file_index)0)); - - 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) { - const bitboard 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) { - const bitboard 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) { - const bitboard 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) { - const bitboard sq = SQ_MASK_FROM_RF(walk_rank, walk_file); - atk |= sq; - if (occ & sq) { - break; - } - } - - return atk; -} - -static bitboard rook_attacks_from_index_slow(enum square_index sq, bitboard occ) -{ - const enum rank_index rank = index_to_rank(sq); - const enum file_index 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)0) - ((enum rank_index)1) > ((enum rank_index)0)); - _Static_assert(((enum file_index)0) - ((enum file_index)1) > ((enum file_index)0)); - - for (enum rank_index walk_rank = rank+1; walk_rank <= RANK_INDEX_8; ++walk_rank) { - const bitboard 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) { - const bitboard 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) { - const bitboard 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) { - const bitboard sq = SQ_MASK_FROM_RF(rank, walk_file); - atk |= sq; - if (occ & sq) { - break; - } - } - - return atk; -} - /* 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) { @@ -116,21 +20,6 @@ uint64_t next_magic(uint64_t seed) { return seed; } -uint64_t rand64() { - union { - uint64_t v; - uint16_t vs[4]; - } x = { - .vs = { - (uint16_t)rand(), - (uint16_t)rand(), - (uint16_t)rand(), - (uint16_t)rand(), - } - }; - - return x.v; -} int main() { @@ -146,6 +35,8 @@ int main() index bishop_relevant_bits[9] = {0}; size_t bishop_relevant_bits_count = 0; + bitboard between_lookup[SQ_INDEX_COUNT][SQ_INDEX_COUNT]; + 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); @@ -183,7 +74,7 @@ int main() occ |= ((test >> i) & 1ULL) << rook_relevant_bits[i]; } - atk = rook_attacks_from_index_slow(sq_index, occ); + atk = rook_attacks_from_index(sq_index, occ); const size_t hash = ((occ * magic) >> (64ULL-12)); @@ -239,7 +130,7 @@ int main() occ |= ((test >> i) & 1ULL) << bishop_relevant_bits[i]; } - atk = bishop_attacks_from_index_slow(sq_index, occ); + atk = bishop_attacks_from_index(sq_index, occ); const size_t hash = ((occ * magic) >> (64ULL-9)); @@ -258,6 +149,16 @@ int main() next_bishop_magic: } } + + /* 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) { + between_lookup[i][j] = between_mask(i, j); + } + } + } } /* --- write to files --- */ @@ -336,6 +237,24 @@ int main() fprintf(f,"};\n"); } + { /* between table */ + FILE* f = fopen("between_lookup.h", "w"); + if (!f) { + perror("fopen"); + 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, "0x%016"BITBOARD_FMT_X"ULL, \n", between_lookup[i][j]); + } + fprintf(f, "\n},\n"); + } + fprintf(f,"};\n"); + } + #undef TAB #undef NL } diff --git a/tests.c b/tests.c index 3c0beda..f539926 100644 --- a/tests.c +++ b/tests.c @@ -357,9 +357,11 @@ static void test_bishops(void) printf("\nAll bishop_attacks_from_index tests passed.\n"); } - int main() { + + printf("sizeof board: %zu\n", sizeof (struct board)); + test_rooks(); test_bishops(); @@ -369,37 +371,61 @@ int main() fprintf(stdout, "\033[0m\n"); /* reset background color */ struct board board = BOARD_INITIAL; - enum player player = PLAYER_WHITE; - enum square_index pieces[SQ_INDEX_COUNT] = {0}; - struct move moves[MOVE_MAX] = {0}; - for (int i = 0; i < 40; ++i) { - board_print_threats(&board, stdout, player); + //board_load_fen_unsafe(&board, "1n1q1rk1/r1p2P2/1p1pp2p/pB2P3/2P5/PPN5/6b1/3QK1NR b - - 0 1"); + //board_print_fen(&board, stdout); + board_print(&board.pos, NULL, stdout); - const size_t piece_count = all_player_pieces(&board, player, pieces); + struct move moves[MOVE_MAX]; - if (piece_count == 0) { - printf("no pieces for %s, aborting\n", player_str[player]); - break; - } - - size_t move_count; - for (size_t i = 0; i < piece_count; ++i) { - move_count = all_moves_from(&board, player, pieces[i], moves); - - if (move_count > 0) { - board_move_piece(&board, player, moves[0]); - break; - } - } + for (int turn = 0; turn < 200; ++turn) { + size_t move_count = 0; + all_moves(&board.pos, board.pos.player, &move_count, moves); if (move_count == 0) { - printf("no moves for %s, aborting\n", player_str[player]); + printf("no moves for %s, aborting\n", player_str[board.pos.player]); + board_print_threats(&board.pos, stdout, NULL); + board.pos.player = opposite_player(board.pos.player); + board_print_threats(&board.pos, stdout, NULL); break; } - player = opposite_player(player); + //struct move move = moves[0]; + struct move move = search(&board, board.pos.player, 6); - usleep(300000); + printf("move %d: {\n" + " .from = %s, (%s)\n" + " .to = %s,\n" + " .mask = ", + turn, + square_index_display[move.from], + piece_str[board.mailbox[move.from]], + square_index_display[move.to] + ); + 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(&board, move); + +#if 1 + board_print(&board.pos, &move, stdout); +#endif + + if (r == MR_STALEMATE) { + printf("stalemate\n"); + break; + } + + if (board.pos.pieces[PLAYER_WHITE][PIECE_KING] == 0ULL) { + printf("white king gone!!\n"); + exit(1); + } + if (board.pos.pieces[PLAYER_BLACK][PIECE_KING] == 0ULL) { + printf("black king gone!!\n"); + exit(1); + } + + //usleep(1000000); } return EXIT_SUCCESS;