diff --git a/Makefile b/Makefile index 89e62f4..98a241c 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ CFLAGS.gcc.release := -Ofast CFLAGS.gcc.debug := -ggdb -O0 -fsanitize=address CFLAGS.clang := -std=c23 -Wall -Wextra -Wconversion -Wno-unused-function -Wimplicit-int-conversion -CFLAGS.clang.release := -Ofast +CFLAGS.clang.release := -Ofast -march=native CFLAGS.clang.debug := -ggdb -O0 -fsanitize=address CFLAGS.clang.wasm := \ --target=wasm32-unknown-unknown -O3 -nostdlib \ diff --git a/board_print.h b/board_print.h index 97bd141..ca51b50 100644 --- a/board_print.h +++ b/board_print.h @@ -172,20 +172,20 @@ 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); + enum square_index const sqi = bitboard_lsb(pos->ep_targets); - assert(sqi >= SQ_INDEX_BEGIN && sqi < SQ_INDEX_COUNT); + assert(sqi >= SQ_INDEX_BEGIN && sqi < SQ_INDEX_COUNT); - enum file_index const fi = index_to_file(sqi); - enum rank_index const ri = index_to_rank(sqi); + enum file_index const fi = index_to_file(sqi); + enum rank_index const ri = index_to_rank(sqi); - int const fch = tolower(file_index_char[fi]); - int const rch = tolower(rank_index_char[ri]); + 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'); + assert(fch >= 'a' && fch <= 'h'); + assert(rch >= '1' && rch <= '8'); - fprintf(out, " %c%c", fch, rch); + fprintf(out, " %c%c", fch, rch); } else { fprintf(out, " -"); } @@ -199,6 +199,7 @@ static void board_print_fen(struct pos const* pos, FILE* out) static void board_print(const struct pos* pos, struct move* move, FILE* out) { 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) { @@ -207,7 +208,10 @@ static void board_print(const struct pos* pos, struct move* move, FILE* out) 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]; + buf[i][j] = piece_unicode[PLAYER_BLACK][piece]; + color[i][j] = (player == PLAYER_WHITE) + ? 1 + : 2; } } } @@ -221,19 +225,36 @@ static void board_print(const struct pos* pos, struct move* move, FILE* out) 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, 104); /* 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); + + int bg, fg; + + /**/ if (color[i][j] == 1) { + fg = 97; /* bright white */ } + else if (color[i][j] == 2) { + fg = 30; /* black */ + } + else { + fg = 35; /* magenta (should not happen) */ + } + + if (move && n == move->from) { + bg = 104; /* blue */ + } else if (move && n == move->to) { + bg = 44; /* bright blue */ + } else { + /* 45: magenta, + * 47: white */ + bg = (i+j) % 2 ? 45 : 47; + } + + fprintf(out, "\033[%d;%dm", fg, bg); + 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] = ' ' */ + is not sufficient when buf[i][j] = ' ' */ } } fprintf(out, "\033[0m"); /* reset background color */ diff --git a/engine.h b/engine.h index aed40cd..467f8d4 100644 --- a/engine.h +++ b/engine.h @@ -4,6 +4,7 @@ #include "sys.h" #include +#include #ifndef NDEBUG #define assert(expr) \ @@ -444,19 +445,17 @@ static inline enum rank_index index_to_rank(enum square_index p) static bitboard cardinals_from_index(enum square_index p) { - /* might benefit from a lookup table */ - return (FILE_MASK(index_to_file(p)) | RANK_MASK(index_to_rank(p))) - & ~SQ_MASK_FROM_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; - /* Ensure signed-underflow detection rules match your style */ _Static_assert(((enum rank_index)-1) > 0, "rank_index must be unsigned"); _Static_assert(((enum file_index)-1) > 0, "file_index must be unsigned"); @@ -469,7 +468,6 @@ static bitboard diagonals_from_index(enum square_index sq) mask |= SQ_MASK_FROM_RF(r, f); } - /* NW (rank+1, file-1) */ for (r = rank+1, f = file-1; r <= RANK_INDEX_8 && f <= FILE_INDEX_H; ++r, --f) @@ -477,7 +475,6 @@ static bitboard diagonals_from_index(enum square_index sq) mask |= SQ_MASK_FROM_RF(r, f); } - /* SE (rank-1, file+1) */ for (r = rank-1, f = file+1; r <= RANK_INDEX_8 && f <= FILE_INDEX_H; --r, ++f) @@ -485,15 +482,21 @@ static bitboard diagonals_from_index(enum square_index sq) mask |= SQ_MASK_FROM_RF(r, f); } - /* SW (rank-1, file-1) */ 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 } @@ -914,13 +917,13 @@ struct board { enum piece mailbox[SQ_INDEX_COUNT]; }; -static void move_compute_appeal(struct move* m, - struct pos const* pos, - enum player us, - enum piece mailbox[restrict static SQ_INDEX_COUNT]) +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]) { - enum player them = opposite_player(us); /* MVV-LVA: https://www.chessprogramming.org/MVV-LVA */ + enum player them = opposite_player(us); enum piece const atk = mailbox[m->from]; uint8_t n = 1; @@ -928,7 +931,10 @@ static void move_compute_appeal(struct move* m, n += (uint8_t)piece_value[mailbox[m->to]]; } - m->appeal = 16*n - (uint8_t)piece_value[atk]; + 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) { \ @@ -1001,6 +1007,21 @@ static void move_compute_appeal(struct move* m, .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 */ @@ -1517,6 +1538,29 @@ static inline void tt_insert(struct tt* tt, uint64_t hash, struct search_option #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, @@ -1708,10 +1752,10 @@ static enum move_result board_move_2(struct board* b, struct move move) move); } -/* - * Placeholder heuristic - * - * */ + +/* --------------------------- MOVE SEARCH --------------------------------- */ +#include "evaluations.h" + static double board_score_heuristic(struct pos const* pos) { /* this function always evaluates from white's perspective before @@ -1723,7 +1767,11 @@ static double board_score_heuristic(struct pos const* pos) - static struct {bitboard const area; double const val} const positional_modifier[PLAYER_COUNT][POSITIONAL_MODIFIER_COUNT][PIECE_COUNT]; * */ -#include "evaluations.h" + bitboard const occw = pos->occupied[PLAYER_WHITE]; + bitboard const occb = pos->occupied[PLAYER_BLACK]; + bitboard const occall = occw | occb; + + enum game_progress const gp = endgameness(pos); for (enum piece p = PIECE_BEGIN; p < PIECE_COUNT; ++p) { /* raw material value */ @@ -1731,34 +1779,35 @@ static double board_score_heuristic(struct pos const* pos) ((double)bitboard_popcount(pos->pieces[PLAYER_WHITE][p]) - (double)bitboard_popcount(pos->pieces[PLAYER_BLACK][p])); - /* pawns defending pieces are desired */ - score += 0.05 * ( - (double)bitboard_popcount( - pawn_attacks_white(pos->pieces[PLAYER_WHITE][PIECE_PAWN]) - & pos->pieces[PLAYER_WHITE][p] - ) - - (double)bitboard_popcount( - pawn_attacks_black(pos->pieces[PLAYER_WHITE][PIECE_PAWN]) - & pos->pieces[PLAYER_WHITE][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))); /* positional bonus, see evaluations.h */ for (size_t i = 0; i < POSITIONAL_MODIFIER_COUNT; ++i) { - score += positional_modifier[PLAYER_WHITE][i][p].val * + score += positional_modifier(PLAYER_WHITE, gp, i, p).val * ( (double)bitboard_popcount( pos->pieces[PLAYER_WHITE][p] - & positional_modifier[PLAYER_WHITE][i][p].area - ) + & positional_modifier(PLAYER_WHITE, gp, i, p).area) - (double)bitboard_popcount( pos->pieces[PLAYER_BLACK][p] - & positional_modifier[PLAYER_BLACK][i][p].area - ) + & positional_modifier(PLAYER_BLACK, gp, i, p).area) ); } } + /* pawns defending pieces are desired */ + score += 0.05 * ( + (double)bitboard_popcount( + pawn_attacks_white(pos->pieces[PLAYER_WHITE][PIECE_PAWN]) & occw + ) + - (double)bitboard_popcount( + pawn_attacks_black(pos->pieces[PLAYER_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) { @@ -1859,68 +1908,6 @@ double quiesce(struct pos const* pos, return highscore; } -struct ab_frame { - enum search_stage { - ST_INIT = 0, - ST_LOOP = 1, - ST_WAIT_CHILD = 2, - } stage; - - struct pos pos; - enum piece mailbox[SQ_INDEX_COUNT]; - enum player us; - int8_t depth; - uint64_t mattr_filter; - - double alpha; - double beta; - double alpha_orig; - - struct search_option tte; - - struct move moves[MOVE_MAX]; - size_t move_count; - - double best_score; - struct move best_move; - - size_t old_hist_length; - struct move pending_move; - - struct search_option result; -}; - -static inline void ab_update_parent_after_score(struct ab_frame *parent, - struct tt *tt, - struct move m, - double score) -{ - if (score > parent->best_score) { - parent->best_score = score; - parent->best_move = m; - } - - if (score > parent->alpha) { - parent->alpha = score; - } - - if (parent->alpha >= parent->beta) { - struct search_option out = { - .score = parent->alpha, - .move = parent->best_move, - .depth = parent->depth, - .hash = parent->pos.hash, - .init = true, - .flag = TT_LOWER, - }; - tt_insert(tt, parent->pos.hash, out); - parent->result = out; - - /* mark parent as complete so it will be popped in st_wait_child. */ - parent->stage = ST_WAIT_CHILD; - } -} - static struct search_option alphabeta_search(struct pos const* pos, struct history* hist, @@ -1928,265 +1915,167 @@ struct search_option alphabeta_search(struct pos const* pos, enum piece mailbox[restrict static SQ_INDEX_COUNT], enum player us, int8_t depth, - uint64_t mattr_filter, double alpha, - double beta) + double beta, + atomic_bool* searching) { - struct { - struct ab_frame stack[50]; - size_t len; - } ab_stack = {0}; + if (depth <= 0) { + double sc = quiesce(pos, mailbox, us, alpha, beta, 0); + return (struct search_option){ + .score = sc, + .move = (struct move){0}, + .depth = 0, + .hash = pos->hash, + .init = true, + .flag = TT_EXACT, + }; + } -#define STACK_PUSH(s, x) \ - do { \ - assert(s.len < sizeof s.stack / sizeof s.stack[0]); \ - (s.stack)[(s.len)++] = (x); \ - } while (0) -#define STACK_TOP(s) &(s.stack)[(s.len) - 1] -#define STACK_POP(s) (assert(s.len > 0), s.stack[--(s.len)]); -#define STACK_EMPTY(s) (s.len == 0) + double const alpha_orig = alpha; - struct ab_frame root = { - .stage = ST_INIT, - .pos = *pos, - .us = us, - .depth = depth, - .mattr_filter = mattr_filter, - .alpha = alpha, - .beta = beta - }; - my_memcpy(root.mailbox, mailbox, sizeof root.mailbox); - - STACK_PUSH(ab_stack, root); - - while (1) { - struct ab_frame *fr = STACK_TOP(ab_stack); - - switch (fr->stage) { - - case ST_INIT: { - if (fr->depth <= 0) { - fr->result = (struct search_option) { - .score = quiesce(&fr->pos, fr->mailbox, fr->us, - fr->alpha, fr->beta, 0), - .move = (struct move){0}, - .depth = 0, - .hash = fr->pos.hash, - .init = true, - .flag = TT_EXACT, - }; - fr->stage = ST_WAIT_CHILD; - break; - } - - fr->alpha_orig = fr->alpha; - - fr->tte = tt_get(tt, fr->pos.hash); - if (fr->tte.init && fr->tte.hash == fr->pos.hash && fr->tte.depth >= fr->depth) { - if (fr->tte.flag == TT_EXACT) { - fr->result = fr->tte; - fr->stage = ST_WAIT_CHILD; - break; - } else if (fr->tte.flag == TT_LOWER) { - if (fr->tte.score > fr->alpha) { - fr->alpha = fr->tte.score; - } - } else if (fr->tte.flag == TT_UPPER) { - if (fr->tte.score < fr->beta) { - fr->beta = fr->tte.score; - } - } - - if (fr->alpha >= fr->beta) { - fr->result = fr->tte; - fr->stage = ST_WAIT_CHILD; - break; - } - } - - fr->move_count = 0; - all_moves(&fr->pos, fr->us, &fr->move_count, fr->moves); - - /* checkmate or stalemate */ - if (fr->move_count == 0) { - double score = 0.0; - if (attacks_to(&fr->pos, - fr->pos.pieces[fr->us][PIECE_KING], - 0ULL, 0ULL) != 0ULL) { - score = -(999.0 + (double)fr->depth); - } - - fr->result = (struct search_option) { - .score = score, - .move = (struct move){0}, - .depth = fr->depth, - .hash = fr->pos.hash, - .init = true, - .flag = TT_EXACT, - }; - fr->stage = ST_WAIT_CHILD; - break; - } - - for (size_t i = 0; i < fr->move_count; ++i) { - move_compute_appeal(&fr->moves[i], &fr->pos, fr->us, fr->mailbox); - } - - /* put existing TT entry first */ - if (fr->tte.init && fr->tte.hash == fr->pos.hash) { - for (size_t i = 0; i < fr->move_count; ++i) { - if (fr->moves[i].from == fr->tte.move.from && - fr->moves[i].to == fr->tte.move.to) { - fr->moves[i].appeal = APPEAL_MAX; - break; - } - } - } - - fr->best_score = -1e300; - fr->best_move = fr->moves[0]; - fr->stage = ST_LOOP; - } break; - - case ST_LOOP: { - if (fr->result.init) { - fr->stage = ST_WAIT_CHILD; - break; - } - - if (fr->move_count == 0) { - enum tt_flag flag = TT_EXACT; - if (fr->best_score <= fr->alpha_orig) flag = TT_UPPER; - - fr->result = (struct search_option) { - .score = fr->best_score, - .move = fr->best_move, - .depth = fr->depth, - .hash = fr->pos.hash, - .init = true, - .flag = flag, - }; - tt_insert(tt, fr->pos.hash, fr->result); - fr->stage = ST_WAIT_CHILD; - break; - } - - struct move m = moves_linear_search(fr->moves, &fr->move_count); - - if (fr->mattr_filter && !(m.attr & fr->mattr_filter)) { - break; - } - - fr->old_hist_length = hist->length; - - struct pos child_pos = fr->pos; - enum piece child_mailbox[SQ_INDEX_COUNT]; - my_memcpy(child_mailbox, fr->mailbox, sizeof child_mailbox); - - enum move_result const r = board_move(&child_pos, hist, child_mailbox, m); - - if (r == MR_STALEMATE || r == MR_REPEATS) { - hist->length = fr->old_hist_length; - ab_update_parent_after_score(fr, tt, m, 0.0); - break; - } - - if (fr->depth - 1 <= 0) { - double score = -quiesce(&child_pos, - child_mailbox, - opposite_player(fr->us), - -fr->beta, - -fr->alpha, - 0); - - hist->length = fr->old_hist_length; - ab_update_parent_after_score(fr, tt, m, score); - break; - } - - fr->pending_move = m; - fr->stage = ST_WAIT_CHILD; - - struct ab_frame child = {0}; - child.stage = ST_INIT; - child.pos = child_pos; - my_memcpy(child.mailbox, child_mailbox, sizeof child.mailbox); - child.us = opposite_player(fr->us); - child.depth = fr->depth - 1; - child.mattr_filter = fr->mattr_filter; - child.alpha = -fr->beta; - child.beta = -fr->alpha; - - STACK_PUSH(ab_stack, child); - } break; - - case ST_WAIT_CHILD: { - struct search_option out = fr->result; - STACK_POP(ab_stack); - - if (STACK_EMPTY(ab_stack)) { - return out; - } - - struct ab_frame *parent = STACK_TOP(ab_stack); - - if (parent->stage == ST_WAIT_CHILD) { - /* parent is waiting on a child (this frame), propagate */ - double score = -out.score; - struct move m = parent->pending_move; - - hist->length = parent->old_hist_length; - - /* resume parent move loop. */ - parent->stage = ST_LOOP; - - ab_update_parent_after_score(parent, tt, m, score); - } else { - /* parent wasn't waiting: treat as a completed frame result being - propagated through a completion chain. */ - parent->result = out; - parent->stage = ST_WAIT_CHILD; - } - } break; - - default: { - /* unreachable */ - assert(0); - __builtin_unreachable(); - } break; + 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); + } + + return (struct search_option){ + .score = score, + .move = (struct move){0}, + .depth = depth, + .hash = pos->hash, + .init = true, + .flag = TT_EXACT, + }; + } + + for (size_t i = 0; i < move_count; ++i) { + move_compute_appeal(&moves[i], pos, us, mailbox); + } + + /* 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; + } + } + } + + double best_score = -1e300; + struct move best_move = moves[0]; + + 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; + } + } + + enum tt_flag flag = TT_EXACT; + if (best_score <= alpha_orig) { + flag = TT_UPPER; + } + else if (best_score >= beta) { + flag = TT_LOWER; + } + + struct search_option out = (struct search_option){ + .score = best_score, + .move = best_move, + .depth = depth, + .hash = pos->hash, + .init = true, + .flag = flag, + }; + + tt_insert(tt, pos->hash, out); + + return out; } static struct search_result {struct move move; double score;} -search(struct board* b, enum player us, int8_t max_depth) +search(struct board* b, enum player us, int8_t max_depth, atomic_bool* searching) { -#if 1 - 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(); - } - } -#endif + struct move moves[MOVE_MAX]; + size_t move_count = 0; + all_moves(&b->pos, us, &move_count, moves); - struct move best_move = {0}; + assert(move_count); + struct move best_move = moves[0]; + +#define SCORE_INF 1e300 double score = 0.0; -#define SCORE_INF 1e80 - - for (int8_t d = 1; d <= max_depth; ++d) { - double window = 2.0; + for (int8_t d = 1; + d <= max_depth && atomic_load_explicit(searching, memory_order_relaxed); + ++d) + { + double window = 1000; /* temp debug solution */ double alpha = score - window; double beta = score + window; - while (true) { + while (atomic_load_explicit(searching, memory_order_relaxed)) { struct search_option so = - alphabeta_search(&b->pos, &b->hist, &b->tt, b->mailbox, us, d, 0, alpha, beta); + alphabeta_search(&b->pos, &b->hist, &b->tt, b->mailbox, us, d, alpha, beta, searching); if (so.score > alpha && so.score < beta) { score = so.score; @@ -2196,12 +2085,11 @@ search(struct board* b, enum player us, int8_t max_depth) window *= 2; - alpha = score - window; - beta = score + window; - if (so.score <= alpha) { alpha = -SCORE_INF; + beta = score + window; } else { + alpha = score - window; beta = SCORE_INF; } diff --git a/evaluations.h b/evaluations.h index 3492f5c..ccc4605 100644 --- a/evaluations.h +++ b/evaluations.h @@ -2,6 +2,52 @@ #pragma once +enum game_progress { + GP_OPENING, + GP_MIDDLE, + GP_END, + GP_COUNT, +}; + +static char const* game_progress_str[GP_COUNT] = { + [GP_OPENING] = "GP_OPENING", + [GP_MIDDLE] = "GP_MIDDLE", + [GP_END] = "GP_END", +}; + +static enum game_progress endgameness(struct pos const* pos) +{ + /* piece_value is already defined similarly elsewhere, but this one should + * remain independent of minor tweaks in the global table */ + static int const piece_value[PIECE_COUNT] = { + [PIECE_KING] = 0, + [PIECE_PAWN] = 1, + [PIECE_BISHOP] = 3, + [PIECE_KNIGHT] = 3, + [PIECE_ROOK] = 5, + [PIECE_QUEEN] = 9, + }; + + int npm = 0; /* non-pawn material */ + for (enum player pl = PLAYER_BEGIN; pl < PLAYER_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]); + npm += piece_value[PIECE_ROOK] * bitboard_popcount(pos->pieces[pl][PIECE_ROOK]); + } + + /**/ if (npm > 24) { + return GP_OPENING; + } + else if (npm > 14) { + return GP_MIDDLE; + } + else { + return GP_END; + } +} + + #define BITBOARD_WHITE( \ a8,b8,c8,d8,e8,f8,g8,h8, \ a7,b7,c7,d7,e7,f7,g7,h7, \ @@ -44,8 +90,97 @@ h7##g7##f7##e7##d7##c7##b7##a7##\ h8##g8##f8##e8##d8##c8##b8##a8##\ ULL -#define RELATIVE_DIAGONAL_A1_H8 \ - RELATIVE_BITBOARD( \ +#define REL_RANK_1 \ + 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, \ + 1,1,1,1,1,1,1,1) + +#define REL_RANK_2 \ + 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, \ + 1,1,1,1,1,1,1,1, \ + 0,0,0,0,0,0,0,0) + +#define REL_RANK_3 \ + 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, \ + 1,1,1,1,1,1,1,1, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0) + +#define REL_RANK_4 \ + 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, \ + 1,1,1,1,1,1,1,1, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0) + +#define REL_RANK_5 \ + 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, \ + 1,1,1,1,1,1,1,1, \ + 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) + +#define REL_RANK_6 \ + REL_BITBOARD( \ + 0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0, \ + 1,1,1,1,1,1,1,1, \ + 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) + +#define REL_RANK_7 \ + REL_BITBOARD( \ + 0,0,0,0,0,0,0,0, \ + 1,1,1,1,1,1,1,1, \ + 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) + +#define REL_RANK_8 \ + REL_BITBOARD( \ + 1,1,1,1,1,1,1,1, \ + 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) + + +#define REL_DIAGONAL_A1_H8 \ + REL_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, \ @@ -55,8 +190,8 @@ ULL 0,1,0,0,0,0,0,0, \ 1,0,0,0,0,0,0,0) -#define RELATIVE_DIAGONAL_A8_H1 \ - RELATIVE_BITBOARD( \ +#define REL_DIAGONAL_A8_H1 \ + REL_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, \ @@ -66,8 +201,8 @@ ULL 0,0,0,0,0,0,1,0, \ 0,0,0,0,0,0,0,1) -#define RELATIVE_BISHOP_KING_ATTACK \ - RELATIVE_BITBOARD( \ +#define REL_BISHOP_KING_ATTACK \ + REL_BITBOARD( \ 0,0,0,0,0,0,1,1, \ 0,0,0,0,0,1,1,0, \ 0,0,0,0,1,1,0,0, \ @@ -77,8 +212,8 @@ ULL 1,1,0,0,0,0,0,0, \ 1,0,0,0,0,0,0,0) -#define RELATIVE_BISHOP_QUEEN_ATTACK \ - RELATIVE_BITBOARD( \ +#define REL_BISHOP_QUEEN_ATTACK \ + REL_BITBOARD( \ 1,1,0,0,0,0,0,0, \ 0,1,1,0,0,0,0,0, \ 0,0,1,1,0,0,0,0, \ @@ -88,8 +223,8 @@ ULL 0,0,0,0,0,0,1,1, \ 0,0,0,0,0,0,0,1) -#define RELATIVE_KING_CASTLE_KINGSIDE \ - RELATIVE_BITBOARD( \ +#define REL_KING_CASTLE_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, \ @@ -99,8 +234,8 @@ ULL 0,0,0,0,0,0,0,0, \ 0,0,0,0,0,0,1,0) -#define RELATIVE_KING_CASTLE_QUEENSIDE \ - RELATIVE_BITBOARD( \ +#define REL_KING_CASTLE_QUEENSIDE \ + 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, \ @@ -121,8 +256,8 @@ ULL 0,0,0,0,0,0,0,0, \ 1,0,0,0,0,0,0,1) -#define RELATIVE_PAWN_SAFE_ZONE \ - BITBOARD( \ +#define REL_EARLY_PAWN_STRUCTURE \ + REL_BITBOARD( \ 0,0,0,0,0,0,0,0, \ 0,0,0,0,0,0,0,0, \ 1,0,0,0,0,0,0,0, \ @@ -132,6 +267,10 @@ ULL 1,1,1,1,1,1,1,1, \ 0,0,0,0,0,0,0,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)) + #define BOARD_CENTER_4X4 \ ((FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F) \ & (RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6)) @@ -140,63 +279,163 @@ ULL ((FILE_MASK_D | FILE_MASK_E) \ & (RANK_MASK_4 | RANK_MASK_5)) +#define POSITIONAL_MODIFIER_COUNT 4 -#define POSITIONAL_BONUS_0 \ +/* ------------------------------ early game ------------------------------- */ + +#define EARLY_POSITIONAL_BONUS_0 \ /* piece bonus area*/ \ X(PIECE_PAWN, 0.02, BOARD_CENTER_4X4) \ X(PIECE_KNIGHT, 0.05, BOARD_CENTER_4X4) \ - X(PIECE_BISHOP, 0.05, RELATIVE_DIAGONAL_A1_H8 | RELATIVE_DIAGONAL_A8_H1) \ - X(PIECE_KING, 0.15, RELATIVE_KING_CASTLE_KINGSIDE) \ + X(PIECE_BISHOP, 0.05, REL_DIAGONAL_A1_H8 | REL_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) \ /**/ -#define POSITIONAL_BONUS_1 \ +#define EARLY_POSITIONAL_BONUS_1 \ /* piece bonus area*/ \ X(PIECE_PAWN, 0.02, BOARD_CENTER_2X2) \ - X(PIECE_BISHOP, 0.05, RELATIVE_BISHOP_KING_ATTACK) \ + X(PIECE_BISHOP, 0.05, REL_BISHOP_KING_ATTACK) \ /**/ -#define POSITIONAL_BONUS_2 \ +#define EARLY_POSITIONAL_BONUS_2 \ /* piece bonus area*/ \ - X(PIECE_PAWN, -0.10, ~RELATIVE_PAWN_SAFE_ZONE) \ + X(PIECE_PAWN, -0.10, ~REL_EARLY_PAWN_STRUCTURE) \ X(PIECE_BISHOP, 0.05, CORNERS) \ /**/ -#define POSITIONAL_BONUS_3 \ +#define EARLY_POSITIONAL_BONUS_3 \ /* piece bonus area*/ \ - X(PIECE_BISHOP, 0.02, RELATIVE_BISHOP_QUEEN_ATTACK) + X(PIECE_BISHOP, 0.02, REL_BISHOP_QUEEN_ATTACK) -#define POSITIONAL_MODIFIER_COUNT 4 -static struct {bitboard const area; double const val;} const -positional_modifier[PLAYER_COUNT][POSITIONAL_MODIFIER_COUNT][PIECE_COUNT] = { -#define X(p, b, a) [p] = {.area = (a), .val = b}, -#define RELATIVE_BITBOARD BITBOARD_WHITE - [PLAYER_WHITE] = { - {POSITIONAL_BONUS_0}, - {POSITIONAL_BONUS_1}, - {POSITIONAL_BONUS_2}, - {POSITIONAL_BONUS_3}, - }, - [PLAYER_BLACK] = { -#undef RELATIVE_BITBOARD -#define RELATIVE_BITBOARD BITBOARD_BLACK - {POSITIONAL_BONUS_0}, - {POSITIONAL_BONUS_1}, - {POSITIONAL_BONUS_2}, - {POSITIONAL_BONUS_3}, - } -#undef X + +/* ------------------------------ middle game ------------------------------ */ +/* (almost same as opening for now, but queen is not punished for moving) */ + +#define MIDDLE_POSITIONAL_BONUS_0 \ + /* 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_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) \ + /**/ + +#define MIDDLE_POSITIONAL_BONUS_2 \ + /* piece bonus area*/ \ + X(PIECE_PAWN, -0.10, ~REL_EARLY_PAWN_STRUCTURE) \ + X(PIECE_BISHOP, 0.05, CORNERS) \ + /**/ + +#define MIDDLE_POSITIONAL_BONUS_3 \ + /* piece bonus area*/ \ + X(PIECE_BISHOP, 0.02, REL_BISHOP_QUEEN_ATTACK) \ + /**/ + +/* ------------------------------- end game -------------------------------- */ + +#define LATE_POSITIONAL_BONUS_0 \ + /* piece bonus area*/ \ + X(PIECE_PAWN, 0.30, REL_RANK_7 | REL_RANK_6 | REL_RANK_5) \ + X(PIECE_KING, 0.10, BOARD_CENTER_6X6) \ + /**/ + +#define LATE_POSITIONAL_BONUS_1 \ + /* piece bonus area*/ \ + X(PIECE_PAWN, 0.30, REL_RANK_7 | REL_RANK_6) \ + X(PIECE_KING, 0.10, BOARD_CENTER_4X4) \ + /**/ + +#define LATE_POSITIONAL_BONUS_2 \ + /* piece bonus area*/ \ + X(PIECE_PAWN, 0.70, REL_RANK_7) \ + X(PIECE_KING, 0.10, BOARD_CENTER_2X2) \ + /**/ + +#define LATE_POSITIONAL_BONUS_3 \ + /* piece bonus area*/ \ + X(PIECE_KING, -0.50, ~BOARD_CENTER_6X6) \ + /**/ + +struct posmod { + bitboard 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 struct posmod const + lookup[PLAYER_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] = { + [GP_OPENING] = { + {EARLY_POSITIONAL_BONUS_0}, + {EARLY_POSITIONAL_BONUS_1}, + {EARLY_POSITIONAL_BONUS_2}, + {EARLY_POSITIONAL_BONUS_3}, + }, + [GP_MIDDLE] = { + {MIDDLE_POSITIONAL_BONUS_0}, + {MIDDLE_POSITIONAL_BONUS_1}, + {MIDDLE_POSITIONAL_BONUS_2}, + {MIDDLE_POSITIONAL_BONUS_3}, + }, + [GP_END] = { + {LATE_POSITIONAL_BONUS_0}, + {LATE_POSITIONAL_BONUS_1}, + {LATE_POSITIONAL_BONUS_2}, + {LATE_POSITIONAL_BONUS_3}, + }, + }, + #undef REL_BITBOARD + + #define REL_BITBOARD BITBOARD_BLACK + [PLAYER_BLACK] = { + [GP_OPENING] = { + {EARLY_POSITIONAL_BONUS_0}, + {EARLY_POSITIONAL_BONUS_1}, + {EARLY_POSITIONAL_BONUS_2}, + {EARLY_POSITIONAL_BONUS_3}, + }, + [GP_MIDDLE] = { + {MIDDLE_POSITIONAL_BONUS_0}, + {MIDDLE_POSITIONAL_BONUS_1}, + {MIDDLE_POSITIONAL_BONUS_2}, + {MIDDLE_POSITIONAL_BONUS_3}, + }, + [GP_END] = { + {LATE_POSITIONAL_BONUS_0}, + {LATE_POSITIONAL_BONUS_1}, + {LATE_POSITIONAL_BONUS_2}, + {LATE_POSITIONAL_BONUS_3}, + }, + } + #undef REL_BITBOARD + }; + return lookup[pl][st][layer][pz]; +} + +/* ------------------------------------------------------------------------- */ + +#undef POSITIONAL_BONUS_0 +#undef POSITIONAL_BONUS_1 +#undef POSITIONAL_BONUS_2 +#undef POSITIONAL_BONUS_3 #undef CORNERS #undef BOARD_CENTER_4X4 #undef BOARD_CENTER_2X2 #undef BITBOARD_WHITE #undef BITBOARD_BLACK -#undef RELATIVE_DIAGONAL_A1_H8 -#undef RELATIVE_DIAGONAL_A8_H1 -#undef RELATIVE_BISHOP_KING_ATTACK -#undef RELATIVE_BISHOP_QUEEN_ATTACK -#undef RELATIVE_KING_CASTLE_KINGSIDE -#undef RELATIVE_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/tests.c b/tests.c index 2bb2d86..d73a1e1 100644 --- a/tests.c +++ b/tests.c @@ -1,6 +1,7 @@ #define _XOPEN_SOURCE 500 #include /* usleep */ +#include #include "engine.h" #include "board_print.h" @@ -372,24 +373,36 @@ static void test_bishops(void) * 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; + expected = SQ_MASK_G7 | SQ_MASK_F6 | SQ_MASK_E5 | SQ_MASK_D4 | + SQ_MASK_C3 | SQ_MASK_B2 | SQ_MASK_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; - if (attacks != expected) { - bitboard_print(attacks, stderr); - } + if (attacks != expected) { + bitboard_print(attacks, stderr); + } assert(attacks == expected); } printf("\nAll bishop_attacks_from_index tests passed.\n"); } +struct timeout_params { + atomic_bool* x; + bool v; + useconds_t us; +}; +void* set_after_timeout(void* x) +{ + struct timeout_params* p = x; + usleep(p->us); + *p->x = p->v; + return NULL; +} + int main() { - printf("sizeof pos: %zu\n", sizeof (struct pos)); printf("sizeof tt: %zu\n", sizeof (struct tt)); @@ -403,16 +416,18 @@ int main() } fprintf(stdout, "\033[0m\n"); /* reset background color */ - /* board is too big for the stack */ - struct board* b = malloc(sizeof *b); - if (!b) { - abort(); - } - *b = BOARD_INIT; + /* board could be too big for the stack */ + struct board* b = malloc(sizeof *b); + if (!b) { + abort(); + } + *b = BOARD_INIT; + board_init(b); - //board_load_fen_unsafe(&board, "1n1q1rk1/r1p2P2/1p1pp2p/pB2P3/2P5/PPN5/6b1/3QK1NR b - - 0 1"); - //board_load_fen_unsafe(&board, "5R2/7k/P7/6pp/3B4/1PPK2bP/4r3/8 b - - 3 57"); - board_print_fen(&b->pos, stdout); + //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_print_fen(b->pos, stdout); board_print(&b->pos, NULL, stdout); struct move moves[MOVE_MAX]; @@ -429,11 +444,20 @@ int main() break; } - //struct move move = moves[0]; - struct search_result sr = search(b, b->pos.player, 7); + pthread_t timer; + atomic_bool searching; + atomic_init(&searching, true); + struct timeout_params timer_params = { + .x = &searching, + .v = false, + .us = 3*1000*1000, + }; + pthread_create(&timer, NULL, set_after_timeout, &timer_params); - struct move move = sr.move; - double const score = sr.score; + struct search_result sr = search(b, b->pos.player, 5, &searching); + + struct move move = sr.move; + double const score = sr.score; printf("move %d: {\n" " .from = %s, (%s)\n" @@ -444,7 +468,7 @@ int main() square_index_display[move.from], piece_str[b->mailbox[move.from]], square_index_display[move.to], - score + score ); if (move.attr & MATTR_CAPTURE) printf("MATTR_CAPTURE "); if (move.attr & MATTR_PROMOTE) printf("MATTR_PROMOTE ");