diff --git a/engine/bitboard.hpp b/engine/bitboard.hpp index d4f5acc..0a1419a 100644 --- a/engine/bitboard.hpp +++ b/engine/bitboard.hpp @@ -70,7 +70,6 @@ struct Board { uint64_t pawn_hash = 0; uint64_t nonpawn_hashval[2] = {0, 0}; // [side] uint64_t major_hash = 0, minor_hash = 0; - TTable ttable; pzstd::largevector hash_hist; // Mailbox representation of the board for faster queries of certain data @@ -81,12 +80,12 @@ struct Board { std::stack move_hist; std::stack halfmove_hist; - Board(int ttsize=DEFAULT_TT_SIZE) : ttable(ttsize) { + Board() { reset_board(); recompute_hash(); } - Board(std::string fen, int ttsize=DEFAULT_TT_SIZE) : ttable(ttsize) { + Board(std::string fen) { load_fen(fen); recompute_hash(); }; diff --git a/engine/boardstate.hpp b/engine/boardstate.hpp index 9add549..c319079 100644 --- a/engine/boardstate.hpp +++ b/engine/boardstate.hpp @@ -8,5 +8,3 @@ struct BoardState { Accumulator w_acc, b_acc; Piece mailbox[64] = {}; }; - -extern BoardState bs[NINPUTS * 2][NINPUTS * 2]; \ No newline at end of file diff --git a/engine/eval.cpp b/engine/eval.cpp index bae237c..80361e8 100644 --- a/engine/eval.cpp +++ b/engine/eval.cpp @@ -3,73 +3,8 @@ // Accumulator w_acc, b_acc; Network nnue_network; -#ifdef HCE -extern Bitboard king_movetable[64]; - -static constexpr Value king_safety_lookup[9] = {-10, 20, 40, 50, 50, 50, 50, 50, 50}; -static constexpr Value multipawn_lookup[7] = {0, 0, 20, 40, 80, 160, 320}; - -/** - * @brief Boards to denote "good" squares for each piece type - * @details The 8 boards map out an 8-bit signed binary number that represents how good or bad a square is for a piece type. - * @details 127 is the best square for a piece, -128 is the worst. - */ -Bitboard PAWN_SQUARES[8]; -Bitboard KNIGHT_SQUARES[8]; -Bitboard BISHOP_SQUARES[8]; -Bitboard ROOK_SQUARES[8]; -Bitboard QUEEN_SQUARES[8]; -Bitboard KING_SQUARES[8]; -Bitboard KING_ENDGAME_SQUARES[8]; -Bitboard PAWN_ENDGAME_SQUARES[8]; - -Bitboard PASSED_PAWN_MASKS[2][64]; - -__attribute__((constructor)) constexpr void gen_lookups() { - // Convert heatmaps - for (int i = 0; i < 8; i++) { - for (int j = 0; j < 64; j++) { - PAWN_SQUARES[7 - i] |= (((Bitboard)pawn_heatmap[j] >> i) & 1) << j; - KNIGHT_SQUARES[7 - i] |= (((Bitboard)knight_heatmap[j] >> i) & 1) << j; - BISHOP_SQUARES[7 - i] |= (((Bitboard)bishop_heatmap[j] >> i) & 1) << j; - ROOK_SQUARES[7 - i] |= (((Bitboard)rook_heatmap[j] >> i) & 1) << j; - QUEEN_SQUARES[7 - i] |= (((Bitboard)queen_heatmap[j] >> i) & 1) << j; - KING_SQUARES[7 - i] |= (((Bitboard)king_heatmap[j] >> i) & 1) << j; - KING_ENDGAME_SQUARES[7 - i] |= (((Bitboard)endgame_heatmap[j] >> i) & 1) << j; - PAWN_ENDGAME_SQUARES[7 - i] |= (((Bitboard)pawn_endgame[j] >> i) & 1) << j; - } - } - - for (int i = 8; i < 56; i++) { - Bitboard white_mask = 0x0101010101010101ULL << (i + 8); - Bitboard black_mask = 0x8080808080808080ULL >> (71 - i); - PASSED_PAWN_MASKS[WHITE][i] = white_mask | ((white_mask << 1) & 0x0101010101010101) | ((white_mask >> 1) & 0x8080808080808080); - PASSED_PAWN_MASKS[BLACK][i] = black_mask | ((black_mask << 1) & 0x0101010101010101) | ((black_mask >> 1) & 0x8080808080808080); - } -} - -float multi(int x) { - // If there are fewer pieces on the board, we should raise the magnitude of the eval - // This allows for the engine to prioritize trading pieces when ahead, especially in the endgame - // The main caveat is that this may cause the engine to draw by insufficient material - int diff = std::min(32 - x, 20); // Number of pieces taken off the board - return 1.0 + 0.02 * diff; -} -#endif - __attribute__((constructor)) void init_network() { -#ifndef HCE nnue_network.load(); - for (int j = 0; j < NINPUTS * 2; j++) { - for (int k = 0; k < NINPUTS * 2; k++) { - for (int i = 0; i < HL_SIZE; i++) { - bs[j][k].w_acc.val[i] = nnue_network.accumulator_biases[i]; - bs[j][k].b_acc.val[i] = nnue_network.accumulator_biases[i]; - } - for (int i = 0; i < 64; i++) bs[j][k].mailbox[i] = NO_PIECE; - } - } -#endif } Value simple_eval(Board &board) { @@ -81,165 +16,7 @@ Value simple_eval(Board &board) { return score; } -#ifdef HCE -Value eval(Board &board) { - if (!(board.piece_boards[KING] & board.piece_boards[OCC(BLACK)])) { - // If black has no king, this is mate for white - return VALUE_MATE; - } - if (!(board.piece_boards[KING] & board.piece_boards[OCC(WHITE)])) { - // Likewise, if white has no king, this is mate for black - return -VALUE_MATE; - } - - Value material = 0; - Value piecesquare = 0; - Value castling = 0; - Value bishop_pair = 0; - Value king_safety = 0; - Value tempo_bonus = 0; - Value pawn_structure = 0; - - material += PawnValue * _mm_popcnt_u64(board.piece_boards[PAWN] & board.piece_boards[OCC(WHITE)]); - material += KnightValue * _mm_popcnt_u64(board.piece_boards[KNIGHT] & board.piece_boards[OCC(WHITE)]); - material += BishopValue * _mm_popcnt_u64(board.piece_boards[BISHOP] & board.piece_boards[OCC(WHITE)]); - material += RookValue * _mm_popcnt_u64(board.piece_boards[ROOK] & board.piece_boards[OCC(WHITE)]); - material += QueenValue * _mm_popcnt_u64(board.piece_boards[QUEEN] & board.piece_boards[OCC(WHITE)]); - material -= PawnValue * _mm_popcnt_u64(board.piece_boards[PAWN] & board.piece_boards[OCC(BLACK)]); - material -= KnightValue * _mm_popcnt_u64(board.piece_boards[KNIGHT] & board.piece_boards[OCC(BLACK)]); - material -= BishopValue * _mm_popcnt_u64(board.piece_boards[BISHOP] & board.piece_boards[OCC(BLACK)]); - material -= RookValue * _mm_popcnt_u64(board.piece_boards[ROOK] & board.piece_boards[OCC(BLACK)]); - material -= QueenValue * _mm_popcnt_u64(board.piece_boards[QUEEN] & board.piece_boards[OCC(BLACK)]); - - // Decide between normal vs endgame king map - const Bitboard *funny = _mm_popcnt_u64(board.piece_boards[OCC(WHITE)] | board.piece_boards[OCC(BLACK)]) >= 10 ? KING_SQUARES : KING_ENDGAME_SQUARES; - const Bitboard *pawn = _mm_popcnt_u64(board.piece_boards[OCC(WHITE)] | board.piece_boards[OCC(BLACK)]) >= 10 ? PAWN_SQUARES : PAWN_ENDGAME_SQUARES; - // Initialize accumulators - int8_t pawn_acc, knight_acc, bishop_acc, rook_acc, queen_acc, king_acc; - int8_t pawn_acc_black, knight_acc_black, bishop_acc_black, rook_acc_black, queen_acc_black, king_acc_black; - pawn_acc = knight_acc = bishop_acc = rook_acc = queen_acc = king_acc = 0; - pawn_acc_black = knight_acc_black = bishop_acc_black = rook_acc_black = queen_acc_black = king_acc_black = 0; - - // Precompute piece boards (flipping for black) - Bitboard boards[12]; - for (int i = 0; i < 6; i++) { - boards[i] = board.piece_boards[i] & board.piece_boards[OCC(WHITE)]; -#ifndef WINDOWS - boards[i + 6] = __bswap_64(board.piece_boards[i] & board.piece_boards[OCC(BLACK)]); -#else - boards[i + 6] = _byteswap_ulong(board.piece_boards[i] & board.piece_boards[OCC(BLACK)]); -#endif - } - - for (int i = 0; i < 8; i++) { - pawn_acc = pawn_acc * 2 + _mm_popcnt_u64(boards[0] & pawn[i]); - knight_acc = knight_acc * 2 + _mm_popcnt_u64(boards[1] & KNIGHT_SQUARES[i]); - bishop_acc = bishop_acc * 2 + _mm_popcnt_u64(boards[2] & BISHOP_SQUARES[i]); - rook_acc = rook_acc * 2 + _mm_popcnt_u64(boards[3] & ROOK_SQUARES[i]); - queen_acc = queen_acc * 2 + _mm_popcnt_u64(boards[4] & QUEEN_SQUARES[i]); - king_acc = king_acc * 2 + _mm_popcnt_u64(boards[5] & funny[i]); - - pawn_acc_black = pawn_acc_black * 2 + _mm_popcnt_u64(boards[6] & pawn[i]); - knight_acc_black = knight_acc_black * 2 + _mm_popcnt_u64(boards[7] & KNIGHT_SQUARES[i]); - bishop_acc_black = bishop_acc_black * 2 + _mm_popcnt_u64(boards[8] & BISHOP_SQUARES[i]); - rook_acc_black = rook_acc_black * 2 + _mm_popcnt_u64(boards[9] & ROOK_SQUARES[i]); - queen_acc_black = queen_acc_black * 2 + _mm_popcnt_u64(boards[10] & QUEEN_SQUARES[i]); - king_acc_black = king_acc_black * 2 + _mm_popcnt_u64(boards[11] & funny[i]); - } - piecesquare += pawn_acc + knight_acc + bishop_acc + rook_acc + queen_acc + king_acc; - piecesquare -= pawn_acc_black + knight_acc_black + bishop_acc_black + rook_acc_black + queen_acc_black + king_acc_black; - - castling += (board.castling & WHITE_OO) ? 5 : 0; - castling += (board.castling & WHITE_OOO) ? 5 : 0; - castling -= (board.castling & BLACK_OO) ? 5 : 0; - castling -= (board.castling & BLACK_OOO) ? 5 : 0; - - bishop_pair += _mm_popcnt_u64(board.piece_boards[BISHOP] & board.piece_boards[OCC(WHITE)]) >= 2 ? 30 : 0; - bishop_pair -= _mm_popcnt_u64(board.piece_boards[BISHOP] & board.piece_boards[OCC(BLACK)]) >= 2 ? 30 : 0; - - // For king safety, check for opponent control on squares around the king - // As well as counting our own pieces in front of the king - - king_safety += king_safety_lookup[_mm_popcnt_u64( - king_movetable[__tzcnt_u64(board.piece_boards[KING] & board.piece_boards[OCC(WHITE)])] & board.piece_boards[OCC(WHITE)] - )]; - king_safety -= king_safety_lookup[_mm_popcnt_u64( - king_movetable[__tzcnt_u64(board.piece_boards[KING] & board.piece_boards[OCC(BLACK)])] & board.piece_boards[OCC(BLACK)] - )]; - Bitboard white_king = (board.piece_boards[KING] & board.piece_boards[OCC(WHITE)]); - Bitboard black_king = (board.piece_boards[KING] & board.piece_boards[OCC(BLACK)]); - if (white_king & (square_bits(SQ_G1) | square_bits(SQ_H1))) { - std::pair control = board.control(SQ_F2); - king_safety -= std::max(0, control.second - control.first) * 10; - control = board.control(SQ_G2); - king_safety -= std::max(0, control.second - control.first) * 15; - control = board.control(SQ_H2); - king_safety -= std::max(0, control.second - control.first) * 15; - } else if (white_king & (square_bits(SQ_B1) | square_bits(SQ_C1))) { - std::pair control = board.control(SQ_A2); - king_safety -= std::max(0, control.second - control.first) * 12; - control = board.control(SQ_B2); - king_safety -= std::max(0, control.second - control.first) * 15; - control = board.control(SQ_C2); - king_safety -= std::max(0, control.second - control.first) * 15; - } - - if (black_king & (square_bits(SQ_G8) | square_bits(SQ_H8))) { - std::pair control = board.control(SQ_F7); - king_safety += std::max(0, control.first - control.second) * 10; - control = board.control(SQ_G7); - king_safety += std::max(0, control.first - control.second) * 15; - control = board.control(SQ_H7); - king_safety += std::max(0, control.first - control.second) * 15; - } else if (black_king & (square_bits(SQ_B8) | square_bits(SQ_C8))) { - std::pair control = board.control(SQ_A7); - king_safety += std::max(0, control.first - control.second) * 12; - control = board.control(SQ_B7); - king_safety += std::max(0, control.first - control.second) * 15; - control = board.control(SQ_C7); - king_safety += std::max(0, control.first - control.second) * 15; - } - - tempo_bonus += board.side == WHITE ? 10 : -10; - - for (Bitboard mask = 0x0101010101010101; mask & 0xff; mask <<= 1) { - // Doubled pawns - pawn_structure -= multipawn_lookup[_mm_popcnt_u64(board.piece_boards[PAWN] & board.piece_boards[OCC(WHITE)] & mask)]; - pawn_structure += multipawn_lookup[_mm_popcnt_u64(board.piece_boards[PAWN] & board.piece_boards[OCC(BLACK)] & mask)]; - - // Isolated pawns - if (mask & 0b01111110) { - if ((board.piece_boards[PAWN] & board.piece_boards[OCC(WHITE)] & ((mask << 1) | (mask >> 1))) == 0) - pawn_structure -= _mm_popcnt_u64(board.piece_boards[PAWN] & board.piece_boards[OCC(WHITE)] & mask) ? 60 : 0; - if ((board.piece_boards[PAWN] & board.piece_boards[OCC(BLACK)] & ((mask << 1) | (mask >> 1))) == 0) - pawn_structure += _mm_popcnt_u64(board.piece_boards[PAWN] & board.piece_boards[OCC(BLACK)] & mask) ? 60 : 0; - } - } - - Bitboard pawns = board.piece_boards[PAWN] & board.piece_boards[OCC(WHITE)]; - while (pawns) { - int sq = _tzcnt_u64(pawns); - pawn_structure += (board.piece_boards[PAWN] & PASSED_PAWN_MASKS[WHITE][sq]) == 0 ? 80 : 0; - pawns = _blsr_u64(pawns); - } - pawns = board.piece_boards[PAWN] & board.piece_boards[OCC(BLACK)]; - while (pawns) { - int sq = _tzcnt_u64(pawns); - pawn_structure -= (board.piece_boards[PAWN] & PASSED_PAWN_MASKS[BLACK][sq]) == 0 ? 80 : 0; - pawns = _blsr_u64(pawns); - } - - int npieces = _mm_popcnt_u64(board.piece_boards[OCC(WHITE)] | board.piece_boards[OCC(BLACK)]); - - return ((int)material * 3 + (int)piecesquare + (int)castling + (int)bishop_pair + (int)king_safety * 2 + (int)tempo_bonus + (int)pawn_structure) * - multi(npieces); -} - -std::array debug_eval(Board &board) { - return {eval(board), 0, 0, 0, 0, 0, 0, 0}; -} -#else -Value eval(Board &board) { +Value eval(Board &board, BoardState *bs) { if (!(board.piece_boards[KING] & board.piece_boards[OCC(BLACK)])) { // If black has no king, this is mate for white return VALUE_MATE; @@ -257,9 +34,12 @@ Value eval(Board &board) { int winbucket = IBUCKET_LAYOUT[wkingsq]; int binbucket = IBUCKET_LAYOUT[bkingsq ^ 56]; + // Convert bs to usable format + BoardState &state = *(bs + winbucket * NINPUTS * 2 + binbucket); + for (uint16_t i = 0; i < 64; i++) { Piece piece = board.mailbox[i]; - Piece prevpiece = bs[winbucket][binbucket].mailbox[i]; + Piece prevpiece = state.mailbox[i]; if (piece == prevpiece) continue; bool side = piece >> 3; // 1 = black, 0 = white bool prevside = prevpiece >> 3; // 1 = black, 0 = white @@ -269,28 +49,28 @@ Value eval(Board &board) { if (piece != NO_PIECE) { // Add to accumulator uint16_t w_index = calculate_index((Square)i, pt, side, 0, winbucket); - accumulator_add(nnue_network, bs[winbucket][binbucket].w_acc, w_index); + accumulator_add(nnue_network, state.w_acc, w_index); uint16_t b_index = calculate_index((Square)i, pt, side, 1, binbucket); - accumulator_add(nnue_network, bs[winbucket][binbucket].b_acc, b_index); + accumulator_add(nnue_network, state.b_acc, b_index); } if (prevpiece != NO_PIECE) { // Subtract from accumulator uint16_t w_index = calculate_index((Square)i, prevpt, prevside, 0, winbucket); - accumulator_sub(nnue_network, bs[winbucket][binbucket].w_acc, w_index); + accumulator_sub(nnue_network, state.w_acc, w_index); uint16_t b_index = calculate_index((Square)i, prevpt, prevside, 1, binbucket); - accumulator_sub(nnue_network, bs[winbucket][binbucket].b_acc, b_index); + accumulator_sub(nnue_network, state.b_acc, b_index); } } - memcpy(bs[winbucket][binbucket].mailbox, board.mailbox, sizeof(bs[winbucket][binbucket].mailbox)); + memcpy(state.mailbox, board.mailbox, sizeof(state.mailbox)); int nbucket = (npieces - 2) / 4; if (board.side == WHITE) { - score = nnue_eval(nnue_network, bs[winbucket][binbucket].w_acc, bs[winbucket][binbucket].b_acc, nbucket); + score = nnue_eval(nnue_network, state.w_acc, state.b_acc, nbucket); } else { - score = -nnue_eval(nnue_network, bs[winbucket][binbucket].b_acc, bs[winbucket][binbucket].w_acc, nbucket); + score = -nnue_eval(nnue_network, state.b_acc, state.w_acc, nbucket); } return score; } @@ -313,49 +93,39 @@ std::array debug_eval(Board &board) { int winbucket = IBUCKET_LAYOUT[wkingsq]; int binbucket = IBUCKET_LAYOUT[bkingsq ^ 56]; + Accumulator w_acc, b_acc; + for (int i = 0; i < HL_SIZE; i++) { + w_acc.val[i] = nnue_network.accumulator_biases[i]; + b_acc.val[i] = nnue_network.accumulator_biases[i]; + } + // Query the NNUE network for (uint16_t i = 0; i < 64; i++) { Piece piece = board.mailbox[i]; - Piece prevpiece = bs[winbucket][binbucket].mailbox[i]; - if (piece == prevpiece) - continue; bool side = piece >> 3; // 1 = black, 0 = white - bool prevside = prevpiece >> 3; // 1 = black, 0 = white PieceType pt = PieceType(piece & 7); - PieceType prevpt = PieceType(prevpiece & 7); if (piece != NO_PIECE) { // Add to accumulator uint16_t w_index = calculate_index((Square)i, pt, side, 0, winbucket); - accumulator_add(nnue_network, bs[winbucket][binbucket].w_acc, w_index); + accumulator_add(nnue_network, w_acc, w_index); uint16_t b_index = calculate_index((Square)i, pt, side, 1, binbucket); - accumulator_add(nnue_network, bs[winbucket][binbucket].b_acc, b_index); - } - - if (prevpiece != NO_PIECE) { - // Subtract from accumulator - uint16_t w_index = calculate_index((Square)i, prevpt, prevside, 0, winbucket); - accumulator_sub(nnue_network, bs[winbucket][binbucket].w_acc, w_index); - uint16_t b_index = calculate_index((Square)i, prevpt, prevside, 1, binbucket); - accumulator_sub(nnue_network, bs[winbucket][binbucket].b_acc, b_index); + accumulator_add(nnue_network, b_acc, b_index); } } - memcpy(bs[winbucket][binbucket].mailbox, board.mailbox, sizeof(bs[winbucket][binbucket].mailbox)); - int npieces = _mm_popcnt_u64(board.piece_boards[OCC(WHITE)] | board.piece_boards[OCC(BLACK)]); std::array score = {}; if (board.side == WHITE) { for (int i = 0; i < 8; i++) { - score[i] = nnue_eval(nnue_network, bs[winbucket][binbucket].w_acc, bs[winbucket][binbucket].b_acc, i); + score[i] = nnue_eval(nnue_network, w_acc, b_acc, i); } } else { for (int i = 0; i < 8; i++) { - score[i] = -nnue_eval(nnue_network, bs[winbucket][binbucket].b_acc, bs[winbucket][binbucket].w_acc, i); + score[i] = -nnue_eval(nnue_network, b_acc, w_acc, i); } } return score; } -#endif diff --git a/engine/eval.hpp b/engine/eval.hpp index c4bde42..6e44b28 100644 --- a/engine/eval.hpp +++ b/engine/eval.hpp @@ -5,109 +5,14 @@ #include "includes.hpp" #include "boardstate.hpp" +extern Network nnue_network; + Value simple_eval(Board &board); -Value eval(Board &board); +Value eval(Board &board, BoardState *bs); std::array debug_eval(Board &board); -#ifdef HCE -constexpr int pawn_heatmap[64] = { - // a b c d e f g h - 0, 0, 0, 0, 0, 0, 0, 0, // 1 - 5, 10, 10, -40, -40, 10, 10, 5, // 2 - 5, -5, -10, 0, 0, -10, -5, 5, // 3 - 0, 0, 0, 30, 30, 0, 0, 0, // 4 - 5, 5, 10, 40, 40, 10, 5, 5, // 5 - 10, 10, 50, 60, 60, 50, 10, 10, // 6 - 80, 80, 80, 80, 80, 80, 80, 80, // 7 - 0, 0, 0, 0, 0, 0, 0, 0, // 8 -}; - -constexpr int knight_heatmap[64] = { - // a b c d e f g h - -50, -40, -30, -30, -30, -30, -40, -50, // 1 - -40, -20, 0, 5, 5, 0, -20, -40, // 2 - -30, 5, 10, 15, 15, 10, 5, -30, // 3 - -30, 0, 15, 20, 20, 15, 0, -30, // 4 - -30, 5, 15, 20, 20, 15, 5, -30, // 5 - -30, 0, 10, 15, 15, 10, 0, -30, // 6 - -40, -20, 0, 0, 0, 0, -20, -40, // 7 - -50, -40, -30, -30, -30, -30, -40, -50, // 8 -}; - -constexpr int bishop_heatmap[64] = { - // a b c d e f g h - -20, -10, -10, -10, -10, -10, -10, -20, // 1 - -10, 5, 0, 0, 0, 0, 5, -10, // 2 - -10, 10, 10, 10, 10, 10, 10, -10, // 3 - -10, 0, 10, 10, 10, 10, 0, -10, // 4 - -10, 5, 5, 10, 10, 5, 5, -10, // 5 - -10, 0, 5, 10, 10, 5, 0, -10, // 6 - -30, 0, 0, 0, 0, 0, 0, -30, // 7 - -20, -10, -10, -10, -10, -10, -10, -20, // 8 -}; - -constexpr int rook_heatmap[64] = { - // a b c d e f g h - -10, 0, 0, 10, 10, 5, 0, -10, // 1 - -5, 0, 0, 0, 0, 0, 0, -5, // 2 - -5, 0, 0, 0, 0, 0, 0, -5, // 3 - -5, 0, 0, 0, 0, 0, 0, -5, // 4 - -5, 0, 0, 0, 0, 0, 0, -5, // 5 - -5, 0, 0, 0, 0, 0, 0, -5, // 6 - -10, 0, 0, 0, 0, 0, 0, -10, // 7 - 0, 0, 0, 0, 0, 0, 0, 0, // 8 -}; - -constexpr int queen_heatmap[64] = { - // a b c d e f g h - -20, -10, -10, -5, -5, -10, -10, -20, // 1 - -10, 0, 5, 0, 0, 0, 0, -10, // 2 - -10, 5, 5, 5, 5, 5, 0, -10, // 3 - -5, 0, 5, 5, 5, 5, 0, -5, // 4 - 0, 0, 5, 5, 5, 5, 0, -5, // 5 - -10, 0, 5, 5, 5, 5, 0, -10, // 6 - -10, 0, 0, 0, 0, 0, 0, -10, // 7 - -20, -10, -10, -5, -5, -10, -10, -20, // 8 -}; - -constexpr int king_heatmap[64] = { - // a b c d e f g h - 30, 50, 40, 0, 0, 10, 50, 30, // 1 - 20, 20, -5, -5, -5, -5, 20, 20, // 2 - -10, -20, -20, -20, -20, -20, -20, -10, // 3 - -20, -30, -30, -40, -40, -30, -30, -20, // 4 - -30, -40, -40, -50, -50, -40, -40, -30, // 5 - -30, -40, -40, -50, -50, -40, -40, -30, // 6 - -30, -40, -40, -50, -50, -40, -40, -30, // 7 - -30, -40, -40, -50, -50, -40, -40, -30, // 8 -}; - -constexpr int endgame_heatmap[64] = { - // a b c d e f g h - 1, 2, 4, 8, 8, 4, 2, 1, // 1 - 2, 4, 8, 16, 16, 8, 4, 2, // 2 - 4, 8, 16, 32, 32, 16, 8, 4, // 3 - 8, 16, 32, 64, 64, 32, 16, 8, // 4 - 8, 16, 32, 64, 64, 32, 16, 8, // 5 - 4, 8, 16, 32, 32, 16, 8, 4, // 6 - 2, 4, 8, 16, 16, 8, 4, 2, // 7 - 1, 2, 4, 8, 8, 4, 2, 1, // 8 -}; - -constexpr int pawn_endgame[64] = { - // a b c d e f g h - 0, 0, 0, 0, 0, 0, 0, 0, // 1 - -10, -10, -10, -10, -10, -10, -10, -10, // 2 - 5, 5, 5, 5, 5, 5, 5, 5, // 3 - 10, 10, 10, 10, 10, 10, 10, 10, // 4 - 20, 20, 20, 20, 20, 20, 20, 20, // 5 - 60, 60, 60, 60, 60, 60, 60, 60, // 6 - 100, 100, 100, 100, 100, 100, 100, 100, // 7 - 0, 0, 0, 0, 0, 0, 0, 0, // 8 -}; -#else constexpr int IBUCKET_LAYOUT[] = { 0, 0, 2, 2, 3, 3, 1, 1, 0, 0, 2, 2, 3, 3, 1, 1, @@ -118,4 +23,3 @@ constexpr int IBUCKET_LAYOUT[] = { 6, 6, 6, 6, 7, 7, 7, 7, 6, 6, 6, 6, 7, 7, 7, 7, }; -#endif \ No newline at end of file diff --git a/engine/includes.hpp b/engine/includes.hpp index 4bc9163..bbb6e6f 100644 --- a/engine/includes.hpp +++ b/engine/includes.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include #include #include "pzstl/vector.hpp" @@ -24,6 +26,7 @@ constexpr bool WHITE = false; constexpr bool BLACK = true; constexpr int MAX_PLY = 300; +constexpr int MAX_THREADS = 64; typedef int16_t Value; constexpr Value VALUE_ZERO = 0; diff --git a/engine/main.cpp b/engine/main.cpp index 93cfea4..7fe2850 100644 --- a/engine/main.cpp +++ b/engine/main.cpp @@ -9,24 +9,24 @@ #include "movegen.hpp" #include "movetimings.hpp" #include "search.hpp" - -BoardState bs[NINPUTS * 2][NINPUTS * 2]; +#include "ttable.hpp" // Options int TT_SIZE = DEFAULT_TT_SIZE; bool quiet = false, online = false; -int multipv = 1; + +ThreadInfo *tis; void run_uci() { + std::thread searchthread; std::string command; - Board board = Board(TT_SIZE); + Board board = Board(); while (getline(std::cin, command)) { if (command == "uci") { std::cout << "id name PZChessBot " << VERSION << std::endl; std::cout << "id author kevlu8 and wdotmathree" << std::endl; std::cout << "option name Hash type spin default 16 min 1 max 1024" << std::endl; - std::cout << "option name Threads type spin default 1 min 1 max 1" << std::endl; // Not implemented yet - std::cout << "option name MultiPV type spin default 1 min 1 max 256" << std::endl; + std::cout << "option name Threads type spin default 1 min 1 max 64" << std::endl; std::cout << "option name Quiet type check default false" << std::endl; std::cout << "uciok" << std::endl; } else if (command == "icu") { @@ -50,15 +50,26 @@ void run_uci() { std::cerr << "Invalid hash size: " << optionint << std::endl; continue; } - TT_SIZE = optionint * 1024 * 1024 / sizeof(TTable::TTBucket); + TT_SIZE = optionint * 1024 * 1024 / sizeof(TTable::TTEntry); } else if (optionname == "Quiet") { quiet = optionvalue == "true"; - } else if (optionname == "MultiPV") { - multipv = std::stoi(optionvalue); + } else if (optionname == "Threads") { + num_threads = std::stoi(optionvalue); + if (num_threads < 1 || num_threads > 64) { + std::cerr << "Invalid number of threads: " << num_threads << std::endl; + num_threads = 1; + } + delete[] tis; + tis = new ThreadInfo[num_threads]; + for (int i = 0; i < num_threads; i++) tis[i].set_bs(); + std::cout << "info string Using " << num_threads << " threads" << std::endl; } } else if (command == "ucinewgame") { - board = Board(TT_SIZE); - clear_search_vars(); + board = Board(); + ttable.resize(TT_SIZE); + for (int i = 0; i < num_threads; i++) { + clear_search_vars(tis[i]); + } } else if (command.substr(0, 8) == "position") { // either `position startpos` or `position fen ...` if (command.find("startpos") != std::string::npos) { @@ -79,12 +90,11 @@ void run_uci() { } } } else if (command == "quit") { + if (searchthread.joinable()) searchthread.join(); exit(0); } else if (command == "stop") { - // stop the search thread - // if (searchthread.joinable()) { - // searchthread.join(); - // } + stop_search = true; + if (searchthread.joinable()) searchthread.join(); } else if (command == "eval") { std::array score = debug_eval(board); board.print_board(); @@ -98,6 +108,8 @@ void run_uci() { std::cout << std::endl; } } else if (command.substr(0, 2) == "go") { + if (!stop_search) continue; // ignore + if (searchthread.joinable()) searchthread.join(); #ifndef HCE std::cout << "info string Using " << NNUE_PATH << " for evaluation" << std::endl; #endif @@ -132,26 +144,23 @@ void run_uci() { } int timeleft = board.side ? btime : wtime; int inc = board.side ? binc : winc; - std::pair res; - if (multipv != 1) { - if (inf) res = search_multipv(board, multipv, 1e9, MAX_PLY, 1e18, quiet)[0]; - else if (depth != -1) res = search_multipv(board, multipv, 1e9, depth, 1e18, quiet)[0]; - else if (nodes != -1) res = search_multipv(board, multipv, 1e9, MAX_PLY, nodes, quiet)[0]; - else if (movetime != -1) res = search_multipv(board, multipv, movetime, MAX_PLY, 1e18, quiet)[0]; - else res = search_multipv(board, multipv, 1e9, MAX_PLY, 1e18, quiet)[0]; - } else { - if (inf) res = search(board, 1e9, MAX_PLY, 1e18, quiet); - else if (depth != -1) res = search(board, 1e9, depth, 1e18, quiet); - else if (nodes != -1) res = search(board, 1e9, MAX_PLY, nodes, quiet); - else if (movetime != -1) res = search(board, movetime, MAX_PLY, 1e18, quiet); - else res = search(board, timemgmt(timeleft, inc, online), MAX_PLY, 1e18, quiet); - } - std::cout << "bestmove " << res.first.to_string() << std::endl; + searchthread = std::thread( + [&]() { + std::cout << "info string Starting search..." << std::endl; + if (inf) search(board, tis, 1e18, MAX_PLY, 1e18, 0); + else if (depth != -1) search(board, tis, 1e18, depth, 1e18, 0); + else if (nodes != -1) search(board, tis, 1e18, MAX_PLY, nodes, 0); + else if (movetime != -1) search(board, tis, movetime, MAX_PLY, 1e18, 0); + else search(board, tis, timemgmt(timeleft, inc, online), MAX_PLY, 1e18, 0); + } + ); } } } __attribute__((weak)) int main(int argc, char *argv[]) { + tis = new ThreadInfo[1]; // single thread for now + tis[0].set_bs(); if (argc == 2 && std::string(argv[1]) == "bench") { const std::string bench_positions[] = { "r3k2r/2pb1ppp/2pp1q2/p7/1nP1B3/1P2P3/P2N1PPP/R2QK2R w KQkq - 0 14", @@ -205,17 +214,17 @@ __attribute__((weak)) int main(int argc, char *argv[]) { "3br1k1/p1pn3p/1p3n2/5pNq/2P1p3/1PN3PP/P2Q1PB1/4R1K1 w - - 0 23", "2r2b2/5p2/5k2/p1r1pP2/P2pB3/1P3P2/K1P3R1/7R w - - 23 93", }; - Board board = Board(TT_SIZE); + Board board = Board(); uint64_t tot_nodes = 0; uint64_t start = clock(); for (const auto &fen : bench_positions) { board.reset(fen); - clear_search_vars(); - search(board, 1e9, 12, 1e18, 0); - tot_nodes += nodes; + clear_search_vars(tis[0]); + search(board, tis, 1e9, 12, 1e18, 0); + tot_nodes += nodes[0]; } uint64_t end = clock(); - std::cout << tot_nodes << " nodes " << (tot_nodes / ((double)(end - start) / CLOCKS_PER_SEC)) << " nps" << std::endl; + std::cout << tot_nodes << " nodes " << int(tot_nodes / ((double)(end - start) / CLOCKS_PER_SEC)) << " nps" << std::endl; return 0; } if (argc == 3 && std::string(argv[2]) == "quit") { @@ -241,7 +250,7 @@ __attribute__((weak)) int main(int argc, char *argv[]) { ss >> nmoves; } } - Board board = Board(TT_SIZE); + Board board = Board(); std::mt19937_64 rng(s); std::ifstream bookfile(book == "None" ? "" : book); std::vector fens; @@ -281,10 +290,11 @@ __attribute__((weak)) int main(int argc, char *argv[]) { if (!restart) { if (_mm_popcnt_u64(board.piece_boards[KING]) != 2) restart = true; else if (filter_weird) { - auto s_eval = eval(board); + int npieces = _mm_popcnt_u64(board.piece_boards[OCC(WHITE)] | board.piece_boards[OCC(BLACK)]); + auto s_eval = debug_eval(board)[(npieces - 2) / 4] * (board.side == WHITE ? 1 : -1); if (abs(s_eval) >= 600) restart = true; // do a fast static eval to quickly filter out crazy positions else { - auto res = search(board, 1e9, MAX_PLY, 10000, 1); + auto res = search(board, tis, 1e9, MAX_PLY, 10000, 1); if (abs(res.second) >= 400) restart = true; } } @@ -300,15 +310,14 @@ __attribute__((weak)) int main(int argc, char *argv[]) { online = argc >= 2 && std::string(argv[1]) == "--online=1"; std::cout << "PZChessBot " << VERSION << " developed by kevlu8 and wdotmathree" << std::endl; std::string command; - Board board = Board(TT_SIZE); + Board board = Board(); std::thread searchthread; while (getline(std::cin, command)) { if (command == "uci") { std::cout << "id name PZChessBot " << VERSION << std::endl; std::cout << "id author kevlu8 and wdotmathree" << std::endl; std::cout << "option name Hash type spin default 16 min 1 max 1024" << std::endl; - std::cout << "option name Threads type spin default 1 min 1 max 1" << std::endl; // Not implemented yet - std::cout << "option name MultiPV type spin default 1 min 1 max 256" << std::endl; + std::cout << "option name Threads type spin default 1 min 1 max 64" << std::endl; std::cout << "option name Quiet type check default false" << std::endl; std::cout << "uciok" << std::endl; run_uci(); @@ -390,7 +399,7 @@ __attribute__((weak)) int main(int argc, char *argv[]) { } } else if (command.substr(0, 2) == "go") { int ms = std::stoi(command.substr(3)); - auto res = search(board, ms, MAX_PLY, 1e18, 2); // Use quiet level 2 for pretty output + auto res = search(board, tis, ms, MAX_PLY, 1e18, 2); // Use quiet level 2 for pretty output std::cout << CYAN "Best move: " RESET BOLD << res.first.to_string() << RESET << CYAN " with score: " RESET << (res.second * (board.side == BLACK ? -1 : 1) > 0 ? GREEN : RED) << std::showpos << res.second * (board.side == BLACK ? -1 : 1) << " cp" << RESET << std::endl << std::noshowpos; @@ -398,10 +407,11 @@ __attribute__((weak)) int main(int argc, char *argv[]) { board.unmake_move(); board.print_board_pretty(); } else if (command == "reset") { - board = Board(TT_SIZE); + board = Board(); + ttable.resize(TT_SIZE); std::cout << "Done" << std::endl; } else if (command.substr(0, 3) == "fen") { - board = Board(TT_SIZE); + board = Board(); std::string fen = command.substr(4); board.reset(fen); std::cout << "Done" << std::endl; diff --git a/engine/nnue/network.hpp b/engine/nnue/network.hpp index 3127188..b57cdbc 100644 --- a/engine/nnue/network.hpp +++ b/engine/nnue/network.hpp @@ -29,4 +29,6 @@ void accumulator_add(const Network &net, Accumulator &acc, uint16_t index); void accumulator_sub(const Network &net, Accumulator &acc, uint16_t index); -int32_t nnue_eval(const Network &net, const Accumulator &stm, const Accumulator &ntm, uint8_t nbucket); \ No newline at end of file +int32_t nnue_eval(const Network &net, const Accumulator &stm, const Accumulator &ntm, uint8_t nbucket); + +extern Network nnue_network; diff --git a/engine/search.cpp b/engine/search.cpp index 8dc31a1..e8990c0 100644 --- a/engine/search.cpp +++ b/engine/search.cpp @@ -3,12 +3,15 @@ #define MOVENUM(x) ((((#x)[1] - '1') << 12) | (((#x)[0] - 'a') << 8) | (((#x)[3] - '1') << 4) | ((#x)[2] - 'a')) -uint64_t nodes = 0; // Node count -int seldepth = 0; // Maximum searched depth, including quiescence search uint64_t mx_nodes = 1e18; // Maximum nodes to search -uint64_t mxtime = 1000; // Maximum time to search in milliseconds -bool early_exit = false, exit_allowed = false; // Whether or not to exit the search, and if we are allowed to exit (so we don't exit on the depth 1) -clock_t start = 0; +bool stop_search = true; +std::chrono::steady_clock::time_point start; +uint64_t mxtime = 1e18; // Maximum time to search in milliseconds + +uint16_t num_threads = 1; + +std::atomic nodecnt[64][64] = {{}}; +uint64_t nodes[MAX_THREADS] = {}; uint64_t perft(Board &board, int depth) { // If white's turn is beginning and black is in check @@ -66,15 +69,6 @@ __attribute__((constructor)) void init_mvvlva() { } } -History main_hist; - -SSEntry line[MAX_PLY]; // Currently searched line - -Move pvtable[MAX_PLY][MAX_PLY]; -int pvlen[MAX_PLY]; - -uint64_t nodecnt[64][64]; - Move next_move(pzstd::vector> &scores, int &end) { if (end == 0) return NullMove; // Ran out Move best_move = NullMove; @@ -111,8 +105,7 @@ Value tt_to_score(Value score, int ply) { } } -double get_ttable_sz(Board &board) { - TTable &ttable = board.ttable; +double get_ttable_sz() { int cnt = 0; for (int i = 0; i < 1024; i++) { if (i >= ttable.TT_SIZE) break; @@ -122,6 +115,16 @@ double get_ttable_sz(Board &board) { return cnt / 2048.0; } +std::string score_to_uci(Value score) { + if (score >= VALUE_MATE_MAX_PLY) { + return "mate " + std::to_string((VALUE_MATE - score + 1) / 2); + } else if (score <= -VALUE_MATE_MAX_PLY) { + return "mate " + std::to_string((-VALUE_MATE - score) / 2); + } else { + return "cp " + std::to_string(score); + } +} + /** * Perform the quiescence search * @@ -133,28 +136,26 @@ double get_ttable_sz(Board &board) { * - Search for checks and check evasions (every time I've tried this it has lost tons of elo) * - Late move reduction (instead of reducing depth, we reduce the search window) (not a known technique, maybe worth trying?) */ -Value quiesce(Board &board, Value alpha, Value beta, int side, int depth, bool pv=false) { - nodes++; +Value quiesce(ThreadInfo &ti, Value alpha, Value beta, int side, int depth, bool pv=false) { + nodes[ti.id]++; - if (early_exit) return 0; + if (stop_search) return 0; - if (!(nodes & 4095)) { - // Check for early exit - // We check every 4096 nodes to avoid slowing down the search too much - uint64_t time = (clock() - start) / CLOCKS_PER_MS; - if ((time > mxtime || nodes > mx_nodes) && exit_allowed) { - early_exit = true; + if (ti.is_main && !(nodes[ti.id] & 4095)) { + auto time = std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); + if (time > mxtime || nodes[ti.id] > mx_nodes) { // currently, the nodes will be broken but time will be accurate + stop_search = true; return 0; } } if (depth >= MAX_PLY) - return eval(board) * side; // Just in case + return eval(ti.board, (BoardState *)ti.bs) * side; // Just in case - TTable::TTEntry *tentry = board.ttable.probe(board.zobrist); + TTable::TTEntry *tentry = ttable.probe(ti.board.zobrist); Value tteval = 0; - if (tentry && tentry->valid()) tteval = tt_to_score(tentry->eval, depth); - if (!pv && tentry && tentry->valid()) { + if (tentry) tteval = tt_to_score(tentry->eval, depth); + if (!pv && tentry) { if (tentry->bound() == EXACT) return tteval; if (tentry->bound() == LOWER_BOUND) { if (tteval >= beta) return tteval; @@ -164,16 +165,15 @@ Value quiesce(Board &board, Value alpha, Value beta, int side, int depth, bool p } } - seldepth = std::max(depth, seldepth); Value stand_pat = 0; Value raw_eval = 0; - stand_pat = tentry ? tentry->s_eval : eval(board) * side; + stand_pat = tentry ? tentry->s_eval : eval(ti.board, (BoardState *)ti.bs) * side; raw_eval = stand_pat; - main_hist.apply_correction(board, stand_pat); - if (tentry && tentry->valid() && abs(tteval) < VALUE_MATE_MAX_PLY && tentry->bound() != (tteval > stand_pat ? UPPER_BOUND : LOWER_BOUND)) + ti.thread_hist.apply_correction(ti.board, stand_pat); + if (tentry && abs(tteval) < VALUE_MATE_MAX_PLY && tentry->bound() != (tteval > stand_pat ? UPPER_BOUND : LOWER_BOUND)) stand_pat = tteval; - if (!tentry) board.ttable.store(board.zobrist, -VALUE_INFINITE, raw_eval, 0, NONE, false, NullMove, depth); + if (!tentry) ttable.store(ti.board.zobrist, -VALUE_INFINITE, raw_eval, 0, NONE, false, NullMove, depth); // If it's a mate, stop here since there's no point in searching further // Theoretically shouldn't ever happen because of stand pat @@ -187,16 +187,16 @@ Value quiesce(Board &board, Value alpha, Value beta, int side, int depth, bool p alpha = stand_pat; pzstd::vector moves; - board.captures(moves); + ti.board.captures(moves); if (moves.empty()) return stand_pat; // Sort captures and promotions pzstd::vector> scores; for (Move &move : moves) { - if (board.piece_boards[OPPOCC(board.side)] & square_bits(move.dst())) { + if (ti.board.piece_boards[OPPOCC(ti.board.side)] & square_bits(move.dst())) { int score = 0; - score = MVV_LVA[board.mailbox[move.dst()] & 7][board.mailbox[move.src()] & 7]; + score = MVV_LVA[ti.board.mailbox[move.dst()] & 7][ti.board.mailbox[move.src()] & 7]; scores.push_back({move, score}); } else if (move.type() == PROMOTION) { scores.push_back({move, PieceValue[move.promotion() + KNIGHT] - PawnValue}); @@ -213,7 +213,7 @@ Value quiesce(Board &board, Value alpha, Value beta, int side, int depth, bool p while ((move = next_move(scores, end)) != NullMove) { if (move.type() != PROMOTION) { - Value see = board.see_capture(move); + Value see = ti.board.see_capture(move); if (see < 0) { continue; // Don't search moves that lose material } else { @@ -223,14 +223,14 @@ Value quiesce(Board &board, Value alpha, Value beta, int side, int depth, bool p } } - line[depth].move = move; + ti.line[depth].move = move; - board.make_move(move); - _mm_prefetch(&board.ttable.TT[board.zobrist % board.ttable.TT_SIZE], _MM_HINT_T0); - Value score = -quiesce(board, -beta, -alpha, -side, depth + 1, pv); - board.unmake_move(); + ti.board.make_move(move); + _mm_prefetch(&ttable.TT[ti.board.zobrist % ttable.TT_SIZE], _MM_HINT_T0); + Value score = -quiesce(ti, -beta, -alpha, -side, depth + 1, pv); + ti.board.unmake_move(); - line[depth].move = NullMove; + ti.line[depth].move = NullMove; if (score > best) { if (score > alpha) { @@ -241,32 +241,32 @@ Value quiesce(Board &board, Value alpha, Value beta, int side, int depth, bool p best_move = move; } if (score >= beta) { - board.ttable.store(board.zobrist, score_to_tt(score, depth), raw_eval, 0, LOWER_BOUND, pv, move, depth); + ttable.store(ti.board.zobrist, score_to_tt(score, depth), raw_eval, 0, LOWER_BOUND, pv, move, depth); return best; } } - board.ttable.store(board.zobrist, score_to_tt(best, depth), raw_eval, 0, alpha_raise ? EXACT : UPPER_BOUND, pv, best_move, depth); + ttable.store(ti.board.zobrist, score_to_tt(best, depth), raw_eval, 0, alpha_raise ? EXACT : UPPER_BOUND, pv, best_move, depth); return best; } -Value __recurse(Board &board, int depth, Value alpha = -VALUE_INFINITE, Value beta = VALUE_INFINITE, int side = 1, bool pv = false, bool cutnode = false, int ply = 0, bool root = false) { - if (pv) pvlen[ply] = 0; +Value __recurse(ThreadInfo &ti, int depth, Value alpha = -VALUE_INFINITE, Value beta = VALUE_INFINITE, int side = 1, bool pv = false, bool cutnode = false, int ply = 0, bool root = false) { + if (pv) ti.pvlen[ply] = 0; + + Board &board = ti.board; if (ply >= MAX_PLY) - return eval(board) * side; + return eval(board, (BoardState *)ti.bs) * side; - nodes++; + nodes[ti.id]++; - if (early_exit) return 0; + if (stop_search) return 0; - if (!(nodes & 4095)) { - // Check for early exit - // We check every 4096 nodes to avoid slowing down the search too much - uint64_t time = (clock() - start) / CLOCKS_PER_MS; - if ((time > mxtime || nodes > mx_nodes) && exit_allowed) { - early_exit = true; + if (ti.is_main && !(nodes[ti.id] & 4095)) { + auto time = std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); + if (time > mxtime || nodes[ti.id] > mx_nodes) { // currently, the nodes will be broken but time will be accurate + stop_search = true; return 0; } } @@ -314,16 +314,16 @@ Value __recurse(Board &board, int depth, Value alpha = -VALUE_INFINITE, Value be if (depth <= 0) { // Reached the maximum depth, perform quiescence search - return quiesce(board, alpha, beta, side, ply, pv); + return quiesce(ti, alpha, beta, side, ply, pv); } bool ttpv = pv; // Check for TTable cutoff - TTable::TTEntry *tentry = board.ttable.probe(board.zobrist); + TTable::TTEntry *tentry = ttable.probe(board.zobrist); Value tteval = 0; - if (tentry && tentry->valid()) tteval = tt_to_score(tentry->eval, ply); - if (!pv && tentry && tentry->depth >= depth && line[ply].excl == NullMove) { + if (tentry) tteval = tt_to_score(tentry->eval, ply); + if (!pv && tentry && tentry->depth >= depth && ti.line[ply].excl == NullMove) { // Check for cutoffs if (tentry->bound() == EXACT) { return tteval; @@ -342,19 +342,19 @@ Value __recurse(Board &board, int depth, Value alpha = -VALUE_INFINITE, Value be uint64_t pawn_hash = 0; if (!in_check) { pawn_hash = board.pawn_struct_hash(); - cur_eval = tentry ? tentry->s_eval : eval(board) * side; + cur_eval = tentry ? tentry->s_eval : eval(board, (BoardState *)ti.bs) * side; raw_eval = cur_eval; - main_hist.apply_correction(board, cur_eval); + ti.thread_hist.apply_correction(board, cur_eval); tt_corr_eval = cur_eval; - if (tentry && tentry->valid() && abs(tteval) < VALUE_MATE_MAX_PLY && tentry->bound() != (tteval > cur_eval ? UPPER_BOUND : LOWER_BOUND)) + if (tentry && abs(tteval) < VALUE_MATE_MAX_PLY && tentry->bound() != (tteval > cur_eval ? UPPER_BOUND : LOWER_BOUND)) tt_corr_eval = tteval; - else if (!tentry) board.ttable.store(board.zobrist, -VALUE_INFINITE, raw_eval, 0, NONE, false, NullMove, board.halfmove); + else if (!tentry) ttable.store(board.zobrist, -VALUE_INFINITE, raw_eval, 0, NONE, false, NullMove, board.halfmove); } - line[ply].eval = in_check ? VALUE_NONE : cur_eval; // If in check, we don't have a valid eval yet + ti.line[ply].eval = in_check ? VALUE_NONE : cur_eval; // If in check, we don't have a valid eval yet bool improving = false; - if (!in_check && ply >= 2 && line[ply-2].eval != VALUE_NONE && cur_eval > line[ply-2].eval) improving = true; + if (!in_check && ply >= 2 && ti.line[ply-2].eval != VALUE_NONE && cur_eval > ti.line[ply-2].eval) improving = true; // Reverse futility pruning if (!in_check && !ttpv && depth <= 8) { @@ -372,7 +372,7 @@ Value __recurse(Board &board, int depth, Value alpha = -VALUE_INFINITE, Value be // Null-move pruning int npieces = _mm_popcnt_u64(board.piece_boards[OCC(WHITE)] | board.piece_boards[OCC(BLACK)]); int npawns_and_kings = _mm_popcnt_u64(board.piece_boards[PAWN] | board.piece_boards[KING]); - if (!in_check && npieces != npawns_and_kings && tt_corr_eval >= beta && depth >= 2 && line[ply].excl == NullMove) { // Avoid NMP in pawn endgames + if (!in_check && npieces != npawns_and_kings && tt_corr_eval >= beta && depth >= 2 && ti.line[ply].excl == NullMove) { // Avoid NMP in pawn endgames /** * This works off the *null-move observation*. * @@ -387,7 +387,7 @@ Value __recurse(Board &board, int depth, Value alpha = -VALUE_INFINITE, Value be board.make_move(NullMove); // Perform a reduced-depth search Value r = NMP_R_VALUE + depth / 4 + std::min(3, (tt_corr_eval - beta) / 400) + improving; - Value null_score = -__recurse(board, depth - r, -beta, -beta + 1, -side, 0, !cutnode, ply+1); + Value null_score = -__recurse(ti, depth - r, -beta, -beta + 1, -side, 0, !cutnode, ply+1); board.unmake_move(); if (null_score >= beta) return null_score >= VALUE_MATE_MAX_PLY ? beta : null_score; @@ -399,14 +399,14 @@ Value __recurse(Board &board, int depth, Value alpha = -VALUE_INFINITE, Value be * If we are losing by a lot, check w/ qsearch to see if we could possibly improve. * If not, we can prune the search. */ - Value razor_score = quiesce(board, alpha, beta, side, ply, 0); + Value razor_score = quiesce(ti, alpha, beta, side, ply, 0); if (razor_score <= alpha) return razor_score; } Value best = -VALUE_INFINITE; - MovePicker mp(board, &line[ply], ply, &main_hist, tentry); + MovePicker mp(board, &ti.line[ply], ply, &ti.thread_hist, tentry); if ((pv || cutnode) && depth > 4 && !(tentry && tentry->best_move != NullMove)) { depth -= 2; // Internal iterative reductions @@ -422,10 +422,10 @@ Value __recurse(Board &board, int depth, Value alpha = -VALUE_INFINITE, Value be Move move = NullMove; int i = 0; - uint64_t prev_nodes = nodes; + uint64_t prev_nodes = nodes[ti.id]; while ((move = mp.next()) != NullMove) { - if (move == line[ply].excl) + if (move == ti.line[ply].excl) continue; bool capt = (board.piece_boards[OPPOCC(board.side)] & square_bits(move.dst())); @@ -433,12 +433,12 @@ Value __recurse(Board &board, int depth, Value alpha = -VALUE_INFINITE, Value be int extension = 0; - if (line[ply].excl == NullMove && depth >= 8 && i == 0 && tentry && move == tentry->best_move && tentry->depth >= depth - 3 && tentry->bound() != UPPER_BOUND) { + if (ti.line[ply].excl == NullMove && depth >= 8 && i == 0 && tentry && move == tentry->best_move && tentry->depth >= depth - 3 && tentry->bound() != UPPER_BOUND) { // Singular extension - line[ply].excl = move; + ti.line[ply].excl = move; Value singular_beta = tteval - 4 * depth; - Value singular_score = __recurse(board, (depth-1) / 2, singular_beta - 1, singular_beta, side, 0, cutnode, ply); - line[ply].excl = NullMove; // Reset exclusion move + Value singular_score = __recurse(ti, (depth-1) / 2, singular_beta - 1, singular_beta, side, 0, cutnode, ply); + ti.line[ply].excl = NullMove; // Reset exclusion move if (singular_score < singular_beta) { extension++; @@ -453,9 +453,9 @@ Value __recurse(Board &board, int depth, Value alpha = -VALUE_INFINITE, Value be } } - line[ply].move = move; + ti.line[ply].move = move; - Value hist = capt ? main_hist.get_capthist(board, move) : main_hist.get_history(board, move, ply, &line[ply]); + Value hist = capt ? ti.thread_hist.get_capthist(board, move) : ti.thread_hist.get_history(board, move, ply, &ti.line[ply]); if (best > -VALUE_MATE_MAX_PLY) { if (i >= (5 + depth * depth) / (2 - improving)) { /** @@ -489,11 +489,11 @@ Value __recurse(Board &board, int depth, Value alpha = -VALUE_INFINITE, Value be } } - line[ply].cont_hist = &main_hist.cont_hist[board.side][board.mailbox[move.src()] & 7][move.dst()]; + ti.line[ply].cont_hist = &ti.thread_hist.cont_hist[board.side][board.mailbox[move.src()] & 7][move.dst()]; board.make_move(move); - _mm_prefetch(&board.ttable.TT[board.zobrist % board.ttable.TT_SIZE], _MM_HINT_T0); + _mm_prefetch(&ttable.TT[board.zobrist % ttable.TT_SIZE], _MM_HINT_T0); Value newdepth = depth - 1 + extension; @@ -515,35 +515,38 @@ Value __recurse(Board &board, int depth, Value alpha = -VALUE_INFINITE, Value be r -= 1024 * pv; r += 1024 * (!pv && cutnode); - if (move == line[ply].killer[0] || move == line[ply].killer[1]) + if (move == ti.line[ply].killer[0] || move == ti.line[ply].killer[1]) r -= 1024; r -= 1024 * ttpv; r -= hist / 16 * !capt; Value searched_depth = depth - r / 1024; - score = -__recurse(board, searched_depth, -alpha - 1, -alpha, -side, 0, true, ply+1); + score = -__recurse(ti, searched_depth, -alpha - 1, -alpha, -side, 0, true, ply+1); if (score > alpha && searched_depth < newdepth) { - score = -__recurse(board, newdepth, -alpha - 1, -alpha, -side, 0, !cutnode, ply+1); + score = -__recurse(ti, newdepth, -alpha - 1, -alpha, -side, 0, !cutnode, ply+1); } } else if (!pv || i > 0) { - score = -__recurse(board, newdepth, -alpha - 1, -alpha, -side, 0, !cutnode, ply+1); + score = -__recurse(ti, newdepth, -alpha - 1, -alpha, -side, 0, !cutnode, ply+1); } if (pv && (i == 0 || score > alpha)) { if (tentry && move == tentry->best_move && tentry->depth > 1) newdepth = std::max((int)newdepth, 1); // Make sure we don't enter QS if we have an available TT move - score = -__recurse(board, newdepth, -beta, -alpha, -side, 1, false, ply+1); + score = -__recurse(ti, newdepth, -beta, -alpha, -side, 1, false, ply+1); } board.unmake_move(); - line[ply].cont_hist = nullptr; + ti.line[ply].cont_hist = nullptr; if (root) { - nodecnt[move.src()][move.dst()] += nodes - prev_nodes; - prev_nodes = nodes; + nodecnt[move.src()][move.dst()] += nodes[ti.id] - prev_nodes; + prev_nodes = nodes[ti.id]; } + if (stop_search) + break; + if (score > best) { if (score > alpha) { best_move = move; @@ -551,10 +554,10 @@ Value __recurse(Board &board, int depth, Value alpha = -VALUE_INFINITE, Value be alpha_raise++; flag = EXACT; if (score < beta) { - pvtable[ply][0] = move; - pvlen[ply] = pvlen[ply+1]+1; - for (int i = 0; i < pvlen[ply+1]; i++) { - pvtable[ply][i+1] = pvtable[ply+1][i]; + ti.pvtable[ply][0] = move; + ti.pvlen[ply] = ti.pvlen[ply+1]+1; + for (int j = 0; j < ti.pvlen[ply+1]; j++) { + ti.pvtable[ply][j+1] = ti.pvtable[ply+1][j]; } } } @@ -567,28 +570,25 @@ Value __recurse(Board &board, int depth, Value alpha = -VALUE_INFINITE, Value be // note that best and score are functionally equivalent here; best is just what's returned + stored to TT best = (score * depth + beta) / (depth + 1); // wtf????? } - if (line[ply].killer[0] != move) { - line[ply].killer[1] = line[ply].killer[0]; - line[ply].killer[0] = move; // Update killer moves + if (ti.line[ply].killer[0] != move) { + ti.line[ply].killer[1] = ti.line[ply].killer[0]; + ti.line[ply].killer[0] = move; // Update killer moves } const Value bonus = std::min(1896, 4 * depth * depth + 120 * depth - 120); // saturate updates at depth 12 if (!capt) { // Not a capture - main_hist.update_history(board, move, ply, &line[ply], bonus); + ti.thread_hist.update_history(board, move, ply, &ti.line[ply], bonus); for (auto &qmove : quiets) { - main_hist.update_history(board, qmove, ply, &line[ply], -bonus); // Penalize quiet moves + ti.thread_hist.update_history(board, qmove, ply, &ti.line[ply], -bonus); // Penalize quiet moves } } else { // Capture - main_hist.update_capthist(PieceType(board.mailbox[move.src()] & 7), PieceType(board.mailbox[move.dst()] & 7), move.dst(), bonus); + ti.thread_hist.update_capthist(PieceType(board.mailbox[move.src()] & 7), PieceType(board.mailbox[move.dst()] & 7), move.dst(), bonus); } for (auto &cmove : captures) { - main_hist.update_capthist(PieceType(board.mailbox[cmove.src()] & 7), PieceType(board.mailbox[cmove.dst()] & 7), cmove.dst(), -bonus); + ti.thread_hist.update_capthist(PieceType(board.mailbox[cmove.src()] & 7), PieceType(board.mailbox[cmove.dst()] & 7), cmove.dst(), -bonus); } break; } - if (early_exit) - break; - if (!capt && !promo) quiets.push_back(move); else if (capt) captures.push_back(move); i++; @@ -597,7 +597,7 @@ Value __recurse(Board &board, int depth, Value alpha = -VALUE_INFINITE, Value be // Stalemate detection if (best == -VALUE_MATE) { // If our engine thinks we are mated but we are not in check, we are stalemated - if (line[ply].excl != NullMove) return alpha; + if (ti.line[ply].excl != NullMove) return alpha; else if (in_check) return -VALUE_MATE + ply; else return 0; } @@ -608,172 +608,36 @@ Value __recurse(Board &board, int depth, Value alpha = -VALUE_INFINITE, Value be && !(best < alpha && best >= raw_eval) && !(best >= beta && best <= raw_eval)) { // Best move is a quiet move, update CorrHist int bonus = (best - raw_eval) * depth / 8; - main_hist.update_corrhist(board, bonus); + ti.thread_hist.update_corrhist(board, bonus); } - if (line[ply].excl == NullMove) { + if (ti.line[ply].excl == NullMove) { Move tt_move = best_move != NullMove ? best_move : tentry ? tentry->best_move : NullMove; - board.ttable.store(board.zobrist, score_to_tt(best, ply), raw_eval, depth, flag, ttpv, tt_move, board.halfmove); + ttable.store(board.zobrist, score_to_tt(best, ply), raw_eval, depth, flag, ttpv, tt_move, board.halfmove); } return best; } -int g_quiet; - -pzstd::vector> __search_multipv(Board &board, int multipv, int depth, Value alpha = -VALUE_INFINITE, Value beta = VALUE_INFINITE, int side = 1) { - Move best_move[256]; - Value best_score[256]; - - std::fill(best_move, best_move+256, NullMove); - std::fill(best_score, best_score+256, -VALUE_INFINITE); - - auto min_score = [multipv](Value *best_score) { - Value min = best_score[0]; - int idx = 0; - for (int i = 1; i < multipv; i++) { - if (best_score[i] < min) { - min = best_score[i]; - idx = i; - } - } - return std::make_pair(min, idx); - }; - - TTable::TTEntry *tentry = board.ttable.probe(board.zobrist); - - MovePicker mp(board, &line[0], 0, &main_hist, tentry); - - Move move = NullMove; - int i = 0; - - bool printing_currmove = false; - int alpha_raise = 0; - - while ((move = mp.next()) != NullMove) { - if (depth >= 20 && nodes >= 10'000'000) { - if (!g_quiet) std::cout << "info depth " << depth << " currmove " << move.to_string() << " currmovenumber " << i+1 << std::endl; - } - - auto res = min_score(best_score); - - line[0].move = move; - board.make_move(move); - Value score; - Value used_alpha = res.first; - if (i > 0 && used_alpha != -VALUE_INFINITE) { - score = -__recurse(board, depth - reduction[i][depth] / 1024, -used_alpha - 1, -used_alpha, -side, 0); - if (score > used_alpha) { - score = -__recurse(board, depth - 1, -beta, -used_alpha, -side, 0); - } - } else { - score = -__recurse(board, depth - 1, -beta, -alpha, -side, 1); - } - - board.unmake_move(); - - if (score > res.first) { - pvtable[0][0] = move; - pvlen[0] = pvlen[1]+1; - for (int i = 0; i < pvlen[1]; i++) { - pvtable[0][i+1] = pvtable[1][i]; - } - if (score > alpha) { - alpha = score; - alpha_raise++; - } - best_score[res.second] = score; - best_move[res.second] = move; - } - - if (score >= beta) { - if (line[0].killer[0] != move) { - line[0].killer[1] = line[0].killer[0]; - line[0].killer[0] = move; - } - pzstd::vector> multipv_res; - multipv_res.push_back({move, score}); - return multipv_res; - } - - if (early_exit) - break; - - i++; - } - - Move final_best_move = NullMove; - Value final_best_score = -VALUE_INFINITE; - pzstd::vector> multipv_res; - - for (int i = 0; i < multipv; i++) { - if (best_move[i] == NullMove) best_score[i] = -VALUE_INFINITE; - if (best_score[i] > final_best_score) { - final_best_score = best_score[i]; - final_best_move = best_move[i]; - } - multipv_res.push_back({best_move[i], best_score[i]}); - } - - return multipv_res; -} - -void __print_pv(bool omit_last = 0) { // Need to omit last to prevent illegal moves during mates - const int ROOT_PLY = 0; - for (int i = 0; i < pvlen[ROOT_PLY] - omit_last; i++) { - if (pvtable[ROOT_PLY][i] == NullMove) break; - std::cout << pvtable[ROOT_PLY][i].to_string() << ' '; - } -} - -void __print_pv_clipped(bool omit_last = 0) { - const int MAX_PLY = 10; - int len = std::min(pvlen[0] - omit_last, MAX_PLY); - for (int i = 0; i < len; i++) { - if (pvtable[0][i] == NullMove) break; - std::cout << pvtable[0][i].to_string() << ' '; - } -} - -std::pair search(Board &board, int64_t time, int depth, int64_t maxnodes, int quiet) { - g_quiet = quiet; - - uint64_t soft_nodes = 1e18; - - std::cout << std::fixed << std::setprecision(0); - nodes = seldepth = 0; - early_exit = exit_allowed = false; - start = clock(); - mxtime = time; - if (maxnodes != 1e18) { - mx_nodes = 1000000; - soft_nodes = maxnodes; - } - - // Clear killer moves and history heuristic - for (int i = 0; i < MAX_PLY; i++) { - line[i].killer[0] = line[i].killer[1] = NullMove; - pvlen[i] = 0; - } - +void iterativedeepening(ThreadInfo &ti, int depth) { for (int i = 0; i < 64; i++) { for (int j = 0; j < 64; j++) { - nodecnt[i][j] = 0; - main_hist.history[0][i][j] /= 2; - main_hist.history[1][i][j] /= 2; + ti.thread_hist.history[0][i][j] /= 2; + ti.thread_hist.history[1][i][j] /= 2; } } - Value static_eval = eval(board) * (board.side ? -1 : 1); + Board &board = ti.board; + + Value static_eval = eval(board, (BoardState *)ti.bs) * (board.side ? -1 : 1); Move best_move = NullMove; Value eval = -VALUE_INFINITE; - bool aspiration_enabled = true; for (int d = 1; d <= depth; d++) { Value alpha = -VALUE_INFINITE, beta = VALUE_INFINITE; - Value window_size = ASPIRATION_WINDOW; - - if (eval != -VALUE_INFINITE && aspiration_enabled) { + Value window_sz = ASPIRATION_WINDOW; + + if (eval != -VALUE_INFINITE) { /** * Aspiration windows work by searching a small window around the expected value * of the position. By having a smaller window, our search runs faster. @@ -781,216 +645,126 @@ std::pair search(Board &board, int64_t time, int depth, int64_t max * If we fail either high or low out of this window, we gradually expand the * window size, eventually getting to a full-width search. */ - alpha = eval - window_size; - beta = eval + window_size; + alpha = eval - window_sz; + beta = eval + window_sz; } - - auto result = __recurse(board, d, alpha, beta, board.side ? -1 : 1, 1, false, 0, true); - + + auto result = __recurse(ti, d, alpha, beta, board.side ? -1 : 1, 1, false, 0, true); + // Gradually expand the window if we fail high or low - while ((result >= beta || result <= alpha) && window_size < VALUE_INFINITE / 4) { + while ((result >= beta || result <= alpha) && window_sz < VALUE_INFINITE / 4) { if (result >= beta) { // Fail high - expand upper bound - beta = eval + window_size * 2; + beta = eval + window_sz * 2; if (beta >= VALUE_INFINITE / 4) beta = VALUE_INFINITE; } if (result <= alpha) { // Fail low - expand lower bound - alpha = eval - window_size * 2; + alpha = eval - window_sz * 2; if (alpha <= -VALUE_INFINITE / 4) alpha = -VALUE_INFINITE; } - window_size *= 2; - result = __recurse(board, d, alpha, beta, board.side ? -1 : 1, 1, false, 0, true); - if (early_exit) break; + window_sz *= 2; + result = __recurse(ti, d, alpha, beta, board.side ? -1 : 1, 1, false, 0, true); + if (stop_search) break; } - if (early_exit) break; + if (stop_search) break; eval = result; - best_move = pvtable[0][0]; - - bool best_iscapt = (board.piece_boards[OPPOCC(board.side)] & square_bits(best_move.dst())); - bool best_ispromo = (best_move.type() == PROMOTION); - bool in_check = false; - if (board.side == WHITE) { - in_check = board.control(__tzcnt_u64(board.piece_boards[KING] & board.piece_boards[OCC(WHITE)]), BLACK) > 0; - } else { - in_check = board.control(__tzcnt_u64(board.piece_boards[KING] & board.piece_boards[OCC(BLACK)]), WHITE) > 0; - } - - seldepth = std::max(seldepth, d); - - #ifndef NOUCI - if (!quiet) { - if (abs(eval) >= VALUE_MATE_MAX_PLY) { - std::cout << "info depth " << d << " seldepth " << seldepth << " score mate " << (VALUE_MATE - abs(eval) + 1) / 2 * (eval > 0 ? 1 : -1) << " nodes " - << nodes << " nps " << (nodes / ((double)(clock() - start) / CLOCKS_PER_SEC)) << " pv "; - __print_pv(1); - std::cout << "hashfull " << (get_ttable_sz(board) * 1000) << " time " << (clock() - start) / CLOCKS_PER_MS << std::endl; - } else { - std::cout << "info depth " << d << " seldepth " << seldepth << " score cp " << eval << " nodes " << nodes << " nps " - << (nodes / ((double)(clock() - start) / CLOCKS_PER_SEC)) << " pv "; - __print_pv(); - std::cout << "hashfull " << (get_ttable_sz(board) * 1000) << " time " << (clock() - start) / CLOCKS_PER_MS << std::endl; + best_move = ti.pvtable[0][0]; + + if (ti.is_main) { + // We must calculate best move nodes and total nodes at around the same time + // so that node counts don't change in between due to race conditions + // This is a really crude way of doing it (todo change later) + uint64_t bm_nodes = nodecnt[best_move.src()][best_move.dst()]; + uint64_t tot_nodes = 0; + for (int t = 0; t < num_threads; t++) { + tot_nodes += nodes[t]; // ig this is dangerous but whatever } - } else if (quiet == 2) { // quiet level: formatted output - auto format_number = [](uint64_t num) -> std::string { - std::string str = std::to_string(num); - int len = str.length(); - for (int i = len - 3; i > 0; i -= 3) { - str.insert(i, ","); - } - return str; - }; // actually cooked - - uint64_t time_ms = (clock() - start) / CLOCKS_PER_MS; - uint64_t nps = time_ms > 0 ? (nodes * 1000 / time_ms) : 0; - uint32_t hashfull = get_ttable_sz(board) * 1000 / board.ttable.mxsize(); - std::string score_color; - std::string score_text; - - if (abs(eval) >= VALUE_MATE_MAX_PLY) { - int mate_moves = (VALUE_MATE - abs(eval) + 1) / 2 * (eval > 0 ? 1 : -1); - score_color = (mate_moves > 0) ? GREEN : RED; - score_text = "mate " + std::to_string(mate_moves); + // UCI output from main thread only + auto time_elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); + std::cout << "info depth " << d << " score " << score_to_uci(eval) << " time " << time_elapsed << " nodes " << tot_nodes << " nps " + << (time_elapsed ? (tot_nodes * 1000 / time_elapsed) : tot_nodes) << " hashfull " << (int)(get_ttable_sz() * 1000) << " pv"; + for (int ply = 0; ply < ti.pvlen[0]; ply++) { + std::cout << " " << ti.pvtable[0][ply].to_string(); + } + std::cout << std::endl; + + // only do time management on main thread + bool best_iscapt = board.is_capture(best_move); + bool best_ispromo = (best_move.type() == PROMOTION); + bool in_check = false; + if (board.side == WHITE) { + in_check = board.control(__tzcnt_u64(board.piece_boards[KING] & board.piece_boards[OCC(WHITE)]), BLACK) > 0; } else { - int cp_score = eval; - if (cp_score > 200) score_color = GREEN; - else if (cp_score > 0) score_color = YELLOW; - else if (cp_score > -200) score_color = MAGENTA; - else score_color = RED; - score_text = std::to_string(cp_score * (board.side ? -1 : 1)) + " cp"; + in_check = board.control(__tzcnt_u64(board.piece_boards[KING] & board.piece_boards[OCC(BLACK)]), WHITE) > 0; } - if (d > 1) - std::cout << "\033[21A\033[J"; // Move cursor up 9 lines and clear + double soft = 0.5; + if (depth >= 6 && !best_iscapt && !best_ispromo && !in_check) { + // adjust soft limit based on complexity + Value complexity = abs(eval - static_eval); + double factor = std::clamp(complexity / 200.0, 0.0, 1.0); + // higher complexity = spend more time, lower complexity = spend less time + soft = 0.3 + 0.4 * factor; + } - int moves = 0; - for (int i = 0; i < pvlen[0]; i++) { - if (pvtable[0][i] == NullMove) break; - board.make_move(pvtable[0][i]); - moves++; + double node_adjustment = 1.5 - (bm_nodes / (double)tot_nodes); + soft *= node_adjustment; + if (time_elapsed > mxtime * soft) { + // We probably won't be able to complete the next ID loop + stop_search = true; + break; } - board.print_board_pretty(); - while (moves--) board.unmake_move(); - - std::cout << CYAN "┌─────────── " BOLD "Depth " << d << RESET CYAN " ───────────┐" RESET << std::endl; - std::cout << CYAN "│ " YELLOW "Depth: " RESET BOLD << d << RESET CYAN " (" << seldepth << " sel)" RESET << std::endl; - std::cout << CYAN "│ " YELLOW "Score: " RESET << score_color << BOLD << score_text << RESET << std::endl; - std::cout << CYAN "│ " YELLOW "Nodes: " RESET << BOLD << format_number(nodes) << RESET << std::endl; - std::cout << CYAN "│ " YELLOW "Speed: " RESET << BOLD << format_number(nps) << RESET " nps" << std::endl; - std::cout << CYAN "│ " YELLOW "Time: " RESET << BOLD << time_ms << RESET " ms" << std::endl; - std::cout << CYAN "│ " YELLOW "Hash: " RESET << BOLD << hashfull / 10.0 << RESET "%" << std::endl; - std::cout << CYAN "│ " YELLOW "Short PV: " RESET << BLUE; - __print_pv_clipped(abs(eval) >= VALUE_MATE_MAX_PLY); - std::cout << RESET << std::endl; - std::cout << CYAN "└────────────────────────────────┘" RESET << std::endl; } - #endif - - exit_allowed = true; - if (nodes >= soft_nodes) break; // soft node limit - - // if (abs(eval) >= VALUE_MATE_MAX_PLY) { - // return {best_move, eval}; - // // We don't need to search further, we found mate - // } - - int time_elapsed = (clock() - start) / CLOCKS_PER_MS; - double soft = 0.5; - if (depth >= 6 && !best_iscapt && !best_ispromo && !in_check) { - // adjust soft limit based on complexity - Value complexity = abs(eval - static_eval); - double factor = std::clamp(complexity / 200.0, 0.0, 1.0); - // higher complexity = spend more time, lower complexity = spend less time - soft = 0.3 + 0.4 * factor; - } - uint64_t bm_nodes = nodecnt[best_move.src()][best_move.dst()]; - double node_adjustment = 1.5 - (bm_nodes / (double)nodes); - soft *= node_adjustment; - if (time_elapsed > mxtime * soft) { - // We probably won't be able to complete the next ID loop - break; - } + ti.maxdepth = d; } - return {best_move, eval}; -} + ti.eval = eval; + stop_search = true; -pzstd::vector> search_multipv(Board &board, int multipv, int64_t time, int depth, int64_t maxnodes, int quiet) { - pzstd::vector> results; + if (ti.is_main) { + std::cout << "bestmove " << best_move.to_string() << std::endl; + } +} - g_quiet = quiet; +std::pair search(Board &board, ThreadInfo *threads, int64_t time, int depth, int64_t maxnodes, int quiet) { + for (int i = 0; i < 64; i++) for (int j = 0; j < 64; j++) nodecnt[i][j] = 0; - std::cout << std::fixed << std::setprecision(0); - nodes = seldepth = 0; - early_exit = exit_allowed = false; - start = clock(); mxtime = time; mx_nodes = maxnodes; - - // Clear killer moves and history heuristic - for (int i = 0; i < MAX_PLY; i++) { - line[i].killer[0] = line[i].killer[1] = NullMove; - pvlen[i] = 0; - } - - for (int i = 0; i < 64; i++) { - for (int j = 0; j < 64; j++) { - main_hist.history[0][i][j] = main_hist.history[1][i][j] = 0; - } - } + start = std::chrono::steady_clock::now(); + stop_search = false; - pzstd::vector> multipv_res; - - for (int d = 1; d <= depth; d++) { - auto result = __search_multipv(board, multipv, d, -VALUE_INFINITE, VALUE_INFINITE, board.side ? -1 : 1); + Move best_move = NullMove; + Value eval = 0; - if (early_exit) - break; + std::vector thread_handles; - multipv_res = result; - - std::stable_sort(multipv_res.begin(), multipv_res.end(), [](const auto &a, const auto &b) { - return a.second > b.second; - }); - - if (!quiet) { - for (int i = 0; i < multipv; i++) { - Value eval = multipv_res[i].second; - if (eval == -VALUE_INFINITE || multipv_res[i].first == NullMove) break; - - if (abs(eval) >= VALUE_MATE_MAX_PLY) { - std::cout << "info depth " << d << " seldepth " << seldepth << " multipv " << i+1 << " score mate " << (VALUE_MATE - abs(eval)) / 2 * (eval > 0 ? 1 : -1) << " nodes " - << nodes << " nps " << (nodes / ((double)(clock() - start) / CLOCKS_PER_SEC)) << " pv " << multipv_res[i].first.to_string() - << " hashfull " << (get_ttable_sz(board) * 1000 / board.ttable.mxsize()) << " time " << (clock() - start) / CLOCKS_PER_MS << std::endl; - } else { - std::cout << "info depth " << d << " seldepth " << seldepth << " multipv " << i+1 << " score cp " << eval << " nodes " << nodes << " nps " - << (nodes / ((double)(clock() - start) / CLOCKS_PER_SEC)) << " pv " << multipv_res[i].first.to_string() - << " hashfull " << (get_ttable_sz(board) * 1000 / board.ttable.mxsize()) << " time " << (clock() - start) / CLOCKS_PER_MS << std::endl; - } - } - } + for (int t = 0; t < num_threads; t++) { + ThreadInfo &ti = threads[t]; + ti.board = board; + nodes[t] = 0; + ti.id = t; + ti.is_main = (t == 0); + // don't clear search vars here; keep history + thread_handles.emplace_back(iterativedeepening, std::ref(ti), depth); + } - exit_allowed = true; + for (int t = 0; t < num_threads; t++) { + thread_handles[t].join(); } - return multipv_res; + ThreadInfo &best_thread = threads[0]; + + return {best_thread.pvtable[0][0], best_thread.eval}; } -void clear_search_vars() { - nodes = seldepth = 0; - early_exit = exit_allowed = false; +void clear_search_vars(ThreadInfo &ti) { + ti.board.reset_startpos(); + memset(&ti.thread_hist, 0, sizeof(History)); for (int i = 0; i < MAX_PLY; i++) { - pvlen[i] = 0; - line[i] = SSEntry(); + ti.line[i] = SSEntry(); } - - memset(main_hist.history, 0, sizeof(main_hist.history)); - memset(main_hist.corrhist_prev, 0, sizeof(main_hist.corrhist_prev)); - memset(main_hist.capthist, 0, sizeof(main_hist.capthist)); - memset(main_hist.corrhist_ps, 0, sizeof(main_hist.corrhist_ps)); - memset(main_hist.corrhist_mat, 0, sizeof(main_hist.corrhist_mat)); - memset(main_hist.corrhist_np, 0, sizeof(main_hist.corrhist_np)); - memset(main_hist.cont_hist, 0, sizeof(main_hist.cont_hist)); } diff --git a/engine/search.hpp b/engine/search.hpp index 2e1d31b..4400049 100644 --- a/engine/search.hpp +++ b/engine/search.hpp @@ -48,12 +48,40 @@ // This is the margin for history pruning (in centipawns) #define HISTORY_MARGIN 2000 -extern uint64_t nodes; +extern bool stop_search; -std::pair search(Board &board, int64_t time = 1e9, int depth = MAX_PLY, int64_t nodes = 1e18, int quiet = 0); +extern uint64_t nodes[MAX_THREADS]; +extern uint16_t num_threads; -pzstd::vector> search_multipv(Board &board, int multipv, int64_t time = 1e9, int depth = MAX_PLY, int64_t maxnodes = 1e18, int quiet = 0); +struct ThreadInfo { + Board board; + int maxdepth = 0; + Value eval = 0; + int id = 0; + bool is_main = false; + History thread_hist; + SSEntry line[MAX_PLY] = {}; + Move pvtable[MAX_PLY][MAX_PLY]; + int pvlen[MAX_PLY] = {}; + BoardState bs[NINPUTS * 2][NINPUTS * 2]; + + void set_bs() { + for (int i = 0; i < NINPUTS * 2; i++) { + for (int j = 0; j < NINPUTS * 2; j++) { + for (int k = 0; k < HL_SIZE; k++) { + bs[i][j].w_acc.val[k] = nnue_network.accumulator_biases[k]; + bs[i][j].b_acc.val[k] = nnue_network.accumulator_biases[k]; + } + for (int k = 0; k < 64; k++) { + bs[i][j].mailbox[k] = NO_PIECE; + } + } + } + } +}; + +std::pair search(Board &board, ThreadInfo *threads, int64_t time = 1e9, int depth = MAX_PLY, int64_t nodes = 1e18, int quiet = 0); uint64_t perft(Board &board, int depth); -void clear_search_vars(); +void clear_search_vars(ThreadInfo &ti); diff --git a/engine/ttable.cpp b/engine/ttable.cpp index 789e4b3..2a53662 100644 --- a/engine/ttable.cpp +++ b/engine/ttable.cpp @@ -1,5 +1,7 @@ #include "ttable.hpp" +TTable ttable(DEFAULT_TT_SIZE); + void TTable::store(uint64_t key, Value eval, Value s_eval, uint8_t depth, uint8_t bound, bool ttpv, Move best_move, uint8_t age) { TTBucket *bucket = TT + (key % TT_SIZE); diff --git a/engine/ttable.hpp b/engine/ttable.hpp index 8f93e24..3208ee1 100644 --- a/engine/ttable.hpp +++ b/engine/ttable.hpp @@ -32,6 +32,8 @@ struct TTable { TTEntry entries[2]; }; + TTEntry NO_ENTRY = TTEntry(); + TTBucket *TT; int TT_SIZE; @@ -58,5 +60,13 @@ struct TTable { TTEntry *probe(uint64_t key); + void resize(int size) { + delete[] TT; + TT_SIZE = size; + TT = new TTBucket[size]; + } + constexpr uint64_t mxsize() const { return TT_SIZE * 2; } }; + +extern TTable ttable;