From bb3d99b0116b4b67c0e75a8b68e365a50d38a716 Mon Sep 17 00:00:00 2001 From: Ole Morud Date: Wed, 7 Jan 2026 20:17:55 +0100 Subject: [PATCH] refactor: (bb64)occupied -> ~(Bb64)PIECE_EMPTY --- board_print.h | 4 ---- chess-worker.js | 17 ++++++++--------- engine-attack-sets.h | 12 ++++++------ engine-board.h | 39 ++++++++++++++------------------------- engine-move-generation.h | 28 ++++++---------------------- engine-types.h | 24 ++++++++++++++++++++++-- engine.h | 23 +++++++++++++---------- sys.h | 4 ++-- tests.c | 3 --- 9 files changed, 71 insertions(+), 83 deletions(-) diff --git a/board_print.h b/board_print.h index e7944cf..6e9cf33 100644 --- a/board_print.h +++ b/board_print.h @@ -197,10 +197,6 @@ static void board_print(struct pos const* pos, struct move* move, FILE* out, boo bg = (i+j) % 2 ? 45 : 43; } - if ((pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK]) & MASK_FROM_SQ(n)) { - bg += 60; /* make bright */ - } - fprintf(out, "\033[%d;%dm", fg, bg); if (buf[i][j]) { diff --git a/chess-worker.js b/chess-worker.js index 685749d..4f9859d 100644 --- a/chess-worker.js +++ b/chess-worker.js @@ -2,25 +2,24 @@ let exports; async function init() { const resp = await fetch("./chess.wasm"); - if (!resp.ok) { - throw new Error("fetch wasm failed ${resp.status} ${resp.statusText}"); - } - - const { instance } = - await WebAssembly.instantiateStreaming(resp, {}); + if (!resp.ok) throw new Error(`fetch wasm failed ${resp.status} ${resp.statusText}`); + const { instance } = await WebAssembly.instantiateStreaming(resp, {}); exports = instance.exports; } + await init(); self.postMessage({ type: "ready" }); self.onmessage = (e) => { const { id, method, args = [] } = e.data; try { - const value = exports[method](...args); + let value; + value = exports[method](...args); self.postMessage({ id, ok: true, value }); } catch (err) { - self.postMessage({ id, ok: false, error: String(err?.message ?? err) }); + self.postMessage({ + id, ok: false, error: String(err?.message ?? err) + }); } }; - diff --git a/engine-attack-sets.h b/engine-attack-sets.h index 1750aaa..fb5cb03 100644 --- a/engine-attack-sets.h +++ b/engine-attack-sets.h @@ -91,7 +91,7 @@ static Bb64 diagonals(Bb64 p) * ================== * * All piece attack functions rely on the caller masking their own pieces. - * e.g. `knight_attacks(knights) & ~our_occupied` + * e.g. `knight_attacks(knights) & empty` * */ static Bb64 knight_attacks(Bb64 p) @@ -522,7 +522,7 @@ static Bb64 attacks_to(struct pos const* pos, Bb64 pretend_occupied, Bb64 pretend_empty) { - Bb64 const occ_orig = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK]; + Bb64 const occ_orig = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]); Bb64 const occ = (occ_orig & ~pretend_empty) | pretend_occupied; Bb64 const* pw = pos->pieces[SIDE_WHITE]; @@ -563,15 +563,15 @@ static Bb64 checkers(struct pos const* pos, Side8 us) { /* TODO: specialize */ - return attacks_to(pos, pos->pieces[us][PIECE_KING], 0ULL, 0ULL) & ~pos->occupied[us]; + return attacks_to(pos, pos->pieces[us][PIECE_KING], 0ULL, 0ULL) & pos->pieces[us][PIECE_EMPTY]; } static Bb64 pinning_lines_to_target(struct pos const* pos, Side8 us, Sq8 tsq) { Side8 const them = other_side(us); - Bb64 const our_occ = pos->occupied[us]; - Bb64 const their_occ = pos->occupied[them]; + Bb64 const our_occ = ~pos->pieces[us][PIECE_EMPTY]; + Bb64 const their_occ = ~pos->pieces[them][PIECE_EMPTY]; Bb64 const* p = pos->pieces[them]; Bb64 const bqs = p[PIECE_QUEEN] | p[PIECE_BISHOP]; @@ -595,7 +595,7 @@ Bb64 pinning_lines_to_target(struct pos const* pos, Side8 us, Sq8 tsq) static Bb64 all_threats_from_side(struct pos const * pos, Side8 who) { - Bb64 const occ = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK]; + Bb64 const occ = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]); Bb64 const* p = pos->pieces[who]; Bb64 t = 0ULL; diff --git a/engine-board.h b/engine-board.h index 6ed3d29..b9579a8 100644 --- a/engine-board.h +++ b/engine-board.h @@ -12,7 +12,6 @@ struct board { Side8 moving_side; bool castling_illegal[SIDE_COUNT][CASTLE_COUNT]; Bb64 ep_targets; - Bb64 occupied[SIDE_COUNT]; int halfmoves; /* FIXME: this duplicates the hist.length value */ int fullmoves; @@ -44,6 +43,7 @@ struct board { .fullmoves = 1, \ .pieces = { \ [SIDE_WHITE] = { \ + [PIECE_EMPTY] = RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6 | RANK_MASK_7 | RANK_MASK_8, \ [PIECE_PAWN] = RANK_MASK_2, \ [PIECE_ROOK] = SQMASK_A1 | SQMASK_H1, \ [PIECE_KNIGHT] = SQMASK_B1 | SQMASK_G1, \ @@ -52,6 +52,7 @@ struct board { [PIECE_KING] = SQMASK_E1, \ }, \ [SIDE_BLACK] = { \ + [PIECE_EMPTY] = RANK_MASK_1 | RANK_MASK_2 | RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6, \ [PIECE_PAWN] = RANK_MASK_7, \ [PIECE_ROOK] = SQMASK_A8 | SQMASK_H8, \ [PIECE_KNIGHT] = SQMASK_B8 | SQMASK_G8, \ @@ -60,16 +61,6 @@ struct board { [PIECE_KING] = SQMASK_E8, \ } \ }, \ - .occupied = { \ - [SIDE_WHITE] = \ - RANK_MASK_2 | SQMASK_A1 | SQMASK_H1 | \ - SQMASK_B1 | SQMASK_G1 | SQMASK_C1 | \ - SQMASK_F1 | SQMASK_D1 | SQMASK_E1, \ - [SIDE_BLACK] = \ - RANK_MASK_7 | SQMASK_A8 | SQMASK_H8 | \ - SQMASK_B8 | SQMASK_G8| SQMASK_C8 | \ - SQMASK_F8| SQMASK_D8| SQMASK_E8, \ - }, \ .hash = ~0ULL, \ }, \ .mailbox = { \ @@ -156,13 +147,20 @@ static bool board_load_fen_unsafe(struct board* b, char const* fen_str) struct piece_side const p = piece_and_side_from_char[(uint8_t)ch]; Bb64 const sq_mask = MASK_FROM_RF(ri, fi); b->pos.pieces[p.side][p.piece] |= sq_mask; - b->pos.occupied[p.side] |= sq_mask; + b->pos.pieces[p.side][PIECE_EMPTY] &= ~sq_mask; b->mailbox[SQ_FROM_RF(ri, fi)] = p.piece; } } (void)BUF_GETCHAR(fen); /* discard '/' or ' ' */ } + b->pos.pieces[SIDE_WHITE][PIECE_EMPTY] = ~0ULL; + b->pos.pieces[SIDE_BLACK][PIECE_EMPTY] = ~0ULL; + for (Piece8 p = PIECE_BEGIN; p < PIECE_COUNT; ++p) { + b->pos.pieces[SIDE_WHITE][PIECE_EMPTY] &= ~b->pos.pieces[SIDE_WHITE][p]; + b->pos.pieces[SIDE_BLACK][PIECE_EMPTY] &= ~b->pos.pieces[SIDE_BLACK][p]; + } + { /* active color */ char const ch = BUF_GETCHAR(fen); if (ch == 'w') { @@ -238,13 +236,6 @@ enum move_result { MR_CHECKMATE, }; -struct move_undo { - Piece8 captured_piece; - Piece8 moved_piece; - Sq8 captured_square; - Sq8 moved_square; -}; - /* does not check validity */ static enum move_result move_piece(struct pos* restrict pos, Side8 us, @@ -252,8 +243,6 @@ static enum move_result move_piece(struct pos* restrict pos, Piece8 mailbox[restrict static SQ_COUNT], struct move move) { - struct move_undo undo; - //Side8 const us = pos->moving_side; Side8 const them = other_side(us); @@ -277,7 +266,7 @@ static enum move_result move_piece(struct pos* restrict pos, do { \ Bb64 const x = MASK_FROM_SQ(from) | MASK_FROM_SQ(to); \ pos->pieces[side][piece] ^= x; \ - pos->occupied[side] ^= x; \ + pos->pieces[side][PIECE_EMPTY] ^= x; \ pos->hash = tt_hash_update(pos->hash, from, side, piece); \ pos->hash = tt_hash_update(pos->hash, to, side, piece); \ mailbox[to] = piece; \ @@ -288,7 +277,7 @@ static enum move_result move_piece(struct pos* restrict pos, do { \ Bb64 const x = MASK_FROM_SQ(at); \ pos->pieces[owner][piece] &= ~x; \ - pos->occupied[owner] &= ~x; \ + pos->pieces[owner][PIECE_EMPTY] |= x; \ pos->hash = tt_hash_update(pos->hash, at, owner, piece); \ hist->length = 0; \ pos->halfmoves = 0; \ @@ -298,7 +287,7 @@ static enum move_result move_piece(struct pos* restrict pos, do { \ Bb64 const x = MASK_FROM_SQ(at); \ pos->pieces[owner][piece] |= x; \ - pos->occupied[owner] |= x; \ + pos->pieces[owner][PIECE_EMPTY] &= ~x; \ pos->hash = tt_hash_update(pos->hash, at, owner, piece); \ mailbox[at] = piece; \ pos->halfmoves = 0; \ @@ -325,7 +314,7 @@ static enum move_result move_piece(struct pos* restrict pos, else { POS_MOVE(us, from_piece, move.from, move.to); /* capture */ - if (to_mask & pos->occupied[them]) { + if (to_mask & ~pos->pieces[them][PIECE_EMPTY]) { POS_REMOVE(them, to_piece, move.to); } diff --git a/engine-move-generation.h b/engine-move-generation.h index d7d1185..c29dff8 100644 --- a/engine-move-generation.h +++ b/engine-move-generation.h @@ -22,23 +22,7 @@ struct move move_make(struct pos const* restrict pos, (void)piece; (void)pos; (void)add_attr; -#if 0 - Side8 const us = pos->moving_side; - Side8 const them = other_side(us); - Bb64 const their_occ = pos->occupied[them]; - Bb64 const tomask = MASK_FROM_SQ(to); - Bb64 const finishline = (us == SIDE_WHITE ? RANK_MASK_8 : RANK_MASK_1); - uint8_t attr = 0ULL; -#define MASK_IF8(x) ((~(uint8_t)0U) + (uint8_t)!(x)) - attr |= MATTR_CAPTURE & MASK_IF8(tomask & their_occ); - attr |= MATTR_CAPTURE & MASK_IF8((piece == PIECE_PAWN) && tomask & pos->ep_targets); - attr |= MATTR_PROMOTE & MASK_IF8((piece == PIECE_PAWN) && (tomask & finishline)); - attr |= add_attr; -#undef MASK_IF8 - - return (struct move){.from = from, .to = to, .attr = attr}; -#endif return (struct move){.from = from, .to = to, .attr = add_attr}; } #define MOVE_MAX 128 @@ -63,8 +47,8 @@ static void all_pseudolegal_from_piece(struct pos const* restrict pos, Side8 them = other_side(us); - Bb64 const our_occ = pos->occupied[us]; - Bb64 const all_occ = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK]; + Bb64 const our_occ = ~pos->pieces[us][PIECE_EMPTY]; + Bb64 const all_occ = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]); if (type == MG_CHECKS) { allowed &= non_pawn_piece_attacks(piece, pos->pieces[them][PIECE_KING], all_occ); @@ -93,7 +77,7 @@ static void all_pseudolegal_pawn_moves_##side(struct pos const* restrict pos,\ size_t* restrict out_count,\ struct move out[restrict static MOVE_MAX])\ {\ - Bb64 const all_occ = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK];\ + Bb64 const all_occ = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]);\ \ if (type == MG_CHECKS) {\ allowed &= pawn_attacks_##opp_side(pos->pieces[other_side(side_enum)][PIECE_KING]);\ @@ -127,7 +111,7 @@ static void all_pseudolegal_pawn_attacks_##side(struct pos const* restrict pos,\ size_t* restrict out_count,\ struct move out[restrict static MOVE_MAX])\ {\ - Bb64 const their_occ = pos->occupied[other_side(side_enum)];\ + Bb64 const their_occ = ~pos->pieces[other_side(side_enum)][PIECE_EMPTY];\ \ if (type == MG_CHECKS) {\ allowed &= pawn_attacks_##opp_side(pos->pieces[other_side(side_enum)][PIECE_KING]);\ @@ -177,7 +161,7 @@ static void all_pseudolegal_moves(struct pos const* restrict pos, Bb64 const their_threats = all_threats_from_side(pos, them); - Bb64 const their_occ = pos->occupied[them]; + Bb64 const their_occ = ~pos->pieces[them][PIECE_EMPTY]; Bb64 allowed; if (type == MG_CAPTURES) { @@ -243,7 +227,7 @@ static void all_pseudolegal_moves(struct pos const* restrict pos, /* castling */ if (!chk && type != MG_CAPTURES) { bool can_castle_kingside, can_castle_queenside; - Bb64 const blocked = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK] | their_threats; + Bb64 const blocked = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]) | their_threats; if (us == SIDE_WHITE) { can_castle_kingside = !(blocked & (SQMASK_F1 | SQMASK_G1)) && (pos->pieces[us][PIECE_ROOK] & SQMASK_H1) diff --git a/engine-types.h b/engine-types.h index 3174dd2..a55b9b6 100644 --- a/engine-types.h +++ b/engine-types.h @@ -195,6 +195,7 @@ static char const* side_str[SIDE_COUNT] = { /* https://en.wikipedia.org/wiki/X_macro */ /* enum value white char white unicode black char black unicode */ #define PIECES \ + X(PIECE_EMPTY, 0.0f, ' ', ' ', ' ', ' ') \ X(PIECE_PAWN, 1.0f, 'P', 0x2659, 'p', 0x265F) \ X(PIECE_KNIGHT, 3.1f, 'N', 0x2658, 'n', 0x265E) \ X(PIECE_BISHOP, 3.2f, 'B', 0x2657, 'b', 0x265D) \ @@ -207,8 +208,8 @@ typedef enum piece : uint8_t { #define X(e, v, wc, wu, bc, bu) e, PIECES PIECE_COUNT, - PIECE_BEGIN = 0, - PIECE_POISONED, /* used as default undefined value in debug builds */ + PIECE_BEGIN = PIECE_PAWN, + PIECE_POISONED, /* used as undefined value in debug builds */ #undef X } Piece8; @@ -273,6 +274,25 @@ struct move { #define APPEAL_MAX UINT8_MAX uint8_t appeal; }; + +/* + * Move32 layout: + * f (from piece): 3 bits + * F (from square): 6 bits + * t (to piece): 3 bits + * T (to square): 6 bits + * p (promotion): ? bits (likely 3) + * [x,x,x,x,x,x,x,x,x,x,x,p,p,p,T,T,T,T,T,T,t,t,t,F,F,F,F,F,F,f,f,f] + * */ + +typedef uint32_t Move32; + +#define MOVE_FROM_PIECE(m) (m & 0b000000000000000000111) +#define MOVE_FROM_SQUARE(m) (m & 0b000000000000111111000) +#define MOVE_TO_PIECE(m) (m & 0b000000000111000000000) +#define MOVE_TO_SQUARE(m) (m & 0b000111111000000000000) +#define MOVE_PROMOTION(m) (m & 0b111000000000000000000) + _Static_assert(sizeof(struct move) == 4, "this static assuming is here to check when sizeof(move) changes"); diff --git a/engine.h b/engine.h index 7c42aec..67fe67e 100644 --- a/engine.h +++ b/engine.h @@ -36,14 +36,17 @@ static void move_compute_appeal(struct move* restrict m, Side8 them = other_side(us); Piece8 const atk = mailbox[m->from]; - uint8_t n = 1; - if (MASK_FROM_SQ(m->to) & pos->occupied[them]) { + uint8_t n = 0; + if ((MASK_FROM_SQ(m->to) & pos->pieces[them][PIECE_EMPTY]) == 0) { n += (uint8_t)piece_value[mailbox[m->to]]; } - uint8_t mmv_lva_bonus = (uint8_t)(16.0f*(float)n - piece_value[atk]); - - m->appeal = mmv_lva_bonus; + /* TODO: remove branch */ + if (n) { + m->appeal = (uint8_t)(16.0f*(float)n - piece_value[atk]); + } else { + m->appeal = 0; + } } static float board_score_heuristic(struct pos const* pos) @@ -52,9 +55,8 @@ static float board_score_heuristic(struct pos const* pos) eventually flipping the sign based on `pos` */ float score = 0.0f; - Bb64 const occw = pos->occupied[SIDE_WHITE]; - Bb64 const occb = pos->occupied[SIDE_BLACK]; - Bb64 const occall = occw | occb; + Bb64 const occw = ~pos->pieces[SIDE_WHITE][PIECE_EMPTY]; + Bb64 const occb = ~pos->pieces[SIDE_BLACK][PIECE_EMPTY]; enum game_progress const gp = endgameness(pos); @@ -172,8 +174,9 @@ float quiesce(struct pos const* pos, all_pseudolegal_moves(pos, MG_CAPTURES, us, &move_count, moves); if (move_count == 0) { - return -(SCORE_CHECKMATE + (float)depth); + return score; } + for (size_t i = 0; i < move_count; ++i) { move_compute_appeal(&moves[i], pos, us, mailbox); } @@ -181,7 +184,7 @@ float quiesce(struct pos const* pos, while (move_count) { struct move m = moves_linear_search(moves, &move_count); - assuming((pos->occupied[them] | pos->ep_targets) & MASK_FROM_SQ(m.to)); + assuming((~pos->pieces[them][PIECE_EMPTY] | pos->ep_targets) & MASK_FROM_SQ(m.to)); struct pos poscpy = *pos; diff --git a/sys.h b/sys.h index 2c72c39..96dd134 100644 --- a/sys.h +++ b/sys.h @@ -20,8 +20,8 @@ static size_t g_buf_len = 0; static void* sys_mmap_anon_shared(size_t size, int, int) { - /* FIXME: this program relies on very few memory allocations, a simple bump - * allocator works for now, but will cause memory leaks in the future */ + /* FIXME: this program relies on very few memory allocations, a simple bump + * allocator works for now, but will cause memory leaks in the future */ size = (size + 7ULL) & ~7ULL; if (g_buf_len + size > sizeof g_buf) { diff --git a/tests.c b/tests.c index 7c3e506..0363008 100644 --- a/tests.c +++ b/tests.c @@ -440,9 +440,6 @@ int main() //board_print_fen(b->pos, stdout); board_print(&b->pos, NULL, stdout, print_threats); - struct move moves[MOVE_MAX]; - size_t move_count; - for (int turn = 0; turn < 200; ++turn) { /* move_count = 0;