From 598da599dc14a4e0bc1f2cb6a1dcbc3db39beaec Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Sun, 24 May 2026 09:54:06 +0700 Subject: [PATCH 01/29] fix bug and improvements --- eval.cpp | 229 ++++++++++++++++++++++++++++++++++++++++++++------- main.cpp | 8 +- movepick.cpp | 13 ++- movepick.h | 7 +- search.cpp | 141 ++++++++++++++++++++++++------- search.h | 16 ++-- timeman.cpp | 4 +- tt.h | 21 ++--- 8 files changed, 348 insertions(+), 91 deletions(-) diff --git a/eval.cpp b/eval.cpp index 17c5304..c6ffdab 100644 --- a/eval.cpp +++ b/eval.cpp @@ -1,11 +1,13 @@ #include "eval.h" #include "tune.h" #include +#include using namespace chess; using namespace engine::eval; + namespace engine::eval { Value PawnValue = 100, KnightValue = 325, BishopValue = 350, RookValue = 500, - QueenValue = 900; + QueenValue = 900, KingValue = 0; Value mg_pawn_table[64] = { 0, 0, 0, 0, 0, 0, 0, 0, 98, 134, 61, 95, 68, 126, 34, -11, -6, 7, 26, 31, 65, 56, 25, -20, -14, 13, 6, 21, 23, 12, 17, -23, @@ -101,56 +103,223 @@ Value *mg_pesto_table[] = {nullptr, mg_pawn_table, mg_knight_table, Value *eg_pesto_table[] = {nullptr, eg_pawn_table, eg_knight_table, eg_bishop_table, eg_rook_table, eg_queen_table, eg_king_table}; +Value mgMobility[] = { + 0, // none + 0, // pawn + 3, // knight + 5, // bishop + 2, // rook + 1, // queen + 0 // king +}; + +Value egMobility[] = {0, 0, 2, 5, 3, 2, 0}; +Value spaceWeight=28; // tuning slop here Value eval(const chess::Board &board) { - int pieceCount[10] = { - board.count(), board.count(), - board.count(), board.count(), - board.count(), board.count(), - board.count(), board.count(), - board.count(), board.count(), - }; - Value material = (pieceCount[0] * PawnValue + pieceCount[1] * KnightValue + - pieceCount[2] * BishopValue + pieceCount[3] * RookValue + - pieceCount[4] * QueenValue) - - (pieceCount[5] * PawnValue + pieceCount[6] * KnightValue + - pieceCount[7] * BishopValue + pieceCount[8] * RookValue + - pieceCount[9] * QueenValue); constexpr int KnightPhase = 1; constexpr int BishopPhase = 1; constexpr int RookPhase = 2; constexpr int QueenPhase = 4; constexpr int TotalPhase = KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; - int phase = (pieceCount[1] + pieceCount[6]) * KnightPhase + - (pieceCount[2] + pieceCount[7]) * BishopPhase + - (pieceCount[3] + pieceCount[8]) * RookPhase + - (pieceCount[4] + pieceCount[9]) * QueenPhase; - phase = (phase * 256 + TotalPhase / 2) / TotalPhase; const int sign = board.sideToMove() == chess::Color::WHITE ? 1 : -1; - int mgScore = material; - int egScore = material; + int mgScore = 0; + int egScore = 0; + int phase = 0; { - Bitboard occ = board.occ(); + mgScore=egScore=board.sideToMove() == WHITE?spaceWeight:0; + Bitboard occ = board.occ(), occ2=occ; while (occ) { - Square i = (Square)pop_lsb(occ); - auto p = board.at(i); + Square sq = (Square)pop_lsb(occ),_sq=sq; + auto p = board.at(sq); int _sign = 1; if (color_of(p) == BLACK) { _sign = -1; - i = square_mirror(i); + _sq = square_mirror(sq); + } + auto pt = piece_of(p); + if (pt == NO_PIECE_TYPE) + continue; + mgScore += _sign * mg_pesto_table[pt][_sq]; + egScore += _sign * eg_pesto_table[pt][_sq]; + mgScore += _sign * piece_value(pt); + egScore += _sign * piece_value(pt); + switch (pt) { + case KNIGHT: + phase += KnightPhase; + break; + case BISHOP: + phase += BishopPhase; + break; + case ROOK: + phase += RookPhase; + break; + case QUEEN: + phase += QueenPhase; + break; + case PAWN: + break; + case KING: + break; + default: + break; + } + Bitboard attacks = 0; + + switch (pt) { + case KNIGHT: + attacks = chess::attacks::knight(sq); + break; + + case BISHOP: + attacks = chess::attacks::bishop(sq, occ2); + break; + + case ROOK: + attacks = chess::attacks::rook(sq, occ2); + break; + + case QUEEN: + attacks = chess::attacks::queen(sq, occ2); + break; + + default: + break; + } + attacks &= ~board.us(color_of(p)); + int mobility = popcount(attacks); + mgScore += _sign * mobility * mgMobility[pt]; + egScore += _sign * mobility * egMobility[pt]; + } + } + // Bishop pair bonus + for (Color c : {WHITE, BLACK}) { + if (board.count(BISHOP, c) >= 2) { + int s = (c == WHITE) ? 1 : -1; + mgScore += s * 30; + egScore += s * 10; + } + } + + // Rook on open/semi-open file + for (Color c : {WHITE, BLACK}) { + int s = (c == WHITE) ? 1 : -1; + Bitboard rooks = board.pieces(ROOK, c); + while (rooks) { + Square sq = Square(pop_lsb(rooks)); + File f = file_of(sq); + Bitboard fileMask = attacks::MASK_FILE[f]; + bool hasOwnPawn = (board.pieces(PAWN, c) & fileMask) != 0; + bool hasEnemyPawn = (board.pieces(PAWN, ~c) & fileMask) != 0; + if (!hasOwnPawn && !hasEnemyPawn) { + mgScore += s * 25; + egScore += s * 10; + } else if (!hasOwnPawn) { + mgScore += s * 15; + egScore += s * 5; + } + } + } + + // Doubled pawn penalty + for (Color c : {WHITE, BLACK}) { + int s = (c == WHITE) ? 1 : -1; + for (int f = 0; f < 8; f++) { + int cnt = popcount(board.pieces(PAWN, c) & attacks::MASK_FILE[f]); + if (cnt >= 2) { + mgScore += s * -(cnt - 1) * 15; + egScore += s * -(cnt - 1) * 20; + } + } + } + + // Isolated pawn penalty + for (Color c : {WHITE, BLACK}) { + int s = (c == WHITE) ? 1 : -1; + Bitboard pawns = board.pieces(PAWN, c); + while (pawns) { + Square sq = Square(pop_lsb(pawns)); + File f = file_of(sq); + bool isolated = true; + if (f > FILE_A && (board.pieces(PAWN, c) & attacks::MASK_FILE[f - 1])) + isolated = false; + if (f < FILE_H && (board.pieces(PAWN, c) & attacks::MASK_FILE[f + 1])) + isolated = false; + if (isolated) { + mgScore += s * -20; + egScore += s * -15; + } + } + } + + // Passed pawn bonus + for (Color c : {WHITE, BLACK}) { + int s = (c == WHITE) ? 1 : -1; + Bitboard pawns = board.pieces(PAWN, c); + Bitboard enemyPawns = board.pieces(PAWN, ~c); + while (pawns) { + Square sq = Square(pop_lsb(pawns)); + Rank relRank = relative_rank(c, sq); + if (relRank < RANK_2) + continue; + File f = file_of(sq); + Bitboard passedMask = 0; + int startF = std::max(0, (int)f - 1); + int endF = std::min(7, (int)f + 1); + for (int adjF = startF; adjF <= endF; adjF++) { + if (c == WHITE) { + for (int r = rank_of(sq) + 1; r <= 7; r++) + passedMask |= attacks::MASK_FILE[adjF] & attacks::MASK_RANK[r]; + } else { + for (int r = rank_of(sq) - 1; r >= 0; r--) + passedMask |= attacks::MASK_FILE[adjF] & attacks::MASK_RANK[r]; + } + } + if ((passedMask & enemyPawns) == 0) { + static const Value passedBonus[] = {0, 0, 10, 20, 40, 80, 160, 200}; + Value bonus = passedBonus[relRank]; + mgScore += s * bonus; + egScore += s * bonus; } - mgScore += _sign * mg_pesto_table[piece_of(p)][i]; - egScore += _sign * eg_pesto_table[piece_of(p)][i]; } } + + // King safety: pawn shelter + for (Color c : {WHITE, BLACK}) { + int s = (c == WHITE) ? 1 : -1; + Square kingSq = board.kingSq(c); + File kf = file_of(kingSq); + Bitboard pawns = board.pieces(PAWN, c); + int shelter = 0; + int startF = std::max(0, (int)kf - 1); + int endF = std::min(7, (int)kf + 1); + for (int adjF = startF; adjF <= endF; adjF++) { + if (c == WHITE) { + for (int r = rank_of(kingSq) + 1; r <= std::min(7, rank_of(kingSq) + 3); r++) { + if (pawns & (Bitboard(1) << make_sq((File)adjF, (Rank)r))) + shelter += 10 - (r - rank_of(kingSq) - 1) * 3; + } + } else { + for (int r = rank_of(kingSq) - 1; r >= std::max(0, rank_of(kingSq) - 3); r--) { + if (pawns & (Bitboard(1) << make_sq((File)adjF, (Rank)r))) + shelter += 10 - (rank_of(kingSq) - r - 1) * 3; + } + } + } + mgScore += s * shelter; + egScore += s * shelter / 2; + } + + phase = (phase * 256 + TotalPhase / 2) / TotalPhase; Value finalScore = - ((mgScore * phase) + (egScore * (256 - phase))) / 256 * sign; + (((mgScore * phase) + (egScore * (256 - phase))) * sign) / 256; return finalScore; } Value piece_value(PieceType pt) { - Value pieces[] = {0, PawnValue, KnightValue, - BishopValue, RookValue, QueenValue}; + Value pieces[] = {0, PawnValue, KnightValue, + BishopValue, RookValue, QueenValue, + KingValue}; return pieces[pt]; } } // namespace engine::eval diff --git a/main.cpp b/main.cpp index 69cc515..1d3e113 100644 --- a/main.cpp +++ b/main.cpp @@ -4,12 +4,18 @@ #include "ucioption.h" #include using namespace engine; +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + int main() { std::cout << std::unitbuf; std::cout << "cppchess_engine version " << BUILD_VERSION << '\n'; options.add("Move Overhead", Option(10, 0, 1000)); options.add("Hash", Option(16, 1, 1 << 25, - [](const Option &o) { return std::nullopt; })); + [](const Option &o) { + search::tt.resize(int(o)); + return std::nullopt; + })); options.add("Clear Hash", Option(+[](const Option &) { search::tt.clear(); diff --git a/movepick.cpp b/movepick.cpp index 14bb196..11a7542 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -1,14 +1,13 @@ #include "movepick.h" #include "eval.h" +#include "search.h" #include -#include using namespace chess; using engine::eval::piece_value; namespace engine::movepick { -Value historyHeuristic[SQUARE_NB][SQUARE_NB]{}; -Move killerMoves[256][2]; + void orderMoves(chess::Board &board, chess::Movelist &moves, chess::Move ttMove, - int ply) { + int ply, const engine::search::Session& session) { std::vector> scoredMoves; scoredMoves.reserve(moves.size()); @@ -23,12 +22,12 @@ void orderMoves(chess::Board &board, chess::Movelist &moves, chess::Move ttMove, : piece_value(PAWN)) * 10 - piece_value(board.at(move.from())); - else if (move == killerMoves[ply][0]) + else if (move == session.killerMoves[ply][0]) score = 8500; - else if (move == killerMoves[ply][1]) + else if (move == session.killerMoves[ply][1]) score = 8000; else - score = historyHeuristic[move.from()][move.to()]; + score = session.historyHeuristic[move.from()][move.to()]; scoredMoves.emplace_back(move, score); } diff --git a/movepick.h b/movepick.h index a789802..6f78099 100644 --- a/movepick.h +++ b/movepick.h @@ -1,7 +1,8 @@ #pragma once #include +namespace engine::search{ +struct Session; +} // namespace engine::search namespace engine::movepick { -void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int); -extern int historyHeuristic[64][64]; -extern chess::Move killerMoves[256][2]; +void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int, const engine::search::Session&); } // namespace engine::movepick diff --git a/search.cpp b/search.cpp index e375583..04d0aaf 100644 --- a/search.cpp +++ b/search.cpp @@ -14,13 +14,6 @@ TranspositionTable search::tt(16); std::atomic stopSearch{false}; void search::stop() { stopSearch.store(true, std::memory_order_relaxed); } bool search::isStopped() { return stopSearch; } -struct Session { - timeman::TimeManagement tm; - timeman::LimitsType tc; - int seldepth = 0; - uint64_t nodes = 0; - chess::Move pv[MAX_PLY][MAX_PLY]; -}; namespace { // SF void update_pv(Move *pv, Move move, const Move *childPv) { @@ -74,21 +67,58 @@ Value value_from_tt(Value v, int ply, int r50c) { return v; } } // namespace -Value qsearch(Board &board, Value alpha, Value beta, Session &session, +Value qsearch(Board &board, Value alpha, Value beta, search::Session &session, int ply = 0) { session.nodes++; session.seldepth = std::max(session.seldepth, ply); + + Value alphaOrig = alpha; + bool tt_hit = false; + Move bestMove = Move::none(); + TTEntry *entry = search::tt.lookup(board.hash()); + if (entry && entry->getDepth() == 0) { + Value ttScore = value_from_tt(entry->getScore(), ply, board.rule50_count()); + TTFlag flag = entry->getFlag(); + if (flag == TTFlag::EXACT) { + tt_hit = true; + return ttScore; + } + if (flag == TTFlag::LOWERBOUND && ttScore >= beta) { + tt_hit = true; + return ttScore; + } + if (flag == TTFlag::UPPERBOUND && ttScore <= alpha) { + tt_hit = true; + return ttScore; + } + } + if (entry) + bestMove = Move(entry->getMove()); + int standPat = eval::eval(board); if (session.tm.elapsed() >= session.tm.optimum() || stopSearch.load(std::memory_order_relaxed)) return standPat; Value maxScore = standPat; - if (maxScore >= beta) + if (maxScore >= beta) { + if (!tt_hit) + search::tt.store(board.hash(), bestMove, value_to_tt(maxScore, ply), 0, + TTFlag::LOWERBOUND); return maxScore; + } if (maxScore > alpha) alpha = maxScore; Movelist moves; board.legals(moves); + // TT move first + if (bestMove != Move::none()) { + for (size_t i = 0; i < moves.size(); i++) { + if (moves[i] == bestMove) { + std::swap(moves[0], moves[i]); + break; + } + } + } for (Move move : moves) { board.doMove(move); Value score = qsearch(board, -beta, -alpha, session, ply + 1); @@ -96,17 +126,34 @@ Value qsearch(Board &board, Value alpha, Value beta, Session &session, if (score == VALUE_NONE) return VALUE_NONE; score = -score; - if (score >= beta) + if (score >= beta) { + if (!tt_hit) + search::tt.store(board.hash(), bestMove, value_to_tt(score, ply), 0, + TTFlag::LOWERBOUND); return score; - if (score > maxScore) + } + if (score > maxScore) { maxScore = score; + bestMove = move; + } if (score > alpha) alpha = score; } + if (!tt_hit) { + TTFlag flag; + if (maxScore <= alphaOrig) + flag = TTFlag::UPPERBOUND; + else if (maxScore >= beta) + flag = TTFlag::LOWERBOUND; + else + flag = TTFlag::EXACT; + search::tt.store(board.hash(), bestMove, value_to_tt(maxScore, ply), 0, + flag); + } return maxScore; } Value doSearch(Board &board, int depth, Value alpha, Value beta, - Session &session, int ply = 0) { + search::Session &session, int ply = 0) { // TLE or exceeded depth limit if (ply >= MAX_PLY - 1) return eval::eval(board); @@ -118,11 +165,11 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, stopSearch.load(std::memory_order_relaxed)) return qsearch(board, alpha, beta, session, ply); Value alphaOrig = alpha; - // Reset PV - std::fill(std::begin(session.pv[ply]), std::end(session.pv[ply]), - Move::none()); - std::fill(std::begin(session.pv[ply + 1]), std::end(session.pv[ply + 1]), - Move::none()); + // Reset PV - explicit loop to avoid any std::fill issues + for (int _i = 0; _i < MAX_PLY; _i++) + session.pv[ply][_i] = Move::none(); + for (int _i = 0; _i < MAX_PLY; _i++) + session.pv[ply + 1][_i] = Move::none(); if (board.is_draw(3) || board.is_insufficient_material()) { session.nodes++; session.pv[ply][0] = Move::none(); @@ -164,9 +211,9 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, session.pv[ply][0] = Move::none(); return board.checkers() ? -MATE(ply) : 0; } - movepick::orderMoves(board, moves, preferred, ply); + movepick::orderMoves(board, moves, preferred, ply, session); if (bool useNMP = depth >= 3 && !board.checkers() && ply > 0) { - int R = 2 + depth / 6; + int R = 2 + depth / 6 + std::min(2, depth / 10); uint64_t hash_ = board.hash(); board.doNullMove(); Value score = @@ -177,20 +224,28 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, if (score >= beta) return score; } + for (size_t i = 0; i < moves.size(); ++i) { Move move = moves[i]; + // Clear child PV before each child search to prevent stale data from + // previous children across iterations of this move loop + for (int _i = 0; _i < MAX_PLY; _i++) + session.pv[ply + 1][_i] = Move::none(); + bool isCapture = board.isCapture(move); bool givesCheck = board.givesCheck(move) != CheckType::NO_CHECK; // --- LMR reduction --- int reduction = 0; if (i >= 3 && depth >= 3 && !isCapture && !givesCheck) { - reduction = 1 + (int)(i / 6) + (depth / 8); + reduction = 1 + i / 6 + depth / 8; - // history heuristic: good moves get reduced less - if (movepick::historyHeuristic[(int)move.from()][(int)move.to()] > 0) + int history = session.historyHeuristic[(int)move.from()][(int)move.to()]; + if (history > 0) reduction--; + else if (history < 0) + reduction++; reduction = std::max(0, reduction); reduction = std::min(reduction, depth - 2); @@ -241,16 +296,16 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, alpha = score; if (!isCapture) - movepick::historyHeuristic[(int)move.from()][(int)move.to()] += + session.historyHeuristic[(int)move.from()][(int)move.to()] += depth * depth; } if (alpha >= beta) { // killer moves if (!isCapture) { - if (movepick::killerMoves[ply][0] != move) { - movepick::killerMoves[ply][1] = movepick::killerMoves[ply][0]; - movepick::killerMoves[ply][0] = move; + if (session.killerMoves[ply][0] != move) { + session.killerMoves[ply][1] = session.killerMoves[ply][0]; + session.killerMoves[ply][0] = move; } } break; @@ -279,22 +334,50 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, void search::search(const chess::Board &board, const timeman::LimitsType timecontrol) { stopSearch = false; + tt.newSearch(); static double originalTimeAdjust = -1; Session session; session.tc = timecontrol; session.tm.init(session.tc, board.sideToMove(), 0, originalTimeAdjust); InfoFull lastInfo{}; chess::Move lastPV[MAX_PLY]{}; + Value prevScore = VALUE_NONE; for (int i = 1; i < timecontrol.depth; i++) { for (int _ = 0; _ < 64; _++) for (int j = 0; j < 64; j++) { - movepick::historyHeuristic[_][j] /= 2; + session.historyHeuristic[_][j] /= 2; // since MAX_PLY=64 session.pv[_][j] = Move::none(); } auto board_ = board; - Value score_ = - doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + Value score_; + + // Aspiration windows + if (i >= 3 && prevScore != VALUE_NONE) { + Value delta = Value(17); + Value alpha = std::max(prevScore - delta, -VALUE_INFINITE); + Value beta = std::min(prevScore + delta, VALUE_INFINITE); + + score_ = doSearch(board_, i, alpha, beta, session); + + if (score_ != VALUE_NONE) { + if (score_ <= alpha) { + alpha = -VALUE_INFINITE; + score_ = doSearch(board_, i, alpha, beta, session); + } + if (score_ >= beta && score_ != VALUE_NONE) { + beta = VALUE_INFINITE; + score_ = doSearch(board_, i, alpha, beta, session); + } + if ((score_ <= alpha || score_ >= beta) && score_ != VALUE_NONE) + score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + } else { + score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + } + } else { + score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + } + prevScore = score_; if (session.tm.elapsed() >= session.tm.optimum() || stopSearch.load(std::memory_order_relaxed) || score_ == VALUE_NONE) break; diff --git a/search.h b/search.h index d0331d9..275c1c7 100644 --- a/search.h +++ b/search.h @@ -1,12 +1,18 @@ #pragma once +#include "eval.h" +#include "timeman.h" #include "tt.h" #include -namespace engine { -namespace timeman { -struct LimitsType; -} -} // namespace engine namespace engine::search { +struct Session { + timeman::TimeManagement tm; + timeman::LimitsType tc; + int seldepth = 0; + uint64_t nodes = 0; + chess::Move pv[MAX_PLY][MAX_PLY]; + Value historyHeuristic[chess::SQUARE_NB][chess::SQUARE_NB]{}; + chess::Move killerMoves[256][2]; +}; void stop(); void search(const chess::Board &, const timeman::LimitsType); bool isStopped(); diff --git a/timeman.cpp b/timeman.cpp index 96d6b29..b47bad9 100644 --- a/timeman.cpp +++ b/timeman.cpp @@ -28,8 +28,10 @@ void TimeManagement::init(LimitsType &limits, chess::Color us, int ply, optimumTime = maximumTime = TimePoint(limits.movetime); return; } - if (limits.time[us] == 0 && limits.movetime == 0) + if (limits.time[us] == 0 && limits.movetime == 0) { + optimumTime = maximumTime = INFINITE_TIME; return; + } // optScale is a percentage of available time to use for the current move. // maxScale is a multiplier applied to optimumTime. double optScale, maxScale; diff --git a/tt.h b/tt.h index 6c69dc3..a74f167 100644 --- a/tt.h +++ b/tt.h @@ -128,27 +128,18 @@ class TranspositionTable { ~TranspositionTable() { delete[] table; } void resize(int sizeInMB) { - TTEntry *old_table = table; - int old_size = size; + int new_size = sizeInMB * 1048576 / sizeof(TTEntry); + if (new_size % 2 != 0) + new_size--; - size = sizeInMB * 1048576 / sizeof(TTEntry); - if (size % 2 != 0) - size--; - buckets = size / 2; - - TTEntry *new_table = new (std::nothrow) TTEntry[size]; + TTEntry *new_table = new (std::nothrow) TTEntry[new_size](); if (!new_table) { - // Restore old values on failure - table = old_table; - size = old_size; - buckets = old_size / 2; throw std::bad_alloc(); } - std::memcpy(new_table, old_table, - sizeof(TTEntry) * std::min(size, old_size)); - delete[] old_table; + delete[] table; table = new_table; + size = new_size; buckets = size / 2; } From 19958f349fcab239c156f3d4744607b407d1cbdc Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 24 May 2026 02:54:53 +0000 Subject: [PATCH 02/29] Apply clang-format --- eval.cpp | 21 +++++++++++---------- main.cpp | 9 ++++----- movepick.cpp | 2 +- movepick.h | 5 +++-- search.cpp | 41 +++++++++++++++++++++-------------------- 5 files changed, 40 insertions(+), 38 deletions(-) diff --git a/eval.cpp b/eval.cpp index c6ffdab..909eed0 100644 --- a/eval.cpp +++ b/eval.cpp @@ -1,7 +1,7 @@ #include "eval.h" #include "tune.h" -#include #include +#include using namespace chess; using namespace engine::eval; @@ -114,7 +114,7 @@ Value mgMobility[] = { }; Value egMobility[] = {0, 0, 2, 5, 3, 2, 0}; -Value spaceWeight=28; +Value spaceWeight = 28; // tuning slop here Value eval(const chess::Board &board) { constexpr int KnightPhase = 1; @@ -128,10 +128,10 @@ Value eval(const chess::Board &board) { int egScore = 0; int phase = 0; { - mgScore=egScore=board.sideToMove() == WHITE?spaceWeight:0; - Bitboard occ = board.occ(), occ2=occ; + mgScore = egScore = board.sideToMove() == WHITE ? spaceWeight : 0; + Bitboard occ = board.occ(), occ2 = occ; while (occ) { - Square sq = (Square)pop_lsb(occ),_sq=sq; + Square sq = (Square)pop_lsb(occ), _sq = sq; auto p = board.at(sq); int _sign = 1; if (color_of(p) == BLACK) { @@ -296,12 +296,14 @@ Value eval(const chess::Board &board) { int endF = std::min(7, (int)kf + 1); for (int adjF = startF; adjF <= endF; adjF++) { if (c == WHITE) { - for (int r = rank_of(kingSq) + 1; r <= std::min(7, rank_of(kingSq) + 3); r++) { + for (int r = rank_of(kingSq) + 1; r <= std::min(7, rank_of(kingSq) + 3); + r++) { if (pawns & (Bitboard(1) << make_sq((File)adjF, (Rank)r))) shelter += 10 - (r - rank_of(kingSq) - 1) * 3; } } else { - for (int r = rank_of(kingSq) - 1; r >= std::max(0, rank_of(kingSq) - 3); r--) { + for (int r = rank_of(kingSq) - 1; r >= std::max(0, rank_of(kingSq) - 3); + r--) { if (pawns & (Bitboard(1) << make_sq((File)adjF, (Rank)r))) shelter += 10 - (rank_of(kingSq) - r - 1) * 3; } @@ -317,9 +319,8 @@ Value eval(const chess::Board &board) { return finalScore; } Value piece_value(PieceType pt) { - Value pieces[] = {0, PawnValue, KnightValue, - BishopValue, RookValue, QueenValue, - KingValue}; + Value pieces[] = {0, PawnValue, KnightValue, BishopValue, + RookValue, QueenValue, KingValue}; return pieces[pt]; } } // namespace engine::eval diff --git a/main.cpp b/main.cpp index 1d3e113..9e229b2 100644 --- a/main.cpp +++ b/main.cpp @@ -11,11 +11,10 @@ int main() { std::cout << std::unitbuf; std::cout << "cppchess_engine version " << BUILD_VERSION << '\n'; options.add("Move Overhead", Option(10, 0, 1000)); - options.add("Hash", Option(16, 1, 1 << 25, - [](const Option &o) { - search::tt.resize(int(o)); - return std::nullopt; - })); + options.add("Hash", Option(16, 1, 1 << 25, [](const Option &o) { + search::tt.resize(int(o)); + return std::nullopt; + })); options.add("Clear Hash", Option(+[](const Option &) { search::tt.clear(); diff --git a/movepick.cpp b/movepick.cpp index 11a7542..8a6a43a 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -7,7 +7,7 @@ using engine::eval::piece_value; namespace engine::movepick { void orderMoves(chess::Board &board, chess::Movelist &moves, chess::Move ttMove, - int ply, const engine::search::Session& session) { + int ply, const engine::search::Session &session) { std::vector> scoredMoves; scoredMoves.reserve(moves.size()); diff --git a/movepick.h b/movepick.h index 6f78099..69d313c 100644 --- a/movepick.h +++ b/movepick.h @@ -1,8 +1,9 @@ #pragma once #include -namespace engine::search{ +namespace engine::search { struct Session; } // namespace engine::search namespace engine::movepick { -void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int, const engine::search::Session&); +void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int, + const engine::search::Session &); } // namespace engine::movepick diff --git a/search.cpp b/search.cpp index 04d0aaf..71ba370 100644 --- a/search.cpp +++ b/search.cpp @@ -354,28 +354,29 @@ void search::search(const chess::Board &board, // Aspiration windows if (i >= 3 && prevScore != VALUE_NONE) { - Value delta = Value(17); - Value alpha = std::max(prevScore - delta, -VALUE_INFINITE); - Value beta = std::min(prevScore + delta, VALUE_INFINITE); - - score_ = doSearch(board_, i, alpha, beta, session); - - if (score_ != VALUE_NONE) { - if (score_ <= alpha) { - alpha = -VALUE_INFINITE; - score_ = doSearch(board_, i, alpha, beta, session); - } - if (score_ >= beta && score_ != VALUE_NONE) { - beta = VALUE_INFINITE; - score_ = doSearch(board_, i, alpha, beta, session); - } - if ((score_ <= alpha || score_ >= beta) && score_ != VALUE_NONE) - score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); - } else { - score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + Value delta = Value(17); + Value alpha = std::max(prevScore - delta, -VALUE_INFINITE); + Value beta = std::min(prevScore + delta, VALUE_INFINITE); + + score_ = doSearch(board_, i, alpha, beta, session); + + if (score_ != VALUE_NONE) { + if (score_ <= alpha) { + alpha = -VALUE_INFINITE; + score_ = doSearch(board_, i, alpha, beta, session); } - } else { + if (score_ >= beta && score_ != VALUE_NONE) { + beta = VALUE_INFINITE; + score_ = doSearch(board_, i, alpha, beta, session); + } + if ((score_ <= alpha || score_ >= beta) && score_ != VALUE_NONE) + score_ = + doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + } else { score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + } + } else { + score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); } prevScore = score_; if (session.tm.elapsed() >= session.tm.optimum() || From 1eca728591d101e2f6f0ed854169e98edf04bb53 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:34:40 +0700 Subject: [PATCH 03/29] Refactor sideToMove to side_to_move for consistency --- eval.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eval.cpp b/eval.cpp index 909eed0..3caa9a0 100644 --- a/eval.cpp +++ b/eval.cpp @@ -123,12 +123,12 @@ Value eval(const chess::Board &board) { constexpr int QueenPhase = 4; constexpr int TotalPhase = KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; - const int sign = board.sideToMove() == chess::Color::WHITE ? 1 : -1; + const int sign = board.side_to_move() == WHITE ? 1 : -1; int mgScore = 0; int egScore = 0; int phase = 0; { - mgScore = egScore = board.sideToMove() == WHITE ? spaceWeight : 0; + mgScore = egScore = board.side_to_move() == WHITE ? spaceWeight : 0; Bitboard occ = board.occ(), occ2 = occ; while (occ) { Square sq = (Square)pop_lsb(occ), _sq = sq; From 53f80a9153c6ffc83a41e6b7641e023fc46ff0b7 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:35:18 +0700 Subject: [PATCH 04/29] Fix method call to use correct side_to_move() --- search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search.cpp b/search.cpp index 71ba370..9b5d536 100644 --- a/search.cpp +++ b/search.cpp @@ -338,7 +338,7 @@ void search::search(const chess::Board &board, static double originalTimeAdjust = -1; Session session; session.tc = timecontrol; - session.tm.init(session.tc, board.sideToMove(), 0, originalTimeAdjust); + session.tm.init(session.tc, board.side_to_move(), 0, originalTimeAdjust); InfoFull lastInfo{}; chess::Move lastPV[MAX_PLY]{}; Value prevScore = VALUE_NONE; From 8d3c4500d0264cf5412feeb6bceff798c344d89c Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:38:32 +0700 Subject: [PATCH 05/29] Update movepick.cpp --- movepick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movepick.cpp b/movepick.cpp index 8a6a43a..9d3c216 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -17,7 +17,7 @@ void orderMoves(chess::Board &board, chess::Movelist &moves, chess::Move ttMove, if (move == ttMove) score = 10000; else if (board.isCapture(move)) - score = ((move.typeOf() & EN_PASSANT) == 0 + score = ((move.type_of() & EN_PASSANT) == 0 ? piece_value(board.at(move.to())) : piece_value(PAWN)) * 10 - From 5cb6226d4c91a2b898c273617c7a35cfe724153b Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:39:04 +0700 Subject: [PATCH 06/29] Fix case of FEN in setFEN method --- uci.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uci.cpp b/uci.cpp index 2ffd2a2..037a8ff 100644 --- a/uci.cpp +++ b/uci.cpp @@ -33,7 +33,7 @@ void handlePosition(std::istringstream &is) { fen += token + " "; else return; - pos.setFen(fen); + pos.setFEN(fen); while (is >> token) { pos.push_uci(token); From 864a7a993ca014bfd68c6e0356bfd16f607625d9 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:41:37 +0700 Subject: [PATCH 07/29] Update uci.cpp --- uci.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uci.cpp b/uci.cpp index 037a8ff..20aab9a 100644 --- a/uci.cpp +++ b/uci.cpp @@ -158,7 +158,7 @@ void engine::report(std::string_view bestmove) { } void engine::loop() { std::string line; - pos.setFen(pos.START_FEN); + pos.setFEN(chess::Position::START_FEN); while (std::getline(std::cin, line)) { std::istringstream ss(line); From 84643b79cebeba12415aab5a9f7999e5e9eff6dd Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Sun, 7 Jun 2026 21:51:07 +0700 Subject: [PATCH 08/29] a lil bit of tuning --- CMakeLists.txt | 12 +- eval.cpp | 662 +++++++++++++++++++--------------- eval.h | 8 +- main.cpp | 32 +- movepick.cpp | 108 ++++-- movepick.h | 5 +- score.cpp | 20 +- score.h | 40 +-- search.cpp | 959 ++++++++++++++++++++++++++++--------------------- search.h | 19 +- timeman.cpp | 112 +++--- timeman.h | 58 ++- tt.cpp | 97 +++-- tt.h | 273 +++++++------- tune.cpp | 68 ++-- tune.h | 204 +++++------ uci.cpp | 334 +++++++++-------- uci.h | 31 +- ucioption.cpp | 192 +++++----- ucioption.h | 102 +++--- 20 files changed, 1796 insertions(+), 1540 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3405d8e..251515c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,9 +11,15 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/winapiadmin/chesslib.git GIT_TAG main ) -FetchContent_MakeAvailable(chesslib) -add_executable(engine "main.cpp" "timeman.cpp" "timeman.h" "eval.h" "eval.cpp" "tune.h" "ucioption.h" "tune.cpp" "ucioption.cpp" "tt.h" "tt.cpp" "uci.cpp" "uci.h" "search.h" "search.cpp" "score.h" "score.cpp" "movepick.h" "movepick.cpp") -target_link_libraries(engine PRIVATE chesslib) +FetchContent_Declare( + tbprobe + GIT_REPOSITORY https://github.com/winapiadmin/tb_probing_tool.git + GIT_TAG main +) +set(BUILD_GAVIOTA OFF CACHE BOOL "Enable Gaviota TB probing" FORCE) +FetchContent_MakeAvailable(chesslib tbprobe) +add_executable(engine "main.cpp" "timeman.cpp" "timeman.h" "eval.h" "eval.cpp" "tune.h" "ucioption.h" "tune.cpp" "ucioption.cpp" "tt.h" "tt.cpp" "uci.cpp" "uci.h" "search.h" "search.cpp" "score.h" "score.cpp" "movepick.h" "movepick.cpp" "tb.h" "tb.cpp") +target_link_libraries(engine PRIVATE chesslib syzygy_probe) target_include_directories(engine PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${chesslib_SOURCE_DIR}) execute_process( COMMAND git rev-parse --short HEAD diff --git a/eval.cpp b/eval.cpp index 3caa9a0..8e26f6a 100644 --- a/eval.cpp +++ b/eval.cpp @@ -6,321 +6,413 @@ using namespace chess; using namespace engine::eval; namespace engine::eval { -Value PawnValue = 100, KnightValue = 325, BishopValue = 350, RookValue = 500, - QueenValue = 900, KingValue = 0; -Value mg_pawn_table[64] = { - 0, 0, 0, 0, 0, 0, 0, 0, 98, 134, 61, 95, 68, 126, 34, -11, - -6, 7, 26, 31, 65, 56, 25, -20, -14, 13, 6, 21, 23, 12, 17, -23, - -27, -2, -5, 12, 17, 6, 10, -25, -26, -4, -4, -10, 3, 3, 33, -12, - -35, -1, -20, -23, -15, 24, 38, -22, 0, 0, 0, 0, 0, 0, 0, 0, -}; - -Value eg_pawn_table[64] = { - 0, 0, 0, 0, 0, 0, 0, 0, 178, 173, 158, 134, 147, 132, 165, 187, - 94, 100, 85, 67, 56, 53, 82, 84, 32, 24, 13, 5, -2, 4, 17, 17, - 13, 9, -3, -7, -7, -8, 3, -1, 4, 7, -6, 1, 0, -5, -1, -8, - 13, 8, 8, 10, 13, 0, 2, -7, 0, 0, 0, 0, 0, 0, 0, 0, -}; - -Value mg_knight_table[64] = { - -167, -89, -34, -49, 61, -97, -15, -107, -73, -41, 72, 36, 23, - 62, 7, -17, -47, 60, 37, 65, 84, 129, 73, 44, -9, 17, - 19, 53, 37, 69, 18, 22, -13, 4, 16, 13, 28, 19, 21, - -8, -23, -9, 12, 10, 19, 17, 25, -16, -29, -53, -12, -3, - -1, 18, -14, -19, -105, -21, -58, -33, -17, -28, -19, -23, -}; - -Value eg_knight_table[64] = { - -58, -38, -13, -28, -31, -27, -63, -99, -25, -8, -25, -2, -9, - -25, -24, -52, -24, -20, 10, 9, -1, -9, -19, -41, -17, 3, - 22, 22, 22, 11, 8, -18, -18, -6, 16, 25, 16, 17, 4, - -18, -23, -3, -1, 15, 10, -3, -20, -22, -42, -20, -10, -5, - -2, -20, -23, -44, -29, -51, -23, -15, -22, -18, -50, -64, -}; - -Value mg_bishop_table[64] = { - -29, 4, -82, -37, -25, -42, 7, -8, -26, 16, -18, -13, 30, 59, 18, -47, - -16, 37, 43, 40, 35, 50, 37, -2, -4, 5, 19, 50, 37, 37, 7, -2, - -6, 13, 13, 26, 34, 12, 10, 4, 0, 15, 15, 15, 14, 27, 18, 10, - 4, 15, 16, 0, 7, 21, 33, 1, -33, -3, -14, -21, -13, -12, -39, -21, -}; - -Value eg_bishop_table[64] = { - -14, -21, -11, -8, -7, -9, -17, -24, -8, -4, 7, -12, -3, -13, -4, -14, - 2, -8, 0, -1, -2, 6, 0, 4, -3, 9, 12, 9, 14, 10, 3, 2, - -6, 3, 13, 19, 7, 10, -3, -9, -12, -3, 8, 10, 13, 3, -7, -15, - -14, -18, -7, -1, 4, -9, -15, -27, -23, -9, -23, -5, -9, -16, -5, -17, +static Bitboard passedMask[2][64]; +struct PassedMaskInit { + PassedMaskInit() { + for (int sq = 0; sq < 64; sq++) { + File f = file_of(Square(sq)); + Rank r = rank_of(Square(sq)); + for (int adjF = std::max(0, (int)f - 1); adjF <= std::min(7, (int)f + 1); adjF++) { + for (int r2 = (int)r + 1; r2 <= 7; r2++) + passedMask[WHITE][sq] |= attacks::MASK_FILE[adjF] & attacks::MASK_RANK[r2]; + for (int r2 = (int)r - 1; r2 >= 0; r2--) + passedMask[BLACK][sq] |= attacks::MASK_FILE[adjF] & attacks::MASK_RANK[r2]; + } + } + } }; +static PassedMaskInit passedMaskInit; +Value tempo = 50; +Value PawnValue = 72; +Value KnightValue = 372; +Value BishopValue = 202; +Value RookValue = 324; +Value QueenValue = 652; +Value fianchettoBonus = 44; +Value trappedBishopPenalty = 29; +Value centerWeight = 33; +Value mopUpKingDistWeight = 24; +Value mopUpEdgeDistWeight = 38; +Value spaceWeight = 73; +Value bishopPairMg = 94; +Value bishopPairEg = 14; +Value rookOpenFileMg = 34; +Value rookOpenFileEg = 29; +Value rookSemiOpenFileMg = 25; +Value rookSemiOpenFileEg = 14; +Value doubledPawnMg = 60; +Value doubledPawnEg = 63; +Value isolatedPawnMg = 77; +Value isolatedPawnEg = 23; +Value kingShelterBaseMg = 16; +Value kingShelterBaseEg = 6; +Value kingShelterDecayMg = 5; +Value kingShelterDecayEg = 10; +Value kqkDistWeight = 28; +Value kqkEdgeWeight = 45; +Value krkDistWeight = 4; +Value krkEdgeWeight = 17; +Value kpkWeight = 17; +Value mgMobilityCnt[7][8] = { { 92, -46, 6, -99, -98, -35, 74, -28 }, { -81, 43, 0, -100, -68, -86, 47, 29 }, { 51, 71, 88, -78, 17, -56, 99, 5 }, { -59, -78, -88, 22, 2, 86, -77, -1 }, { -55, -98, 93, 70, 27, -54, -100, -83 }, { 13, 1, -81, -7, -42, -95, -68, -99 }, { -37, 43, 8, -100, -99, 36, 91, -42 } }; +Value egMobilityCnt[7][8] = { { -75, -98, 51, 26, -10, 68, 2, 10 }, { -65, -80, 95, 79, -43, -16, 100, -99 }, { 63, -25, -74, -41, 100, -37, 4, -85 }, { -66, 40, 41, 46, -65, -75, 29, 78 }, { 12, -53, -91, 23, -100, -66, 40, 73 }, { -28, 5, -46, 25, -13, 11, 87, 33 }, { -89, -4, 28, -27, 7, -1, 26, -98 } }; +Value kingTropismMg[7] = { -29, 17, 7, -15, -49, -14, -1 }; +Value kingTropismEg[7] = { -41, 0, -31, -41, 19, 36, 50 }; +Value passedBonusMg[8] = { 313, 227, 386, 163, 109, 284, 220, 363 }; +Value passedBonusEg[8] = { 380, 350, 394, 388, 149, 394, 190, 316 }; +Value mg_pawn_table[56] = { 114, -495, 41, -368, -477, 412, -158, -276, -129, -129, -237, 437, -158, 121, -2, -160, 163, 459, 425, -298, 52, -296, -496, 333, -390, -473, -314, -319, -357, 348, 127, -347, 175, -346, -398, 155, 362, 477, -147, -418, -477, 311, 191, -489, -414, -352, 479, -89, 499, 265, -325, -291, -55, 491, -499, 470 }; +Value mg_knight_table[64] = { 443, 51, 110, 497, 243, 173, 224, 109, -498, 280, -499, 185, 58, 494, 408, -33, -395, 45, -15, 208, 245, -13, 378, 359, -489, 352, 96, -152, 422, -414, -153, 304, -483, -113, 346, 1, 104, -185, 141, -483, 405, -500, -117, 158, -306, -145, 465, 458, 257, -344, -107, 26, 334, 369, -338, 352, -412, -223, 69, 244, -156, 335, -368, 359 }; +Value mg_bishop_table[64] = { -258, -250, 404, 459, 322, 66, 411, 227, -93, -52, -352, 13, 450, -441, 392, -139, 315, 263, 210, 299, -397, 133, 371, 440, 272, 221, -76, -125, -13, 483, 135, 182, -351, 203, 101, 298, 352, 277, -478, 488, -62, 42, -144, 496, -396, 195, -414, 11, 409, 116, -373, 386, 377, 47, 493, 0, -302, 312, 81, 459, 350, -187, -265, 153 }; +Value mg_rook_table[64] = { -27, -134, -499, 159, 77, -60, 266, 171, 77, -364, -338, -315, -123, 482, -341, 412, 213, 132, 134, 254, 354, -40, -145, -364, 249, 497, -490, 25, -191, -245, 104, 428, 417, 49, 499, -426, -261, 466, 80, -61, -457, 75, 95, 15, 270, -250, -171, 165, 186, 92, 401, 482, -223, -70, 384, 316, -189, 13, 185, -110, 207, -307, -257, 175 }; +Value mg_king_table[64] = { -498, 290, 205, 125, -483, -313, -372, -330, 302, 312, -171, -115, 403, 43, -259, 212, -264, -396, -329, 260, -288, 374, 454, 499, -124, -366, -335, 371, 161, -361, 368, 102, -454, -133, -52, 139, -102, -418, 380, 499, -108, 340, -352, 326, 415, 2, 150, -314, 430, -161, -399, -99, 8, -292, -81, 79, -491, 309, -160, -208, -173, -283, -313, -62 }; +Value mg_queen_table[64] = { -301, -182, 487, 58, 270, 273, 177, -207, -175, 246, 266, 332, -416, -363, 54, -202, 261, -423, -97, 280, -251, -265, 79, 493, 332, -133, 499, -375, -251, 210, 139, -248, 424, 193, -36, -279, 429, 62, 379, 173, 446, 0, -370, 426, -462, 287, 165, -499, 94, -425, 143, -409, 144, 495, 111, 452, 78, 157, -143, -478, 243, -97, -489, 112 }; +Value eg_pawn_table[56] = { -273, -6, 100, 320, -127, 163, -33, 79, 235, 334, 47, 435, -149, -1, -155, -277, -25, 220, 340, -396, 401, 446, -30, 424, 472, -385, 486, -91, 99, -275, 340, -330, -125, 150, -121, -30, -207, -244, -277, 411, 301, -298, 77, -313, -141, -357, 386, 255, -495, 80, -500, 400, -450, 130, -111, -28 }; +Value eg_knight_table[64] = { 371, -385, 222, -192, 139, 46, -343, 59, 366, -360, 248, 384, -63, -425, 225, 414, -333, 396, -421, -99, 10, 367, -368, 500, -500, 270, 41, 168, 154, 327, -161, 457, -368, 79, -265, -457, 94, -294, -234, 348, 233, -471, -360, 380, -228, 3, 314, 415, -327, 200, -91, 251, -390, -294, -187, -335, -29, 177, -341, 197, -37, 143, 217, 384 }; +Value eg_bishop_table[64] = { 31, 57, -369, -271, 267, -495, -141, -276, 239, 405, 217, 34, -391, -36, 122, -427, -35, 406, 286, 52, 497, 162, 262, -147, -251, -470, -258, -171, -63, -203, 296, 497, 90, 444, 422, -241, -499, 62, -312, 299, -345, 309, 168, 142, 161, -170, 138, -116, 359, -45, 214, 191, -360, 163, 433, 386, 182, -181, 348, 258, 415, 375, -233, 214 }; +Value eg_rook_table[64] = { -227, 492, 17, -205, 495, 12, 207, -246, 195, -22, 312, 16, -124, -259, 405, -494, -262, -344, -218, -12, 8, 388, 306, 316, 376, 455, -139, -194, 41, 158, 283, 476, 469, 94, 63, -498, -414, 0, -292, -373, -270, 256, -162, -385, 453, -220, 162, -53, -221, -435, -426, -222, 235, 116, 182, 409, -327, -486, 80, 109, -455, -247, 106, -307 }; +Value eg_king_table[64] = { -319, -481, 495, -290, 1, 426, -347, -112, 423, -89, 500, -432, 54, -223, 148, 118, -292, -351, -205, 195, 377, -185, -296, -176, -73, 247, -491, -411, -232, 106, -295, 326, 60, 500, -488, 218, 207, -409, 91, -390, 35, -249, 111, -179, -169, -182, -372, -330, 425, 455, 457, 500, -494, -442, 24, 170, 367, 353, -297, 97, -137, 125, 221, -32 }; +Value eg_queen_table[64] = { -326, 119, 358, 91, -311, -289, 373, 329, 44, -83, 162, -470, -470, -2, 45, -486, 438, 39, -493, 371, 493, -278, 338, -416, 360, 499, 357, -145, -500, 398, -210, -199, 218, -22, -367, 69, 144, 488, -55, -71, 499, -36, -262, -351, -407, 291, -497, -412, -164, -46, 186, 323, -56, -416, -129, 394, 71, -343, 183, -360, 91, -295, -180, 88 }; -Value mg_rook_table[64] = { - 32, 42, 32, 51, 63, 9, 31, 43, 27, 32, 58, 62, 80, 67, 26, 44, - -5, 19, 26, 36, 17, 45, 61, 16, -24, -11, 7, 26, 24, 35, -8, -20, - -36, -26, -12, -1, 9, -7, 6, -23, -45, -25, -16, -17, 3, 0, -5, -33, - -44, -16, -20, -9, -1, 11, -6, -71, -19, -13, 1, 17, 16, 7, -37, -26, -}; +Value *mgPst[] = { nullptr, mg_pawn_table, mg_knight_table, mg_bishop_table, + mg_rook_table, mg_queen_table, mg_king_table }; -Value eg_rook_table[64] = { - 13, 10, 18, 15, 12, 12, 8, 5, 11, 13, 13, 11, -3, 3, 8, 3, - 7, 7, 7, 5, 4, -3, -5, -3, 4, 3, 13, 1, 2, 1, -1, 2, - 3, 5, 8, 4, -5, -6, -8, -11, -4, 0, -5, -1, -7, -12, -8, -16, - -6, -6, 0, 2, -9, -9, -11, -3, -9, 2, 3, -1, -5, -13, 4, -20, -}; +Value *egPst[] = { nullptr, eg_pawn_table, eg_knight_table, eg_bishop_table, + eg_rook_table, eg_queen_table, eg_king_table }; -Value mg_queen_table[64] = { - -28, 0, 29, 12, 59, 44, 43, 45, -24, -39, -5, 1, -16, 57, 28, 54, - -13, -17, 7, 8, 29, 56, 47, 57, -27, -27, -16, -16, -1, 17, -2, 1, - -9, -26, -9, -10, -2, -4, 3, -3, -14, 2, -11, -2, -5, 2, 14, 5, - -35, -8, 11, 2, 8, 15, -3, 1, -1, -18, -9, 10, -15, -25, -31, -50, -}; +// tuning slop here +TUNE(SetRange(0, 50), tempo, SetRange(50, 200), PawnValue, SetRange(200, 500), KnightValue, + SetRange(200, 500), BishopValue, SetRange(300, 700), RookValue, SetRange(600, 1400), QueenValue); +TUNE(SetRange(-100, 100), mgMobilityCnt, egMobilityCnt); +TUNE(SetRange(0, 50), fianchettoBonus, SetRange(0, 150), trappedBishopPenalty); +TUNE(SetRange(-50, 50), kingTropismMg, kingTropismEg); +TUNE(SetRange(0, 50), centerWeight, SetRange(0, 30), mopUpKingDistWeight, + SetRange(0, 50), mopUpEdgeDistWeight, SetRange(0, 100), spaceWeight); +TUNE(SetRange(0, 100), bishopPairMg, SetRange(0, 100), bishopPairEg); +TUNE(SetRange(0, 50), rookOpenFileMg, SetRange(0, 50), rookOpenFileEg, + SetRange(0, 50), rookSemiOpenFileMg, SetRange(0, 50), rookSemiOpenFileEg); +TUNE(SetRange(0, 100), doubledPawnMg, SetRange(0, 100), doubledPawnEg, + SetRange(0, 100), isolatedPawnMg, SetRange(0, 100), isolatedPawnEg); +TUNE(SetRange(0, 400), passedBonusMg, passedBonusEg); +TUNE(SetRange(0, 30), kingShelterBaseMg, SetRange(0, 30), kingShelterBaseEg, + SetRange(0, 10), kingShelterDecayMg, SetRange(0, 10), kingShelterDecayEg); +TUNE(SetRange(-500, 500), mg_pawn_table, mg_knight_table, mg_bishop_table, + mg_rook_table, mg_king_table, mg_queen_table, + eg_pawn_table, eg_knight_table, eg_bishop_table, + eg_rook_table, eg_king_table, eg_queen_table); +TUNE(SetRange(0, 50), kqkDistWeight, SetRange(0, 50), kqkEdgeWeight, + SetRange(0, 50), krkDistWeight, SetRange(0, 50), krkEdgeWeight, + SetRange(0, 50), kpkWeight); -Value eg_queen_table[64] = { - -9, 22, 22, 27, 27, 19, 10, 20, -17, 20, 32, 41, 58, - 25, 30, 0, -20, 6, 9, 49, 47, 35, 19, 9, 3, 22, - 24, 45, 57, 40, 57, 36, -18, 28, 19, 47, 31, 34, 39, - 23, -16, -27, 15, 6, 9, 17, 10, 5, -22, -23, -30, -16, - -16, -23, -36, -32, -33, -28, -22, -43, -5, -32, -20, -41, -}; +Value eval(const chess::Board &board) { + constexpr int KnightPhase = 1; + constexpr int BishopPhase = 1; + constexpr int RookPhase = 2; + constexpr int QueenPhase = 4; + constexpr int TotalPhase = KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; + const int sign = board.side_to_move() == WHITE ? 1 : -1; + int mgScore = 0; + int egScore = 0; + int phase = 0; + { + Bitboard pinMask = board.pin_mask(); + Bitboard occ = board.occ(), occ2 = occ; + while (occ) { + Square sq = Square(pop_lsb(occ)); + auto p = board.at(sq); + Color pc = color_of(p); + int _sign = pc == WHITE ? 1 : -1; + Square _sq = _sign == 1 ? sq : square_mirror(sq); + auto pt = piece_of(p); + if (pt == NO_PIECE_TYPE) + continue; + mgScore += _sign * mgPst[pt][_sq]; + egScore += _sign * egPst[pt][_sq]; + mgScore += _sign * piece_value(pt); + egScore += _sign * piece_value(pt); + if (pt == KNIGHT) phase += KnightPhase; + else if (pt == BISHOP) phase += BishopPhase; + else if (pt == ROOK) phase += RookPhase; + else if (pt == QUEEN) phase += QueenPhase; + Bitboard attacks = 0; + if (pt == KNIGHT) attacks = chess::attacks::knight(sq); + else if (pt == BISHOP) attacks = chess::attacks::bishop(sq, occ2); + else if (pt == ROOK) attacks = chess::attacks::rook(sq, occ2); + else if (pt == QUEEN) attacks = chess::attacks::queen(sq, occ2); + attacks &= ~board.us(pc); + int mobility = popcount(attacks); + int clampedMobility = std::min(mobility, 7); + if ((1ULL << sq) & pinMask) + clampedMobility = std::min(clampedMobility / 4, 7); + mgScore += _sign * mgMobilityCnt[pt][clampedMobility]; + egScore += _sign * egMobilityCnt[pt][clampedMobility]; + if (pt != PAWN && pt != KING) { + int kd = square_distance(sq, board.kingSq(~pc)); + mgScore += _sign * (7 - kd) * kingTropismMg[pt]; + egScore += _sign * (7 - kd) * kingTropismEg[pt]; + } + } + } + // Bishop pair bonus + for (Color c : { WHITE, BLACK }) { + if (board.count(BISHOP, c) >= 2) { + int s = (c == WHITE) ? 1 : -1; + mgScore += s * bishopPairMg; + egScore += s * bishopPairEg; + } + } -Value mg_king_table[64] = { - -65, 23, 16, -15, -56, -34, 2, 13, 29, -1, -20, -7, -8, - -4, -38, -29, -9, 24, 2, -16, -20, 6, 22, -22, -17, -20, - -12, -27, -30, -25, -14, -36, -49, -1, -27, -39, -46, -44, -33, - -51, -14, -14, -22, -46, -44, -30, -15, -27, 1, 7, -8, -64, - -43, -16, 9, 8, -15, 36, 12, -54, 8, -28, 24, 14, -}; + // Fianchetto bonus + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Square fianchettoSq[2] = { relative_square(c, SQ_B2), relative_square(c, SQ_G2) }; + Square pawnSq[2] = { relative_square(c, SQ_B3), relative_square(c, SQ_G3) }; + for (int i = 0; i < 2; i++) { + if (board.at(fianchettoSq[i]) == BISHOP && board.at(fianchettoSq[i]) == c + && board.at(pawnSq[i]) == PAWN && board.at(pawnSq[i]) == c) { + mgScore += s * fianchettoBonus; + egScore += s * fianchettoBonus; + } + } + } -Value eg_king_table[64] = {-74, -35, -18, -18, -11, 15, 4, -17, -12, 17, 14, - 17, 17, 38, 23, 11, 10, 17, 23, 15, 20, 45, - 44, 13, -8, 22, 24, 27, 26, 33, 26, 3, -18, - -4, 21, 24, 27, 23, 9, -11, -19, -3, 11, 21, - 23, 16, 7, -9, -27, -11, 4, 13, 14, 4, -5, - -17, -53, -34, -21, -11, -28, -14, -24, -43}; + // Trapped bishop penalty + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Square trapSq[2] = { relative_square(c, SQ_A2), relative_square(c, SQ_H2) }; + Square kingAdj[2] = { relative_square(c, SQ_B1), relative_square(c, SQ_G1) }; + for (int i = 0; i < 2; i++) { + if (board.at(trapSq[i]) == BISHOP && board.at(trapSq[i]) == c + && board.kingSq(c) == kingAdj[i]) { + mgScore -= s * trappedBishopPenalty; + egScore -= s * trappedBishopPenalty; + } + } + } -Value *mg_pesto_table[] = {nullptr, mg_pawn_table, mg_knight_table, - mg_bishop_table, mg_rook_table, mg_queen_table, - mg_king_table}; + // Rook on open/semi-open file + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Bitboard rooks = board.pieces(ROOK, c); + while (rooks) { + Square sq = Square(pop_lsb(rooks)); + File f = file_of(sq); + Bitboard fileMask = attacks::MASK_FILE[f]; + bool hasOwnPawn = (board.pieces(PAWN, c) & fileMask) != 0; + bool hasEnemyPawn = (board.pieces(PAWN, ~c) & fileMask) != 0; + if (!hasOwnPawn && !hasEnemyPawn) { + mgScore += s * rookOpenFileMg; + egScore += s * rookOpenFileEg; + } else if (!hasOwnPawn) { + mgScore += s * rookSemiOpenFileMg; + egScore += s * rookSemiOpenFileEg; + } + } + } -Value *eg_pesto_table[] = {nullptr, eg_pawn_table, eg_knight_table, - eg_bishop_table, eg_rook_table, eg_queen_table, - eg_king_table}; -Value mgMobility[] = { - 0, // none - 0, // pawn - 3, // knight - 5, // bishop - 2, // rook - 1, // queen - 0 // king -}; + // Precompute pawn data + Bitboard pawnBB[2] = { board.pieces(PAWN, WHITE), board.pieces(PAWN, BLACK) }; + Bitboard pawnAtks[2] = { attacks::pawn(pawnBB[WHITE]), attacks::pawn(pawnBB[BLACK]) }; -Value egMobility[] = {0, 0, 2, 5, 3, 2, 0}; -Value spaceWeight = 28; -// tuning slop here -Value eval(const chess::Board &board) { - constexpr int KnightPhase = 1; - constexpr int BishopPhase = 1; - constexpr int RookPhase = 2; - constexpr int QueenPhase = 4; - constexpr int TotalPhase = - KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; - const int sign = board.side_to_move() == WHITE ? 1 : -1; - int mgScore = 0; - int egScore = 0; - int phase = 0; - { - mgScore = egScore = board.side_to_move() == WHITE ? spaceWeight : 0; - Bitboard occ = board.occ(), occ2 = occ; - while (occ) { - Square sq = (Square)pop_lsb(occ), _sq = sq; - auto p = board.at(sq); - int _sign = 1; - if (color_of(p) == BLACK) { - _sign = -1; - _sq = square_mirror(sq); - } - auto pt = piece_of(p); - if (pt == NO_PIECE_TYPE) - continue; - mgScore += _sign * mg_pesto_table[pt][_sq]; - egScore += _sign * eg_pesto_table[pt][_sq]; - mgScore += _sign * piece_value(pt); - egScore += _sign * piece_value(pt); - switch (pt) { - case KNIGHT: - phase += KnightPhase; - break; - case BISHOP: - phase += BishopPhase; - break; - case ROOK: - phase += RookPhase; - break; - case QUEEN: - phase += QueenPhase; - break; - case PAWN: - break; - case KING: - break; - default: - break; - } - Bitboard attacks = 0; + // Pawn structure: doubled, isolated, passed in one pass per color + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Bitboard pawns = pawnBB[c]; + Bitboard enemyPawns = pawnBB[~c]; - switch (pt) { - case KNIGHT: - attacks = chess::attacks::knight(sq); - break; + bool fileHasPawn[8] = {false}; + int fileCount[8] = {0}; + Bitboard tmp = pawns; + while (tmp) { + Square sq = Square(pop_lsb(tmp)); + File f = file_of(sq); + fileCount[f]++; + fileHasPawn[f] = true; + } - case BISHOP: - attacks = chess::attacks::bishop(sq, occ2); - break; + for (int f = 0; f < 8; f++) + if (fileCount[f] >= 2) { + mgScore -= s * (fileCount[f] - 1) * doubledPawnMg; + egScore -= s * (fileCount[f] - 1) * doubledPawnEg; + } - case ROOK: - attacks = chess::attacks::rook(sq, occ2); - break; + tmp = pawns; + while (tmp) { + Square sq = Square(pop_lsb(tmp)); + File f = file_of(sq); + Rank relRank = relative_rank(c, sq); - case QUEEN: - attacks = chess::attacks::queen(sq, occ2); - break; + bool isolated = true; + if (f > FILE_A && fileHasPawn[f - 1]) isolated = false; + if (f < FILE_H && fileHasPawn[f + 1]) isolated = false; + if (isolated) { + mgScore -= s * isolatedPawnMg; + egScore -= s * isolatedPawnEg; + } - default: - break; - } - attacks &= ~board.us(color_of(p)); - int mobility = popcount(attacks); - mgScore += _sign * mobility * mgMobility[pt]; - egScore += _sign * mobility * egMobility[pt]; - } - } - // Bishop pair bonus - for (Color c : {WHITE, BLACK}) { - if (board.count(BISHOP, c) >= 2) { - int s = (c == WHITE) ? 1 : -1; - mgScore += s * 30; - egScore += s * 10; + if (relRank >= RANK_2 && (passedMask[c][sq] & enemyPawns) == 0) { + mgScore += s * passedBonusMg[relRank]; + egScore += s * passedBonusEg[relRank]; + } + } } - } - // Rook on open/semi-open file - for (Color c : {WHITE, BLACK}) { - int s = (c == WHITE) ? 1 : -1; - Bitboard rooks = board.pieces(ROOK, c); - while (rooks) { - Square sq = Square(pop_lsb(rooks)); - File f = file_of(sq); - Bitboard fileMask = attacks::MASK_FILE[f]; - bool hasOwnPawn = (board.pieces(PAWN, c) & fileMask) != 0; - bool hasEnemyPawn = (board.pieces(PAWN, ~c) & fileMask) != 0; - if (!hasOwnPawn && !hasEnemyPawn) { - mgScore += s * 25; - egScore += s * 10; - } else if (!hasOwnPawn) { - mgScore += s * 15; - egScore += s * 5; - } + // Center control (using precomputed pawn attacks) + { + Bitboard centerMask = (1ULL << SQ_D4) | (1ULL << SQ_E4) | (1ULL << SQ_D5) | (1ULL << SQ_E5); + int wc = popcount(pawnAtks[WHITE] & centerMask); + int bc = popcount(pawnAtks[BLACK] & centerMask); + mgScore += (wc - bc) * centerWeight; + egScore += (wc - bc) * centerWeight; } - } - // Doubled pawn penalty - for (Color c : {WHITE, BLACK}) { - int s = (c == WHITE) ? 1 : -1; - for (int f = 0; f < 8; f++) { - int cnt = popcount(board.pieces(PAWN, c) & attacks::MASK_FILE[f]); - if (cnt >= 2) { - mgScore += s * -(cnt - 1) * 15; - egScore += s * -(cnt - 1) * 20; - } + // Space evaluation + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Bitboard safe = ~pawnAtks[~c]; + Bitboard enemyCamp = c == WHITE + ? (attacks::MASK_RANK[4] | attacks::MASK_RANK[5] | attacks::MASK_RANK[6]) + : (attacks::MASK_RANK[3] | attacks::MASK_RANK[2] | attacks::MASK_RANK[1]); + int space = popcount(pawnAtks[c] & safe & enemyCamp); + mgScore += s * space * spaceWeight; + egScore += s * space * spaceWeight; } - } - // Isolated pawn penalty - for (Color c : {WHITE, BLACK}) { - int s = (c == WHITE) ? 1 : -1; - Bitboard pawns = board.pieces(PAWN, c); - while (pawns) { - Square sq = Square(pop_lsb(pawns)); - File f = file_of(sq); - bool isolated = true; - if (f > FILE_A && (board.pieces(PAWN, c) & attacks::MASK_FILE[f - 1])) - isolated = false; - if (f < FILE_H && (board.pieces(PAWN, c) & attacks::MASK_FILE[f + 1])) - isolated = false; - if (isolated) { - mgScore += s * -20; - egScore += s * -15; - } + // King safety: pawn shelter + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Square kingSq = board.kingSq(c); + File kf = file_of(kingSq); + Rank kr = rank_of(kingSq); + Bitboard pawns = board.pieces(PAWN, c); + int shelterMg = 0, shelterEg = 0; + int startF = std::max(0, (int)kf - 1); + int endF = std::min(7, (int)kf + 1); + for (int adjF = startF; adjF <= endF; adjF++) { + Bitboard fileMask = attacks::MASK_FILE[adjF]; + if (c == WHITE) { + for (int r = (int)kr + 1; r <= std::min(7, (int)kr + 3); r++) { + if (pawns & (fileMask & attacks::MASK_RANK[r])) { + shelterMg += kingShelterBaseMg - (r - (int)kr - 1) * kingShelterDecayMg; + shelterEg += kingShelterBaseEg - (r - (int)kr - 1) * kingShelterDecayEg; + } + } + } else { + for (int r = (int)kr - 1; r >= std::max(0, (int)kr - 3); r--) { + if (pawns & (fileMask & attacks::MASK_RANK[r])) { + shelterMg += kingShelterBaseMg - ((int)kr - r - 1) * kingShelterDecayMg; + shelterEg += kingShelterBaseEg - ((int)kr - r - 1) * kingShelterDecayEg; + } + } + } + } + mgScore += s * shelterMg; + egScore += s * shelterEg; } - } - // Passed pawn bonus - for (Color c : {WHITE, BLACK}) { - int s = (c == WHITE) ? 1 : -1; - Bitboard pawns = board.pieces(PAWN, c); - Bitboard enemyPawns = board.pieces(PAWN, ~c); - while (pawns) { - Square sq = Square(pop_lsb(pawns)); - Rank relRank = relative_rank(c, sq); - if (relRank < RANK_2) - continue; - File f = file_of(sq); - Bitboard passedMask = 0; - int startF = std::max(0, (int)f - 1); - int endF = std::min(7, (int)f + 1); - for (int adjF = startF; adjF <= endF; adjF++) { - if (c == WHITE) { - for (int r = rank_of(sq) + 1; r <= 7; r++) - passedMask |= attacks::MASK_FILE[adjF] & attacks::MASK_RANK[r]; - } else { - for (int r = rank_of(sq) - 1; r >= 0; r--) - passedMask |= attacks::MASK_FILE[adjF] & attacks::MASK_RANK[r]; + // Endgame bonuses + mop-up in one pass per color + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + int oppCount = popcount(board.occ(~c)); + Square myKing = board.kingSq(c); + Square oppKing = board.kingSq(~c); + + // KQK: queen vs lone king + if (board.count(QUEEN, c) >= 1 && oppCount == 1) { + int kingDist = std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), + std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); + File ef = file_of(oppKing); + Rank er = rank_of(oppKing); + int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), std::min((int)er, 7 - (int)er)); + mgScore += s * ((14 - kingDist) * kqkDistWeight + (7 - edgeDist) * kqkEdgeWeight); + egScore += s * ((14 - kingDist) * kqkDistWeight + (7 - edgeDist) * kqkEdgeWeight); } - } - if ((passedMask & enemyPawns) == 0) { - static const Value passedBonus[] = {0, 0, 10, 20, 40, 80, 160, 200}; - Value bonus = passedBonus[relRank]; - mgScore += s * bonus; - egScore += s * bonus; - } - } - } - // King safety: pawn shelter - for (Color c : {WHITE, BLACK}) { - int s = (c == WHITE) ? 1 : -1; - Square kingSq = board.kingSq(c); - File kf = file_of(kingSq); - Bitboard pawns = board.pieces(PAWN, c); - int shelter = 0; - int startF = std::max(0, (int)kf - 1); - int endF = std::min(7, (int)kf + 1); - for (int adjF = startF; adjF <= endF; adjF++) { - if (c == WHITE) { - for (int r = rank_of(kingSq) + 1; r <= std::min(7, rank_of(kingSq) + 3); - r++) { - if (pawns & (Bitboard(1) << make_sq((File)adjF, (Rank)r))) - shelter += 10 - (r - rank_of(kingSq) - 1) * 3; + // KRK: rook vs lone king + if (board.count(ROOK, c) >= 1 && oppCount == 1 + && board.count(QUEEN, c) == 0 && board.count(BISHOP, c) == 0 + && board.count(KNIGHT, c) == 0 && board.count(PAWN, c) == 0) { + int kingDist = std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), + std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); + File ef = file_of(oppKing); + Rank er = rank_of(oppKing); + int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), std::min((int)er, 7 - (int)er)); + mgScore += s * ((14 - kingDist) * krkDistWeight + (7 - edgeDist) * krkEdgeWeight); + egScore += s * ((14 - kingDist) * krkDistWeight + (7 - edgeDist) * krkEdgeWeight); + } + + // KPK: king + pawn(s) vs lone king + if (board.count(PAWN, c) >= 1 && oppCount == 1 + && board.count(QUEEN, c) == 0 && board.count(ROOK, c) == 0 + && board.count(BISHOP, c) == 0 && board.count(KNIGHT, c) == 0) { + Bitboard ps = pawnBB[c]; + while (ps) { + Square psq = Square(pop_lsb(ps)); + Rank pr = relative_rank(c, psq); + if (pr < RANK_4) continue; + + File pf = file_of(psq); + Square promSq = make_sq(pf, c == WHITE ? RANK_8 : RANK_1); + int pawnDist = (c == WHITE) ? (7 - (int)rank_of(psq)) : (int)rank_of(psq); + int pkDist = std::max(std::abs((int)file_of(oppKing) - (int)pf), + std::abs((int)rank_of(oppKing) - (int)rank_of(promSq))); + int mkDist = std::max(std::abs((int)file_of(myKing) - (int)file_of(psq)), + std::abs((int)rank_of(myKing) - (int)rank_of(psq))); + + bool winning = pkDist > pawnDist + || mkDist <= pawnDist + 1 + || (pr >= RANK_6 && file_of(myKing) == pf + && ((c == WHITE && rank_of(myKing) >= RANK_6) + || (c == BLACK && rank_of(myKing) <= RANK_3))); + if ((pf == FILE_A || pf == FILE_H) && oppKing == promSq) + winning = false; + + if (winning) { + mgScore += s * kpkWeight * pawnDist; + egScore += s * kpkWeight * pawnDist; + } + } } - } else { - for (int r = rank_of(kingSq) - 1; r >= std::max(0, rank_of(kingSq) - 3); - r--) { - if (pawns & (Bitboard(1) << make_sq((File)adjF, (Rank)r))) - shelter += 10 - (rank_of(kingSq) - r - 1) * 3; + + // Mop-up: general endgame drive + Bitboard ourMat = board.occ(c) & ~board.pieces(PAWN) & ~board.pieces(KING); + Bitboard theirMat = board.occ(~c) & ~board.pieces(PAWN) & ~board.pieces(KING); + if (ourMat && !theirMat && oppCount <= 2) { + int kingDist = std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), + std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); + File ef = file_of(oppKing); + Rank er = rank_of(oppKing); + int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), std::min((int)er, 7 - (int)er)); + mgScore += s * ((14 - kingDist) * mopUpKingDistWeight + (7 - edgeDist) * mopUpEdgeDistWeight); + egScore += s * ((14 - kingDist) * mopUpKingDistWeight + (7 - edgeDist) * mopUpEdgeDistWeight); } - } } - mgScore += s * shelter; - egScore += s * shelter / 2; - } - phase = (phase * 256 + TotalPhase / 2) / TotalPhase; - Value finalScore = - (((mgScore * phase) + (egScore * (256 - phase))) * sign) / 256; - return finalScore; + // Draw detection: score 0 for positions where neither side can force a win + int totalPieces = popcount(board.occ()); + int pawnCount = board.count(); + + // KBKB same-colored bishops (no pawns) - drawn + if (totalPieces == 4 && pawnCount == 0 && board.count() == 2 + && board.count() == 0 && board.count() == 0 && board.count() == 0 + && board.count(BISHOP, WHITE) == 1 && board.count(BISHOP, BLACK) == 1) { + Bitboard wbBB = board.pieces(BISHOP, WHITE); + Bitboard bbBB = board.pieces(BISHOP, BLACK); + Square wb = Square(pop_lsb(wbBB)); + Square bb = Square(pop_lsb(bbBB)); + if (square_color(wb) == square_color(bb)) + return 0; + } + + // KNNK (no pawns) - drawn + if (totalPieces == 4 && pawnCount == 0 && board.count() == 2 + && board.count() == 0 && board.count() == 0 && board.count() == 0) + return 0; + + phase = (phase * 256 + TotalPhase / 2) / TotalPhase; + Value finalScore = (((mgScore * phase) + (egScore * (256 - phase))) * sign) / 256 + tempo; + return finalScore; } Value piece_value(PieceType pt) { - Value pieces[] = {0, PawnValue, KnightValue, BishopValue, - RookValue, QueenValue, KingValue}; - return pieces[pt]; + Value pieces[] = { 0, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, 0 }; + return pieces[pt]; } } // namespace engine::eval diff --git a/eval.h b/eval.h index 74c1e20..6ad2a96 100644 --- a/eval.h +++ b/eval.h @@ -20,13 +20,9 @@ constexpr bool is_valid(Value value) { return value != VALUE_NONE; } constexpr bool is_win(Value value) { return value >= VALUE_TB_WIN_IN_MAX_PLY; } -constexpr bool is_loss(Value value) { - return value <= VALUE_TB_LOSS_IN_MAX_PLY; -} +constexpr bool is_loss(Value value) { return value <= VALUE_TB_LOSS_IN_MAX_PLY; } -constexpr bool is_decisive(Value value) { - return is_win(value) || is_loss(value); -} +constexpr bool is_decisive(Value value) { return is_win(value) || is_loss(value); } constexpr Value MATE(int i) { return VALUE_MATE - i; } constexpr Value MATE_DISTANCE(int i) { return VALUE_MATE - (i < 0 ? -i : i); } namespace eval { diff --git a/main.cpp b/main.cpp index 9e229b2..f9290bd 100644 --- a/main.cpp +++ b/main.cpp @@ -1,4 +1,5 @@ #include "search.h" +#include "tb.h" #include "tune.h" #include "uci.h" #include "ucioption.h" @@ -8,19 +9,24 @@ using namespace engine; #define STR(x) STR_HELPER(x) int main() { - std::cout << std::unitbuf; - std::cout << "cppchess_engine version " << BUILD_VERSION << '\n'; - options.add("Move Overhead", Option(10, 0, 1000)); - options.add("Hash", Option(16, 1, 1 << 25, [](const Option &o) { - search::tt.resize(int(o)); - return std::nullopt; - })); + std::cout << std::unitbuf; + std::cout << "cppchess_engine version " << BUILD_VERSION << '\n'; + options.add("Move Overhead", Option(10, 0, 1000)); + options.add("Hash", Option(16, 1, 1 << 25, [](const Option &o) { + search::tt.resize(int(o)); + return std::nullopt; + })); - options.add("Clear Hash", Option(+[](const Option &) { - search::tt.clear(); + options.add("Clear Hash", Option(+[](const Option &) { + search::tt.clear(); - return std::nullopt; - })); - Tune::init(options); - loop(); + return std::nullopt; + })); + // work in progress + options.add("SyzygyPath", Option("", [](const Option &o) { + tb::init(std::string(o)); + return std::nullopt; + })); + Tune::init(options); + loop(); } diff --git a/movepick.cpp b/movepick.cpp index 9d3c216..6956bf2 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -6,37 +6,83 @@ using namespace chess; using engine::eval::piece_value; namespace engine::movepick { -void orderMoves(chess::Board &board, chess::Movelist &moves, chess::Move ttMove, - int ply, const engine::search::Session &session) { - std::vector> scoredMoves; - scoredMoves.reserve(moves.size()); - - for (const auto &move : moves) { - Value score = 0; - - if (move == ttMove) - score = 10000; - else if (board.isCapture(move)) - score = ((move.type_of() & EN_PASSANT) == 0 - ? piece_value(board.at(move.to())) - : piece_value(PAWN)) * - 10 - - piece_value(board.at(move.from())); - else if (move == session.killerMoves[ply][0]) - score = 8500; - else if (move == session.killerMoves[ply][1]) - score = 8000; - else - score = session.historyHeuristic[move.from()][move.to()]; - - scoredMoves.emplace_back(move, score); - } +static Bitboard att(PieceType pt, Square sq, Bitboard occ) { + switch (pt) { + case BISHOP: return attacks::bishop(sq, occ); + case ROOK: return attacks::rook(sq, occ); + case QUEEN: return attacks::queen(sq, occ); + default: return 0; + } +} - std::stable_sort( - scoredMoves.begin(), scoredMoves.end(), - [](const auto &a, const auto &b) { return a.second > b.second; }); +Value see(Board& board, Move move) { + if (move.type_of() == EN_PASSANT) + return piece_value(PAWN); + PieceType captured = board.at(move.to()); + if (captured == NO_PIECE_TYPE) + return 0; + PieceType attacker = board.at(move.from()); + Bitboard occ = board.occ() ^ (1ULL << move.from()); + Bitboard attackers = board.attackers(WHITE, move.to(), occ) | board.attackers(BLACK, move.to(), occ); + Value gain[32]; + int d = 0; + gain[d] = piece_value(captured); + Color stm = ~board.side_to_move(); + while (++d < 32) { + gain[d] = piece_value(attacker) - gain[d - 1]; + if (gain[d] < 0) + break; + attackers &= occ; + Bitboard stmAttackers = attackers & board.occ(stm); + if (!stmAttackers) + break; + Square sq = (Square)pop_lsb(stmAttackers); + attacker = board.at(sq); + if (attacker == NO_PIECE_TYPE) + break; + if (attacker == BISHOP || attacker == ROOK || attacker == QUEEN) + attackers |= att(attacker, move.to(), occ); + occ ^= (1ULL << sq); + stm = ~stm; + } + while (--d) + gain[d - 1] = -gain[d]; + return gain[0]; +} - for (size_t i = 0; i < scoredMoves.size(); ++i) - moves[i] = scoredMoves[i].first; +void orderMoves(Board &board, Movelist &moves, Move ttMove, int ply, const engine::search::Session &session, Move prevMove) { + Value scores[300]; + size_t n = moves.size(); + for (size_t i = 0; i < n; ++i) { + Move move = moves[i]; + if (move == ttMove) + scores[i] = 10000; + else if (board.isCapture(move)) { + Value s = see(board, move); + Value capturedVal = move.type_of() == EN_PASSANT ? piece_value(PAWN) : piece_value(board.at(move.to())); + Value attackerVal = piece_value(board.at(move.from())); + scores[i] = (s >= -50 ? 9000 : 4000) + std::max(s, Value(-50)) + (capturedVal * 10 - attackerVal) / 100; + } else if (move == session.killerMoves[ply][0]) + scores[i] = 8500; + else if (move == session.killerMoves[ply][1]) + scores[i] = 8000; + else if (prevMove.is_ok() && move == session.counterMoves[prevMove.from_to()]) + scores[i] = 7500; + else if (board.givesCheck(move) != CheckType::NO_CHECK) + scores[i] = 7000; + else + scores[i] = session.historyHeuristic[move.from()][move.to()]; + } + size_t limit = std::min(n, size_t(12)); + for (size_t i = 0; i + 1 < limit; ++i) { + size_t best = i; + for (size_t j = i + 1; j < n; ++j) + if (scores[j] > scores[best]) + best = j; + if (best != i) { + std::swap(moves[i], moves[best]); + std::swap(scores[i], scores[best]); + } + } } -} // namespace engine::movepick +} // namespace engine::movepick \ No newline at end of file diff --git a/movepick.h b/movepick.h index 69d313c..c7728ff 100644 --- a/movepick.h +++ b/movepick.h @@ -1,9 +1,10 @@ #pragma once +#include "eval.h" #include namespace engine::search { struct Session; } // namespace engine::search namespace engine::movepick { -void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int, - const engine::search::Session &); +void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int, const engine::search::Session &, chess::Move); +Value see(chess::Board &, chess::Move); } // namespace engine::movepick diff --git a/score.cpp b/score.cpp index 2d07d9e..ca3a1c2 100644 --- a/score.cpp +++ b/score.cpp @@ -3,16 +3,16 @@ #include namespace engine { Score::Score(Value v) { - assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); - if (!is_decisive(v)) { - score = InternalUnits{v}; - } else if (std::abs(v) <= VALUE_TB) { - auto distance = VALUE_TB - std::abs(v); - score = (v > 0) ? Tablebase{distance, true} : Tablebase{-distance, false}; - } else { - auto distance = VALUE_MATE - std::abs(v); - score = (v > 0) ? Mate{distance} : Mate{-distance}; - } + if (!is_decisive(v)) { + score = InternalUnits{ v }; + } else if (std::abs(v) <= VALUE_TB) { + auto distance = VALUE_TB - std::abs(v); + score = (v > 0) ? Tablebase{ distance, true } : Tablebase{ -distance, false }; + } else { + auto distance = VALUE_MATE - std::abs(v); + score = (v > 0) ? Mate{ distance } : Mate{ -distance }; + } } } // namespace engine diff --git a/score.h b/score.h index 2b7b0e1..34d01bd 100644 --- a/score.h +++ b/score.h @@ -25,35 +25,31 @@ namespace engine { class Score { -public: - struct Mate { - int plies; - }; + public: + struct Mate { + int plies; + }; - struct Tablebase { - int plies; - bool win; - }; + struct Tablebase { + int plies; + bool win; + }; - struct InternalUnits { - int value; - }; + struct InternalUnits { + int value; + }; - Score() = default; - Score(Value v); + Score() = default; + Score(Value v); - template bool is() const { - return std::holds_alternative(score); - } + template bool is() const { return std::holds_alternative(score); } - template T get() const { return std::get(score); } + template T get() const { return std::get(score); } - template decltype(auto) visit(F &&f) const { - return std::visit(std::forward(f), score); - } + template decltype(auto) visit(F &&f) const { return std::visit(std::forward(f), score); } -private: - std::variant score; + private: + std::variant score; }; } // namespace engine diff --git a/search.cpp b/search.cpp index 9b5d536..c37f470 100644 --- a/search.cpp +++ b/search.cpp @@ -1,6 +1,7 @@ #include "search.h" #include "eval.h" #include "movepick.h" +#include "tb.h" #include "timeman.h" #include "uci.h" #include @@ -8,448 +9,600 @@ #include #include #include +#include using namespace chess; -namespace engine { -TranspositionTable search::tt(16); -std::atomic stopSearch{false}; -void search::stop() { stopSearch.store(true, std::memory_order_relaxed); } -bool search::isStopped() { return stopSearch; } -namespace { // SF +namespace engine::search { +TranspositionTable tt(16); +std::atomic stopSearch{ false }; +void stop() { stopSearch.store(true, std::memory_order_relaxed); } +bool isStopped() { return stopSearch.load(std::memory_order_relaxed); } +namespace { void update_pv(Move *pv, Move move, const Move *childPv) { - - for (*pv++ = move; childPv && *childPv != Move::none();) - *pv++ = *childPv++; - *pv = Move::none(); -} -// Adjusts a mate or TB score from "plies to mate from the root" to -// "plies to mate from the current position". Standard scores are unchanged. -// The function is called before storing a value in the transposition table. -Value value_to_tt(Value v, int ply) { - return is_win(v) ? v + ply : is_loss(v) ? v - ply : v; + for (*pv++ = move; childPv && *childPv != Move::none();) + *pv++ = *childPv++; + *pv = Move::none(); } +Value value_to_tt(Value v, int ply) { return is_win(v) ? v + ply : is_loss(v) ? v - ply : v; } -// Inverse of value_to_tt(): it adjusts a mate or TB score from the -// transposition table (which refers to the plies to mate/be mated from current -// position) to "plies to mate/be mated (TB win/loss) from the root". However, -// to avoid potentially false mate or TB scores related to the 50 moves rule and -// the graph history interaction, we return the highest non-TB score instead. Value value_from_tt(Value v, int ply, int r50c) { + if (!is_valid(v)) + return VALUE_NONE; + if (is_win(v)) { + if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; + if (VALUE_TB - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; + return v - ply; + } + if (is_loss(v)) { + if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; + if (VALUE_TB + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; + return v + ply; + } + return v; +} +} // namespace +Value qsearch(Board &board, Value alpha, Value beta, search::Session &session, int ply) { + session.nodes++; + session.qnodes++; + session.seldepth = std::max(session.seldepth, ply); + if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || stopSearch.load(std::memory_order_relaxed)) + return VALUE_NONE; - if (!is_valid(v)) - return VALUE_NONE; + bool inCheck = board.checkers(); - // handle TB win or better - if (is_win(v)) { - // Downgrade a potentially false mate score - if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c) - return VALUE_TB_WIN_IN_MAX_PLY - 1; + if (ply >= MAX_PLY - 1) + return inCheck ? -MATE(ply) : eval::eval(board); - // Downgrade a potentially false TB score. - if (VALUE_TB - v > 100 - r50c) - return VALUE_TB_WIN_IN_MAX_PLY - 1; + Move ttMove = Move::none(); + TTEntry *entry = search::tt.lookup(board.hash()); + Value alphaOrig = alpha; + if (entry) { + session.ttHits++; + Value ttScore = value_from_tt(entry->getScore(), ply, board.rule50_count()); + + if (entry->getFlag() == TTFlag::EXACT){ + session.ttCutoffs++; + return ttScore; + } + if (entry->getFlag() == TTFlag::LOWERBOUND && ttScore >= beta){ + session.ttCutoffs++; + return ttScore; + } + if (entry->getFlag() == TTFlag::UPPERBOUND && ttScore <= alpha){ + session.ttCutoffs++; + return ttScore; + } + if (entry->getFlag() == TTFlag::LOWERBOUND) + alpha = std::max(alpha, ttScore); + else if (entry->getFlag() == TTFlag::UPPERBOUND) + beta = std::min(beta, ttScore); + if (alpha >= beta) { + session.ttCutoffs++; + return ttScore; + } + ttMove = Move(entry->getMove()); + } - return v - ply; - } + Movelist moves; - // handle TB loss or worse - if (is_loss(v)) { - // Downgrade a potentially false mate score. - if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c) - return VALUE_TB_LOSS_IN_MAX_PLY + 1; + if (inCheck) + board.legals(moves); + else board.legals(moves); + Value best = -VALUE_INFINITE; + Value standPat = VALUE_NONE; - // Downgrade a potentially false TB score. - if (VALUE_TB + v > 100 - r50c) - return VALUE_TB_LOSS_IN_MAX_PLY + 1; + if (!inCheck) { + standPat = eval::eval(board); - return v + ply; - } + if (standPat >= beta) + return standPat; - return v; -} -} // namespace -Value qsearch(Board &board, Value alpha, Value beta, search::Session &session, - int ply = 0) { - session.nodes++; - session.seldepth = std::max(session.seldepth, ply); - - Value alphaOrig = alpha; - bool tt_hit = false; - Move bestMove = Move::none(); - TTEntry *entry = search::tt.lookup(board.hash()); - if (entry && entry->getDepth() == 0) { - Value ttScore = value_from_tt(entry->getScore(), ply, board.rule50_count()); - TTFlag flag = entry->getFlag(); - if (flag == TTFlag::EXACT) { - tt_hit = true; - return ttScore; - } - if (flag == TTFlag::LOWERBOUND && ttScore >= beta) { - tt_hit = true; - return ttScore; - } - if (flag == TTFlag::UPPERBOUND && ttScore <= alpha) { - tt_hit = true; - return ttScore; - } - } - if (entry) - bestMove = Move(entry->getMove()); - - int standPat = eval::eval(board); - if (session.tm.elapsed() >= session.tm.optimum() || - stopSearch.load(std::memory_order_relaxed)) - return standPat; - Value maxScore = standPat; - if (maxScore >= beta) { - if (!tt_hit) - search::tt.store(board.hash(), bestMove, value_to_tt(maxScore, ply), 0, - TTFlag::LOWERBOUND); - return maxScore; - } - if (maxScore > alpha) - alpha = maxScore; - Movelist moves; - board.legals(moves); - // TT move first - if (bestMove != Move::none()) { - for (size_t i = 0; i < moves.size(); i++) { - if (moves[i] == bestMove) { - std::swap(moves[0], moves[i]); - break; - } - } - } - for (Move move : moves) { - board.doMove(move); - Value score = qsearch(board, -beta, -alpha, session, ply + 1); - board.undoMove(); - if (score == VALUE_NONE) - return VALUE_NONE; - score = -score; - if (score >= beta) { - if (!tt_hit) - search::tt.store(board.hash(), bestMove, value_to_tt(score, ply), 0, - TTFlag::LOWERBOUND); - return score; + if (standPat > alpha) + alpha = standPat; + + best = standPat; } - if (score > maxScore) { - maxScore = score; - bestMove = move; + + if (!moves.size()) + return inCheck ? -MATE(ply) : best; + + movepick::orderMoves(board, moves, ttMove, ply, session, Move::none()); + int movesSearched = 0; + for (Move move : moves) { + bool isCapture = board.isCapture(move); + bool givesCheck = board.givesCheck(move) != CheckType::NO_CHECK; + + if (!inCheck) { + if (!isCapture && !givesCheck) + continue; + if (isCapture && !givesCheck && move.type_of() != PROMOTION) { + Value capturedValue = move.type_of() == EN_PASSANT + ? eval::piece_value(PAWN) + : eval::piece_value(board.at(move.to())); + if (standPat + capturedValue + 200 < alpha) + continue; + if (movepick::see(board, move) < 0) + continue; + } + } else { + int plyFromDepth = ply - session.depth; + int maxUncond = plyFromDepth < 1 ? 6 : plyFromDepth < 4 ? 3 : 0; + if (movesSearched >= maxUncond && movesSearched >= 1) { + if (!isCapture && !givesCheck) + continue; + if (isCapture && !givesCheck && move.type_of() != PROMOTION && movepick::see(board, move) < 0) + continue; + } + } + + board.doMove(move); + + Value score = -qsearch(board, -beta, -alpha, session, ply + 1); + + board.undoMove(); + + if (score == VALUE_NONE) + return VALUE_NONE; + + if (score > best){ + ttMove = move; + best = score; + } + if (score > alpha) + alpha = score; + if (alpha >= beta) + break; + + movesSearched++; } - if (score > alpha) - alpha = score; - } - if (!tt_hit) { - TTFlag flag; - if (maxScore <= alphaOrig) - flag = TTFlag::UPPERBOUND; - else if (maxScore >= beta) - flag = TTFlag::LOWERBOUND; - else - flag = TTFlag::EXACT; - search::tt.store(board.hash(), bestMove, value_to_tt(maxScore, ply), 0, - flag); - } - return maxScore; + TTFlag flag = best >= beta ? TTFlag::LOWERBOUND + : best <= alphaOrig ? TTFlag::UPPERBOUND + : TTFlag::EXACT; + tt.store(board.hash(), + ttMove, + value_to_tt(best, ply), + 0, + flag); + return best; } -Value doSearch(Board &board, int depth, Value alpha, Value beta, - search::Session &session, int ply = 0) { - // TLE or exceeded depth limit - if (ply >= MAX_PLY - 1) - return eval::eval(board); - if (depth <= 0) { +Value doSearch(Board &board, int depth, Value alpha, Value beta, search::Session &session, int ply = 0, Move prevMove = Move::none()) { session.nodes++; - return qsearch(board, alpha, beta, session, ply); - } - if (session.tm.elapsed() >= session.tm.optimum() || - stopSearch.load(std::memory_order_relaxed)) - return qsearch(board, alpha, beta, session, ply); - Value alphaOrig = alpha; - // Reset PV - explicit loop to avoid any std::fill issues - for (int _i = 0; _i < MAX_PLY; _i++) - session.pv[ply][_i] = Move::none(); - for (int _i = 0; _i < MAX_PLY; _i++) - session.pv[ply + 1][_i] = Move::none(); - if (board.is_draw(3) || board.is_insufficient_material()) { - session.nodes++; - session.pv[ply][0] = Move::none(); - return 0; - } - session.seldepth = std::max(session.seldepth, ply); - uint64_t hash = board.hash(); - Move preferred = Move::none(); - if (TTEntry *entry = search::tt.lookup(hash)) { - if (entry->getDepth() >= depth) { - Value ttScore = - value_from_tt(entry->getScore(), ply, board.rule50_count()); - TTFlag flag = entry->getFlag(); - - if (flag == TTFlag::EXACT) { - session.pv[ply][0] = Move(entry->getMove()); - session.pv[ply][1] = Move::none(); - return ttScore; - } - - if (flag == TTFlag::LOWERBOUND && ttScore >= beta) { - session.pv[ply][0] = Move(entry->getMove()); - session.pv[ply][1] = Move::none(); - return ttScore; - } - - if (flag == TTFlag::UPPERBOUND && ttScore <= alpha) { - session.pv[ply][0] = Move(entry->getMove()); - session.pv[ply][1] = Move::none(); - return ttScore; - } - } - preferred = Move(entry->getMove()); - } - Value maxScore = -VALUE_INFINITE; - Movelist moves; - board.legals(moves); - if (!moves.size()) { - session.pv[ply][0] = Move::none(); - return board.checkers() ? -MATE(ply) : 0; - } - movepick::orderMoves(board, moves, preferred, ply, session); - if (bool useNMP = depth >= 3 && !board.checkers() && ply > 0) { - int R = 2 + depth / 6 + std::min(2, depth / 10); - uint64_t hash_ = board.hash(); - board.doNullMove(); - Value score = - doSearch(board, depth - 1 - R, -beta, -beta + 1, session, ply + 1); - - score = -score; - board.undoMove(); - if (score >= beta) - return score; - } - - for (size_t i = 0; i < moves.size(); ++i) { - Move move = moves[i]; - - // Clear child PV before each child search to prevent stale data from - // previous children across iterations of this move loop - for (int _i = 0; _i < MAX_PLY; _i++) - session.pv[ply + 1][_i] = Move::none(); - - bool isCapture = board.isCapture(move); - bool givesCheck = board.givesCheck(move) != CheckType::NO_CHECK; - - // --- LMR reduction --- - int reduction = 0; - if (i >= 3 && depth >= 3 && !isCapture && !givesCheck) { - reduction = 1 + i / 6 + depth / 8; - - int history = session.historyHeuristic[(int)move.from()][(int)move.to()]; - if (history > 0) - reduction--; - else if (history < 0) - reduction++; - - reduction = std::max(0, reduction); - reduction = std::min(reduction, depth - 2); - } + session.seldepth = std::max(session.seldepth, ply); + if (ply >= MAX_PLY - 1) + return board.checkers() ? -MATE(ply) : eval::eval(board); + if (depth <= 0) + return qsearch(board, alpha, beta, session, ply); + if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || stopSearch.load(std::memory_order_relaxed)) + return VALUE_NONE; - board.doMove(move); + bool inCheck = board.checkers(); - Value score; + alpha = std::max(-MATE(ply), alpha); + beta = std::min(MATE(ply + 1), beta); + if (alpha >= beta) + return alpha; - if (i == 0) { - uint64_t hash_ = board.hash(); - // --- First move: full window (PVS root move) --- - score = doSearch(board, depth - 1, -beta, -alpha, session, ply + 1); + session.pv[ply][0] = Move::none(); + if (board.is_draw(2) || board.is_insufficient_material()) + return 0; + + Value alphaOrig = alpha; + uint64_t hash = board.hash(); + Move ttMove = Move::none(); + Value staticEval = eval::eval(board); + + TTEntry *entry = search::tt.lookup(hash); + if (entry) { + if (entry->getDepth() >= depth) { + session.ttHits++; + Value ttScore = value_from_tt(entry->getScore(), ply, board.rule50_count()); + TTFlag flag = entry->getFlag(); + + if (flag == TTFlag::EXACT && ply > 0) { + session.ttCutoffs++; + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); + return ttScore; + } + if (flag == TTFlag::LOWERBOUND && ttScore >= beta && ply > 0) { + session.ttCutoffs++; + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); + return ttScore; + } + if (flag == TTFlag::UPPERBOUND && ttScore <= alpha && ply > 0) { + session.ttCutoffs++; + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); + return ttScore; + } + } + ttMove = Move(entry->getMove()); + } - if (score == VALUE_NONE) { - board.undoMove(); - return VALUE_NONE; - } - score = -score; - } else { - uint64_t hash_ = board.hash(); - // --- Null-window search (PVS + LMR) --- - score = doSearch(board, depth - 1 - reduction, -alpha - 1, -alpha, - session, ply + 1); - if (score == VALUE_NONE) { - board.undoMove(); - return VALUE_NONE; - } - score = -score; - // --- Re-search if it improves alpha --- - if (score > alpha) { - score = doSearch(board, depth - 1, -beta, -alpha, session, ply + 1); - if (score == VALUE_NONE) { - board.undoMove(); - return VALUE_NONE; + // Reverse futility pruning: if eval is well above beta, prune + if (!inCheck && ply > 0 && depth <= 3 && staticEval - 150 * depth >= beta + && !is_win(beta) && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) + return staticEval; + + // Razoring: if eval is far below alpha, try qsearch to verify + if (depth <= 2 && !inCheck && ply > 0 && !is_win(alpha)) { + Value razorMargin = Value(256 + 100 * depth); + if (staticEval + razorMargin < alpha) { + Value v = qsearch(board, alpha - 1, alpha, session, ply); + if (v == VALUE_NONE) return VALUE_NONE; + if (v < alpha && std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) + return v; } + } + + // Null move pruning (skip when a mate threat is possible) + if (depth >= 3 && !inCheck && ply > 0 && staticEval >= beta && !is_win(beta)) { + int R = 2 + depth / 6 + std::min(2, depth / 10); + board.doNullMove(); + Value score = doSearch(board, depth - 1 - R, -beta, -beta + 1, session, ply + 1, Move::none()); + board.undoMove(); + if (score == VALUE_NONE) return VALUE_NONE; score = -score; - } + if (score >= beta){ + session.nullCutoffs++; + return score; + } } - board.undoMove(); - if (score > maxScore) { - maxScore = score; - update_pv(session.pv[ply], move, session.pv[ply + 1]); + // ProbCut: if eval is well above beta, verify with reduced-depth search + if (depth >= 5 && !inCheck && !is_win(beta) && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) { + Value probCutMargin = Value(200 + 100 * (depth - 5)); + if (staticEval >= beta + probCutMargin) { + Value v = doSearch(board, depth - 2, beta - 1, beta, session, ply, prevMove); + if (v == VALUE_NONE) return VALUE_NONE; + if (v >= beta) return v; + } + } + + // Existing static null-move / futility pruning + if (!inCheck && staticEval < alpha - 512 - 293 * depth * depth) + { + Value value = qsearch(board, alpha - 1, alpha, session, ply); + if (value == VALUE_NONE) return VALUE_NONE; + if (value < alpha && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) + return value; } - if (score > alpha) { - alpha = score; + // Tablebase probing (unchanged) + if (ply != 0){ + if (popcount(board.occ()) <= 7 && board.castlingRights()==NO_CASTLING) + { + int wdl = engine::tb::probe_wdl(board); + if (wdl != engine::tb::TB_ERROR){ + session.tbHits++; + + int drawScore = 1; + + Value tbValue = VALUE_TB - ply; + + Value value = wdl < -drawScore ? -tbValue + : wdl > drawScore ? tbValue + : VALUE_DRAW + 2 * wdl * drawScore; + TTFlag b = wdl < -drawScore ? TTFlag::UPPERBOUND + : wdl > drawScore ? TTFlag::LOWERBOUND + : TTFlag::EXACT; + if (b == TTFlag::EXACT || (b == TTFlag::LOWERBOUND ? value >= beta : value <= alpha)) + { + tt.store(hash, Move::none(), value_to_tt(value, ply), std::min(MAX_PLY - 1, depth + 6), b); + return value; + } + + if (b == TTFlag::LOWERBOUND) + alpha = std::max(alpha, value); + } + } + } - if (!isCapture) - session.historyHeuristic[(int)move.from()][(int)move.to()] += - depth * depth; + Movelist moves; + board.legals(moves); + if (!moves.size()) { + session.pv[ply][0] = Move::none(); + return board.checkers() ? -MATE(ply) : 0; + } + movepick::orderMoves(board, moves, ttMove, ply, session, prevMove); + + // Internal Iterative Deepening (IID): get a TT move when we don't have one + if (depth >= 8 && ttMove == Move::none() && !inCheck && alpha != beta - 1) { + int d = std::max(2, depth - 2 - depth / 4); + doSearch(board, d, alpha, beta, session, ply, prevMove); + if (TTEntry *e = search::tt.lookup(hash)) + ttMove = Move(e->getMove()); } - if (alpha >= beta) { - // killer moves - if (!isCapture) { - if (session.killerMoves[ply][0] != move) { - session.killerMoves[ply][1] = session.killerMoves[ply][0]; - session.killerMoves[ply][0] = move; + // Singular Extension: extend TT move when it dominates all others + int singularExt = 0; + if (depth >= 12 && ttMove.is_ok() && !inCheck && ply > 0 && entry + && entry->getDepth() >= depth - 4 && entry->getFlag() != TTFlag::UPPERBOUND + && alpha != beta - 1 && !is_win(beta)) { + Value sBeta = std::max(staticEval - 2 * depth, Value(-VALUE_MATE)); + int r = std::max(2, depth / 4); + if (moves.size() <= 5) { + bool singular = true; + for (size_t si = 0; si < moves.size() && singular; ++si) { + if (moves[si] == ttMove) continue; + board.doMove(moves[si]); + Value v = doSearch(board, r, sBeta - 1, sBeta, session, ply + 1, moves[si]); + board.undoMove(); + if (v == VALUE_NONE) { board.undoMove(); singular = false; break; } + v = -v; + if (v >= sBeta) singular = false; + } + if (singular) singularExt = 1; } - } - break; } - if (session.tm.elapsed() >= session.tm.optimum() || - stopSearch.load(std::memory_order_relaxed)) - return maxScore; - } - - if (maxScore != -VALUE_INFINITE) { - TTFlag flag; - - if (maxScore <= alphaOrig) - flag = TTFlag::UPPERBOUND; - else if (maxScore >= beta) - flag = TTFlag::LOWERBOUND; - else - flag = TTFlag::EXACT; - - search::tt.store(hash, session.pv[ply][0], value_to_tt(maxScore, ply), - depth, flag); - } - return maxScore; -} -void search::search(const chess::Board &board, - const timeman::LimitsType timecontrol) { - stopSearch = false; - tt.newSearch(); - static double originalTimeAdjust = -1; - Session session; - session.tc = timecontrol; - session.tm.init(session.tc, board.side_to_move(), 0, originalTimeAdjust); - InfoFull lastInfo{}; - chess::Move lastPV[MAX_PLY]{}; - Value prevScore = VALUE_NONE; - for (int i = 1; i < timecontrol.depth; i++) { - for (int _ = 0; _ < 64; _++) - for (int j = 0; j < 64; j++) { - session.historyHeuristic[_][j] /= 2; - // since MAX_PLY=64 - session.pv[_][j] = Move::none(); - } - auto board_ = board; - Value score_; - - // Aspiration windows - if (i >= 3 && prevScore != VALUE_NONE) { - Value delta = Value(17); - Value alpha = std::max(prevScore - delta, -VALUE_INFINITE); - Value beta = std::min(prevScore + delta, VALUE_INFINITE); - - score_ = doSearch(board_, i, alpha, beta, session); - - if (score_ != VALUE_NONE) { - if (score_ <= alpha) { - alpha = -VALUE_INFINITE; - score_ = doSearch(board_, i, alpha, beta, session); + Value maxScore = -VALUE_INFINITE; + int movesSearched = 0; + + for (size_t i = 0; i < moves.size(); ++i) { + Move move = moves[i]; + + bool isCapture = board.isCapture(move); + bool givesCheck = board.givesCheck(move) != CheckType::NO_CHECK; + + // Futility pruning at shallow depths + if (!inCheck && !isCapture && !givesCheck && depth <= 2 && ply > 0 && movesSearched > 0) { + Value margin = Value(128 + 128 * depth); + if (staticEval + margin <= alpha && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) + continue; } - if (score_ >= beta && score_ != VALUE_NONE) { - beta = VALUE_INFINITE; - score_ = doSearch(board_, i, alpha, beta, session); + + // Late move pruning at very shallow depths + if (!inCheck && !isCapture && !givesCheck && depth <= 2 && movesSearched > 3 + 2 * depth + && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) + continue; + + // SEE pruning for losing captures at shallow depths + if (!inCheck && isCapture && !givesCheck && depth <= 2 && movesSearched > 0 + && move.type_of() != PROMOTION && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) + { + if (movepick::see(board, move) < 0) + continue; } - if ((score_ <= alpha || score_ >= beta) && score_ != VALUE_NONE) - score_ = - doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); - } else { - score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); - } - } else { - score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); - } - prevScore = score_; - if (session.tm.elapsed() >= session.tm.optimum() || - stopSearch.load(std::memory_order_relaxed) || score_ == VALUE_NONE) - break; - InfoFull info{}; - info.depth = i; - info.selDepth = session.seldepth; - info.hashfull = tt.hashfull(); - info.nodes = session.nodes; - info.nps = session.nodes * 1000 / - std::max(session.tm.elapsed(), (timeman::TimePoint)1); - info.timeMs = session.tm.elapsed(); - info.multiPV = 1; - info.score = score_; - TTEntry *entry = tt.lookup(board.hash()); - if (entry) - switch (entry->getFlag()) { - case LOWERBOUND: - info.bound = "lowerbound"; - break; - case UPPERBOUND: - info.bound = "upperbound"; - break; - default: - break; - } - std::string pv = ""; - for (Move *m = session.pv[0]; *m != Move::none(); m++) - pv += chess::uci::moveToUci(*m, board.chess960()) + " "; - info.pv = pv; - report(info); - lastInfo = info; - std::copy(session.pv[0], &session.pv[0][MAX_PLY], lastPV); - } - if (lastPV[0].is_ok()) - report(chess::uci::moveToUci(lastPV[0])); - else { - // try to TT probe it - TTEntry *entry = tt.lookup(board.hash()); - if (entry && entry->getMove() != Move::none().raw()) - report(chess::uci::moveToUci(Move(entry->getMove()), board.chess960())); - else { - Movelist moves; - board.legals(moves); - - if (moves.size()) { - Board board_ = board; - Move best = moves[0]; - Value bestScore = -VALUE_INFINITE; - for (Move move : moves) { - board_.doMove(move); - Value score = -eval::eval(board_); - if (score > bestScore) { - bestScore = score; - best = move; - } - board_.undoMove(); + + // LMR reduction + int reduction = 0; + if (movesSearched >= 2 && depth >= 3) { + if (!isCapture && !givesCheck) { + reduction = 1 + movesSearched / 5 + depth / 7; + int history = session.historyHeuristic[(int)move.from()][(int)move.to()]; + if (history > 0) reduction--; + else if (history < 0) reduction++; + if (move == session.killerMoves[ply][0] || move == session.killerMoves[ply][1]) + reduction--; + if (prevMove.is_ok() && move == session.counterMoves[prevMove.from_to()]) + reduction--; + if (staticEval + 50 < alphaOrig) reduction++; + else if (staticEval - 50 >= alphaOrig) reduction--; + } else if (movesSearched >= 6) { + reduction = 1 + movesSearched / 8; + } + reduction = std::clamp(reduction, 1, depth - 2); + } + + int ext = 0; + if (singularExt && movesSearched == 0) ext = 1; + if (ext == 0 && moves.size() == 1) ext = 1; + if (ext == 0 && isCapture && movesSearched == 0) ext = 1; + + board.doMove(move); + + Value score; + + if (movesSearched == 0 || reduction == 0) { + score = doSearch(board, depth - 1 + ext, -beta, -alpha, session, ply + 1, move); + if (score == VALUE_NONE) { board.undoMove(); score = -VALUE_INFINITE; break; } + score = -score; + } else { + int d = depth - 1 - reduction + ext; + score = doSearch(board, d, -alpha - 1, -alpha, session, ply + 1, move); + if (score == VALUE_NONE) { board.undoMove(); score = -VALUE_INFINITE; break; } + score = -score; + if (score > alpha && reduction) { + session.lmrResearches++; + score = doSearch(board, depth - 1 + ext, -beta, -alpha, session, ply + 1, move); + if (score == VALUE_NONE) { board.undoMove(); score = -VALUE_INFINITE; break; } + score = -score; + } } + board.undoMove(); + movesSearched++; + + if (score > maxScore) { + maxScore = score; + update_pv(session.pv[ply], move, session.pv[ply + 1]); + } + + if (score > alpha) { + alpha = score; + + if (!isCapture) { + int bonus = depth * depth; + if (is_win(score)) + bonus += 4 * depth * depth; + session.historyHeuristic[(int)move.from()][(int)move.to()] = + std::clamp(session.historyHeuristic[(int)move.from()][(int)move.to()] + bonus, -16384, 16384); + } + } + + if (alpha >= beta) { + if (!isCapture) { + if (session.killerMoves[ply][0] != move) { + session.killerMoves[ply][1] = session.killerMoves[ply][0]; + session.killerMoves[ply][0] = move; + } + if (prevMove.is_ok()) + session.counterMoves[prevMove.from_to()] = move; + } + break; + } + + if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || stopSearch.load(std::memory_order_relaxed)) + return VALUE_NONE; + } + if (maxScore != -VALUE_INFINITE) { + TTFlag flag = maxScore >= beta ? TTFlag::LOWERBOUND + : maxScore <= alphaOrig ? TTFlag::UPPERBOUND + : TTFlag::EXACT; + + tt.store(hash, session.pv[ply][0], value_to_tt(maxScore, ply), depth, flag); + } + return maxScore; +} +std::string extract_pv(const chess::Board &root, int maxPly) { + std::string pv; + chess::Board pos = root; + uint64_t cycle[64]{}; + for (int ply = 0; ply < maxPly; ply++) { + TTEntry *e = search::tt.lookup(pos.hash()); + if (!e) break; + chess::Move m(e->getMove()); + if (!m.is_ok()) break; + uint64_t h = pos.hash(); + int idx = (h >> 6) & 0x3F; + uint64_t bit = 1ULL << (h & 0x3F); + if (cycle[idx] & bit) break; + cycle[idx] |= bit; + chess::Movelist ml; + pos.legals(ml); + bool legal = false; + for (size_t i = 0; i < ml.size(); i++) + if (ml[i] == m) { legal = true; break; } + if (!legal) break; + pv += chess::uci::moveToUci(m, root.chess960()) + " "; + if (ply + 1 >= maxPly) break; + pos.doMove(m); + } + return pv; +} + +void search(const chess::Board &board, const timeman::LimitsType timecontrol) { + stopSearch = false; + tt.newSearch(); + static double originalTimeAdjust = -1; + Session session; + session.tc = timecontrol; + session.tm.init(session.tc, board.side_to_move(), 0, originalTimeAdjust); + session.lastLogTime = session.tm.elapsed(); + session.ogcolor = board.side_to_move(); + chess::Move lastPV[MAX_PLY]{}; + Value prevScore = VALUE_NONE; + + for (int i = 1; i <= timecontrol.depth; i++) { + session.lastLogTime = session.tm.elapsed(); + session.depth = i; + for (int _ = 0; _ < 64; _++) + for (int j = 0; j < 64; j++) + session.historyHeuristic[_][j] /= 2; + auto board_ = board; + Value score_; + + if (i >= 3 && prevScore != VALUE_NONE && !is_win(prevScore) && !is_loss(prevScore)) { + Value delta = Value(20 + i * 5); + Value alpha0 = std::max(prevScore - delta, -VALUE_INFINITE); + Value beta0 = std::min(prevScore + delta, VALUE_INFINITE); + + score_ = doSearch(board_, i, alpha0, beta0, session); + + if (score_ != VALUE_NONE && score_ <= alpha0) { + score_ = doSearch(board_, i, -VALUE_INFINITE, beta0, session); + if (score_ != VALUE_NONE && score_ <= alpha0) + score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + } else if (score_ != VALUE_NONE && score_ >= beta0) { + score_ = doSearch(board_, i, alpha0, VALUE_INFINITE, session); + if (score_ != VALUE_NONE && score_ >= beta0) + score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + } + } else { + score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + } + prevScore = score_; + if (session.tm.elapsed() >= session.tm.optimum() || session.tm.elapsed() >= session.tm.maximum() + || stopSearch.load(std::memory_order_relaxed) || score_ == VALUE_NONE) + break; InfoFull info{}; - info.depth = 1; - info.nodes = 1; - info.score = 0; + info.depth = i; + info.selDepth = session.seldepth; + info.hashfull = tt.hashfull(); + info.nodes = session.nodes; + info.nps = session.nodes * 1000 / std::max(session.tm.elapsed(), (timeman::TimePoint)1); + info.timeMs = session.tm.elapsed(); + info.tbHits = session.tbHits; info.multiPV = 1; - info.pv = std::string(chess::uci::moveToUci(best, board.chess960())); + info.score = score_; + TTEntry *entry = tt.lookup(board.hash()); + if (entry) + switch (entry->getFlag()) { + case LOWERBOUND: + info.bound = "lowerbound"; + break; + case UPPERBOUND: + info.bound = "upperbound"; + break; + default: + break; + } + info.pv = extract_pv(board, 2 * i + 4); + // Save first move from PV for bestmove output + std::string pvStr = info.pv; + size_t sp = pvStr.find(' '); + std::string firstMove = (sp == std::string::npos) ? pvStr : pvStr.substr(0, sp); + if (!firstMove.empty()) + lastPV[0] = chess::Move(chess::uci::uciToMove(board, firstMove).raw()); + std::stringstream ss; + ss << "qnodes "<getMove() != Move::none().raw()) + report(chess::uci::moveToUci(Move(entry->getMove()), board.chess960())); + else { + Movelist moves; + board.legals(moves); + + if (moves.size()) { + Board board_ = board; + Move best = moves[0]; + Value bestScore = -VALUE_INFINITE; + Session tmpSession{}; + for (Move move : moves) { + board_.doMove(move); + Value score = -qsearch(board_, -VALUE_INFINITE, VALUE_INFINITE, tmpSession, 0); + if (score > bestScore) { + bestScore = score; + best = move; + } + board_.undoMove(); + } + + InfoFull info{}; + info.depth = 1; + info.nodes = 1; + info.score = 0; + info.multiPV = 1; + info.pv = std::string(chess::uci::moveToUci(best, board.chess960())); + report(info); + + report(chess::uci::moveToUci(best, board.chess960())); + } else { + report("0000"); + } + } + } } -} // namespace engine +} // namespace engine::search \ No newline at end of file diff --git a/search.h b/search.h index 275c1c7..be1c304 100644 --- a/search.h +++ b/search.h @@ -5,13 +5,18 @@ #include namespace engine::search { struct Session { - timeman::TimeManagement tm; - timeman::LimitsType tc; - int seldepth = 0; - uint64_t nodes = 0; - chess::Move pv[MAX_PLY][MAX_PLY]; - Value historyHeuristic[chess::SQUARE_NB][chess::SQUARE_NB]{}; - chess::Move killerMoves[256][2]; + timeman::TimeManagement tm; + timeman::LimitsType tc; + int seldepth = 0; + uint64_t nodes = 0, qnodes = 0, lmrResearches = 0; + uint64_t tbHits = 0, ttHits = 0, ttCutoffs = 0, nullCutoffs = 0; + chess::Move pv[MAX_PLY][MAX_PLY]; + Value historyHeuristic[chess::SQUARE_NB][chess::SQUARE_NB]{}; + chess::Move killerMoves[MAX_PLY][2]; + chess::Move counterMoves[4096]; + timeman::TimePoint lastLogTime; + int depth = 0; + chess::Color ogcolor; }; void stop(); void search(const chess::Board &, const timeman::LimitsType); diff --git a/timeman.cpp b/timeman.cpp index b47bad9..3bd6469 100644 --- a/timeman.cpp +++ b/timeman.cpp @@ -18,77 +18,67 @@ void TimeManagement::clear() {} // the bounds of time allowed for the current game ply. We currently support: // 1) x basetime (+ z increment) // 2) x moves in y seconds (+ z increment) -void TimeManagement::init(LimitsType &limits, chess::Color us, int ply, - double &originalTimeAdjust) { +void TimeManagement::init(LimitsType &limits, chess::Color us, int ply, double &originalTimeAdjust) { - // If we have no time, we don't need to fully initialize TM. - // startTime is used by movetime and useNodesTime is used in elapsed calls. - startTime = limits.startTime; - if (limits.movetime != 0 && limits.time[us] == 0) { - optimumTime = maximumTime = TimePoint(limits.movetime); - return; - } - if (limits.time[us] == 0 && limits.movetime == 0) { - optimumTime = maximumTime = INFINITE_TIME; - return; - } - // optScale is a percentage of available time to use for the current move. - // maxScale is a multiplier applied to optimumTime. - double optScale, maxScale; + // If we have no time, we don't need to fully initialize TM. + // startTime is used by movetime and useNodesTime is used in elapsed calls. + startTime = limits.startTime; + if (limits.movetime != 0 && limits.time[us] == 0) { + optimumTime = maximumTime = TimePoint(limits.movetime); + return; + } + if (limits.time[us] == 0 && limits.movetime == 0) { + optimumTime = maximumTime = INFINITE_TIME; + return; + } + // optScale is a percentage of available time to use for the current move. + // maxScale is a multiplier applied to optimumTime. + double optScale, maxScale; - // These numbers are used where multiplications, divisions or comparisons - // with constants are involved. - const TimePoint time = limits.time[us]; - const int moveOverhead = options["Move Overhead"]; - // Maximum move horizon - int centiMTG = - limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051; + // These numbers are used where multiplications, divisions or comparisons + // with constants are involved. + const TimePoint time = limits.time[us]; + const int moveOverhead = options["Move Overhead"]; + // Maximum move horizon + int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051; - // If less than one second, gradually reduce mtg - if (time < 1000) - centiMTG = int(time * 5.051); + // If less than one second, gradually reduce mtg + if (time < 1000) + centiMTG = int(time * 5.051); - // Make sure timeLeft is > 0 since we may use it as a divisor - TimePoint timeLeft = - std::max(TimePoint(1), time + (limits.inc[us] * (centiMTG - 100) - - moveOverhead * (200 + centiMTG)) / - 100); + // Make sure timeLeft is > 0 since we may use it as a divisor + TimePoint timeLeft = + std::max(TimePoint(1), time + (limits.inc[us] * (centiMTG - 100) - moveOverhead * (200 + centiMTG)) / 100); - // x basetime (+ z increment) - // If there is a healthy increment, timeLeft can exceed the actual available - // game time for the current move, so also cap to a percentage of available - // game time. - if (limits.movestogo == 0) { - // Extra time according to timeLeft - if (originalTimeAdjust < 0) - originalTimeAdjust = 0.3128 * std::log10(timeLeft) - 0.4354; + // x basetime (+ z increment) + // If there is a healthy increment, timeLeft can exceed the actual available + // game time for the current move, so also cap to a percentage of available + // game time. + if (limits.movestogo == 0) { + // Extra time according to timeLeft + if (originalTimeAdjust < 0) + originalTimeAdjust = 0.3128 * std::log10(timeLeft) - 0.4354; - // Calculate time constants based on current time left. - double logTimeInSec = std::log10(time / 1000.0); - double optConstant = - std::min(0.0032116 + 0.000321123 * logTimeInSec, 0.00508017); - double maxConstant = std::max(3.3977 + 3.03950 * logTimeInSec, 2.94761); + // Calculate time constants based on current time left. + double logTimeInSec = std::log10(time / 1000.0); + double optConstant = std::min(0.0032116 + 0.000321123 * logTimeInSec, 0.00508017); + double maxConstant = std::max(3.3977 + 3.03950 * logTimeInSec, 2.94761); - optScale = - std::min(0.0121431 + std::pow(ply + 2.94693, 0.461073) * optConstant, - 0.213035 * time / timeLeft) * - originalTimeAdjust; + optScale = std::min(0.0121431 + std::pow(ply + 2.94693, 0.461073) * optConstant, 0.213035 * time / timeLeft) * + originalTimeAdjust; - maxScale = std::min(6.67704, maxConstant + ply / 11.9847); - } + maxScale = std::min(6.67704, maxConstant + ply / 11.9847); + } - // x moves in y seconds (+ z increment) - else { - optScale = std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), - 0.88 * time / timeLeft); - maxScale = 1.3 + 0.11 * (centiMTG / 100.0); - } + // x moves in y seconds (+ z increment) + else { + optScale = std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), 0.88 * time / timeLeft); + maxScale = 1.3 + 0.11 * (centiMTG / 100.0); + } - // Limit the maximum possible time for this move - optimumTime = TimePoint(optScale * timeLeft); - maximumTime = TimePoint(std::min(0.825179 * time - moveOverhead, - maxScale * optimumTime)) - - 10; + // Limit the maximum possible time for this move + optimumTime = TimePoint(optScale * timeLeft); + maximumTime = TimePoint(std::min(0.825179 * time - moveOverhead, maxScale * optimumTime)) - 10; } } // namespace engine::timeman diff --git a/timeman.h b/timeman.h index ca90fe2..6a09fdb 100644 --- a/timeman.h +++ b/timeman.h @@ -8,47 +8,41 @@ constexpr TimePoint INFINITE_TIME = 864000000; // LimitsType struct stores information sent by the caller about the analysis // required. inline TimePoint now() { - return std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()) - .count(); + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); } struct LimitsType { - // Init explicitly due to broken value-initialization of non POD in MSVC - LimitsType() { - time[chess::WHITE] = time[chess::BLACK] = inc[chess::WHITE] = - inc[chess::BLACK] = movetime = TimePoint(0); - movestogo = mate = perft = infinite = 0; - depth = 64; - nodes = 0; - ponderMode = false; - } + // Init explicitly due to broken value-initialization of non POD in MSVC + LimitsType() { + time[chess::WHITE] = time[chess::BLACK] = inc[chess::WHITE] = inc[chess::BLACK] = movetime = TimePoint(0); + movestogo = mate = perft = infinite = 0; + depth = 64; + nodes = 0; + ponderMode = false; + } - bool use_time_management() const { - return time[chess::WHITE] || time[chess::BLACK]; - } + bool use_time_management() const { return time[chess::WHITE] || time[chess::BLACK]; } - std::vector searchmoves; - TimePoint time[chess::COLOR_NB], inc[chess::COLOR_NB], movetime, startTime; - int movestogo, depth, mate, perft, infinite; - uint64_t nodes; - bool ponderMode; + std::vector searchmoves; + TimePoint time[chess::COLOR_NB], inc[chess::COLOR_NB], movetime, startTime; + int movestogo, depth, mate, perft, infinite; + uint64_t nodes; + bool ponderMode; }; class TimeManagement { -public: - void init(LimitsType &limits, chess::Color us, int ply, - double &originalTimeAdjust); + public: + void init(LimitsType &limits, chess::Color us, int ply, double &originalTimeAdjust); - TimePoint optimum() const; - TimePoint maximum() const; - TimePoint elapsed() const { return elapsed_time(); } - TimePoint elapsed_time() const { return now() - startTime; }; + TimePoint optimum() const; + TimePoint maximum() const; + TimePoint elapsed() const { return elapsed_time(); } + TimePoint elapsed_time() const { return now() - startTime; }; - void clear(); + void clear(); -private: - TimePoint startTime; - TimePoint optimumTime; - TimePoint maximumTime; + private: + TimePoint startTime; + TimePoint optimumTime; + TimePoint maximumTime; }; } // namespace engine::timeman \ No newline at end of file diff --git a/tt.cpp b/tt.cpp index f084236..59dd547 100644 --- a/tt.cpp +++ b/tt.cpp @@ -6,70 +6,69 @@ #endif using namespace engine; static inline uint64_t index_for_hash(uint64_t hash, uint64_t buckets) { - if (buckets == 0) - return 0; + if (buckets == 0) + return 0; #if defined(_MSC_VER) - // MSVC: use _umul128 to get high 64 bits of 128-bit product - unsigned long long high = 0; - (void)_umul128((unsigned long long)hash, (unsigned long long)buckets, &high); - return (uint64_t)high; + // MSVC: use _umul128 to get high 64 bits of 128-bit product + unsigned long long high = 0; + (void)_umul128((unsigned long long)hash, (unsigned long long)buckets, &high); + return (uint64_t)high; #elif defined(__SIZEOF_INT128__) - // GCC/Clang: use __uint128_t - __uint128_t prod = (__uint128_t)hash * (__uint128_t)buckets; - return (uint64_t)(prod >> 64); + // GCC/Clang: use __uint128_t + __uint128_t prod = (__uint128_t)hash * (__uint128_t)buckets; + return (uint64_t)(prod >> 64); #else - // Portable fallback (rare): fall back to modulo if no 128-bit or _umul128 - // available. This is only used on very uncommon toolchains; primary - // implementations above avoid division. - return hash % buckets; + uint64_t aL = uint32_t(hash), aH = a >> 32; + uint64_t bL = uint32_t(buckets), bH = b >> 32; + uint64_t c1 = (aL * bL) >> 32; + uint64_t c2 = aH * bL + c1; + uint64_t c3 = aL * bH + uint32_t(c2); + return aH * bH + (c2 >> 32) + (c3 >> 32); #endif } void TranspositionTable::newSearch() { time++; } -void TranspositionTable::store(uint64_t hash, chess::Move best, int16_t score, - int8_t depth, TTFlag flag) { - // 2 entries per bucket - if (buckets == 0) - return; +void TranspositionTable::store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, TTFlag flag) { + // 2 entries per bucket + if (buckets == 0) + return; - uint64_t index = index_for_hash(hash, buckets); - if (index >= buckets - 1) - index = buckets - 2; // Ensure we don't overflow + uint64_t index = index_for_hash(hash, buckets); - TTEntry &e0 = table[index], &e1 = table[index + 1]; - // Store the entry - for (TTEntry *e : {&e0, &e1}) { - if (e->key == hash || e->getDepth() < depth) { - e->key = hash; - e->setPackedFields(score, depth, flag, best.raw(), time); + TTEntry &e0 = table[2*index], &e1 = table[2*index + 1]; + // Store the entry + for (TTEntry *e : { &e0, &e1 }) { + if (e->key == hash || e->getDepth() < depth) { + e->key = hash; + e->setPackedFields(score, depth, flag, best.raw(), time); - return; + return; + } } - } - // If we get here, we need to evict an entry - // Find the oldest entry - TTEntry *oldest = (e0.timestamp() < e1.timestamp()) ? &e0 : &e1; - // Evict it - oldest->key = hash; - oldest->setPackedFields(score, depth, flag, best.raw(), time); + // If we get here, we need to evict an entry + // Find the oldest entry + TTEntry *oldest = (e0.timestamp() < e1.timestamp()) ? &e0 : &e1; + // Evict it + oldest->key = hash; + oldest->setPackedFields(score, depth, flag, best.raw(), time); } -TTEntry *TranspositionTable::lookup(uint64_t hash) { - // 2 entries per bucket - if (buckets == 0) - return nullptr; +TTEntry* TranspositionTable::lookup(uint64_t hash) { + if (buckets == 0) + return nullptr; + + uint64_t bucket = index_for_hash(hash, buckets); + + TTEntry& e0 = table[2 * bucket]; + TTEntry& e1 = table[2 * bucket + 1]; - uint64_t index = index_for_hash(hash, buckets); - if (index >= buckets - 1) - index = buckets - 2; // Ensure we don't overflow + if (e0.key == hash) + return &e0; - TTEntry &e0 = table[index], &e1 = table[index + 1]; - // Check the entries - for (TTEntry *e : {&e0, &e1}) { - if (e->key == hash && e->getGeneration() == this->time) - return e; - } - return nullptr; + if (e1.key == hash) + return &e1; + + return nullptr; } \ No newline at end of file diff --git a/tt.h b/tt.h index a74f167..e648370 100644 --- a/tt.h +++ b/tt.h @@ -6,165 +6,138 @@ namespace engine { enum TTFlag : uint8_t { EXACT = 0, LOWERBOUND = 1, UPPERBOUND = 2 }; struct TTEntry { - uint64_t key; - uint64_t pack; // 16-bit score, 8-bit depth, 3-bit flags, 16-bit move, 21 bits - // for generation - - // bit layout constants - static constexpr unsigned SCORE_SHIFT = 0; - static constexpr unsigned SCORE_BITS = 16; - static constexpr uint64_t SCORE_MASK = ((uint64_t(1) << SCORE_BITS) - 1) - << SCORE_SHIFT; - - static constexpr unsigned DEPTH_SHIFT = 16; - static constexpr unsigned DEPTH_BITS = 8; - static constexpr uint64_t DEPTH_MASK = ((uint64_t(1) << DEPTH_BITS) - 1) - << DEPTH_SHIFT; - - static constexpr unsigned FLAG_SHIFT = 24; - static constexpr unsigned FLAG_BITS = 3; - static constexpr uint64_t FLAG_MASK = ((uint64_t(1) << FLAG_BITS) - 1) - << FLAG_SHIFT; - - static constexpr unsigned MOVE_SHIFT = 27; - static constexpr unsigned MOVE_BITS = 16; - static constexpr uint64_t MOVE_MASK = ((uint64_t(1) << MOVE_BITS) - 1) - << MOVE_SHIFT; - - static constexpr unsigned GEN_SHIFT = 43; - static constexpr unsigned GEN_BITS = 21; - static constexpr uint64_t GEN_MASK = ((uint64_t(1) << GEN_BITS) - 1) - << GEN_SHIFT; - - // getters - inline int16_t getScore() const noexcept { - return static_cast((pack & SCORE_MASK) >> SCORE_SHIFT); - } - - inline uint8_t getDepth() const noexcept { - return static_cast((pack & DEPTH_MASK) >> DEPTH_SHIFT); - } - - inline TTFlag getFlag() const noexcept { - return static_cast((pack & FLAG_MASK) >> FLAG_SHIFT); - } - - inline uint16_t getMove() const noexcept { - return static_cast((pack & MOVE_MASK) >> MOVE_SHIFT); - } - - inline uint32_t getGeneration() const noexcept { - return static_cast((pack & GEN_MASK) >> GEN_SHIFT); - } - - // setters - inline void setScore(int16_t score) noexcept { - // preserve two's complement by casting through uint16_t - const uint64_t v = - (static_cast(static_cast(score)) << SCORE_SHIFT) & - SCORE_MASK; - pack = (pack & ~SCORE_MASK) | v; - } - - inline void setDepth(uint8_t depth) noexcept { - const uint64_t v = - (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; - pack = (pack & ~DEPTH_MASK) | v; - } - - inline void setFlag(TTFlag flag) noexcept { - const uint64_t v = - (static_cast(static_cast(flag)) << FLAG_SHIFT) & - FLAG_MASK; - pack = (pack & ~FLAG_MASK) | v; - } - - inline void setMove(uint16_t move) noexcept { - const uint64_t v = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; - pack = (pack & ~MOVE_MASK) | v; - } - - inline void setGeneration(uint32_t gen) noexcept { - const uint64_t v = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; - pack = (pack & ~GEN_MASK) | v; - } - - // convenience: set all packed fields at once - inline void setPackedFields(int16_t score, uint8_t depth, TTFlag flag, - uint16_t move, uint32_t gen) noexcept { - const uint64_t s = - (static_cast(static_cast(score)) << SCORE_SHIFT) & - SCORE_MASK; - const uint64_t d = - (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; - const uint64_t f = - (static_cast(static_cast(flag)) << FLAG_SHIFT) & - FLAG_MASK; - const uint64_t m = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; - const uint64_t g = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; - pack = s | d | f | m | g; - } - - inline uint32_t timestamp() const noexcept { return getGeneration(); } + uint64_t key; + uint64_t pack; // 16-bit score, 8-bit depth, 3-bit flags, 16-bit move, 21 bits + // for generation + + // bit layout constants + static constexpr unsigned SCORE_SHIFT = 0; + static constexpr unsigned SCORE_BITS = 16; + static constexpr uint64_t SCORE_MASK = ((uint64_t(1) << SCORE_BITS) - 1) << SCORE_SHIFT; + + static constexpr unsigned DEPTH_SHIFT = 16; + static constexpr unsigned DEPTH_BITS = 8; + static constexpr uint64_t DEPTH_MASK = ((uint64_t(1) << DEPTH_BITS) - 1) << DEPTH_SHIFT; + + static constexpr unsigned FLAG_SHIFT = 24; + static constexpr unsigned FLAG_BITS = 3; + static constexpr uint64_t FLAG_MASK = ((uint64_t(1) << FLAG_BITS) - 1) << FLAG_SHIFT; + + static constexpr unsigned MOVE_SHIFT = 27; + static constexpr unsigned MOVE_BITS = 16; + static constexpr uint64_t MOVE_MASK = ((uint64_t(1) << MOVE_BITS) - 1) << MOVE_SHIFT; + + static constexpr unsigned GEN_SHIFT = 43; + static constexpr unsigned GEN_BITS = 21; + static constexpr uint64_t GEN_MASK = ((uint64_t(1) << GEN_BITS) - 1) << GEN_SHIFT; + + // getters + inline int16_t getScore() const noexcept { return static_cast((pack & SCORE_MASK) >> SCORE_SHIFT); } + + inline uint8_t getDepth() const noexcept { return static_cast((pack & DEPTH_MASK) >> DEPTH_SHIFT); } + + inline TTFlag getFlag() const noexcept { return static_cast((pack & FLAG_MASK) >> FLAG_SHIFT); } + + inline uint16_t getMove() const noexcept { return static_cast((pack & MOVE_MASK) >> MOVE_SHIFT); } + + inline uint32_t getGeneration() const noexcept { return static_cast((pack & GEN_MASK) >> GEN_SHIFT); } + + // setters + inline void setScore(int16_t score) noexcept { + // preserve two's complement by casting through uint16_t + const uint64_t v = (static_cast(static_cast(score)) << SCORE_SHIFT) & SCORE_MASK; + pack = (pack & ~SCORE_MASK) | v; + } + + inline void setDepth(uint8_t depth) noexcept { + const uint64_t v = (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; + pack = (pack & ~DEPTH_MASK) | v; + } + + inline void setFlag(TTFlag flag) noexcept { + const uint64_t v = (static_cast(static_cast(flag)) << FLAG_SHIFT) & FLAG_MASK; + pack = (pack & ~FLAG_MASK) | v; + } + + inline void setMove(uint16_t move) noexcept { + const uint64_t v = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; + pack = (pack & ~MOVE_MASK) | v; + } + + inline void setGeneration(uint32_t gen) noexcept { + const uint64_t v = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; + pack = (pack & ~GEN_MASK) | v; + } + + // convenience: set all packed fields at once + inline void setPackedFields(int16_t score, uint8_t depth, TTFlag flag, uint16_t move, uint32_t gen) noexcept { + const uint64_t s = (static_cast(static_cast(score)) << SCORE_SHIFT) & SCORE_MASK; + const uint64_t d = (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; + const uint64_t f = (static_cast(static_cast(flag)) << FLAG_SHIFT) & FLAG_MASK; + const uint64_t m = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; + const uint64_t g = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; + pack = s | d | f | m | g; + } + + inline uint32_t timestamp() const noexcept { return getGeneration(); } }; class TranspositionTable { - TTEntry *table; - int buckets; // number of buckets (pairs) - uint32_t time; - -public: - int size; // total number of TTEntry elements (must be even) - TranspositionTable() : table(nullptr), buckets(0), time(0), size(0) {} - - TranspositionTable(int sizeInMB) : time(0) { - size = sizeInMB * 1048576 / sizeof(TTEntry); - if (size % 2 != 0) - size--; // Ensure even size - buckets = size / 2; - table = new TTEntry[size]; - clear(); - } - - ~TranspositionTable() { delete[] table; } - - void resize(int sizeInMB) { - int new_size = sizeInMB * 1048576 / sizeof(TTEntry); - if (new_size % 2 != 0) - new_size--; - - TTEntry *new_table = new (std::nothrow) TTEntry[new_size](); - if (!new_table) { - throw std::bad_alloc(); + TTEntry *table; + int buckets; // number of buckets (pairs) + uint32_t time; + + public: + size_t size; // total number of TTEntry elements (must be even) + TranspositionTable() : table(nullptr), buckets(0), time(0), size(0) {} + + TranspositionTable(size_t sizeInMB) : time(0) { + size = sizeInMB * 1048576LL / sizeof(TTEntry); + if (size % 2 != 0) + size--; // Ensure even size + buckets = size / 2; + table = new TTEntry[size]; + clear(); } - delete[] table; - table = new_table; - size = new_size; - buckets = size / 2; - } + ~TranspositionTable() { delete[] table; } + + void resize(int sizeInMB) { + int new_size = sizeInMB * 1048576 / sizeof(TTEntry); + if (new_size % 2 != 0) + new_size--; - void newSearch(); - void store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, - TTFlag flag); + TTEntry *new_table = new (std::nothrow) TTEntry[new_size](); + if (!new_table) { + throw std::bad_alloc(); + } - inline void clear() { std::fill_n(table, size, TTEntry{}); } + delete[] table; + table = new_table; + size = new_size; + buckets = size / 2; + } - TTEntry *lookup(uint64_t hash); + void newSearch(); + void store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, TTFlag flag); - // Returns hash usage as percentage [0..100] based on occupied buckets. - inline int hashfull() const noexcept { - if (!table || buckets <= 0) - return 0; + inline void clear() { std::fill_n(table, size, TTEntry{}); } - int used = 0; - for (int i = 0; i < buckets; ++i) { - const TTEntry &a = table[2 * i]; - const TTEntry &b = table[2 * i + 1]; - if (a.key != 0 || b.key != 0) - ++used; - } + TTEntry *lookup(uint64_t hash); - return (used * 1000) / buckets; - } + // Returns hash usage as percentage [0..100] based on occupied buckets. + inline int hashfull() const noexcept { + if (!table || buckets <= 0) + return 0; + + int used = 0; + for (int i = 0; i < buckets; ++i) { + const TTEntry &a = table[2 * i]; + const TTEntry &b = table[2 * i + 1]; + if (a.key != 0 || b.key != 0) + ++used; + } + + return (used * 1000LL) / buckets; + } }; } // namespace engine diff --git a/tune.cpp b/tune.cpp index d29fed6..94077ec 100644 --- a/tune.cpp +++ b/tune.cpp @@ -39,65 +39,61 @@ std::map TuneResults; std::optional on_tune(const Option &o) { - if (!Tune::update_on_last || LastOption == &o) - Tune::read_options(); + if (!Tune::update_on_last || LastOption == &o) + Tune::read_options(); - return std::nullopt; + return std::nullopt; } } // namespace -void Tune::make_option(OptionsMap *opts, const string &n, int v, - const SetRange &r) { +void Tune::make_option(OptionsMap *opts, const string &n, int v, const SetRange &r) { - // Do not generate option when there is nothing to tune (ie. min = max) - if (r(v).first == r(v).second) - return; + // Do not generate option when there is nothing to tune (ie. min = max) + if (r(v).first == r(v).second) + return; - if (TuneResults.count(n)) - v = TuneResults[n]; + if (TuneResults.count(n)) + v = TuneResults[n]; - opts->add(n, Option(v, r(v).first, r(v).second, on_tune)); - LastOption = &((*opts)[n]); + opts->add(n, Option(v, r(v).first, r(v).second, on_tune)); + LastOption = &((*opts)[n]); - // Print formatted parameters, ready to be copy-pasted in Fishtest - std::cout << n << "," // + // Print formatted parameters, ready to be copy-pasted in Fishtest + std::cout << n << "," // #ifdef OPENBENCH_SUPPORT - // or OpenBench - << "int" << "," + // or OpenBench + << "int" << "," #endif - << v << "," // - << r(v).first << "," // - << r(v).second << "," // - << (r(v).second - r(v).first) / 20.0 << "," // - << "0.0020" << std::endl; + << v << "," // + << r(v).first << "," // + << r(v).second << "," // + << (r(v).second - r(v).first) / 20.0 << "," // + << "0.0020" << std::endl; } string Tune::next(string &names, bool pop) { - string name; + string name; - do { - string token = names.substr(0, names.find(',')); + do { + string token = names.substr(0, names.find(',')); - if (pop) - names.erase(0, token.size() + 1); + if (pop) + names.erase(0, token.size() + 1); - std::stringstream ws(token); - name += (ws >> token, token); // Remove trailing whitespace + std::stringstream ws(token); + name += (ws >> token, token); // Remove trailing whitespace - } while (std::count(name.begin(), name.end(), '(') - - std::count(name.begin(), name.end(), ')')); + } while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')')); - return name; + return name; } -template <> void Tune::Entry::init_option() { - make_option(options, name, value, range); -} +template <> void Tune::Entry::init_option() { make_option(options, name, value, range); } template <> void Tune::Entry::read_option() { - if (options->count(name)) - value = int((*options)[name]); + if (options->count(name)) + value = int((*options)[name]); } // Instead of a variable here we have a PostUpdate function: just call it diff --git a/tune.h b/tune.h index d6a0022..e337d11 100644 --- a/tune.h +++ b/tune.h @@ -34,17 +34,15 @@ using Range = std::pair; // Option's min-max values using RangeFun = Range(int); // Default Range function, to calculate Option's min-max values -inline Range default_range(int v) { - return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); -} +inline Range default_range(int v) { return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); } struct SetRange { - explicit SetRange(RangeFun f) : fun(f) {} - SetRange(int min, int max) : fun(nullptr), range(min, max) {} - Range operator()(int v) const { return fun ? fun(v) : range; } + explicit SetRange(RangeFun f) : fun(f) {} + SetRange(int min, int max) : fun(nullptr), range(min, max) {} + Range operator()(int v) const { return fun ? fun(v) : range; } - RangeFun *fun; - Range range; + RangeFun *fun; + Range range; }; #define SetDefaultRange SetRange(default_range) @@ -78,105 +76,93 @@ struct SetRange { class Tune { - using PostUpdate = void(); // Post-update function - - Tune() { read_results(); } - Tune(const Tune &) = delete; - void operator=(const Tune &) = delete; - void read_results(); - - static Tune &instance() { - static Tune t; - return t; - } // Singleton - - // Use polymorphism to accommodate Entry of different types in the same vector - struct EntryBase { - virtual ~EntryBase() = default; - virtual void init_option() = 0; - virtual void read_option() = 0; - }; - - template struct Entry : public EntryBase { - - static_assert(!std::is_const_v, "Parameter cannot be const!"); - - static_assert(std::is_same_v || std::is_same_v, - "Parameter type not supported!"); - - Entry(const std::string &n, T &v, const SetRange &r) - : name(n), value(v), range(r) {} - void operator=(const Entry &) = delete; // Because 'value' is a reference - void init_option() override; - void read_option() override; - - std::string name; - T &value; - SetRange range; - }; - - // Our facility to fill the container, each Entry corresponds to a parameter - // to tune. We use variadic templates to deal with an unspecified number of - // entries, each one of a possible different type. - static std::string next(std::string &names, bool pop = true); - - int add(const SetRange &, std::string &&) { return 0; } - - template - int add(const SetRange &range, std::string &&names, T &value, - Args &&...args) { - list.push_back( - std::unique_ptr(new Entry(next(names), value, range))); - return add(range, std::move(names), args...); - } - - // Template specialization for arrays: recursively handle multi-dimensional - // arrays - template - int add(const SetRange &range, std::string &&names, T (&value)[N], - Args &&...args) { - for (size_t i = 0; i < N; i++) - add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", - value[i]); - return add(range, std::move(names), args...); - } - - // Template specialization for SetRange - template - int add(const SetRange &, std::string &&names, SetRange &value, - Args &&...args) { - return add(value, (next(names), std::move(names)), args...); - } - - static void make_option(OptionsMap *options, const std::string &n, int v, - const SetRange &r); - - std::vector> list; - -public: - template - static int add(const std::string &names, Args &&...args) { - return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), - args...); // Remove trailing parenthesis - } - static void init(OptionsMap &o) { - options = &o; - for (auto &e : instance().list) - e->init_option(); - read_options(); - } // Deferred, due to UCIEngine::Options access - static void read_options() { - for (auto &e : instance().list) - e->read_option(); - } - - static bool update_on_last; - static OptionsMap *options; + using PostUpdate = void(); // Post-update function + + Tune() { read_results(); } + Tune(const Tune &) = delete; + void operator=(const Tune &) = delete; + void read_results(); + + static Tune &instance() { + static Tune t; + return t; + } // Singleton + + // Use polymorphism to accommodate Entry of different types in the same vector + struct EntryBase { + virtual ~EntryBase() = default; + virtual void init_option() = 0; + virtual void read_option() = 0; + }; + + template struct Entry : public EntryBase { + + static_assert(!std::is_const_v, "Parameter cannot be const!"); + + static_assert(std::is_same_v || std::is_same_v, "Parameter type not supported!"); + + Entry(const std::string &n, T &v, const SetRange &r) : name(n), value(v), range(r) {} + void operator=(const Entry &) = delete; // Because 'value' is a reference + void init_option() override; + void read_option() override; + + std::string name; + T &value; + SetRange range; + }; + + // Our facility to fill the container, each Entry corresponds to a parameter + // to tune. We use variadic templates to deal with an unspecified number of + // entries, each one of a possible different type. + static std::string next(std::string &names, bool pop = true); + + int add(const SetRange &, std::string &&) { return 0; } + + template int add(const SetRange &range, std::string &&names, T &value, Args &&...args) { + list.push_back(std::unique_ptr(new Entry(next(names), value, range))); + return add(range, std::move(names), args...); + } + + // Template specialization for arrays: recursively handle multi-dimensional + // arrays + template + int add(const SetRange &range, std::string &&names, T (&value)[N], Args &&...args) { + for (size_t i = 0; i < N; i++) + add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]); + return add(range, std::move(names), args...); + } + + // Template specialization for SetRange + template int add(const SetRange &, std::string &&names, SetRange &value, Args &&...args) { + return add(value, (next(names), std::move(names)), args...); + } + + static void make_option(OptionsMap *options, const std::string &n, int v, const SetRange &r); + + std::vector> list; + + public: + template static int add(const std::string &names, Args &&...args) { + return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), + args...); // Remove trailing parenthesis + } + static void init(OptionsMap &o) { + options = &o; + for (auto &e : instance().list) + e->init_option(); + read_options(); + } // Deferred, due to UCIEngine::Options access + static void read_options() { + for (auto &e : instance().list) + e->read_option(); + } + + static bool update_on_last; + static OptionsMap *options; }; template constexpr void tune_check_args(Args &&...) { - static_assert((!std::is_fundamental_v && ...), - "TUNE macro arguments wrong"); + static_assert((!std::is_fundamental_v && ...), "TUNE macro arguments wrong"); } // Some macro magic :-) we define a dummy int variable that the compiler @@ -184,11 +170,11 @@ template constexpr void tune_check_args(Args &&...) { #define STRINGIFY(x) #x #define UNIQUE2(x, y) x##y #define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ -#define TUNE(...) \ - int UNIQUE(p, __LINE__) = []() -> int { \ - tune_check_args(__VA_ARGS__); \ - return engine::Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__); \ - }(); +#define TUNE(...) \ + int UNIQUE(p, __LINE__) = []() -> int { \ + tune_check_args(__VA_ARGS__); \ + return engine::Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__); \ + }(); #define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true diff --git a/uci.cpp b/uci.cpp index 20aab9a..24ebd8a 100644 --- a/uci.cpp +++ b/uci.cpp @@ -1,8 +1,11 @@ #include "uci.h" +#include "eval.h" #include "search.h" #include "timeman.h" #include "ucioption.h" #include +#include +#include #include #include #include @@ -12,189 +15,216 @@ using namespace engine; chess::Position pos; OptionsMap engine::options; std::thread searchThread; + +namespace { +std::string strip_optional_quotes(std::string fen) { + auto notSpace = [](unsigned char c) { return !std::isspace(c); }; + + fen.erase(fen.begin(), std::find_if(fen.begin(), fen.end(), notSpace)); + fen.erase(std::find_if(fen.rbegin(), fen.rend(), notSpace).base(), fen.end()); + + if (fen.size() >= 2 && fen.front() == '"' && fen.back() == '"') + return fen.substr(1, fen.size() - 2); + + if (!fen.empty() && fen.front() == '"') + fen.erase(fen.begin()); + + if (!fen.empty() && fen.back() == '"') + fen.pop_back(); + + return fen; +} +} // namespace + void engine::stop() { - search::stop(); - if (searchThread.joinable()) { search::stop(); - searchThread.join(); - } + if (searchThread.joinable()) { + searchThread.join(); + } } void handlePosition(std::istringstream &is) { - stop(); - std::string token, fen; - - is >> token; - - if (token == "startpos") { - fen = chess::Position::START_FEN; - is >> token; // Consume the "moves" token, if any - } else if (token == "fen") - while (is >> token && token != "moves") - fen += token + " "; - else - return; - pos.setFEN(fen); - - while (is >> token) { - pos.push_uci(token); - } + stop(); + std::string token, fen; + + is >> token; + + if (token == "startpos") { + fen = chess::Position::START_FEN; + is >> token; // Consume the "moves" token, if any + } else if (token == "fen") + while (is >> token && token != "moves") + fen += token + " "; + else + return; + + try { + pos.setFEN(strip_optional_quotes(fen)); + } catch (const std::exception &e) { + std::cerr << "info string Invalid FEN: " << e.what() << std::endl; + return; + } + + while (is >> token) { + try { + pos.push_uci(token); + } catch (const std::exception &e) { + std::cerr << "info string Invalid move " << token << ": " << e.what() << std::endl; + return; + } + } } timeman::LimitsType parse_limits(std::istream &is) { - timeman::LimitsType limits; - std::string token; - - limits.startTime = timeman::now(); // The search starts as early as possible - - while (is >> token) - if (token == "searchmoves") // Needs to be the last command on the line - while (is >> token) { - std::transform(token.begin(), token.end(), token.begin(), - [](auto c) { return std::tolower(c); }); - limits.searchmoves.push_back(token); - } - else if (token == "wtime") - is >> limits.time[chess::WHITE]; - else if (token == "btime") - is >> limits.time[chess::BLACK]; - else if (token == "winc") - is >> limits.inc[chess::WHITE]; - else if (token == "binc") - is >> limits.inc[chess::BLACK]; - else if (token == "movestogo") - is >> limits.movestogo; - else if (token == "depth") - is >> limits.depth; - else if (token == "nodes") - is >> limits.nodes; - else if (token == "movetime") - is >> limits.movetime; - else if (token == "mate") - is >> limits.mate; - else if (token == "perft") - is >> limits.perft; - else if (token == "infinite") - limits.infinite = 1; - else if (token == "ponder") - ; - - return limits; + timeman::LimitsType limits; + std::string token; + + limits.startTime = timeman::now(); // The search starts as early as possible + + while (is >> token) + if (token == "searchmoves") // Needs to be the last command on the line + while (is >> token) { + std::transform(token.begin(), token.end(), token.begin(), [](auto c) { return std::tolower(c); }); + limits.searchmoves.push_back(token); + } + else if (token == "wtime") + is >> limits.time[chess::WHITE]; + else if (token == "btime") + is >> limits.time[chess::BLACK]; + else if (token == "winc") + is >> limits.inc[chess::WHITE]; + else if (token == "binc") + is >> limits.inc[chess::BLACK]; + else if (token == "movestogo") + is >> limits.movestogo; + else if (token == "depth") + is >> limits.depth; + else if (token == "nodes") + is >> limits.nodes; + else if (token == "movetime") + is >> limits.movetime; + else if (token == "mate") + is >> limits.mate; + else if (token == "perft") + is >> limits.perft; + else if (token == "infinite") + limits.infinite = 1; + else if (token == "ponder") { + } + + return limits; } void handleGo(std::istringstream &ss) { - stop(); - chess::Position copy = pos; + stop(); + chess::Position copy = pos; - searchThread = std::thread([copy, ss = std::move(ss)]() mutable { - search::search(copy, parse_limits(ss)); - }); + searchThread = std::thread([copy, ss = std::move(ss)]() mutable { search::search(copy, parse_limits(ss)); }); } template struct overload : Ts... { - using Ts::operator()...; + using Ts::operator()...; }; template overload(Ts...) -> overload; std::string engine::format_score(const Score &s) { - const auto format = overload{ - [](Score::Mate mate) -> std::string { - auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2; - return std::string("mate ") + std::to_string(m); - }, - [](Score::Tablebase tb) -> std::string { - constexpr int TB_CP = 20000; - return std::string("cp ") + - std::to_string((tb.win ? TB_CP - tb.plies : -TB_CP - tb.plies)); - }, - [](Score::InternalUnits units) -> std::string { - return std::string("cp ") + std::to_string(units.value); - }}; - - return s.visit(format); + const auto format = + overload{ [](Score::Mate mate) -> std::string { + auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2; + return std::string("mate ") + std::to_string(m); + }, + [](Score::Tablebase tb) -> std::string { + constexpr int TB_CP = 20000; + return std::string("cp ") + std::to_string((tb.win ? TB_CP - tb.plies : -TB_CP - tb.plies)); + }, + [](Score::InternalUnits units) -> std::string { return std::string("cp ") + std::to_string(units.value); } }; + + return s.visit(format); } void engine::report(const InfoShort &info) { - std::cout << "info depth " << info.depth << " score " - << format_score(info.score) << std::endl; + std::cout << "info depth " << info.depth << " score " << format_score(info.score) << std::endl; } void engine::report(const InfoFull &info, bool showWDL) { - std::stringstream ss; - - ss << "info"; - ss << " depth " << info.depth // - << " seldepth " << info.selDepth // - << " multipv " << info.multiPV // - << " score " << format_score(info.score); // - - if (!info.bound.empty()) - ss << " " << info.bound; - - if (showWDL) - ss << " wdl " << info.wdl; - - ss << " nodes " << info.nodes // - << " nps " << info.nps // - << " hashfull " << info.hashfull // - << " tbhits " << info.tbHits // - << " time " << info.timeMs // - << " pv " << info.pv; // - - std::cout << ss.str() << std::endl; + std::stringstream ss; + + ss << "info"; + ss << " depth " << info.depth // + << " seldepth " << info.selDepth // + << " multipv " << info.multiPV // + << " score " << format_score(info.score); // + + if (!info.bound.empty()) + ss << " " << info.bound; + + if (showWDL) + ss << " wdl " << info.wdl; + + ss << " nodes " << info.nodes // + << " nps " << info.nps // + << " hashfull " << info.hashfull // + << " tbhits " << info.tbHits // + << " time " << info.timeMs // + << " pv " << info.pv; // + if (!info.extrainfo.empty()) ss << "\ninfo string extras "<> token) { - if (token == "uci") { - std::cout << "id name cppchess_engine\n"; - std::cout << "id author winapiadmin\n"; - std::cout << options << '\n'; - std::cout << "uciok\n"; - break; - } else if (token == "isready") { - std::cout << "readyok\n"; - break; - } else if (token == "position") { - handlePosition(ss); - break; - } else if (token == "go") { - handleGo(ss); - break; // rest belongs to go - } else if (token == "ucinewgame") { - search::tt.clear(); - break; - } else if (token == "stop") { - break; - } else if (token == "quit") { - return; - } else if (token == "setoption") { - options.setoption(ss); - break; - } else if (token == "visualize" || token == "d") { - std::cout << pos << std::endl; - break; - } + std::string line; + pos.setFEN(chess::Position::START_FEN); + + while (std::getline(std::cin, line)) { + std::istringstream ss(line); + std::string token; + stop(); + while (ss >> token) { + if (token == "uci") { + std::cout << "id name cppchess_engine\n"; + std::cout << "id author winapiadmin\n"; + std::cout << options << '\n'; + std::cout << "uciok\n"; + break; + } else if (token == "isready") { + std::cout << "readyok\n"; + break; + } else if (token == "position") { + handlePosition(ss); + break; + } else if (token == "go") { + handleGo(ss); + break; // rest belongs to go + } else if (token == "ucinewgame") { + search::tt.clear(); + break; + } else if (token == "stop") { + break; + } else if (token == "quit") { + return; + } else if (token == "setoption") { + options.setoption(ss); + break; + } else if (token == "visualize" || token == "d") { + std::cout << pos << std::endl; + break; + } else if (token == "eval") { + std::cout << eval::eval(pos) << std::endl; + break; + } + } } - } - stop(); + stop(); } diff --git a/uci.h b/uci.h index 800535d..18d5e17 100644 --- a/uci.h +++ b/uci.h @@ -4,27 +4,28 @@ #include namespace engine { struct InfoShort { - int depth; - Score score; + int depth; + Score score; }; struct InfoFull : InfoShort { - int selDepth; - size_t multiPV; - std::string_view wdl; - std::string_view bound; - size_t timeMs; - size_t nodes; - size_t nps; - size_t tbHits; - std::string_view pv; - int hashfull; + int selDepth; + size_t multiPV; + std::string_view wdl; + std::string_view bound; + size_t timeMs; + size_t nodes; + size_t nps; + size_t tbHits; + std::string pv; + std::string extrainfo; + int hashfull; }; struct InfoIteration { - int depth; - std::string_view currmove; - size_t currmovenumber; + int depth; + std::string currmove; + size_t currmovenumber; }; std::string format_score(const Score &s); void report(const InfoFull &info, bool showWDL = false); diff --git a/ucioption.cpp b/ucioption.cpp index 68fc9e6..3d62ca3 100644 --- a/ucioption.cpp +++ b/ucioption.cpp @@ -28,102 +28,91 @@ namespace engine { -bool CaseInsensitiveLess::operator()(const std::string &s1, - const std::string &s2) const { +bool CaseInsensitiveLess::operator()(const std::string &s1, const std::string &s2) const { - return std::lexicographical_compare( - s1.begin(), s1.end(), s2.begin(), s2.end(), - [](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); }); + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), [](char c1, char c2) { + return std::tolower(c1) < std::tolower(c2); + }); } -void OptionsMap::add_info_listener(InfoListener &&message_func) { - info = std::move(message_func); -} +void OptionsMap::add_info_listener(InfoListener &&message_func) { info = std::move(message_func); } void OptionsMap::setoption(std::istringstream &is) { - std::string token, name, value; + std::string token, name, value; - is >> token; // Consume the "name" token + is >> token; // Consume the "name" token - // Read the option name (can contain spaces) - while (is >> token && token != "value") - name += (name.empty() ? "" : " ") + token; + // Read the option name (can contain spaces) + while (is >> token && token != "value") + name += (name.empty() ? "" : " ") + token; - // Read the option value (can contain spaces) - while (is >> token) - value += (value.empty() ? "" : " ") + token; + // Read the option value (can contain spaces) + while (is >> token) + value += (value.empty() ? "" : " ") + token; - if (options_map.count(name)) - options_map[name] = value; - else - std::cerr << "No such option: " << name << std::endl; + if (options_map.count(name)) + options_map[name] = value; + else + std::cerr << "No such option: " << name << std::endl; } const Option &OptionsMap::operator[](const std::string &name) const { - auto it = options_map.find(name); - assert(it != options_map.end()); - return it->second; + auto it = options_map.find(name); + assert(it != options_map.end()); + return it->second; } // Inits options and assigns idx in the correct printing order void OptionsMap::add(const std::string &name, const Option &option) { - if (!options_map.count(name)) { - static size_t insert_order = 0; + if (!options_map.count(name)) { + static size_t insert_order = 0; - options_map[name] = option; + options_map[name] = option; - options_map[name].parent = this; - options_map[name].idx = insert_order++; - } else { - std::cerr << "Option \"" << name << "\" was already added!" << std::endl; - std::exit(EXIT_FAILURE); - } + options_map[name].parent = this; + options_map[name].idx = insert_order++; + } else { + std::cerr << "Option \"" << name << "\" was already added!" << std::endl; + std::exit(EXIT_FAILURE); + } } -std::size_t OptionsMap::count(const std::string &name) const { - return options_map.count(name); -} +std::size_t OptionsMap::count(const std::string &name) const { return options_map.count(name); } Option::Option(const OptionsMap *map) : parent(map) {} -Option::Option(const char *v, OnChange f) - : type("string"), min(0), max(0), on_change(std::move(f)) { - defaultValue = currentValue = v; +Option::Option(const char *v, OnChange f) : type("string"), min(0), max(0), on_change(std::move(f)) { + defaultValue = currentValue = v; } -Option::Option(bool v, OnChange f) - : type("check"), min(0), max(0), on_change(std::move(f)) { - defaultValue = currentValue = (v ? "true" : "false"); +Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(std::move(f)) { + defaultValue = currentValue = (v ? "true" : "false"); } -Option::Option(OnChange f) - : type("button"), min(0), max(0), on_change(std::move(f)) {} +Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(std::move(f)) {} -Option::Option(int v, int minv, int maxv, OnChange f) - : type("spin"), min(minv), max(maxv), on_change(std::move(f)) { - defaultValue = currentValue = std::to_string(v); +Option::Option(int v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(std::move(f)) { + defaultValue = currentValue = std::to_string(v); } -Option::Option(const char *v, const char *cur, OnChange f) - : type("combo"), min(0), max(0), on_change(std::move(f)) { - defaultValue = v; - currentValue = cur; +Option::Option(const char *v, const char *cur, OnChange f) : type("combo"), min(0), max(0), on_change(std::move(f)) { + defaultValue = v; + currentValue = cur; } Option::operator int() const { - assert(type == "check" || type == "spin"); - return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); + assert(type == "check" || type == "spin"); + return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); } Option::operator std::string() const { - assert(type == "string"); - return currentValue; + assert(type == "string"); + return currentValue; } bool Option::operator==(const char *s) const { - assert(type == "combo"); - return !CaseInsensitiveLess()(currentValue, s) && - !CaseInsensitiveLess()(s, currentValue); + assert(type == "combo"); + return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue); } bool Option::operator!=(const char *s) const { return !(*this == s); } @@ -133,61 +122,58 @@ bool Option::operator!=(const char *s) const { return !(*this == s); } // from the user by console window, so let's check the bounds anyway. Option &Option::operator=(const std::string &v) { - assert(!type.empty()); + assert(!type.empty()); - if ((type != "button" && type != "string" && v.empty()) || - (type == "check" && v != "true" && v != "false") || - (type == "spin" && (std::stoi(v) < min || std::stoi(v) > max))) - return *this; + if ((type != "button" && type != "string" && v.empty()) || (type == "check" && v != "true" && v != "false") || + (type == "spin" && (std::stoi(v) < min || std::stoi(v) > max))) + return *this; + + if (type == "combo") { + OptionsMap comboMap; // To have case insensitive compare + std::string token; + std::istringstream ss(defaultValue); + while (ss >> token) + comboMap.add(token, Option()); + if (!comboMap.count(v) || v == "var") + return *this; + } - if (type == "combo") { - OptionsMap comboMap; // To have case insensitive compare - std::string token; - std::istringstream ss(defaultValue); - while (ss >> token) - comboMap.add(token, Option()); - if (!comboMap.count(v) || v == "var") - return *this; - } - - if (type == "string") - currentValue = v == "" ? "" : v; - else if (type != "button") - currentValue = v; - - if (on_change) { - const auto ret = on_change(*this); - - if (ret && parent != nullptr && parent->info != nullptr) - parent->info(ret); - } - - return *this; + if (type == "string") + currentValue = v == "" ? "" : v; + else if (type != "button") + currentValue = v; + + if (on_change) { + const auto ret = on_change(*this); + + if (ret && parent != nullptr && parent->info != nullptr) + parent->info(ret); + } + + return *this; } std::ostream &operator<<(std::ostream &os, const OptionsMap &om) { - for (size_t idx = 0; idx < om.options_map.size(); ++idx) - for (const auto &it : om.options_map) - if (it.second.idx == idx) { - const Option &o = it.second; - os << "\noption name " << it.first << " type " << o.type; + for (size_t idx = 0; idx < om.options_map.size(); ++idx) + for (const auto &it : om.options_map) + if (it.second.idx == idx) { + const Option &o = it.second; + os << "\noption name " << it.first << " type " << o.type; - if (o.type == "check" || o.type == "combo") - os << " default " << o.defaultValue; + if (o.type == "check" || o.type == "combo") + os << " default " << o.defaultValue; - else if (o.type == "string") { - std::string defaultValue = - o.defaultValue.empty() ? "" : o.defaultValue; - os << " default " << defaultValue; - } + else if (o.type == "string") { + std::string defaultValue = o.defaultValue.empty() ? "" : o.defaultValue; + os << " default " << defaultValue; + } - else if (o.type == "spin") - os << " default " << stoi(o.defaultValue) << " min " << o.min - << " max " << o.max; + else if (o.type == "spin") + os << " default " << stoi(o.defaultValue) << " min " << o.min << " max " << o.max; - break; - } + break; + } - return os; + return os; } } // namespace engine diff --git a/ucioption.h b/ucioption.h index 012bc22..813b4c7 100644 --- a/ucioption.h +++ b/ucioption.h @@ -30,76 +30,76 @@ namespace engine { // Define a custom comparator, because the UCI options should be // case-insensitive struct CaseInsensitiveLess { - bool operator()(const std::string &, const std::string &) const; + bool operator()(const std::string &, const std::string &) const; }; class OptionsMap; // The Option class implements each option as specified by the UCI protocol class Option { -public: - using OnChange = std::function(const Option &)>; - - Option(const OptionsMap *); - Option(OnChange = nullptr); - Option(bool v, OnChange = nullptr); - Option(const char *v, OnChange = nullptr); - Option(int v, int minv, int maxv, OnChange = nullptr); - Option(const char *v, const char *cur, OnChange = nullptr); - - Option &operator=(const std::string &); - operator int() const; - operator std::string() const; - bool operator==(const char *) const; - bool operator!=(const char *) const; - - friend std::ostream &operator<<(std::ostream &, const OptionsMap &); - - int operator<<(const Option &) = delete; - -private: - friend class OptionsMap; - friend class Engine; - friend class Tune; - - std::string defaultValue, currentValue, type; - int min, max; - size_t idx; - OnChange on_change; - const OptionsMap *parent = nullptr; + public: + using OnChange = std::function(const Option &)>; + + Option(const OptionsMap *); + Option(OnChange = nullptr); + Option(bool v, OnChange = nullptr); + Option(const char *v, OnChange = nullptr); + Option(int v, int minv, int maxv, OnChange = nullptr); + Option(const char *v, const char *cur, OnChange = nullptr); + + Option &operator=(const std::string &); + operator int() const; + operator std::string() const; + bool operator==(const char *) const; + bool operator!=(const char *) const; + + friend std::ostream &operator<<(std::ostream &, const OptionsMap &); + + int operator<<(const Option &) = delete; + + private: + friend class OptionsMap; + friend class Engine; + friend class Tune; + + std::string defaultValue, currentValue, type; + int min, max; + size_t idx; + OnChange on_change; + const OptionsMap *parent = nullptr; }; class OptionsMap { -public: - using InfoListener = std::function)>; + public: + using InfoListener = std::function)>; - OptionsMap() = default; - OptionsMap(const OptionsMap &) = delete; - OptionsMap(OptionsMap &&) = delete; - OptionsMap &operator=(const OptionsMap &) = delete; - OptionsMap &operator=(OptionsMap &&) = delete; + OptionsMap() = default; + OptionsMap(const OptionsMap &) = delete; + OptionsMap(OptionsMap &&) = delete; + OptionsMap &operator=(const OptionsMap &) = delete; + OptionsMap &operator=(OptionsMap &&) = delete; - void add_info_listener(InfoListener &&); + void add_info_listener(InfoListener &&); - void setoption(std::istringstream &); + void setoption(std::istringstream &); - const Option &operator[](const std::string &) const; + const Option &operator[](const std::string &) const; - void add(const std::string &, const Option &option); + void add(const std::string &, const Option &option); - std::size_t count(const std::string &) const; + std::size_t count(const std::string &) const; -private: - friend class Engine; - friend class Option; + private: + friend class Engine; + friend class Option; - friend std::ostream &operator<<(std::ostream &, const OptionsMap &); + friend std::ostream &operator<<(std::ostream &, const OptionsMap &); - // The options container is defined as a std::map - using OptionsStore = std::map; + // The options container is defined as a std::map + using OptionsStore = std::map; - OptionsStore options_map; - InfoListener info; + OptionsStore options_map; + InfoListener info; }; } // namespace engine From c8ed2f43c43a19e2fbd736eeb89961a8a2272fff Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Sun, 7 Jun 2026 21:51:32 +0700 Subject: [PATCH 09/29] formats --- eval.cpp | 253 ++++++++++++++++++++++++++++++++++++--------------- movepick.cpp | 14 ++- search.cpp | 181 ++++++++++++++++++++---------------- tt.cpp | 8 +- uci.cpp | 3 +- 5 files changed, 299 insertions(+), 160 deletions(-) diff --git a/eval.cpp b/eval.cpp index 8e26f6a..4595de2 100644 --- a/eval.cpp +++ b/eval.cpp @@ -22,6 +22,7 @@ struct PassedMaskInit { } }; static PassedMaskInit passedMaskInit; +//clang-format off Value tempo = 50; Value PawnValue = 72; Value KnightValue = 372; @@ -53,54 +54,155 @@ Value kqkEdgeWeight = 45; Value krkDistWeight = 4; Value krkEdgeWeight = 17; Value kpkWeight = 17; -Value mgMobilityCnt[7][8] = { { 92, -46, 6, -99, -98, -35, 74, -28 }, { -81, 43, 0, -100, -68, -86, 47, 29 }, { 51, 71, 88, -78, 17, -56, 99, 5 }, { -59, -78, -88, 22, 2, 86, -77, -1 }, { -55, -98, 93, 70, 27, -54, -100, -83 }, { 13, 1, -81, -7, -42, -95, -68, -99 }, { -37, 43, 8, -100, -99, 36, 91, -42 } }; -Value egMobilityCnt[7][8] = { { -75, -98, 51, 26, -10, 68, 2, 10 }, { -65, -80, 95, 79, -43, -16, 100, -99 }, { 63, -25, -74, -41, 100, -37, 4, -85 }, { -66, 40, 41, 46, -65, -75, 29, 78 }, { 12, -53, -91, 23, -100, -66, 40, 73 }, { -28, 5, -46, 25, -13, 11, 87, 33 }, { -89, -4, 28, -27, 7, -1, 26, -98 } }; +Value mgMobilityCnt[7][8] = { + { 92, -46, 6, -99, -98, -35, 74, -28 }, + { -81, 43, 0, -100, -68, -86, 47, 29 }, + { 51, 71, 88, -78, 17, -56, 99, 5 }, + { -59, -78, -88, 22, 2, 86, -77, -1 }, + { -55, -98, 93, 70, 27, -54, -100, -83 }, + { 13, 1, -81, -7, -42, -95, -68, -99 }, + { -37, 43, 8, -100, -99, 36, 91, -42 } +}; +Value egMobilityCnt[7][8] = { + { -75, -98, 51, 26, -10, 68, 2, 10 }, + { -65, -80, 95, 79, -43, -16, 100, -99 }, + { 63, -25, -74, -41, 100, -37, 4, -85 }, + { -66, 40, 41, 46, -65, -75, 29, 78 }, + { 12, -53, -91, 23, -100, -66, 40, 73 }, + { -28, 5, -46, 25, -13, 11, 87, 33 }, + { -89, -4, 28, -27, 7, -1, 26, -98 } +}; Value kingTropismMg[7] = { -29, 17, 7, -15, -49, -14, -1 }; Value kingTropismEg[7] = { -41, 0, -31, -41, 19, 36, 50 }; Value passedBonusMg[8] = { 313, 227, 386, 163, 109, 284, 220, 363 }; Value passedBonusEg[8] = { 380, 350, 394, 388, 149, 394, 190, 316 }; -Value mg_pawn_table[56] = { 114, -495, 41, -368, -477, 412, -158, -276, -129, -129, -237, 437, -158, 121, -2, -160, 163, 459, 425, -298, 52, -296, -496, 333, -390, -473, -314, -319, -357, 348, 127, -347, 175, -346, -398, 155, 362, 477, -147, -418, -477, 311, 191, -489, -414, -352, 479, -89, 499, 265, -325, -291, -55, 491, -499, 470 }; -Value mg_knight_table[64] = { 443, 51, 110, 497, 243, 173, 224, 109, -498, 280, -499, 185, 58, 494, 408, -33, -395, 45, -15, 208, 245, -13, 378, 359, -489, 352, 96, -152, 422, -414, -153, 304, -483, -113, 346, 1, 104, -185, 141, -483, 405, -500, -117, 158, -306, -145, 465, 458, 257, -344, -107, 26, 334, 369, -338, 352, -412, -223, 69, 244, -156, 335, -368, 359 }; -Value mg_bishop_table[64] = { -258, -250, 404, 459, 322, 66, 411, 227, -93, -52, -352, 13, 450, -441, 392, -139, 315, 263, 210, 299, -397, 133, 371, 440, 272, 221, -76, -125, -13, 483, 135, 182, -351, 203, 101, 298, 352, 277, -478, 488, -62, 42, -144, 496, -396, 195, -414, 11, 409, 116, -373, 386, 377, 47, 493, 0, -302, 312, 81, 459, 350, -187, -265, 153 }; -Value mg_rook_table[64] = { -27, -134, -499, 159, 77, -60, 266, 171, 77, -364, -338, -315, -123, 482, -341, 412, 213, 132, 134, 254, 354, -40, -145, -364, 249, 497, -490, 25, -191, -245, 104, 428, 417, 49, 499, -426, -261, 466, 80, -61, -457, 75, 95, 15, 270, -250, -171, 165, 186, 92, 401, 482, -223, -70, 384, 316, -189, 13, 185, -110, 207, -307, -257, 175 }; -Value mg_king_table[64] = { -498, 290, 205, 125, -483, -313, -372, -330, 302, 312, -171, -115, 403, 43, -259, 212, -264, -396, -329, 260, -288, 374, 454, 499, -124, -366, -335, 371, 161, -361, 368, 102, -454, -133, -52, 139, -102, -418, 380, 499, -108, 340, -352, 326, 415, 2, 150, -314, 430, -161, -399, -99, 8, -292, -81, 79, -491, 309, -160, -208, -173, -283, -313, -62 }; -Value mg_queen_table[64] = { -301, -182, 487, 58, 270, 273, 177, -207, -175, 246, 266, 332, -416, -363, 54, -202, 261, -423, -97, 280, -251, -265, 79, 493, 332, -133, 499, -375, -251, 210, 139, -248, 424, 193, -36, -279, 429, 62, 379, 173, 446, 0, -370, 426, -462, 287, 165, -499, 94, -425, 143, -409, 144, 495, 111, 452, 78, 157, -143, -478, 243, -97, -489, 112 }; -Value eg_pawn_table[56] = { -273, -6, 100, 320, -127, 163, -33, 79, 235, 334, 47, 435, -149, -1, -155, -277, -25, 220, 340, -396, 401, 446, -30, 424, 472, -385, 486, -91, 99, -275, 340, -330, -125, 150, -121, -30, -207, -244, -277, 411, 301, -298, 77, -313, -141, -357, 386, 255, -495, 80, -500, 400, -450, 130, -111, -28 }; -Value eg_knight_table[64] = { 371, -385, 222, -192, 139, 46, -343, 59, 366, -360, 248, 384, -63, -425, 225, 414, -333, 396, -421, -99, 10, 367, -368, 500, -500, 270, 41, 168, 154, 327, -161, 457, -368, 79, -265, -457, 94, -294, -234, 348, 233, -471, -360, 380, -228, 3, 314, 415, -327, 200, -91, 251, -390, -294, -187, -335, -29, 177, -341, 197, -37, 143, 217, 384 }; -Value eg_bishop_table[64] = { 31, 57, -369, -271, 267, -495, -141, -276, 239, 405, 217, 34, -391, -36, 122, -427, -35, 406, 286, 52, 497, 162, 262, -147, -251, -470, -258, -171, -63, -203, 296, 497, 90, 444, 422, -241, -499, 62, -312, 299, -345, 309, 168, 142, 161, -170, 138, -116, 359, -45, 214, 191, -360, 163, 433, 386, 182, -181, 348, 258, 415, 375, -233, 214 }; -Value eg_rook_table[64] = { -227, 492, 17, -205, 495, 12, 207, -246, 195, -22, 312, 16, -124, -259, 405, -494, -262, -344, -218, -12, 8, 388, 306, 316, 376, 455, -139, -194, 41, 158, 283, 476, 469, 94, 63, -498, -414, 0, -292, -373, -270, 256, -162, -385, 453, -220, 162, -53, -221, -435, -426, -222, 235, 116, 182, 409, -327, -486, 80, 109, -455, -247, 106, -307 }; -Value eg_king_table[64] = { -319, -481, 495, -290, 1, 426, -347, -112, 423, -89, 500, -432, 54, -223, 148, 118, -292, -351, -205, 195, 377, -185, -296, -176, -73, 247, -491, -411, -232, 106, -295, 326, 60, 500, -488, 218, 207, -409, 91, -390, 35, -249, 111, -179, -169, -182, -372, -330, 425, 455, 457, 500, -494, -442, 24, 170, 367, 353, -297, 97, -137, 125, 221, -32 }; -Value eg_queen_table[64] = { -326, 119, 358, 91, -311, -289, 373, 329, 44, -83, 162, -470, -470, -2, 45, -486, 438, 39, -493, 371, 493, -278, 338, -416, 360, 499, 357, -145, -500, 398, -210, -199, 218, -22, -367, 69, 144, 488, -55, -71, 499, -36, -262, -351, -407, 291, -497, -412, -164, -46, 186, 323, -56, -416, -129, 394, 71, -343, 183, -360, 91, -295, -180, 88 }; +Value mg_pawn_table[56] = { 114, -495, 41, -368, -477, 412, -158, -276, -129, -129, -237, 437, -158, 121, + -2, -160, 163, 459, 425, -298, 52, -296, -496, 333, -390, -473, -314, -319, + -357, 348, 127, -347, 175, -346, -398, 155, 362, 477, -147, -418, -477, 311, + 191, -489, -414, -352, 479, -89, 499, 265, -325, -291, -55, 491, -499, 470 }; +Value mg_knight_table[64] = { 443, 51, 110, 497, 243, 173, 224, 109, -498, 280, -499, 185, 58, 494, 408, -33, + -395, 45, -15, 208, 245, -13, 378, 359, -489, 352, 96, -152, 422, -414, -153, 304, + -483, -113, 346, 1, 104, -185, 141, -483, 405, -500, -117, 158, -306, -145, 465, 458, + 257, -344, -107, 26, 334, 369, -338, 352, -412, -223, 69, 244, -156, 335, -368, 359 }; +Value mg_bishop_table[64] = { -258, -250, 404, 459, 322, 66, 411, 227, -93, -52, -352, 13, 450, -441, 392, -139, + 315, 263, 210, 299, -397, 133, 371, 440, 272, 221, -76, -125, -13, 483, 135, 182, + -351, 203, 101, 298, 352, 277, -478, 488, -62, 42, -144, 496, -396, 195, -414, 11, + 409, 116, -373, 386, 377, 47, 493, 0, -302, 312, 81, 459, 350, -187, -265, 153 }; +Value mg_rook_table[64] = { -27, -134, -499, 159, 77, -60, 266, 171, 77, -364, -338, -315, -123, 482, -341, 412, + 213, 132, 134, 254, 354, -40, -145, -364, 249, 497, -490, 25, -191, -245, 104, 428, + 417, 49, 499, -426, -261, 466, 80, -61, -457, 75, 95, 15, 270, -250, -171, 165, + 186, 92, 401, 482, -223, -70, 384, 316, -189, 13, 185, -110, 207, -307, -257, 175 }; +Value mg_king_table[64] = { -498, 290, 205, 125, -483, -313, -372, -330, 302, 312, -171, -115, 403, 43, -259, 212, + -264, -396, -329, 260, -288, 374, 454, 499, -124, -366, -335, 371, 161, -361, 368, 102, + -454, -133, -52, 139, -102, -418, 380, 499, -108, 340, -352, 326, 415, 2, 150, -314, + 430, -161, -399, -99, 8, -292, -81, 79, -491, 309, -160, -208, -173, -283, -313, -62 }; +Value mg_queen_table[64] = { -301, -182, 487, 58, 270, 273, 177, -207, -175, 246, 266, 332, -416, -363, 54, -202, + 261, -423, -97, 280, -251, -265, 79, 493, 332, -133, 499, -375, -251, 210, 139, -248, + 424, 193, -36, -279, 429, 62, 379, 173, 446, 0, -370, 426, -462, 287, 165, -499, + 94, -425, 143, -409, 144, 495, 111, 452, 78, 157, -143, -478, 243, -97, -489, 112 }; +Value eg_pawn_table[56] = { -273, -6, 100, 320, -127, 163, -33, 79, 235, 334, 47, 435, -149, -1, + -155, -277, -25, 220, 340, -396, 401, 446, -30, 424, 472, -385, 486, -91, + 99, -275, 340, -330, -125, 150, -121, -30, -207, -244, -277, 411, 301, -298, + 77, -313, -141, -357, 386, 255, -495, 80, -500, 400, -450, 130, -111, -28 }; +Value eg_knight_table[64] = { 371, -385, 222, -192, 139, 46, -343, 59, 366, -360, 248, 384, -63, -425, 225, 414, + -333, 396, -421, -99, 10, 367, -368, 500, -500, 270, 41, 168, 154, 327, -161, 457, + -368, 79, -265, -457, 94, -294, -234, 348, 233, -471, -360, 380, -228, 3, 314, 415, + -327, 200, -91, 251, -390, -294, -187, -335, -29, 177, -341, 197, -37, 143, 217, 384 }; +Value eg_bishop_table[64] = { 31, 57, -369, -271, 267, -495, -141, -276, 239, 405, 217, 34, -391, -36, 122, -427, + -35, 406, 286, 52, 497, 162, 262, -147, -251, -470, -258, -171, -63, -203, 296, 497, + 90, 444, 422, -241, -499, 62, -312, 299, -345, 309, 168, 142, 161, -170, 138, -116, + 359, -45, 214, 191, -360, 163, 433, 386, 182, -181, 348, 258, 415, 375, -233, 214 }; +Value eg_rook_table[64] = { -227, 492, 17, -205, 495, 12, 207, -246, 195, -22, 312, 16, -124, -259, 405, -494, + -262, -344, -218, -12, 8, 388, 306, 316, 376, 455, -139, -194, 41, 158, 283, 476, + 469, 94, 63, -498, -414, 0, -292, -373, -270, 256, -162, -385, 453, -220, 162, -53, + -221, -435, -426, -222, 235, 116, 182, 409, -327, -486, 80, 109, -455, -247, 106, -307 }; +Value eg_king_table[64] = { -319, -481, 495, -290, 1, 426, -347, -112, 423, -89, 500, -432, 54, -223, 148, 118, + -292, -351, -205, 195, 377, -185, -296, -176, -73, 247, -491, -411, -232, 106, -295, 326, + 60, 500, -488, 218, 207, -409, 91, -390, 35, -249, 111, -179, -169, -182, -372, -330, + 425, 455, 457, 500, -494, -442, 24, 170, 367, 353, -297, 97, -137, 125, 221, -32 }; +Value eg_queen_table[64] = { -326, 119, 358, 91, -311, -289, 373, 329, 44, -83, 162, -470, -470, -2, 45, -486, + 438, 39, -493, 371, 493, -278, 338, -416, 360, 499, 357, -145, -500, 398, -210, -199, + 218, -22, -367, 69, 144, 488, -55, -71, 499, -36, -262, -351, -407, 291, -497, -412, + -164, -46, 186, 323, -56, -416, -129, 394, 71, -343, 183, -360, 91, -295, -180, 88 }; -Value *mgPst[] = { nullptr, mg_pawn_table, mg_knight_table, mg_bishop_table, - mg_rook_table, mg_queen_table, mg_king_table }; +//clang-format on +Value *mgPst[] = { nullptr, mg_pawn_table, mg_knight_table, mg_bishop_table, mg_rook_table, mg_queen_table, mg_king_table }; -Value *egPst[] = { nullptr, eg_pawn_table, eg_knight_table, eg_bishop_table, - eg_rook_table, eg_queen_table, eg_king_table }; +Value *egPst[] = { nullptr, eg_pawn_table, eg_knight_table, eg_bishop_table, eg_rook_table, eg_queen_table, eg_king_table }; // tuning slop here -TUNE(SetRange(0, 50), tempo, SetRange(50, 200), PawnValue, SetRange(200, 500), KnightValue, - SetRange(200, 500), BishopValue, SetRange(300, 700), RookValue, SetRange(600, 1400), QueenValue); +TUNE(SetRange(0, 50), + tempo, + SetRange(50, 200), + PawnValue, + SetRange(200, 500), + KnightValue, + SetRange(200, 500), + BishopValue, + SetRange(300, 700), + RookValue, + SetRange(600, 1400), + QueenValue); TUNE(SetRange(-100, 100), mgMobilityCnt, egMobilityCnt); TUNE(SetRange(0, 50), fianchettoBonus, SetRange(0, 150), trappedBishopPenalty); TUNE(SetRange(-50, 50), kingTropismMg, kingTropismEg); -TUNE(SetRange(0, 50), centerWeight, SetRange(0, 30), mopUpKingDistWeight, - SetRange(0, 50), mopUpEdgeDistWeight, SetRange(0, 100), spaceWeight); +TUNE(SetRange(0, 50), + centerWeight, + SetRange(0, 30), + mopUpKingDistWeight, + SetRange(0, 50), + mopUpEdgeDistWeight, + SetRange(0, 100), + spaceWeight); TUNE(SetRange(0, 100), bishopPairMg, SetRange(0, 100), bishopPairEg); -TUNE(SetRange(0, 50), rookOpenFileMg, SetRange(0, 50), rookOpenFileEg, - SetRange(0, 50), rookSemiOpenFileMg, SetRange(0, 50), rookSemiOpenFileEg); -TUNE(SetRange(0, 100), doubledPawnMg, SetRange(0, 100), doubledPawnEg, - SetRange(0, 100), isolatedPawnMg, SetRange(0, 100), isolatedPawnEg); +TUNE(SetRange(0, 50), + rookOpenFileMg, + SetRange(0, 50), + rookOpenFileEg, + SetRange(0, 50), + rookSemiOpenFileMg, + SetRange(0, 50), + rookSemiOpenFileEg); +TUNE(SetRange(0, 100), + doubledPawnMg, + SetRange(0, 100), + doubledPawnEg, + SetRange(0, 100), + isolatedPawnMg, + SetRange(0, 100), + isolatedPawnEg); TUNE(SetRange(0, 400), passedBonusMg, passedBonusEg); -TUNE(SetRange(0, 30), kingShelterBaseMg, SetRange(0, 30), kingShelterBaseEg, - SetRange(0, 10), kingShelterDecayMg, SetRange(0, 10), kingShelterDecayEg); -TUNE(SetRange(-500, 500), mg_pawn_table, mg_knight_table, mg_bishop_table, - mg_rook_table, mg_king_table, mg_queen_table, - eg_pawn_table, eg_knight_table, eg_bishop_table, - eg_rook_table, eg_king_table, eg_queen_table); -TUNE(SetRange(0, 50), kqkDistWeight, SetRange(0, 50), kqkEdgeWeight, - SetRange(0, 50), krkDistWeight, SetRange(0, 50), krkEdgeWeight, - SetRange(0, 50), kpkWeight); +TUNE(SetRange(0, 30), + kingShelterBaseMg, + SetRange(0, 30), + kingShelterBaseEg, + SetRange(0, 10), + kingShelterDecayMg, + SetRange(0, 10), + kingShelterDecayEg); +TUNE(SetRange(-500, 500), + mg_pawn_table, + mg_knight_table, + mg_bishop_table, + mg_rook_table, + mg_king_table, + mg_queen_table, + eg_pawn_table, + eg_knight_table, + eg_bishop_table, + eg_rook_table, + eg_king_table, + eg_queen_table); +TUNE(SetRange(0, 50), + kqkDistWeight, + SetRange(0, 50), + kqkEdgeWeight, + SetRange(0, 50), + krkDistWeight, + SetRange(0, 50), + krkEdgeWeight, + SetRange(0, 50), + kpkWeight); Value eval(const chess::Board &board) { constexpr int KnightPhase = 1; @@ -128,15 +230,23 @@ Value eval(const chess::Board &board) { egScore += _sign * egPst[pt][_sq]; mgScore += _sign * piece_value(pt); egScore += _sign * piece_value(pt); - if (pt == KNIGHT) phase += KnightPhase; - else if (pt == BISHOP) phase += BishopPhase; - else if (pt == ROOK) phase += RookPhase; - else if (pt == QUEEN) phase += QueenPhase; + if (pt == KNIGHT) + phase += KnightPhase; + else if (pt == BISHOP) + phase += BishopPhase; + else if (pt == ROOK) + phase += RookPhase; + else if (pt == QUEEN) + phase += QueenPhase; Bitboard attacks = 0; - if (pt == KNIGHT) attacks = chess::attacks::knight(sq); - else if (pt == BISHOP) attacks = chess::attacks::bishop(sq, occ2); - else if (pt == ROOK) attacks = chess::attacks::rook(sq, occ2); - else if (pt == QUEEN) attacks = chess::attacks::queen(sq, occ2); + if (pt == KNIGHT) + attacks = chess::attacks::knight(sq); + else if (pt == BISHOP) + attacks = chess::attacks::bishop(sq, occ2); + else if (pt == ROOK) + attacks = chess::attacks::rook(sq, occ2); + else if (pt == QUEEN) + attacks = chess::attacks::queen(sq, occ2); attacks &= ~board.us(pc); int mobility = popcount(attacks); int clampedMobility = std::min(mobility, 7); @@ -166,8 +276,8 @@ Value eval(const chess::Board &board) { Square fianchettoSq[2] = { relative_square(c, SQ_B2), relative_square(c, SQ_G2) }; Square pawnSq[2] = { relative_square(c, SQ_B3), relative_square(c, SQ_G3) }; for (int i = 0; i < 2; i++) { - if (board.at(fianchettoSq[i]) == BISHOP && board.at(fianchettoSq[i]) == c - && board.at(pawnSq[i]) == PAWN && board.at(pawnSq[i]) == c) { + if (board.at(fianchettoSq[i]) == BISHOP && board.at(fianchettoSq[i]) == c && + board.at(pawnSq[i]) == PAWN && board.at(pawnSq[i]) == c) { mgScore += s * fianchettoBonus; egScore += s * fianchettoBonus; } @@ -180,8 +290,7 @@ Value eval(const chess::Board &board) { Square trapSq[2] = { relative_square(c, SQ_A2), relative_square(c, SQ_H2) }; Square kingAdj[2] = { relative_square(c, SQ_B1), relative_square(c, SQ_G1) }; for (int i = 0; i < 2; i++) { - if (board.at(trapSq[i]) == BISHOP && board.at(trapSq[i]) == c - && board.kingSq(c) == kingAdj[i]) { + if (board.at(trapSq[i]) == BISHOP && board.at(trapSq[i]) == c && board.kingSq(c) == kingAdj[i]) { mgScore -= s * trappedBishopPenalty; egScore -= s * trappedBishopPenalty; } @@ -218,8 +327,8 @@ Value eval(const chess::Board &board) { Bitboard pawns = pawnBB[c]; Bitboard enemyPawns = pawnBB[~c]; - bool fileHasPawn[8] = {false}; - int fileCount[8] = {0}; + bool fileHasPawn[8] = { false }; + int fileCount[8] = { 0 }; Bitboard tmp = pawns; while (tmp) { Square sq = Square(pop_lsb(tmp)); @@ -241,8 +350,10 @@ Value eval(const chess::Board &board) { Rank relRank = relative_rank(c, sq); bool isolated = true; - if (f > FILE_A && fileHasPawn[f - 1]) isolated = false; - if (f < FILE_H && fileHasPawn[f + 1]) isolated = false; + if (f > FILE_A && fileHasPawn[f - 1]) + isolated = false; + if (f < FILE_H && fileHasPawn[f + 1]) + isolated = false; if (isolated) { mgScore -= s * isolatedPawnMg; egScore -= s * isolatedPawnEg; @@ -268,9 +379,8 @@ Value eval(const chess::Board &board) { for (Color c : { WHITE, BLACK }) { int s = (c == WHITE) ? 1 : -1; Bitboard safe = ~pawnAtks[~c]; - Bitboard enemyCamp = c == WHITE - ? (attacks::MASK_RANK[4] | attacks::MASK_RANK[5] | attacks::MASK_RANK[6]) - : (attacks::MASK_RANK[3] | attacks::MASK_RANK[2] | attacks::MASK_RANK[1]); + Bitboard enemyCamp = c == WHITE ? (attacks::MASK_RANK[4] | attacks::MASK_RANK[5] | attacks::MASK_RANK[6]) + : (attacks::MASK_RANK[3] | attacks::MASK_RANK[2] | attacks::MASK_RANK[1]); int space = popcount(pawnAtks[c] & safe & enemyCamp); mgScore += s * space * spaceWeight; egScore += s * space * spaceWeight; @@ -327,9 +437,8 @@ Value eval(const chess::Board &board) { } // KRK: rook vs lone king - if (board.count(ROOK, c) >= 1 && oppCount == 1 - && board.count(QUEEN, c) == 0 && board.count(BISHOP, c) == 0 - && board.count(KNIGHT, c) == 0 && board.count(PAWN, c) == 0) { + if (board.count(ROOK, c) >= 1 && oppCount == 1 && board.count(QUEEN, c) == 0 && board.count(BISHOP, c) == 0 && + board.count(KNIGHT, c) == 0 && board.count(PAWN, c) == 0) { int kingDist = std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); File ef = file_of(oppKing); @@ -340,28 +449,26 @@ Value eval(const chess::Board &board) { } // KPK: king + pawn(s) vs lone king - if (board.count(PAWN, c) >= 1 && oppCount == 1 - && board.count(QUEEN, c) == 0 && board.count(ROOK, c) == 0 - && board.count(BISHOP, c) == 0 && board.count(KNIGHT, c) == 0) { + if (board.count(PAWN, c) >= 1 && oppCount == 1 && board.count(QUEEN, c) == 0 && board.count(ROOK, c) == 0 && + board.count(BISHOP, c) == 0 && board.count(KNIGHT, c) == 0) { Bitboard ps = pawnBB[c]; while (ps) { Square psq = Square(pop_lsb(ps)); Rank pr = relative_rank(c, psq); - if (pr < RANK_4) continue; + if (pr < RANK_4) + continue; File pf = file_of(psq); Square promSq = make_sq(pf, c == WHITE ? RANK_8 : RANK_1); int pawnDist = (c == WHITE) ? (7 - (int)rank_of(psq)) : (int)rank_of(psq); - int pkDist = std::max(std::abs((int)file_of(oppKing) - (int)pf), - std::abs((int)rank_of(oppKing) - (int)rank_of(promSq))); + int pkDist = + std::max(std::abs((int)file_of(oppKing) - (int)pf), std::abs((int)rank_of(oppKing) - (int)rank_of(promSq))); int mkDist = std::max(std::abs((int)file_of(myKing) - (int)file_of(psq)), std::abs((int)rank_of(myKing) - (int)rank_of(psq))); - bool winning = pkDist > pawnDist - || mkDist <= pawnDist + 1 - || (pr >= RANK_6 && file_of(myKing) == pf - && ((c == WHITE && rank_of(myKing) >= RANK_6) - || (c == BLACK && rank_of(myKing) <= RANK_3))); + bool winning = pkDist > pawnDist || mkDist <= pawnDist + 1 || + (pr >= RANK_6 && file_of(myKing) == pf && + ((c == WHITE && rank_of(myKing) >= RANK_6) || (c == BLACK && rank_of(myKing) <= RANK_3))); if ((pf == FILE_A || pf == FILE_H) && oppKing == promSq) winning = false; @@ -391,9 +498,9 @@ Value eval(const chess::Board &board) { int pawnCount = board.count(); // KBKB same-colored bishops (no pawns) - drawn - if (totalPieces == 4 && pawnCount == 0 && board.count() == 2 - && board.count() == 0 && board.count() == 0 && board.count() == 0 - && board.count(BISHOP, WHITE) == 1 && board.count(BISHOP, BLACK) == 1) { + if (totalPieces == 4 && pawnCount == 0 && board.count() == 2 && board.count() == 0 && + board.count() == 0 && board.count() == 0 && board.count(BISHOP, WHITE) == 1 && + board.count(BISHOP, BLACK) == 1) { Bitboard wbBB = board.pieces(BISHOP, WHITE); Bitboard bbBB = board.pieces(BISHOP, BLACK); Square wb = Square(pop_lsb(wbBB)); @@ -403,8 +510,8 @@ Value eval(const chess::Board &board) { } // KNNK (no pawns) - drawn - if (totalPieces == 4 && pawnCount == 0 && board.count() == 2 - && board.count() == 0 && board.count() == 0 && board.count() == 0) + if (totalPieces == 4 && pawnCount == 0 && board.count() == 2 && board.count() == 0 && + board.count() == 0 && board.count() == 0) return 0; phase = (phase * 256 + TotalPhase / 2) / TotalPhase; diff --git a/movepick.cpp b/movepick.cpp index 6956bf2..da896c3 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -8,14 +8,18 @@ namespace engine::movepick { static Bitboard att(PieceType pt, Square sq, Bitboard occ) { switch (pt) { - case BISHOP: return attacks::bishop(sq, occ); - case ROOK: return attacks::rook(sq, occ); - case QUEEN: return attacks::queen(sq, occ); - default: return 0; + case BISHOP: + return attacks::bishop(sq, occ); + case ROOK: + return attacks::rook(sq, occ); + case QUEEN: + return attacks::queen(sq, occ); + default: + return 0; } } -Value see(Board& board, Move move) { +Value see(Board &board, Move move) { if (move.type_of() == EN_PASSANT) return piece_value(PAWN); PieceType captured = board.at(move.to()); diff --git a/search.cpp b/search.cpp index c37f470..50062ff 100644 --- a/search.cpp +++ b/search.cpp @@ -48,7 +48,8 @@ Value qsearch(Board &board, Value alpha, Value beta, search::Session &session, i session.nodes++; session.qnodes++; session.seldepth = std::max(session.seldepth, ply); - if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || stopSearch.load(std::memory_order_relaxed)) + if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || + stopSearch.load(std::memory_order_relaxed)) return VALUE_NONE; bool inCheck = board.checkers(); @@ -63,15 +64,15 @@ Value qsearch(Board &board, Value alpha, Value beta, search::Session &session, i session.ttHits++; Value ttScore = value_from_tt(entry->getScore(), ply, board.rule50_count()); - if (entry->getFlag() == TTFlag::EXACT){ + if (entry->getFlag() == TTFlag::EXACT) { session.ttCutoffs++; return ttScore; } - if (entry->getFlag() == TTFlag::LOWERBOUND && ttScore >= beta){ + if (entry->getFlag() == TTFlag::LOWERBOUND && ttScore >= beta) { session.ttCutoffs++; return ttScore; } - if (entry->getFlag() == TTFlag::UPPERBOUND && ttScore <= alpha){ + if (entry->getFlag() == TTFlag::UPPERBOUND && ttScore <= alpha) { session.ttCutoffs++; return ttScore; } @@ -90,7 +91,8 @@ Value qsearch(Board &board, Value alpha, Value beta, search::Session &session, i if (inCheck) board.legals(moves); - else board.legals(moves); + else + board.legals(moves); Value best = -VALUE_INFINITE; Value standPat = VALUE_NONE; @@ -119,9 +121,8 @@ Value qsearch(Board &board, Value alpha, Value beta, search::Session &session, i if (!isCapture && !givesCheck) continue; if (isCapture && !givesCheck && move.type_of() != PROMOTION) { - Value capturedValue = move.type_of() == EN_PASSANT - ? eval::piece_value(PAWN) - : eval::piece_value(board.at(move.to())); + Value capturedValue = + move.type_of() == EN_PASSANT ? eval::piece_value(PAWN) : eval::piece_value(board.at(move.to())); if (standPat + capturedValue + 200 < alpha) continue; if (movepick::see(board, move) < 0) @@ -147,7 +148,7 @@ Value qsearch(Board &board, Value alpha, Value beta, search::Session &session, i if (score == VALUE_NONE) return VALUE_NONE; - if (score > best){ + if (score > best) { ttMove = move; best = score; } @@ -158,30 +159,26 @@ Value qsearch(Board &board, Value alpha, Value beta, search::Session &session, i movesSearched++; } - TTFlag flag = best >= beta ? TTFlag::LOWERBOUND - : best <= alphaOrig ? TTFlag::UPPERBOUND - : TTFlag::EXACT; - tt.store(board.hash(), - ttMove, - value_to_tt(best, ply), - 0, - flag); + TTFlag flag = best >= beta ? TTFlag::LOWERBOUND : best <= alphaOrig ? TTFlag::UPPERBOUND : TTFlag::EXACT; + tt.store(board.hash(), ttMove, value_to_tt(best, ply), 0, flag); return best; } -Value doSearch(Board &board, int depth, Value alpha, Value beta, search::Session &session, int ply = 0, Move prevMove = Move::none()) { +Value doSearch( + Board &board, int depth, Value alpha, Value beta, search::Session &session, int ply = 0, Move prevMove = Move::none()) { session.nodes++; session.seldepth = std::max(session.seldepth, ply); if (ply >= MAX_PLY - 1) return board.checkers() ? -MATE(ply) : eval::eval(board); if (depth <= 0) return qsearch(board, alpha, beta, session, ply); - if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || stopSearch.load(std::memory_order_relaxed)) + if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || + stopSearch.load(std::memory_order_relaxed)) return VALUE_NONE; bool inCheck = board.checkers(); alpha = std::max(-MATE(ply), alpha); - beta = std::min(MATE(ply + 1), beta); + beta = std::min(MATE(ply + 1), beta); if (alpha >= beta) return alpha; @@ -224,8 +221,8 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, search::Session } // Reverse futility pruning: if eval is well above beta, prune - if (!inCheck && ply > 0 && depth <= 3 && staticEval - 150 * depth >= beta - && !is_win(beta) && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) + if (!inCheck && ply > 0 && depth <= 3 && staticEval - 150 * depth >= beta && !is_win(beta) && + std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) return staticEval; // Razoring: if eval is far below alpha, try qsearch to verify @@ -233,7 +230,8 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, search::Session Value razorMargin = Value(256 + 100 * depth); if (staticEval + razorMargin < alpha) { Value v = qsearch(board, alpha - 1, alpha, session, ply); - if (v == VALUE_NONE) return VALUE_NONE; + if (v == VALUE_NONE) + return VALUE_NONE; if (v < alpha && std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) return v; } @@ -245,9 +243,10 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, search::Session board.doNullMove(); Value score = doSearch(board, depth - 1 - R, -beta, -beta + 1, session, ply + 1, Move::none()); board.undoMove(); - if (score == VALUE_NONE) return VALUE_NONE; + if (score == VALUE_NONE) + return VALUE_NONE; score = -score; - if (score >= beta){ + if (score >= beta) { session.nullCutoffs++; return score; } @@ -258,40 +257,36 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, search::Session Value probCutMargin = Value(200 + 100 * (depth - 5)); if (staticEval >= beta + probCutMargin) { Value v = doSearch(board, depth - 2, beta - 1, beta, session, ply, prevMove); - if (v == VALUE_NONE) return VALUE_NONE; - if (v >= beta) return v; + if (v == VALUE_NONE) + return VALUE_NONE; + if (v >= beta) + return v; } } // Existing static null-move / futility pruning - if (!inCheck && staticEval < alpha - 512 - 293 * depth * depth) - { + if (!inCheck && staticEval < alpha - 512 - 293 * depth * depth) { Value value = qsearch(board, alpha - 1, alpha, session, ply); - if (value == VALUE_NONE) return VALUE_NONE; + if (value == VALUE_NONE) + return VALUE_NONE; if (value < alpha && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) return value; } // Tablebase probing (unchanged) - if (ply != 0){ - if (popcount(board.occ()) <= 7 && board.castlingRights()==NO_CASTLING) - { + if (ply != 0) { + if (popcount(board.occ()) <= 7 && board.castlingRights() == NO_CASTLING) { int wdl = engine::tb::probe_wdl(board); - if (wdl != engine::tb::TB_ERROR){ + if (wdl != engine::tb::TB_ERROR) { session.tbHits++; int drawScore = 1; Value tbValue = VALUE_TB - ply; - Value value = wdl < -drawScore ? -tbValue - : wdl > drawScore ? tbValue - : VALUE_DRAW + 2 * wdl * drawScore; - TTFlag b = wdl < -drawScore ? TTFlag::UPPERBOUND - : wdl > drawScore ? TTFlag::LOWERBOUND - : TTFlag::EXACT; - if (b == TTFlag::EXACT || (b == TTFlag::LOWERBOUND ? value >= beta : value <= alpha)) - { + Value value = wdl < -drawScore ? -tbValue : wdl > drawScore ? tbValue : VALUE_DRAW + 2 * wdl * drawScore; + TTFlag b = wdl < -drawScore ? TTFlag::UPPERBOUND : wdl > drawScore ? TTFlag::LOWERBOUND : TTFlag::EXACT; + if (b == TTFlag::EXACT || (b == TTFlag::LOWERBOUND ? value >= beta : value <= alpha)) { tt.store(hash, Move::none(), value_to_tt(value, ply), std::min(MAX_PLY - 1, depth + 6), b); return value; } @@ -320,23 +315,29 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, search::Session // Singular Extension: extend TT move when it dominates all others int singularExt = 0; - if (depth >= 12 && ttMove.is_ok() && !inCheck && ply > 0 && entry - && entry->getDepth() >= depth - 4 && entry->getFlag() != TTFlag::UPPERBOUND - && alpha != beta - 1 && !is_win(beta)) { + if (depth >= 12 && ttMove.is_ok() && !inCheck && ply > 0 && entry && entry->getDepth() >= depth - 4 && + entry->getFlag() != TTFlag::UPPERBOUND && alpha != beta - 1 && !is_win(beta)) { Value sBeta = std::max(staticEval - 2 * depth, Value(-VALUE_MATE)); int r = std::max(2, depth / 4); if (moves.size() <= 5) { bool singular = true; for (size_t si = 0; si < moves.size() && singular; ++si) { - if (moves[si] == ttMove) continue; + if (moves[si] == ttMove) + continue; board.doMove(moves[si]); Value v = doSearch(board, r, sBeta - 1, sBeta, session, ply + 1, moves[si]); board.undoMove(); - if (v == VALUE_NONE) { board.undoMove(); singular = false; break; } + if (v == VALUE_NONE) { + board.undoMove(); + singular = false; + break; + } v = -v; - if (v >= sBeta) singular = false; + if (v >= sBeta) + singular = false; } - if (singular) singularExt = 1; + if (singular) + singularExt = 1; } } @@ -357,14 +358,13 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, search::Session } // Late move pruning at very shallow depths - if (!inCheck && !isCapture && !givesCheck && depth <= 2 && movesSearched > 3 + 2 * depth - && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) + if (!inCheck && !isCapture && !givesCheck && depth <= 2 && movesSearched > 3 + 2 * depth && + std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) continue; // SEE pruning for losing captures at shallow depths - if (!inCheck && isCapture && !givesCheck && depth <= 2 && movesSearched > 0 - && move.type_of() != PROMOTION && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) - { + if (!inCheck && isCapture && !givesCheck && depth <= 2 && movesSearched > 0 && move.type_of() != PROMOTION && + std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) { if (movepick::see(board, move) < 0) continue; } @@ -375,14 +375,18 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, search::Session if (!isCapture && !givesCheck) { reduction = 1 + movesSearched / 5 + depth / 7; int history = session.historyHeuristic[(int)move.from()][(int)move.to()]; - if (history > 0) reduction--; - else if (history < 0) reduction++; + if (history > 0) + reduction--; + else if (history < 0) + reduction++; if (move == session.killerMoves[ply][0] || move == session.killerMoves[ply][1]) reduction--; if (prevMove.is_ok() && move == session.counterMoves[prevMove.from_to()]) reduction--; - if (staticEval + 50 < alphaOrig) reduction++; - else if (staticEval - 50 >= alphaOrig) reduction--; + if (staticEval + 50 < alphaOrig) + reduction++; + else if (staticEval - 50 >= alphaOrig) + reduction--; } else if (movesSearched >= 6) { reduction = 1 + movesSearched / 8; } @@ -390,9 +394,12 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, search::Session } int ext = 0; - if (singularExt && movesSearched == 0) ext = 1; - if (ext == 0 && moves.size() == 1) ext = 1; - if (ext == 0 && isCapture && movesSearched == 0) ext = 1; + if (singularExt && movesSearched == 0) + ext = 1; + if (ext == 0 && moves.size() == 1) + ext = 1; + if (ext == 0 && isCapture && movesSearched == 0) + ext = 1; board.doMove(move); @@ -400,17 +407,29 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, search::Session if (movesSearched == 0 || reduction == 0) { score = doSearch(board, depth - 1 + ext, -beta, -alpha, session, ply + 1, move); - if (score == VALUE_NONE) { board.undoMove(); score = -VALUE_INFINITE; break; } + if (score == VALUE_NONE) { + board.undoMove(); + score = -VALUE_INFINITE; + break; + } score = -score; } else { int d = depth - 1 - reduction + ext; score = doSearch(board, d, -alpha - 1, -alpha, session, ply + 1, move); - if (score == VALUE_NONE) { board.undoMove(); score = -VALUE_INFINITE; break; } + if (score == VALUE_NONE) { + board.undoMove(); + score = -VALUE_INFINITE; + break; + } score = -score; if (score > alpha && reduction) { session.lmrResearches++; score = doSearch(board, depth - 1 + ext, -beta, -alpha, session, ply + 1, move); - if (score == VALUE_NONE) { board.undoMove(); score = -VALUE_INFINITE; break; } + if (score == VALUE_NONE) { + board.undoMove(); + score = -VALUE_INFINITE; + break; + } score = -score; } } @@ -447,13 +466,12 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, search::Session break; } - if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || stopSearch.load(std::memory_order_relaxed)) + if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || + stopSearch.load(std::memory_order_relaxed)) return VALUE_NONE; } if (maxScore != -VALUE_INFINITE) { - TTFlag flag = maxScore >= beta ? TTFlag::LOWERBOUND - : maxScore <= alphaOrig ? TTFlag::UPPERBOUND - : TTFlag::EXACT; + TTFlag flag = maxScore >= beta ? TTFlag::LOWERBOUND : maxScore <= alphaOrig ? TTFlag::UPPERBOUND : TTFlag::EXACT; tt.store(hash, session.pv[ply][0], value_to_tt(maxScore, ply), depth, flag); } @@ -465,22 +483,30 @@ std::string extract_pv(const chess::Board &root, int maxPly) { uint64_t cycle[64]{}; for (int ply = 0; ply < maxPly; ply++) { TTEntry *e = search::tt.lookup(pos.hash()); - if (!e) break; + if (!e) + break; chess::Move m(e->getMove()); - if (!m.is_ok()) break; + if (!m.is_ok()) + break; uint64_t h = pos.hash(); int idx = (h >> 6) & 0x3F; uint64_t bit = 1ULL << (h & 0x3F); - if (cycle[idx] & bit) break; + if (cycle[idx] & bit) + break; cycle[idx] |= bit; chess::Movelist ml; pos.legals(ml); bool legal = false; for (size_t i = 0; i < ml.size(); i++) - if (ml[i] == m) { legal = true; break; } - if (!legal) break; + if (ml[i] == m) { + legal = true; + break; + } + if (!legal) + break; pv += chess::uci::moveToUci(m, root.chess960()) + " "; - if (ply + 1 >= maxPly) break; + if (ply + 1 >= maxPly) + break; pos.doMove(m); } return pv; @@ -527,8 +553,8 @@ void search(const chess::Board &board, const timeman::LimitsType timecontrol) { score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); } prevScore = score_; - if (session.tm.elapsed() >= session.tm.optimum() || session.tm.elapsed() >= session.tm.maximum() - || stopSearch.load(std::memory_order_relaxed) || score_ == VALUE_NONE) + if (session.tm.elapsed() >= session.tm.optimum() || session.tm.elapsed() >= session.tm.maximum() || + stopSearch.load(std::memory_order_relaxed) || score_ == VALUE_NONE) break; InfoFull info{}; info.depth = i; @@ -560,7 +586,8 @@ void search(const chess::Board &board, const timeman::LimitsType timecontrol) { if (!firstMove.empty()) lastPV[0] = chess::Move(chess::uci::uciToMove(board, firstMove).raw()); std::stringstream ss; - ss << "qnodes "<key == hash || e->getDepth() < depth) { @@ -55,14 +55,14 @@ void TranspositionTable::store(uint64_t hash, chess::Move best, int16_t score, i oldest->setPackedFields(score, depth, flag, best.raw(), time); } -TTEntry* TranspositionTable::lookup(uint64_t hash) { +TTEntry *TranspositionTable::lookup(uint64_t hash) { if (buckets == 0) return nullptr; uint64_t bucket = index_for_hash(hash, buckets); - TTEntry& e0 = table[2 * bucket]; - TTEntry& e1 = table[2 * bucket + 1]; + TTEntry &e0 = table[2 * bucket]; + TTEntry &e1 = table[2 * bucket + 1]; if (e0.key == hash) return &e0; diff --git a/uci.cpp b/uci.cpp index 24ebd8a..230bd52 100644 --- a/uci.cpp +++ b/uci.cpp @@ -164,7 +164,8 @@ void engine::report(const InfoFull &info, bool showWDL) { << " tbhits " << info.tbHits // << " time " << info.timeMs // << " pv " << info.pv; // - if (!info.extrainfo.empty()) ss << "\ninfo string extras "< Date: Sun, 7 Jun 2026 21:51:47 +0700 Subject: [PATCH 10/29] tb --- tb.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tb.h | 15 +++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 tb.cpp create mode 100644 tb.h diff --git a/tb.cpp b/tb.cpp new file mode 100644 index 0000000..61d0efa --- /dev/null +++ b/tb.cpp @@ -0,0 +1,55 @@ +#include "tb.h" +#include +#include +#include +#include + +namespace engine::tb { + +tbprobe::syzygy::Tablebase tablebase; + +namespace { +std::size_t unique_table_count(const std::unordered_map &tables) { + std::unordered_set uniqueTables; + + for (const auto &[_, table] : tables) + if (table != nullptr) + uniqueTables.insert(table); + + return uniqueTables.size(); +} +} // namespace + +void init(const std::string &path) { + tablebase.close(); + + if (path.empty()) { + std::cout << "info string Found 0 WDL and 0 DTZ tablebase files\n"; + return; + } + + tbprobe::syzygy::initialize(); + tablebase.add_directory(path, true, true); + std::cout << "info string Found " << wdl_count() << " WDL and " << dtz_count() << " DTZ tablebase files\n"; +} + +std::size_t wdl_count() { return unique_table_count(tablebase.wdl); } + +std::size_t dtz_count() { return unique_table_count(tablebase.dtz); } + +int probe_wdl(chess::Board &board) { + try { + return *tablebase.get_wdl(board, TB_ERROR); + } catch (const std::exception &) { + return TB_ERROR; + } +} + +int probe_dtz(chess::Board &board) { + try { + return *tablebase.get_dtz(board, TB_ERROR); + } catch (const std::exception &) { + return TB_ERROR; + } +} +} // namespace engine::tb diff --git a/tb.h b/tb.h new file mode 100644 index 0000000..4425f2a --- /dev/null +++ b/tb.h @@ -0,0 +1,15 @@ +#pragma once +#include "eval.h" +#include +#include +#include + +namespace engine::tb { + +void init(const std::string &path); +constexpr int TB_ERROR = 1000; +std::size_t wdl_count(); +std::size_t dtz_count(); +int probe_wdl(chess::Board &board); +int probe_dtz(chess::Board &board); +} // namespace engine::tb From ec0c935e38744f06e07a436ae6e7574e10247c3a Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 7 Jun 2026 14:52:21 +0000 Subject: [PATCH 11/29] Apply clang-format --- eval.cpp | 872 +++++++++++++++++++------------------ eval.h | 8 +- main.cpp | 36 +- movepick.cpp | 156 +++---- movepick.h | 3 +- score.cpp | 20 +- score.h | 40 +- search.cpp | 1150 +++++++++++++++++++++++++------------------------ search.h | 24 +- tb.cpp | 50 +-- timeman.cpp | 112 ++--- timeman.h | 58 +-- tt.cpp | 89 ++-- tt.h | 273 ++++++------ tune.cpp | 68 +-- tune.h | 204 +++++---- uci.cpp | 340 ++++++++------- uci.h | 32 +- ucioption.cpp | 192 +++++---- ucioption.h | 102 ++--- 20 files changed, 1996 insertions(+), 1833 deletions(-) diff --git a/eval.cpp b/eval.cpp index 4595de2..868ac06 100644 --- a/eval.cpp +++ b/eval.cpp @@ -8,18 +8,21 @@ using namespace engine::eval; namespace engine::eval { static Bitboard passedMask[2][64]; struct PassedMaskInit { - PassedMaskInit() { - for (int sq = 0; sq < 64; sq++) { - File f = file_of(Square(sq)); - Rank r = rank_of(Square(sq)); - for (int adjF = std::max(0, (int)f - 1); adjF <= std::min(7, (int)f + 1); adjF++) { - for (int r2 = (int)r + 1; r2 <= 7; r2++) - passedMask[WHITE][sq] |= attacks::MASK_FILE[adjF] & attacks::MASK_RANK[r2]; - for (int r2 = (int)r - 1; r2 >= 0; r2--) - passedMask[BLACK][sq] |= attacks::MASK_FILE[adjF] & attacks::MASK_RANK[r2]; - } - } + PassedMaskInit() { + for (int sq = 0; sq < 64; sq++) { + File f = file_of(Square(sq)); + Rank r = rank_of(Square(sq)); + for (int adjF = std::max(0, (int)f - 1); adjF <= std::min(7, (int)f + 1); + adjF++) { + for (int r2 = (int)r + 1; r2 <= 7; r2++) + passedMask[WHITE][sq] |= + attacks::MASK_FILE[adjF] & attacks::MASK_RANK[r2]; + for (int r2 = (int)r - 1; r2 >= 0; r2--) + passedMask[BLACK][sq] |= + attacks::MASK_FILE[adjF] & attacks::MASK_RANK[r2]; + } } + } }; static PassedMaskInit passedMaskInit; //clang-format off @@ -54,472 +57,485 @@ Value kqkEdgeWeight = 45; Value krkDistWeight = 4; Value krkEdgeWeight = 17; Value kpkWeight = 17; -Value mgMobilityCnt[7][8] = { - { 92, -46, 6, -99, -98, -35, 74, -28 }, - { -81, 43, 0, -100, -68, -86, 47, 29 }, - { 51, 71, 88, -78, 17, -56, 99, 5 }, - { -59, -78, -88, 22, 2, 86, -77, -1 }, - { -55, -98, 93, 70, 27, -54, -100, -83 }, - { 13, 1, -81, -7, -42, -95, -68, -99 }, - { -37, 43, 8, -100, -99, 36, 91, -42 } -}; -Value egMobilityCnt[7][8] = { - { -75, -98, 51, 26, -10, 68, 2, 10 }, - { -65, -80, 95, 79, -43, -16, 100, -99 }, - { 63, -25, -74, -41, 100, -37, 4, -85 }, - { -66, 40, 41, 46, -65, -75, 29, 78 }, - { 12, -53, -91, 23, -100, -66, 40, 73 }, - { -28, 5, -46, 25, -13, 11, 87, 33 }, - { -89, -4, 28, -27, 7, -1, 26, -98 } -}; -Value kingTropismMg[7] = { -29, 17, 7, -15, -49, -14, -1 }; -Value kingTropismEg[7] = { -41, 0, -31, -41, 19, 36, 50 }; -Value passedBonusMg[8] = { 313, 227, 386, 163, 109, 284, 220, 363 }; -Value passedBonusEg[8] = { 380, 350, 394, 388, 149, 394, 190, 316 }; -Value mg_pawn_table[56] = { 114, -495, 41, -368, -477, 412, -158, -276, -129, -129, -237, 437, -158, 121, - -2, -160, 163, 459, 425, -298, 52, -296, -496, 333, -390, -473, -314, -319, - -357, 348, 127, -347, 175, -346, -398, 155, 362, 477, -147, -418, -477, 311, - 191, -489, -414, -352, 479, -89, 499, 265, -325, -291, -55, 491, -499, 470 }; -Value mg_knight_table[64] = { 443, 51, 110, 497, 243, 173, 224, 109, -498, 280, -499, 185, 58, 494, 408, -33, - -395, 45, -15, 208, 245, -13, 378, 359, -489, 352, 96, -152, 422, -414, -153, 304, - -483, -113, 346, 1, 104, -185, 141, -483, 405, -500, -117, 158, -306, -145, 465, 458, - 257, -344, -107, 26, 334, 369, -338, 352, -412, -223, 69, 244, -156, 335, -368, 359 }; -Value mg_bishop_table[64] = { -258, -250, 404, 459, 322, 66, 411, 227, -93, -52, -352, 13, 450, -441, 392, -139, - 315, 263, 210, 299, -397, 133, 371, 440, 272, 221, -76, -125, -13, 483, 135, 182, - -351, 203, 101, 298, 352, 277, -478, 488, -62, 42, -144, 496, -396, 195, -414, 11, - 409, 116, -373, 386, 377, 47, 493, 0, -302, 312, 81, 459, 350, -187, -265, 153 }; -Value mg_rook_table[64] = { -27, -134, -499, 159, 77, -60, 266, 171, 77, -364, -338, -315, -123, 482, -341, 412, - 213, 132, 134, 254, 354, -40, -145, -364, 249, 497, -490, 25, -191, -245, 104, 428, - 417, 49, 499, -426, -261, 466, 80, -61, -457, 75, 95, 15, 270, -250, -171, 165, - 186, 92, 401, 482, -223, -70, 384, 316, -189, 13, 185, -110, 207, -307, -257, 175 }; -Value mg_king_table[64] = { -498, 290, 205, 125, -483, -313, -372, -330, 302, 312, -171, -115, 403, 43, -259, 212, - -264, -396, -329, 260, -288, 374, 454, 499, -124, -366, -335, 371, 161, -361, 368, 102, - -454, -133, -52, 139, -102, -418, 380, 499, -108, 340, -352, 326, 415, 2, 150, -314, - 430, -161, -399, -99, 8, -292, -81, 79, -491, 309, -160, -208, -173, -283, -313, -62 }; -Value mg_queen_table[64] = { -301, -182, 487, 58, 270, 273, 177, -207, -175, 246, 266, 332, -416, -363, 54, -202, - 261, -423, -97, 280, -251, -265, 79, 493, 332, -133, 499, -375, -251, 210, 139, -248, - 424, 193, -36, -279, 429, 62, 379, 173, 446, 0, -370, 426, -462, 287, 165, -499, - 94, -425, 143, -409, 144, 495, 111, 452, 78, 157, -143, -478, 243, -97, -489, 112 }; -Value eg_pawn_table[56] = { -273, -6, 100, 320, -127, 163, -33, 79, 235, 334, 47, 435, -149, -1, - -155, -277, -25, 220, 340, -396, 401, 446, -30, 424, 472, -385, 486, -91, - 99, -275, 340, -330, -125, 150, -121, -30, -207, -244, -277, 411, 301, -298, - 77, -313, -141, -357, 386, 255, -495, 80, -500, 400, -450, 130, -111, -28 }; -Value eg_knight_table[64] = { 371, -385, 222, -192, 139, 46, -343, 59, 366, -360, 248, 384, -63, -425, 225, 414, - -333, 396, -421, -99, 10, 367, -368, 500, -500, 270, 41, 168, 154, 327, -161, 457, - -368, 79, -265, -457, 94, -294, -234, 348, 233, -471, -360, 380, -228, 3, 314, 415, - -327, 200, -91, 251, -390, -294, -187, -335, -29, 177, -341, 197, -37, 143, 217, 384 }; -Value eg_bishop_table[64] = { 31, 57, -369, -271, 267, -495, -141, -276, 239, 405, 217, 34, -391, -36, 122, -427, - -35, 406, 286, 52, 497, 162, 262, -147, -251, -470, -258, -171, -63, -203, 296, 497, - 90, 444, 422, -241, -499, 62, -312, 299, -345, 309, 168, 142, 161, -170, 138, -116, - 359, -45, 214, 191, -360, 163, 433, 386, 182, -181, 348, 258, 415, 375, -233, 214 }; -Value eg_rook_table[64] = { -227, 492, 17, -205, 495, 12, 207, -246, 195, -22, 312, 16, -124, -259, 405, -494, - -262, -344, -218, -12, 8, 388, 306, 316, 376, 455, -139, -194, 41, 158, 283, 476, - 469, 94, 63, -498, -414, 0, -292, -373, -270, 256, -162, -385, 453, -220, 162, -53, - -221, -435, -426, -222, 235, 116, 182, 409, -327, -486, 80, 109, -455, -247, 106, -307 }; -Value eg_king_table[64] = { -319, -481, 495, -290, 1, 426, -347, -112, 423, -89, 500, -432, 54, -223, 148, 118, - -292, -351, -205, 195, 377, -185, -296, -176, -73, 247, -491, -411, -232, 106, -295, 326, - 60, 500, -488, 218, 207, -409, 91, -390, 35, -249, 111, -179, -169, -182, -372, -330, - 425, 455, 457, 500, -494, -442, 24, 170, 367, 353, -297, 97, -137, 125, 221, -32 }; -Value eg_queen_table[64] = { -326, 119, 358, 91, -311, -289, 373, 329, 44, -83, 162, -470, -470, -2, 45, -486, - 438, 39, -493, 371, 493, -278, 338, -416, 360, 499, 357, -145, -500, 398, -210, -199, - 218, -22, -367, 69, 144, 488, -55, -71, 499, -36, -262, -351, -407, 291, -497, -412, - -164, -46, 186, 323, -56, -416, -129, 394, 71, -343, 183, -360, 91, -295, -180, 88 }; +Value mgMobilityCnt[7][8] = {{92, -46, 6, -99, -98, -35, 74, -28}, + {-81, 43, 0, -100, -68, -86, 47, 29}, + {51, 71, 88, -78, 17, -56, 99, 5}, + {-59, -78, -88, 22, 2, 86, -77, -1}, + {-55, -98, 93, 70, 27, -54, -100, -83}, + {13, 1, -81, -7, -42, -95, -68, -99}, + {-37, 43, 8, -100, -99, 36, 91, -42}}; +Value egMobilityCnt[7][8] = {{-75, -98, 51, 26, -10, 68, 2, 10}, + {-65, -80, 95, 79, -43, -16, 100, -99}, + {63, -25, -74, -41, 100, -37, 4, -85}, + {-66, 40, 41, 46, -65, -75, 29, 78}, + {12, -53, -91, 23, -100, -66, 40, 73}, + {-28, 5, -46, 25, -13, 11, 87, 33}, + {-89, -4, 28, -27, 7, -1, 26, -98}}; +Value kingTropismMg[7] = {-29, 17, 7, -15, -49, -14, -1}; +Value kingTropismEg[7] = {-41, 0, -31, -41, 19, 36, 50}; +Value passedBonusMg[8] = {313, 227, 386, 163, 109, 284, 220, 363}; +Value passedBonusEg[8] = {380, 350, 394, 388, 149, 394, 190, 316}; +Value mg_pawn_table[56] = { + 114, -495, 41, -368, -477, 412, -158, -276, -129, -129, -237, 437, + -158, 121, -2, -160, 163, 459, 425, -298, 52, -296, -496, 333, + -390, -473, -314, -319, -357, 348, 127, -347, 175, -346, -398, 155, + 362, 477, -147, -418, -477, 311, 191, -489, -414, -352, 479, -89, + 499, 265, -325, -291, -55, 491, -499, 470}; +Value mg_knight_table[64] = { + 443, 51, 110, 497, 243, 173, 224, 109, -498, 280, -499, 185, 58, + 494, 408, -33, -395, 45, -15, 208, 245, -13, 378, 359, -489, 352, + 96, -152, 422, -414, -153, 304, -483, -113, 346, 1, 104, -185, 141, + -483, 405, -500, -117, 158, -306, -145, 465, 458, 257, -344, -107, 26, + 334, 369, -338, 352, -412, -223, 69, 244, -156, 335, -368, 359}; +Value mg_bishop_table[64] = { + -258, -250, 404, 459, 322, 66, 411, 227, -93, -52, -352, 13, 450, + -441, 392, -139, 315, 263, 210, 299, -397, 133, 371, 440, 272, 221, + -76, -125, -13, 483, 135, 182, -351, 203, 101, 298, 352, 277, -478, + 488, -62, 42, -144, 496, -396, 195, -414, 11, 409, 116, -373, 386, + 377, 47, 493, 0, -302, 312, 81, 459, 350, -187, -265, 153}; +Value mg_rook_table[64] = { + -27, -134, -499, 159, 77, -60, 266, 171, 77, -364, -338, -315, -123, + 482, -341, 412, 213, 132, 134, 254, 354, -40, -145, -364, 249, 497, + -490, 25, -191, -245, 104, 428, 417, 49, 499, -426, -261, 466, 80, + -61, -457, 75, 95, 15, 270, -250, -171, 165, 186, 92, 401, 482, + -223, -70, 384, 316, -189, 13, 185, -110, 207, -307, -257, 175}; +Value mg_king_table[64] = { + -498, 290, 205, 125, -483, -313, -372, -330, 302, 312, -171, -115, 403, + 43, -259, 212, -264, -396, -329, 260, -288, 374, 454, 499, -124, -366, + -335, 371, 161, -361, 368, 102, -454, -133, -52, 139, -102, -418, 380, + 499, -108, 340, -352, 326, 415, 2, 150, -314, 430, -161, -399, -99, + 8, -292, -81, 79, -491, 309, -160, -208, -173, -283, -313, -62}; +Value mg_queen_table[64] = { + -301, -182, 487, 58, 270, 273, 177, -207, -175, 246, 266, 332, -416, + -363, 54, -202, 261, -423, -97, 280, -251, -265, 79, 493, 332, -133, + 499, -375, -251, 210, 139, -248, 424, 193, -36, -279, 429, 62, 379, + 173, 446, 0, -370, 426, -462, 287, 165, -499, 94, -425, 143, -409, + 144, 495, 111, 452, 78, 157, -143, -478, 243, -97, -489, 112}; +Value eg_pawn_table[56] = { + -273, -6, 100, 320, -127, 163, -33, 79, 235, 334, 47, 435, + -149, -1, -155, -277, -25, 220, 340, -396, 401, 446, -30, 424, + 472, -385, 486, -91, 99, -275, 340, -330, -125, 150, -121, -30, + -207, -244, -277, 411, 301, -298, 77, -313, -141, -357, 386, 255, + -495, 80, -500, 400, -450, 130, -111, -28}; +Value eg_knight_table[64] = { + 371, -385, 222, -192, 139, 46, -343, 59, 366, -360, 248, 384, -63, + -425, 225, 414, -333, 396, -421, -99, 10, 367, -368, 500, -500, 270, + 41, 168, 154, 327, -161, 457, -368, 79, -265, -457, 94, -294, -234, + 348, 233, -471, -360, 380, -228, 3, 314, 415, -327, 200, -91, 251, + -390, -294, -187, -335, -29, 177, -341, 197, -37, 143, 217, 384}; +Value eg_bishop_table[64] = { + 31, 57, -369, -271, 267, -495, -141, -276, 239, 405, 217, 34, -391, + -36, 122, -427, -35, 406, 286, 52, 497, 162, 262, -147, -251, -470, + -258, -171, -63, -203, 296, 497, 90, 444, 422, -241, -499, 62, -312, + 299, -345, 309, 168, 142, 161, -170, 138, -116, 359, -45, 214, 191, + -360, 163, 433, 386, 182, -181, 348, 258, 415, 375, -233, 214}; +Value eg_rook_table[64] = { + -227, 492, 17, -205, 495, 12, 207, -246, 195, -22, 312, + 16, -124, -259, 405, -494, -262, -344, -218, -12, 8, 388, + 306, 316, 376, 455, -139, -194, 41, 158, 283, 476, 469, + 94, 63, -498, -414, 0, -292, -373, -270, 256, -162, -385, + 453, -220, 162, -53, -221, -435, -426, -222, 235, 116, 182, + 409, -327, -486, 80, 109, -455, -247, 106, -307}; +Value eg_king_table[64] = { + -319, -481, 495, -290, 1, 426, -347, -112, 423, -89, 500, -432, 54, + -223, 148, 118, -292, -351, -205, 195, 377, -185, -296, -176, -73, 247, + -491, -411, -232, 106, -295, 326, 60, 500, -488, 218, 207, -409, 91, + -390, 35, -249, 111, -179, -169, -182, -372, -330, 425, 455, 457, 500, + -494, -442, 24, 170, 367, 353, -297, 97, -137, 125, 221, -32}; +Value eg_queen_table[64] = { + -326, 119, 358, 91, -311, -289, 373, 329, 44, -83, 162, -470, -470, + -2, 45, -486, 438, 39, -493, 371, 493, -278, 338, -416, 360, 499, + 357, -145, -500, 398, -210, -199, 218, -22, -367, 69, 144, 488, -55, + -71, 499, -36, -262, -351, -407, 291, -497, -412, -164, -46, 186, 323, + -56, -416, -129, 394, 71, -343, 183, -360, 91, -295, -180, 88}; //clang-format on -Value *mgPst[] = { nullptr, mg_pawn_table, mg_knight_table, mg_bishop_table, mg_rook_table, mg_queen_table, mg_king_table }; +Value *mgPst[] = {nullptr, mg_pawn_table, mg_knight_table, + mg_bishop_table, mg_rook_table, mg_queen_table, + mg_king_table}; -Value *egPst[] = { nullptr, eg_pawn_table, eg_knight_table, eg_bishop_table, eg_rook_table, eg_queen_table, eg_king_table }; +Value *egPst[] = {nullptr, eg_pawn_table, eg_knight_table, + eg_bishop_table, eg_rook_table, eg_queen_table, + eg_king_table}; // tuning slop here -TUNE(SetRange(0, 50), - tempo, - SetRange(50, 200), - PawnValue, - SetRange(200, 500), - KnightValue, - SetRange(200, 500), - BishopValue, - SetRange(300, 700), - RookValue, - SetRange(600, 1400), - QueenValue); +TUNE(SetRange(0, 50), tempo, SetRange(50, 200), PawnValue, SetRange(200, 500), + KnightValue, SetRange(200, 500), BishopValue, SetRange(300, 700), + RookValue, SetRange(600, 1400), QueenValue); TUNE(SetRange(-100, 100), mgMobilityCnt, egMobilityCnt); TUNE(SetRange(0, 50), fianchettoBonus, SetRange(0, 150), trappedBishopPenalty); TUNE(SetRange(-50, 50), kingTropismMg, kingTropismEg); -TUNE(SetRange(0, 50), - centerWeight, - SetRange(0, 30), - mopUpKingDistWeight, - SetRange(0, 50), - mopUpEdgeDistWeight, - SetRange(0, 100), - spaceWeight); +TUNE(SetRange(0, 50), centerWeight, SetRange(0, 30), mopUpKingDistWeight, + SetRange(0, 50), mopUpEdgeDistWeight, SetRange(0, 100), spaceWeight); TUNE(SetRange(0, 100), bishopPairMg, SetRange(0, 100), bishopPairEg); -TUNE(SetRange(0, 50), - rookOpenFileMg, - SetRange(0, 50), - rookOpenFileEg, - SetRange(0, 50), - rookSemiOpenFileMg, - SetRange(0, 50), - rookSemiOpenFileEg); -TUNE(SetRange(0, 100), - doubledPawnMg, - SetRange(0, 100), - doubledPawnEg, - SetRange(0, 100), - isolatedPawnMg, - SetRange(0, 100), - isolatedPawnEg); +TUNE(SetRange(0, 50), rookOpenFileMg, SetRange(0, 50), rookOpenFileEg, + SetRange(0, 50), rookSemiOpenFileMg, SetRange(0, 50), rookSemiOpenFileEg); +TUNE(SetRange(0, 100), doubledPawnMg, SetRange(0, 100), doubledPawnEg, + SetRange(0, 100), isolatedPawnMg, SetRange(0, 100), isolatedPawnEg); TUNE(SetRange(0, 400), passedBonusMg, passedBonusEg); -TUNE(SetRange(0, 30), - kingShelterBaseMg, - SetRange(0, 30), - kingShelterBaseEg, - SetRange(0, 10), - kingShelterDecayMg, - SetRange(0, 10), - kingShelterDecayEg); -TUNE(SetRange(-500, 500), - mg_pawn_table, - mg_knight_table, - mg_bishop_table, - mg_rook_table, - mg_king_table, - mg_queen_table, - eg_pawn_table, - eg_knight_table, - eg_bishop_table, - eg_rook_table, - eg_king_table, +TUNE(SetRange(0, 30), kingShelterBaseMg, SetRange(0, 30), kingShelterBaseEg, + SetRange(0, 10), kingShelterDecayMg, SetRange(0, 10), kingShelterDecayEg); +TUNE(SetRange(-500, 500), mg_pawn_table, mg_knight_table, mg_bishop_table, + mg_rook_table, mg_king_table, mg_queen_table, eg_pawn_table, + eg_knight_table, eg_bishop_table, eg_rook_table, eg_king_table, eg_queen_table); -TUNE(SetRange(0, 50), - kqkDistWeight, - SetRange(0, 50), - kqkEdgeWeight, - SetRange(0, 50), - krkDistWeight, - SetRange(0, 50), - krkEdgeWeight, - SetRange(0, 50), - kpkWeight); +TUNE(SetRange(0, 50), kqkDistWeight, SetRange(0, 50), kqkEdgeWeight, + SetRange(0, 50), krkDistWeight, SetRange(0, 50), krkEdgeWeight, + SetRange(0, 50), kpkWeight); Value eval(const chess::Board &board) { - constexpr int KnightPhase = 1; - constexpr int BishopPhase = 1; - constexpr int RookPhase = 2; - constexpr int QueenPhase = 4; - constexpr int TotalPhase = KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; - const int sign = board.side_to_move() == WHITE ? 1 : -1; - int mgScore = 0; - int egScore = 0; - int phase = 0; - { - Bitboard pinMask = board.pin_mask(); - Bitboard occ = board.occ(), occ2 = occ; - while (occ) { - Square sq = Square(pop_lsb(occ)); - auto p = board.at(sq); - Color pc = color_of(p); - int _sign = pc == WHITE ? 1 : -1; - Square _sq = _sign == 1 ? sq : square_mirror(sq); - auto pt = piece_of(p); - if (pt == NO_PIECE_TYPE) - continue; - mgScore += _sign * mgPst[pt][_sq]; - egScore += _sign * egPst[pt][_sq]; - mgScore += _sign * piece_value(pt); - egScore += _sign * piece_value(pt); - if (pt == KNIGHT) - phase += KnightPhase; - else if (pt == BISHOP) - phase += BishopPhase; - else if (pt == ROOK) - phase += RookPhase; - else if (pt == QUEEN) - phase += QueenPhase; - Bitboard attacks = 0; - if (pt == KNIGHT) - attacks = chess::attacks::knight(sq); - else if (pt == BISHOP) - attacks = chess::attacks::bishop(sq, occ2); - else if (pt == ROOK) - attacks = chess::attacks::rook(sq, occ2); - else if (pt == QUEEN) - attacks = chess::attacks::queen(sq, occ2); - attacks &= ~board.us(pc); - int mobility = popcount(attacks); - int clampedMobility = std::min(mobility, 7); - if ((1ULL << sq) & pinMask) - clampedMobility = std::min(clampedMobility / 4, 7); - mgScore += _sign * mgMobilityCnt[pt][clampedMobility]; - egScore += _sign * egMobilityCnt[pt][clampedMobility]; - if (pt != PAWN && pt != KING) { - int kd = square_distance(sq, board.kingSq(~pc)); - mgScore += _sign * (7 - kd) * kingTropismMg[pt]; - egScore += _sign * (7 - kd) * kingTropismEg[pt]; - } - } + constexpr int KnightPhase = 1; + constexpr int BishopPhase = 1; + constexpr int RookPhase = 2; + constexpr int QueenPhase = 4; + constexpr int TotalPhase = + KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; + const int sign = board.side_to_move() == WHITE ? 1 : -1; + int mgScore = 0; + int egScore = 0; + int phase = 0; + { + Bitboard pinMask = board.pin_mask(); + Bitboard occ = board.occ(), occ2 = occ; + while (occ) { + Square sq = Square(pop_lsb(occ)); + auto p = board.at(sq); + Color pc = color_of(p); + int _sign = pc == WHITE ? 1 : -1; + Square _sq = _sign == 1 ? sq : square_mirror(sq); + auto pt = piece_of(p); + if (pt == NO_PIECE_TYPE) + continue; + mgScore += _sign * mgPst[pt][_sq]; + egScore += _sign * egPst[pt][_sq]; + mgScore += _sign * piece_value(pt); + egScore += _sign * piece_value(pt); + if (pt == KNIGHT) + phase += KnightPhase; + else if (pt == BISHOP) + phase += BishopPhase; + else if (pt == ROOK) + phase += RookPhase; + else if (pt == QUEEN) + phase += QueenPhase; + Bitboard attacks = 0; + if (pt == KNIGHT) + attacks = chess::attacks::knight(sq); + else if (pt == BISHOP) + attacks = chess::attacks::bishop(sq, occ2); + else if (pt == ROOK) + attacks = chess::attacks::rook(sq, occ2); + else if (pt == QUEEN) + attacks = chess::attacks::queen(sq, occ2); + attacks &= ~board.us(pc); + int mobility = popcount(attacks); + int clampedMobility = std::min(mobility, 7); + if ((1ULL << sq) & pinMask) + clampedMobility = std::min(clampedMobility / 4, 7); + mgScore += _sign * mgMobilityCnt[pt][clampedMobility]; + egScore += _sign * egMobilityCnt[pt][clampedMobility]; + if (pt != PAWN && pt != KING) { + int kd = square_distance(sq, board.kingSq(~pc)); + mgScore += _sign * (7 - kd) * kingTropismMg[pt]; + egScore += _sign * (7 - kd) * kingTropismEg[pt]; + } } - // Bishop pair bonus - for (Color c : { WHITE, BLACK }) { - if (board.count(BISHOP, c) >= 2) { - int s = (c == WHITE) ? 1 : -1; - mgScore += s * bishopPairMg; - egScore += s * bishopPairEg; - } + } + // Bishop pair bonus + for (Color c : {WHITE, BLACK}) { + if (board.count(BISHOP, c) >= 2) { + int s = (c == WHITE) ? 1 : -1; + mgScore += s * bishopPairMg; + egScore += s * bishopPairEg; } + } - // Fianchetto bonus - for (Color c : { WHITE, BLACK }) { - int s = (c == WHITE) ? 1 : -1; - Square fianchettoSq[2] = { relative_square(c, SQ_B2), relative_square(c, SQ_G2) }; - Square pawnSq[2] = { relative_square(c, SQ_B3), relative_square(c, SQ_G3) }; - for (int i = 0; i < 2; i++) { - if (board.at(fianchettoSq[i]) == BISHOP && board.at(fianchettoSq[i]) == c && - board.at(pawnSq[i]) == PAWN && board.at(pawnSq[i]) == c) { - mgScore += s * fianchettoBonus; - egScore += s * fianchettoBonus; - } - } + // Fianchetto bonus + for (Color c : {WHITE, BLACK}) { + int s = (c == WHITE) ? 1 : -1; + Square fianchettoSq[2] = {relative_square(c, SQ_B2), + relative_square(c, SQ_G2)}; + Square pawnSq[2] = {relative_square(c, SQ_B3), relative_square(c, SQ_G3)}; + for (int i = 0; i < 2; i++) { + if (board.at(fianchettoSq[i]) == BISHOP && + board.at(fianchettoSq[i]) == c && + board.at(pawnSq[i]) == PAWN && + board.at(pawnSq[i]) == c) { + mgScore += s * fianchettoBonus; + egScore += s * fianchettoBonus; + } } + } - // Trapped bishop penalty - for (Color c : { WHITE, BLACK }) { - int s = (c == WHITE) ? 1 : -1; - Square trapSq[2] = { relative_square(c, SQ_A2), relative_square(c, SQ_H2) }; - Square kingAdj[2] = { relative_square(c, SQ_B1), relative_square(c, SQ_G1) }; - for (int i = 0; i < 2; i++) { - if (board.at(trapSq[i]) == BISHOP && board.at(trapSq[i]) == c && board.kingSq(c) == kingAdj[i]) { - mgScore -= s * trappedBishopPenalty; - egScore -= s * trappedBishopPenalty; - } - } + // Trapped bishop penalty + for (Color c : {WHITE, BLACK}) { + int s = (c == WHITE) ? 1 : -1; + Square trapSq[2] = {relative_square(c, SQ_A2), relative_square(c, SQ_H2)}; + Square kingAdj[2] = {relative_square(c, SQ_B1), relative_square(c, SQ_G1)}; + for (int i = 0; i < 2; i++) { + if (board.at(trapSq[i]) == BISHOP && + board.at(trapSq[i]) == c && board.kingSq(c) == kingAdj[i]) { + mgScore -= s * trappedBishopPenalty; + egScore -= s * trappedBishopPenalty; + } } + } - // Rook on open/semi-open file - for (Color c : { WHITE, BLACK }) { - int s = (c == WHITE) ? 1 : -1; - Bitboard rooks = board.pieces(ROOK, c); - while (rooks) { - Square sq = Square(pop_lsb(rooks)); - File f = file_of(sq); - Bitboard fileMask = attacks::MASK_FILE[f]; - bool hasOwnPawn = (board.pieces(PAWN, c) & fileMask) != 0; - bool hasEnemyPawn = (board.pieces(PAWN, ~c) & fileMask) != 0; - if (!hasOwnPawn && !hasEnemyPawn) { - mgScore += s * rookOpenFileMg; - egScore += s * rookOpenFileEg; - } else if (!hasOwnPawn) { - mgScore += s * rookSemiOpenFileMg; - egScore += s * rookSemiOpenFileEg; - } - } + // Rook on open/semi-open file + for (Color c : {WHITE, BLACK}) { + int s = (c == WHITE) ? 1 : -1; + Bitboard rooks = board.pieces(ROOK, c); + while (rooks) { + Square sq = Square(pop_lsb(rooks)); + File f = file_of(sq); + Bitboard fileMask = attacks::MASK_FILE[f]; + bool hasOwnPawn = (board.pieces(PAWN, c) & fileMask) != 0; + bool hasEnemyPawn = (board.pieces(PAWN, ~c) & fileMask) != 0; + if (!hasOwnPawn && !hasEnemyPawn) { + mgScore += s * rookOpenFileMg; + egScore += s * rookOpenFileEg; + } else if (!hasOwnPawn) { + mgScore += s * rookSemiOpenFileMg; + egScore += s * rookSemiOpenFileEg; + } } + } - // Precompute pawn data - Bitboard pawnBB[2] = { board.pieces(PAWN, WHITE), board.pieces(PAWN, BLACK) }; - Bitboard pawnAtks[2] = { attacks::pawn(pawnBB[WHITE]), attacks::pawn(pawnBB[BLACK]) }; + // Precompute pawn data + Bitboard pawnBB[2] = {board.pieces(PAWN, WHITE), board.pieces(PAWN, BLACK)}; + Bitboard pawnAtks[2] = {attacks::pawn(pawnBB[WHITE]), + attacks::pawn(pawnBB[BLACK])}; - // Pawn structure: doubled, isolated, passed in one pass per color - for (Color c : { WHITE, BLACK }) { - int s = (c == WHITE) ? 1 : -1; - Bitboard pawns = pawnBB[c]; - Bitboard enemyPawns = pawnBB[~c]; + // Pawn structure: doubled, isolated, passed in one pass per color + for (Color c : {WHITE, BLACK}) { + int s = (c == WHITE) ? 1 : -1; + Bitboard pawns = pawnBB[c]; + Bitboard enemyPawns = pawnBB[~c]; - bool fileHasPawn[8] = { false }; - int fileCount[8] = { 0 }; - Bitboard tmp = pawns; - while (tmp) { - Square sq = Square(pop_lsb(tmp)); - File f = file_of(sq); - fileCount[f]++; - fileHasPawn[f] = true; - } + bool fileHasPawn[8] = {false}; + int fileCount[8] = {0}; + Bitboard tmp = pawns; + while (tmp) { + Square sq = Square(pop_lsb(tmp)); + File f = file_of(sq); + fileCount[f]++; + fileHasPawn[f] = true; + } - for (int f = 0; f < 8; f++) - if (fileCount[f] >= 2) { - mgScore -= s * (fileCount[f] - 1) * doubledPawnMg; - egScore -= s * (fileCount[f] - 1) * doubledPawnEg; - } + for (int f = 0; f < 8; f++) + if (fileCount[f] >= 2) { + mgScore -= s * (fileCount[f] - 1) * doubledPawnMg; + egScore -= s * (fileCount[f] - 1) * doubledPawnEg; + } - tmp = pawns; - while (tmp) { - Square sq = Square(pop_lsb(tmp)); - File f = file_of(sq); - Rank relRank = relative_rank(c, sq); + tmp = pawns; + while (tmp) { + Square sq = Square(pop_lsb(tmp)); + File f = file_of(sq); + Rank relRank = relative_rank(c, sq); - bool isolated = true; - if (f > FILE_A && fileHasPawn[f - 1]) - isolated = false; - if (f < FILE_H && fileHasPawn[f + 1]) - isolated = false; - if (isolated) { - mgScore -= s * isolatedPawnMg; - egScore -= s * isolatedPawnEg; - } + bool isolated = true; + if (f > FILE_A && fileHasPawn[f - 1]) + isolated = false; + if (f < FILE_H && fileHasPawn[f + 1]) + isolated = false; + if (isolated) { + mgScore -= s * isolatedPawnMg; + egScore -= s * isolatedPawnEg; + } - if (relRank >= RANK_2 && (passedMask[c][sq] & enemyPawns) == 0) { - mgScore += s * passedBonusMg[relRank]; - egScore += s * passedBonusEg[relRank]; - } - } + if (relRank >= RANK_2 && (passedMask[c][sq] & enemyPawns) == 0) { + mgScore += s * passedBonusMg[relRank]; + egScore += s * passedBonusEg[relRank]; + } } + } - // Center control (using precomputed pawn attacks) - { - Bitboard centerMask = (1ULL << SQ_D4) | (1ULL << SQ_E4) | (1ULL << SQ_D5) | (1ULL << SQ_E5); - int wc = popcount(pawnAtks[WHITE] & centerMask); - int bc = popcount(pawnAtks[BLACK] & centerMask); - mgScore += (wc - bc) * centerWeight; - egScore += (wc - bc) * centerWeight; - } + // Center control (using precomputed pawn attacks) + { + Bitboard centerMask = + (1ULL << SQ_D4) | (1ULL << SQ_E4) | (1ULL << SQ_D5) | (1ULL << SQ_E5); + int wc = popcount(pawnAtks[WHITE] & centerMask); + int bc = popcount(pawnAtks[BLACK] & centerMask); + mgScore += (wc - bc) * centerWeight; + egScore += (wc - bc) * centerWeight; + } - // Space evaluation - for (Color c : { WHITE, BLACK }) { - int s = (c == WHITE) ? 1 : -1; - Bitboard safe = ~pawnAtks[~c]; - Bitboard enemyCamp = c == WHITE ? (attacks::MASK_RANK[4] | attacks::MASK_RANK[5] | attacks::MASK_RANK[6]) - : (attacks::MASK_RANK[3] | attacks::MASK_RANK[2] | attacks::MASK_RANK[1]); - int space = popcount(pawnAtks[c] & safe & enemyCamp); - mgScore += s * space * spaceWeight; - egScore += s * space * spaceWeight; - } + // Space evaluation + for (Color c : {WHITE, BLACK}) { + int s = (c == WHITE) ? 1 : -1; + Bitboard safe = ~pawnAtks[~c]; + Bitboard enemyCamp = c == WHITE + ? (attacks::MASK_RANK[4] | attacks::MASK_RANK[5] | + attacks::MASK_RANK[6]) + : (attacks::MASK_RANK[3] | attacks::MASK_RANK[2] | + attacks::MASK_RANK[1]); + int space = popcount(pawnAtks[c] & safe & enemyCamp); + mgScore += s * space * spaceWeight; + egScore += s * space * spaceWeight; + } - // King safety: pawn shelter - for (Color c : { WHITE, BLACK }) { - int s = (c == WHITE) ? 1 : -1; - Square kingSq = board.kingSq(c); - File kf = file_of(kingSq); - Rank kr = rank_of(kingSq); - Bitboard pawns = board.pieces(PAWN, c); - int shelterMg = 0, shelterEg = 0; - int startF = std::max(0, (int)kf - 1); - int endF = std::min(7, (int)kf + 1); - for (int adjF = startF; adjF <= endF; adjF++) { - Bitboard fileMask = attacks::MASK_FILE[adjF]; - if (c == WHITE) { - for (int r = (int)kr + 1; r <= std::min(7, (int)kr + 3); r++) { - if (pawns & (fileMask & attacks::MASK_RANK[r])) { - shelterMg += kingShelterBaseMg - (r - (int)kr - 1) * kingShelterDecayMg; - shelterEg += kingShelterBaseEg - (r - (int)kr - 1) * kingShelterDecayEg; - } - } - } else { - for (int r = (int)kr - 1; r >= std::max(0, (int)kr - 3); r--) { - if (pawns & (fileMask & attacks::MASK_RANK[r])) { - shelterMg += kingShelterBaseMg - ((int)kr - r - 1) * kingShelterDecayMg; - shelterEg += kingShelterBaseEg - ((int)kr - r - 1) * kingShelterDecayEg; - } - } - } + // King safety: pawn shelter + for (Color c : {WHITE, BLACK}) { + int s = (c == WHITE) ? 1 : -1; + Square kingSq = board.kingSq(c); + File kf = file_of(kingSq); + Rank kr = rank_of(kingSq); + Bitboard pawns = board.pieces(PAWN, c); + int shelterMg = 0, shelterEg = 0; + int startF = std::max(0, (int)kf - 1); + int endF = std::min(7, (int)kf + 1); + for (int adjF = startF; adjF <= endF; adjF++) { + Bitboard fileMask = attacks::MASK_FILE[adjF]; + if (c == WHITE) { + for (int r = (int)kr + 1; r <= std::min(7, (int)kr + 3); r++) { + if (pawns & (fileMask & attacks::MASK_RANK[r])) { + shelterMg += + kingShelterBaseMg - (r - (int)kr - 1) * kingShelterDecayMg; + shelterEg += + kingShelterBaseEg - (r - (int)kr - 1) * kingShelterDecayEg; + } + } + } else { + for (int r = (int)kr - 1; r >= std::max(0, (int)kr - 3); r--) { + if (pawns & (fileMask & attacks::MASK_RANK[r])) { + shelterMg += + kingShelterBaseMg - ((int)kr - r - 1) * kingShelterDecayMg; + shelterEg += + kingShelterBaseEg - ((int)kr - r - 1) * kingShelterDecayEg; + } } - mgScore += s * shelterMg; - egScore += s * shelterEg; + } } + mgScore += s * shelterMg; + egScore += s * shelterEg; + } - // Endgame bonuses + mop-up in one pass per color - for (Color c : { WHITE, BLACK }) { - int s = (c == WHITE) ? 1 : -1; - int oppCount = popcount(board.occ(~c)); - Square myKing = board.kingSq(c); - Square oppKing = board.kingSq(~c); + // Endgame bonuses + mop-up in one pass per color + for (Color c : {WHITE, BLACK}) { + int s = (c == WHITE) ? 1 : -1; + int oppCount = popcount(board.occ(~c)); + Square myKing = board.kingSq(c); + Square oppKing = board.kingSq(~c); - // KQK: queen vs lone king - if (board.count(QUEEN, c) >= 1 && oppCount == 1) { - int kingDist = std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), - std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); - File ef = file_of(oppKing); - Rank er = rank_of(oppKing); - int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), std::min((int)er, 7 - (int)er)); - mgScore += s * ((14 - kingDist) * kqkDistWeight + (7 - edgeDist) * kqkEdgeWeight); - egScore += s * ((14 - kingDist) * kqkDistWeight + (7 - edgeDist) * kqkEdgeWeight); - } + // KQK: queen vs lone king + if (board.count(QUEEN, c) >= 1 && oppCount == 1) { + int kingDist = + std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), + std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); + File ef = file_of(oppKing); + Rank er = rank_of(oppKing); + int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), + std::min((int)er, 7 - (int)er)); + mgScore += s * ((14 - kingDist) * kqkDistWeight + + (7 - edgeDist) * kqkEdgeWeight); + egScore += s * ((14 - kingDist) * kqkDistWeight + + (7 - edgeDist) * kqkEdgeWeight); + } - // KRK: rook vs lone king - if (board.count(ROOK, c) >= 1 && oppCount == 1 && board.count(QUEEN, c) == 0 && board.count(BISHOP, c) == 0 && - board.count(KNIGHT, c) == 0 && board.count(PAWN, c) == 0) { - int kingDist = std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), - std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); - File ef = file_of(oppKing); - Rank er = rank_of(oppKing); - int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), std::min((int)er, 7 - (int)er)); - mgScore += s * ((14 - kingDist) * krkDistWeight + (7 - edgeDist) * krkEdgeWeight); - egScore += s * ((14 - kingDist) * krkDistWeight + (7 - edgeDist) * krkEdgeWeight); - } + // KRK: rook vs lone king + if (board.count(ROOK, c) >= 1 && oppCount == 1 && + board.count(QUEEN, c) == 0 && board.count(BISHOP, c) == 0 && + board.count(KNIGHT, c) == 0 && board.count(PAWN, c) == 0) { + int kingDist = + std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), + std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); + File ef = file_of(oppKing); + Rank er = rank_of(oppKing); + int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), + std::min((int)er, 7 - (int)er)); + mgScore += s * ((14 - kingDist) * krkDistWeight + + (7 - edgeDist) * krkEdgeWeight); + egScore += s * ((14 - kingDist) * krkDistWeight + + (7 - edgeDist) * krkEdgeWeight); + } - // KPK: king + pawn(s) vs lone king - if (board.count(PAWN, c) >= 1 && oppCount == 1 && board.count(QUEEN, c) == 0 && board.count(ROOK, c) == 0 && - board.count(BISHOP, c) == 0 && board.count(KNIGHT, c) == 0) { - Bitboard ps = pawnBB[c]; - while (ps) { - Square psq = Square(pop_lsb(ps)); - Rank pr = relative_rank(c, psq); - if (pr < RANK_4) - continue; + // KPK: king + pawn(s) vs lone king + if (board.count(PAWN, c) >= 1 && oppCount == 1 && + board.count(QUEEN, c) == 0 && board.count(ROOK, c) == 0 && + board.count(BISHOP, c) == 0 && board.count(KNIGHT, c) == 0) { + Bitboard ps = pawnBB[c]; + while (ps) { + Square psq = Square(pop_lsb(ps)); + Rank pr = relative_rank(c, psq); + if (pr < RANK_4) + continue; - File pf = file_of(psq); - Square promSq = make_sq(pf, c == WHITE ? RANK_8 : RANK_1); - int pawnDist = (c == WHITE) ? (7 - (int)rank_of(psq)) : (int)rank_of(psq); - int pkDist = - std::max(std::abs((int)file_of(oppKing) - (int)pf), std::abs((int)rank_of(oppKing) - (int)rank_of(promSq))); - int mkDist = std::max(std::abs((int)file_of(myKing) - (int)file_of(psq)), - std::abs((int)rank_of(myKing) - (int)rank_of(psq))); + File pf = file_of(psq); + Square promSq = make_sq(pf, c == WHITE ? RANK_8 : RANK_1); + int pawnDist = + (c == WHITE) ? (7 - (int)rank_of(psq)) : (int)rank_of(psq); + int pkDist = + std::max(std::abs((int)file_of(oppKing) - (int)pf), + std::abs((int)rank_of(oppKing) - (int)rank_of(promSq))); + int mkDist = + std::max(std::abs((int)file_of(myKing) - (int)file_of(psq)), + std::abs((int)rank_of(myKing) - (int)rank_of(psq))); - bool winning = pkDist > pawnDist || mkDist <= pawnDist + 1 || - (pr >= RANK_6 && file_of(myKing) == pf && - ((c == WHITE && rank_of(myKing) >= RANK_6) || (c == BLACK && rank_of(myKing) <= RANK_3))); - if ((pf == FILE_A || pf == FILE_H) && oppKing == promSq) - winning = false; + bool winning = pkDist > pawnDist || mkDist <= pawnDist + 1 || + (pr >= RANK_6 && file_of(myKing) == pf && + ((c == WHITE && rank_of(myKing) >= RANK_6) || + (c == BLACK && rank_of(myKing) <= RANK_3))); + if ((pf == FILE_A || pf == FILE_H) && oppKing == promSq) + winning = false; - if (winning) { - mgScore += s * kpkWeight * pawnDist; - egScore += s * kpkWeight * pawnDist; - } - } + if (winning) { + mgScore += s * kpkWeight * pawnDist; + egScore += s * kpkWeight * pawnDist; } + } + } - // Mop-up: general endgame drive - Bitboard ourMat = board.occ(c) & ~board.pieces(PAWN) & ~board.pieces(KING); - Bitboard theirMat = board.occ(~c) & ~board.pieces(PAWN) & ~board.pieces(KING); - if (ourMat && !theirMat && oppCount <= 2) { - int kingDist = std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), - std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); - File ef = file_of(oppKing); - Rank er = rank_of(oppKing); - int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), std::min((int)er, 7 - (int)er)); - mgScore += s * ((14 - kingDist) * mopUpKingDistWeight + (7 - edgeDist) * mopUpEdgeDistWeight); - egScore += s * ((14 - kingDist) * mopUpKingDistWeight + (7 - edgeDist) * mopUpEdgeDistWeight); - } + // Mop-up: general endgame drive + Bitboard ourMat = board.occ(c) & ~board.pieces(PAWN) & ~board.pieces(KING); + Bitboard theirMat = + board.occ(~c) & ~board.pieces(PAWN) & ~board.pieces(KING); + if (ourMat && !theirMat && oppCount <= 2) { + int kingDist = + std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), + std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); + File ef = file_of(oppKing); + Rank er = rank_of(oppKing); + int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), + std::min((int)er, 7 - (int)er)); + mgScore += s * ((14 - kingDist) * mopUpKingDistWeight + + (7 - edgeDist) * mopUpEdgeDistWeight); + egScore += s * ((14 - kingDist) * mopUpKingDistWeight + + (7 - edgeDist) * mopUpEdgeDistWeight); } + } - // Draw detection: score 0 for positions where neither side can force a win - int totalPieces = popcount(board.occ()); - int pawnCount = board.count(); + // Draw detection: score 0 for positions where neither side can force a win + int totalPieces = popcount(board.occ()); + int pawnCount = board.count(); - // KBKB same-colored bishops (no pawns) - drawn - if (totalPieces == 4 && pawnCount == 0 && board.count() == 2 && board.count() == 0 && - board.count() == 0 && board.count() == 0 && board.count(BISHOP, WHITE) == 1 && - board.count(BISHOP, BLACK) == 1) { - Bitboard wbBB = board.pieces(BISHOP, WHITE); - Bitboard bbBB = board.pieces(BISHOP, BLACK); - Square wb = Square(pop_lsb(wbBB)); - Square bb = Square(pop_lsb(bbBB)); - if (square_color(wb) == square_color(bb)) - return 0; - } + // KBKB same-colored bishops (no pawns) - drawn + if (totalPieces == 4 && pawnCount == 0 && board.count() == 2 && + board.count() == 0 && board.count() == 0 && + board.count() == 0 && board.count(BISHOP, WHITE) == 1 && + board.count(BISHOP, BLACK) == 1) { + Bitboard wbBB = board.pieces(BISHOP, WHITE); + Bitboard bbBB = board.pieces(BISHOP, BLACK); + Square wb = Square(pop_lsb(wbBB)); + Square bb = Square(pop_lsb(bbBB)); + if (square_color(wb) == square_color(bb)) + return 0; + } - // KNNK (no pawns) - drawn - if (totalPieces == 4 && pawnCount == 0 && board.count() == 2 && board.count() == 0 && - board.count() == 0 && board.count() == 0) - return 0; + // KNNK (no pawns) - drawn + if (totalPieces == 4 && pawnCount == 0 && board.count() == 2 && + board.count() == 0 && board.count() == 0 && + board.count() == 0) + return 0; - phase = (phase * 256 + TotalPhase / 2) / TotalPhase; - Value finalScore = (((mgScore * phase) + (egScore * (256 - phase))) * sign) / 256 + tempo; - return finalScore; + phase = (phase * 256 + TotalPhase / 2) / TotalPhase; + Value finalScore = + (((mgScore * phase) + (egScore * (256 - phase))) * sign) / 256 + tempo; + return finalScore; } Value piece_value(PieceType pt) { - Value pieces[] = { 0, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, 0 }; - return pieces[pt]; + Value pieces[] = {0, PawnValue, KnightValue, BishopValue, RookValue, + QueenValue, 0}; + return pieces[pt]; } } // namespace engine::eval diff --git a/eval.h b/eval.h index 6ad2a96..74c1e20 100644 --- a/eval.h +++ b/eval.h @@ -20,9 +20,13 @@ constexpr bool is_valid(Value value) { return value != VALUE_NONE; } constexpr bool is_win(Value value) { return value >= VALUE_TB_WIN_IN_MAX_PLY; } -constexpr bool is_loss(Value value) { return value <= VALUE_TB_LOSS_IN_MAX_PLY; } +constexpr bool is_loss(Value value) { + return value <= VALUE_TB_LOSS_IN_MAX_PLY; +} -constexpr bool is_decisive(Value value) { return is_win(value) || is_loss(value); } +constexpr bool is_decisive(Value value) { + return is_win(value) || is_loss(value); +} constexpr Value MATE(int i) { return VALUE_MATE - i; } constexpr Value MATE_DISTANCE(int i) { return VALUE_MATE - (i < 0 ? -i : i); } namespace eval { diff --git a/main.cpp b/main.cpp index f9290bd..6607fd0 100644 --- a/main.cpp +++ b/main.cpp @@ -9,24 +9,24 @@ using namespace engine; #define STR(x) STR_HELPER(x) int main() { - std::cout << std::unitbuf; - std::cout << "cppchess_engine version " << BUILD_VERSION << '\n'; - options.add("Move Overhead", Option(10, 0, 1000)); - options.add("Hash", Option(16, 1, 1 << 25, [](const Option &o) { - search::tt.resize(int(o)); - return std::nullopt; - })); + std::cout << std::unitbuf; + std::cout << "cppchess_engine version " << BUILD_VERSION << '\n'; + options.add("Move Overhead", Option(10, 0, 1000)); + options.add("Hash", Option(16, 1, 1 << 25, [](const Option &o) { + search::tt.resize(int(o)); + return std::nullopt; + })); - options.add("Clear Hash", Option(+[](const Option &) { - search::tt.clear(); + options.add("Clear Hash", Option(+[](const Option &) { + search::tt.clear(); - return std::nullopt; - })); - // work in progress - options.add("SyzygyPath", Option("", [](const Option &o) { - tb::init(std::string(o)); - return std::nullopt; - })); - Tune::init(options); - loop(); + return std::nullopt; + })); + // work in progress + options.add("SyzygyPath", Option("", [](const Option &o) { + tb::init(std::string(o)); + return std::nullopt; + })); + Tune::init(options); + loop(); } diff --git a/movepick.cpp b/movepick.cpp index da896c3..66b2eb0 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -7,86 +7,92 @@ using engine::eval::piece_value; namespace engine::movepick { static Bitboard att(PieceType pt, Square sq, Bitboard occ) { - switch (pt) { - case BISHOP: - return attacks::bishop(sq, occ); - case ROOK: - return attacks::rook(sq, occ); - case QUEEN: - return attacks::queen(sq, occ); - default: - return 0; - } + switch (pt) { + case BISHOP: + return attacks::bishop(sq, occ); + case ROOK: + return attacks::rook(sq, occ); + case QUEEN: + return attacks::queen(sq, occ); + default: + return 0; + } } Value see(Board &board, Move move) { - if (move.type_of() == EN_PASSANT) - return piece_value(PAWN); - PieceType captured = board.at(move.to()); - if (captured == NO_PIECE_TYPE) - return 0; - PieceType attacker = board.at(move.from()); - Bitboard occ = board.occ() ^ (1ULL << move.from()); - Bitboard attackers = board.attackers(WHITE, move.to(), occ) | board.attackers(BLACK, move.to(), occ); - Value gain[32]; - int d = 0; - gain[d] = piece_value(captured); - Color stm = ~board.side_to_move(); - while (++d < 32) { - gain[d] = piece_value(attacker) - gain[d - 1]; - if (gain[d] < 0) - break; - attackers &= occ; - Bitboard stmAttackers = attackers & board.occ(stm); - if (!stmAttackers) - break; - Square sq = (Square)pop_lsb(stmAttackers); - attacker = board.at(sq); - if (attacker == NO_PIECE_TYPE) - break; - if (attacker == BISHOP || attacker == ROOK || attacker == QUEEN) - attackers |= att(attacker, move.to(), occ); - occ ^= (1ULL << sq); - stm = ~stm; - } - while (--d) - gain[d - 1] = -gain[d]; - return gain[0]; + if (move.type_of() == EN_PASSANT) + return piece_value(PAWN); + PieceType captured = board.at(move.to()); + if (captured == NO_PIECE_TYPE) + return 0; + PieceType attacker = board.at(move.from()); + Bitboard occ = board.occ() ^ (1ULL << move.from()); + Bitboard attackers = board.attackers(WHITE, move.to(), occ) | + board.attackers(BLACK, move.to(), occ); + Value gain[32]; + int d = 0; + gain[d] = piece_value(captured); + Color stm = ~board.side_to_move(); + while (++d < 32) { + gain[d] = piece_value(attacker) - gain[d - 1]; + if (gain[d] < 0) + break; + attackers &= occ; + Bitboard stmAttackers = attackers & board.occ(stm); + if (!stmAttackers) + break; + Square sq = (Square)pop_lsb(stmAttackers); + attacker = board.at(sq); + if (attacker == NO_PIECE_TYPE) + break; + if (attacker == BISHOP || attacker == ROOK || attacker == QUEEN) + attackers |= att(attacker, move.to(), occ); + occ ^= (1ULL << sq); + stm = ~stm; + } + while (--d) + gain[d - 1] = -gain[d]; + return gain[0]; } -void orderMoves(Board &board, Movelist &moves, Move ttMove, int ply, const engine::search::Session &session, Move prevMove) { - Value scores[300]; - size_t n = moves.size(); - for (size_t i = 0; i < n; ++i) { - Move move = moves[i]; - if (move == ttMove) - scores[i] = 10000; - else if (board.isCapture(move)) { - Value s = see(board, move); - Value capturedVal = move.type_of() == EN_PASSANT ? piece_value(PAWN) : piece_value(board.at(move.to())); - Value attackerVal = piece_value(board.at(move.from())); - scores[i] = (s >= -50 ? 9000 : 4000) + std::max(s, Value(-50)) + (capturedVal * 10 - attackerVal) / 100; - } else if (move == session.killerMoves[ply][0]) - scores[i] = 8500; - else if (move == session.killerMoves[ply][1]) - scores[i] = 8000; - else if (prevMove.is_ok() && move == session.counterMoves[prevMove.from_to()]) - scores[i] = 7500; - else if (board.givesCheck(move) != CheckType::NO_CHECK) - scores[i] = 7000; - else - scores[i] = session.historyHeuristic[move.from()][move.to()]; - } - size_t limit = std::min(n, size_t(12)); - for (size_t i = 0; i + 1 < limit; ++i) { - size_t best = i; - for (size_t j = i + 1; j < n; ++j) - if (scores[j] > scores[best]) - best = j; - if (best != i) { - std::swap(moves[i], moves[best]); - std::swap(scores[i], scores[best]); - } +void orderMoves(Board &board, Movelist &moves, Move ttMove, int ply, + const engine::search::Session &session, Move prevMove) { + Value scores[300]; + size_t n = moves.size(); + for (size_t i = 0; i < n; ++i) { + Move move = moves[i]; + if (move == ttMove) + scores[i] = 10000; + else if (board.isCapture(move)) { + Value s = see(board, move); + Value capturedVal = move.type_of() == EN_PASSANT + ? piece_value(PAWN) + : piece_value(board.at(move.to())); + Value attackerVal = piece_value(board.at(move.from())); + scores[i] = (s >= -50 ? 9000 : 4000) + std::max(s, Value(-50)) + + (capturedVal * 10 - attackerVal) / 100; + } else if (move == session.killerMoves[ply][0]) + scores[i] = 8500; + else if (move == session.killerMoves[ply][1]) + scores[i] = 8000; + else if (prevMove.is_ok() && + move == session.counterMoves[prevMove.from_to()]) + scores[i] = 7500; + else if (board.givesCheck(move) != CheckType::NO_CHECK) + scores[i] = 7000; + else + scores[i] = session.historyHeuristic[move.from()][move.to()]; + } + size_t limit = std::min(n, size_t(12)); + for (size_t i = 0; i + 1 < limit; ++i) { + size_t best = i; + for (size_t j = i + 1; j < n; ++j) + if (scores[j] > scores[best]) + best = j; + if (best != i) { + std::swap(moves[i], moves[best]); + std::swap(scores[i], scores[best]); } + } } } // namespace engine::movepick \ No newline at end of file diff --git a/movepick.h b/movepick.h index c7728ff..9798927 100644 --- a/movepick.h +++ b/movepick.h @@ -5,6 +5,7 @@ namespace engine::search { struct Session; } // namespace engine::search namespace engine::movepick { -void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int, const engine::search::Session &, chess::Move); +void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int, + const engine::search::Session &, chess::Move); Value see(chess::Board &, chess::Move); } // namespace engine::movepick diff --git a/score.cpp b/score.cpp index ca3a1c2..2d07d9e 100644 --- a/score.cpp +++ b/score.cpp @@ -3,16 +3,16 @@ #include namespace engine { Score::Score(Value v) { - assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); - if (!is_decisive(v)) { - score = InternalUnits{ v }; - } else if (std::abs(v) <= VALUE_TB) { - auto distance = VALUE_TB - std::abs(v); - score = (v > 0) ? Tablebase{ distance, true } : Tablebase{ -distance, false }; - } else { - auto distance = VALUE_MATE - std::abs(v); - score = (v > 0) ? Mate{ distance } : Mate{ -distance }; - } + if (!is_decisive(v)) { + score = InternalUnits{v}; + } else if (std::abs(v) <= VALUE_TB) { + auto distance = VALUE_TB - std::abs(v); + score = (v > 0) ? Tablebase{distance, true} : Tablebase{-distance, false}; + } else { + auto distance = VALUE_MATE - std::abs(v); + score = (v > 0) ? Mate{distance} : Mate{-distance}; + } } } // namespace engine diff --git a/score.h b/score.h index 34d01bd..2b7b0e1 100644 --- a/score.h +++ b/score.h @@ -25,31 +25,35 @@ namespace engine { class Score { - public: - struct Mate { - int plies; - }; +public: + struct Mate { + int plies; + }; - struct Tablebase { - int plies; - bool win; - }; + struct Tablebase { + int plies; + bool win; + }; - struct InternalUnits { - int value; - }; + struct InternalUnits { + int value; + }; - Score() = default; - Score(Value v); + Score() = default; + Score(Value v); - template bool is() const { return std::holds_alternative(score); } + template bool is() const { + return std::holds_alternative(score); + } - template T get() const { return std::get(score); } + template T get() const { return std::get(score); } - template decltype(auto) visit(F &&f) const { return std::visit(std::forward(f), score); } + template decltype(auto) visit(F &&f) const { + return std::visit(std::forward(f), score); + } - private: - std::variant score; +private: + std::variant score; }; } // namespace engine diff --git a/search.cpp b/search.cpp index 50062ff..f15eb1b 100644 --- a/search.cpp +++ b/search.cpp @@ -13,623 +13,669 @@ using namespace chess; namespace engine::search { TranspositionTable tt(16); -std::atomic stopSearch{ false }; +std::atomic stopSearch{false}; void stop() { stopSearch.store(true, std::memory_order_relaxed); } bool isStopped() { return stopSearch.load(std::memory_order_relaxed); } namespace { void update_pv(Move *pv, Move move, const Move *childPv) { - for (*pv++ = move; childPv && *childPv != Move::none();) - *pv++ = *childPv++; - *pv = Move::none(); + for (*pv++ = move; childPv && *childPv != Move::none();) + *pv++ = *childPv++; + *pv = Move::none(); +} +Value value_to_tt(Value v, int ply) { + return is_win(v) ? v + ply : is_loss(v) ? v - ply : v; } -Value value_to_tt(Value v, int ply) { return is_win(v) ? v + ply : is_loss(v) ? v - ply : v; } Value value_from_tt(Value v, int ply, int r50c) { - if (!is_valid(v)) - return VALUE_NONE; - if (is_win(v)) { - if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c) - return VALUE_TB_WIN_IN_MAX_PLY - 1; - if (VALUE_TB - v > 100 - r50c) - return VALUE_TB_WIN_IN_MAX_PLY - 1; - return v - ply; - } - if (is_loss(v)) { - if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c) - return VALUE_TB_LOSS_IN_MAX_PLY + 1; - if (VALUE_TB + v > 100 - r50c) - return VALUE_TB_LOSS_IN_MAX_PLY + 1; - return v + ply; - } - return v; + if (!is_valid(v)) + return VALUE_NONE; + if (is_win(v)) { + if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; + if (VALUE_TB - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; + return v - ply; + } + if (is_loss(v)) { + if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; + if (VALUE_TB + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; + return v + ply; + } + return v; } } // namespace -Value qsearch(Board &board, Value alpha, Value beta, search::Session &session, int ply) { - session.nodes++; - session.qnodes++; - session.seldepth = std::max(session.seldepth, ply); - if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || - stopSearch.load(std::memory_order_relaxed)) - return VALUE_NONE; - - bool inCheck = board.checkers(); +Value qsearch(Board &board, Value alpha, Value beta, search::Session &session, + int ply) { + session.nodes++; + session.qnodes++; + session.seldepth = std::max(session.seldepth, ply); + if (((session.nodes & 2047) == 0 && + session.tm.elapsed() >= session.tm.optimum()) || + stopSearch.load(std::memory_order_relaxed)) + return VALUE_NONE; + + bool inCheck = board.checkers(); + + if (ply >= MAX_PLY - 1) + return inCheck ? -MATE(ply) : eval::eval(board); + + Move ttMove = Move::none(); + TTEntry *entry = search::tt.lookup(board.hash()); + Value alphaOrig = alpha; + if (entry) { + session.ttHits++; + Value ttScore = value_from_tt(entry->getScore(), ply, board.rule50_count()); + + if (entry->getFlag() == TTFlag::EXACT) { + session.ttCutoffs++; + return ttScore; + } + if (entry->getFlag() == TTFlag::LOWERBOUND && ttScore >= beta) { + session.ttCutoffs++; + return ttScore; + } + if (entry->getFlag() == TTFlag::UPPERBOUND && ttScore <= alpha) { + session.ttCutoffs++; + return ttScore; + } + if (entry->getFlag() == TTFlag::LOWERBOUND) + alpha = std::max(alpha, ttScore); + else if (entry->getFlag() == TTFlag::UPPERBOUND) + beta = std::min(beta, ttScore); + if (alpha >= beta) { + session.ttCutoffs++; + return ttScore; + } + ttMove = Move(entry->getMove()); + } - if (ply >= MAX_PLY - 1) - return inCheck ? -MATE(ply) : eval::eval(board); + Movelist moves; - Move ttMove = Move::none(); - TTEntry *entry = search::tt.lookup(board.hash()); - Value alphaOrig = alpha; - if (entry) { - session.ttHits++; - Value ttScore = value_from_tt(entry->getScore(), ply, board.rule50_count()); + if (inCheck) + board.legals(moves); + else + board.legals(moves); + Value best = -VALUE_INFINITE; + Value standPat = VALUE_NONE; - if (entry->getFlag() == TTFlag::EXACT) { - session.ttCutoffs++; - return ttScore; - } - if (entry->getFlag() == TTFlag::LOWERBOUND && ttScore >= beta) { - session.ttCutoffs++; - return ttScore; - } - if (entry->getFlag() == TTFlag::UPPERBOUND && ttScore <= alpha) { - session.ttCutoffs++; - return ttScore; - } - if (entry->getFlag() == TTFlag::LOWERBOUND) - alpha = std::max(alpha, ttScore); - else if (entry->getFlag() == TTFlag::UPPERBOUND) - beta = std::min(beta, ttScore); - if (alpha >= beta) { - session.ttCutoffs++; - return ttScore; - } - ttMove = Move(entry->getMove()); - } + if (!inCheck) { + standPat = eval::eval(board); - Movelist moves; + if (standPat >= beta) + return standPat; - if (inCheck) - board.legals(moves); - else - board.legals(moves); - Value best = -VALUE_INFINITE; - Value standPat = VALUE_NONE; + if (standPat > alpha) + alpha = standPat; - if (!inCheck) { - standPat = eval::eval(board); + best = standPat; + } - if (standPat >= beta) - return standPat; + if (!moves.size()) + return inCheck ? -MATE(ply) : best; - if (standPat > alpha) - alpha = standPat; + movepick::orderMoves(board, moves, ttMove, ply, session, Move::none()); + int movesSearched = 0; + for (Move move : moves) { + bool isCapture = board.isCapture(move); + bool givesCheck = board.givesCheck(move) != CheckType::NO_CHECK; - best = standPat; + if (!inCheck) { + if (!isCapture && !givesCheck) + continue; + if (isCapture && !givesCheck && move.type_of() != PROMOTION) { + Value capturedValue = + move.type_of() == EN_PASSANT + ? eval::piece_value(PAWN) + : eval::piece_value(board.at(move.to())); + if (standPat + capturedValue + 200 < alpha) + continue; + if (movepick::see(board, move) < 0) + continue; + } + } else { + int plyFromDepth = ply - session.depth; + int maxUncond = plyFromDepth < 1 ? 6 : plyFromDepth < 4 ? 3 : 0; + if (movesSearched >= maxUncond && movesSearched >= 1) { + if (!isCapture && !givesCheck) + continue; + if (isCapture && !givesCheck && move.type_of() != PROMOTION && + movepick::see(board, move) < 0) + continue; + } } - if (!moves.size()) - return inCheck ? -MATE(ply) : best; - - movepick::orderMoves(board, moves, ttMove, ply, session, Move::none()); - int movesSearched = 0; - for (Move move : moves) { - bool isCapture = board.isCapture(move); - bool givesCheck = board.givesCheck(move) != CheckType::NO_CHECK; - - if (!inCheck) { - if (!isCapture && !givesCheck) - continue; - if (isCapture && !givesCheck && move.type_of() != PROMOTION) { - Value capturedValue = - move.type_of() == EN_PASSANT ? eval::piece_value(PAWN) : eval::piece_value(board.at(move.to())); - if (standPat + capturedValue + 200 < alpha) - continue; - if (movepick::see(board, move) < 0) - continue; - } - } else { - int plyFromDepth = ply - session.depth; - int maxUncond = plyFromDepth < 1 ? 6 : plyFromDepth < 4 ? 3 : 0; - if (movesSearched >= maxUncond && movesSearched >= 1) { - if (!isCapture && !givesCheck) - continue; - if (isCapture && !givesCheck && move.type_of() != PROMOTION && movepick::see(board, move) < 0) - continue; - } - } + board.doMove(move); - board.doMove(move); + Value score = -qsearch(board, -beta, -alpha, session, ply + 1); - Value score = -qsearch(board, -beta, -alpha, session, ply + 1); + board.undoMove(); - board.undoMove(); - - if (score == VALUE_NONE) - return VALUE_NONE; + if (score == VALUE_NONE) + return VALUE_NONE; - if (score > best) { - ttMove = move; - best = score; - } - if (score > alpha) - alpha = score; - if (alpha >= beta) - break; - - movesSearched++; + if (score > best) { + ttMove = move; + best = score; } - TTFlag flag = best >= beta ? TTFlag::LOWERBOUND : best <= alphaOrig ? TTFlag::UPPERBOUND : TTFlag::EXACT; - tt.store(board.hash(), ttMove, value_to_tt(best, ply), 0, flag); - return best; + if (score > alpha) + alpha = score; + if (alpha >= beta) + break; + + movesSearched++; + } + TTFlag flag = best >= beta ? TTFlag::LOWERBOUND + : best <= alphaOrig ? TTFlag::UPPERBOUND + : TTFlag::EXACT; + tt.store(board.hash(), ttMove, value_to_tt(best, ply), 0, flag); + return best; } -Value doSearch( - Board &board, int depth, Value alpha, Value beta, search::Session &session, int ply = 0, Move prevMove = Move::none()) { - session.nodes++; - session.seldepth = std::max(session.seldepth, ply); - if (ply >= MAX_PLY - 1) - return board.checkers() ? -MATE(ply) : eval::eval(board); - if (depth <= 0) - return qsearch(board, alpha, beta, session, ply); - if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || - stopSearch.load(std::memory_order_relaxed)) +Value doSearch(Board &board, int depth, Value alpha, Value beta, + search::Session &session, int ply = 0, + Move prevMove = Move::none()) { + session.nodes++; + session.seldepth = std::max(session.seldepth, ply); + if (ply >= MAX_PLY - 1) + return board.checkers() ? -MATE(ply) : eval::eval(board); + if (depth <= 0) + return qsearch(board, alpha, beta, session, ply); + if (((session.nodes & 2047) == 0 && + session.tm.elapsed() >= session.tm.optimum()) || + stopSearch.load(std::memory_order_relaxed)) + return VALUE_NONE; + + bool inCheck = board.checkers(); + + alpha = std::max(-MATE(ply), alpha); + beta = std::min(MATE(ply + 1), beta); + if (alpha >= beta) + return alpha; + + session.pv[ply][0] = Move::none(); + if (board.is_draw(2) || board.is_insufficient_material()) + return 0; + + Value alphaOrig = alpha; + uint64_t hash = board.hash(); + Move ttMove = Move::none(); + Value staticEval = eval::eval(board); + + TTEntry *entry = search::tt.lookup(hash); + if (entry) { + if (entry->getDepth() >= depth) { + session.ttHits++; + Value ttScore = + value_from_tt(entry->getScore(), ply, board.rule50_count()); + TTFlag flag = entry->getFlag(); + + if (flag == TTFlag::EXACT && ply > 0) { + session.ttCutoffs++; + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); + return ttScore; + } + if (flag == TTFlag::LOWERBOUND && ttScore >= beta && ply > 0) { + session.ttCutoffs++; + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); + return ttScore; + } + if (flag == TTFlag::UPPERBOUND && ttScore <= alpha && ply > 0) { + session.ttCutoffs++; + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); + return ttScore; + } + } + ttMove = Move(entry->getMove()); + } + + // Reverse futility pruning: if eval is well above beta, prune + if (!inCheck && ply > 0 && depth <= 3 && staticEval - 150 * depth >= beta && + !is_win(beta) && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) + return staticEval; + + // Razoring: if eval is far below alpha, try qsearch to verify + if (depth <= 2 && !inCheck && ply > 0 && !is_win(alpha)) { + Value razorMargin = Value(256 + 100 * depth); + if (staticEval + razorMargin < alpha) { + Value v = qsearch(board, alpha - 1, alpha, session, ply); + if (v == VALUE_NONE) return VALUE_NONE; - - bool inCheck = board.checkers(); - - alpha = std::max(-MATE(ply), alpha); - beta = std::min(MATE(ply + 1), beta); - if (alpha >= beta) - return alpha; - - session.pv[ply][0] = Move::none(); - if (board.is_draw(2) || board.is_insufficient_material()) - return 0; - - Value alphaOrig = alpha; - uint64_t hash = board.hash(); - Move ttMove = Move::none(); - Value staticEval = eval::eval(board); - - TTEntry *entry = search::tt.lookup(hash); - if (entry) { - if (entry->getDepth() >= depth) { - session.ttHits++; - Value ttScore = value_from_tt(entry->getScore(), ply, board.rule50_count()); - TTFlag flag = entry->getFlag(); - - if (flag == TTFlag::EXACT && ply > 0) { - session.ttCutoffs++; - session.pv[ply][0] = Move(entry->getMove()); - session.pv[ply][1] = Move::none(); - return ttScore; - } - if (flag == TTFlag::LOWERBOUND && ttScore >= beta && ply > 0) { - session.ttCutoffs++; - session.pv[ply][0] = Move(entry->getMove()); - session.pv[ply][1] = Move::none(); - return ttScore; - } - if (flag == TTFlag::UPPERBOUND && ttScore <= alpha && ply > 0) { - session.ttCutoffs++; - session.pv[ply][0] = Move(entry->getMove()); - session.pv[ply][1] = Move::none(); - return ttScore; - } - } - ttMove = Move(entry->getMove()); + if (v < alpha && std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) + return v; } - - // Reverse futility pruning: if eval is well above beta, prune - if (!inCheck && ply > 0 && depth <= 3 && staticEval - 150 * depth >= beta && !is_win(beta) && - std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) - return staticEval; - - // Razoring: if eval is far below alpha, try qsearch to verify - if (depth <= 2 && !inCheck && ply > 0 && !is_win(alpha)) { - Value razorMargin = Value(256 + 100 * depth); - if (staticEval + razorMargin < alpha) { - Value v = qsearch(board, alpha - 1, alpha, session, ply); - if (v == VALUE_NONE) - return VALUE_NONE; - if (v < alpha && std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) - return v; - } + } + + // Null move pruning (skip when a mate threat is possible) + if (depth >= 3 && !inCheck && ply > 0 && staticEval >= beta && + !is_win(beta)) { + int R = 2 + depth / 6 + std::min(2, depth / 10); + board.doNullMove(); + Value score = doSearch(board, depth - 1 - R, -beta, -beta + 1, session, + ply + 1, Move::none()); + board.undoMove(); + if (score == VALUE_NONE) + return VALUE_NONE; + score = -score; + if (score >= beta) { + session.nullCutoffs++; + return score; } - - // Null move pruning (skip when a mate threat is possible) - if (depth >= 3 && !inCheck && ply > 0 && staticEval >= beta && !is_win(beta)) { - int R = 2 + depth / 6 + std::min(2, depth / 10); - board.doNullMove(); - Value score = doSearch(board, depth - 1 - R, -beta, -beta + 1, session, ply + 1, Move::none()); - board.undoMove(); - if (score == VALUE_NONE) - return VALUE_NONE; - score = -score; - if (score >= beta) { - session.nullCutoffs++; - return score; - } + } + + // ProbCut: if eval is well above beta, verify with reduced-depth search + if (depth >= 5 && !inCheck && !is_win(beta) && + std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) { + Value probCutMargin = Value(200 + 100 * (depth - 5)); + if (staticEval >= beta + probCutMargin) { + Value v = + doSearch(board, depth - 2, beta - 1, beta, session, ply, prevMove); + if (v == VALUE_NONE) + return VALUE_NONE; + if (v >= beta) + return v; } - - // ProbCut: if eval is well above beta, verify with reduced-depth search - if (depth >= 5 && !inCheck && !is_win(beta) && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) { - Value probCutMargin = Value(200 + 100 * (depth - 5)); - if (staticEval >= beta + probCutMargin) { - Value v = doSearch(board, depth - 2, beta - 1, beta, session, ply, prevMove); - if (v == VALUE_NONE) - return VALUE_NONE; - if (v >= beta) - return v; + } + + // Existing static null-move / futility pruning + if (!inCheck && staticEval < alpha - 512 - 293 * depth * depth) { + Value value = qsearch(board, alpha - 1, alpha, session, ply); + if (value == VALUE_NONE) + return VALUE_NONE; + if (value < alpha && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) + return value; + } + + // Tablebase probing (unchanged) + if (ply != 0) { + if (popcount(board.occ()) <= 7 && board.castlingRights() == NO_CASTLING) { + int wdl = engine::tb::probe_wdl(board); + if (wdl != engine::tb::TB_ERROR) { + session.tbHits++; + + int drawScore = 1; + + Value tbValue = VALUE_TB - ply; + + Value value = wdl < -drawScore ? -tbValue + : wdl > drawScore ? tbValue + : VALUE_DRAW + 2 * wdl * drawScore; + TTFlag b = wdl < -drawScore ? TTFlag::UPPERBOUND + : wdl > drawScore ? TTFlag::LOWERBOUND + : TTFlag::EXACT; + if (b == TTFlag::EXACT || + (b == TTFlag::LOWERBOUND ? value >= beta : value <= alpha)) { + tt.store(hash, Move::none(), value_to_tt(value, ply), + std::min(MAX_PLY - 1, depth + 6), b); + return value; } - } - // Existing static null-move / futility pruning - if (!inCheck && staticEval < alpha - 512 - 293 * depth * depth) { - Value value = qsearch(board, alpha - 1, alpha, session, ply); - if (value == VALUE_NONE) - return VALUE_NONE; - if (value < alpha && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) - return value; + if (b == TTFlag::LOWERBOUND) + alpha = std::max(alpha, value); + } } + } - // Tablebase probing (unchanged) - if (ply != 0) { - if (popcount(board.occ()) <= 7 && board.castlingRights() == NO_CASTLING) { - int wdl = engine::tb::probe_wdl(board); - if (wdl != engine::tb::TB_ERROR) { - session.tbHits++; + Movelist moves; + board.legals(moves); + if (!moves.size()) { + session.pv[ply][0] = Move::none(); + return board.checkers() ? -MATE(ply) : 0; + } + movepick::orderMoves(board, moves, ttMove, ply, session, prevMove); + + // Internal Iterative Deepening (IID): get a TT move when we don't have one + if (depth >= 8 && ttMove == Move::none() && !inCheck && alpha != beta - 1) { + int d = std::max(2, depth - 2 - depth / 4); + doSearch(board, d, alpha, beta, session, ply, prevMove); + if (TTEntry *e = search::tt.lookup(hash)) + ttMove = Move(e->getMove()); + } + + // Singular Extension: extend TT move when it dominates all others + int singularExt = 0; + if (depth >= 12 && ttMove.is_ok() && !inCheck && ply > 0 && entry && + entry->getDepth() >= depth - 4 && + entry->getFlag() != TTFlag::UPPERBOUND && alpha != beta - 1 && + !is_win(beta)) { + Value sBeta = std::max(staticEval - 2 * depth, Value(-VALUE_MATE)); + int r = std::max(2, depth / 4); + if (moves.size() <= 5) { + bool singular = true; + for (size_t si = 0; si < moves.size() && singular; ++si) { + if (moves[si] == ttMove) + continue; + board.doMove(moves[si]); + Value v = + doSearch(board, r, sBeta - 1, sBeta, session, ply + 1, moves[si]); + board.undoMove(); + if (v == VALUE_NONE) { + board.undoMove(); + singular = false; + break; + } + v = -v; + if (v >= sBeta) + singular = false; + } + if (singular) + singularExt = 1; + } + } - int drawScore = 1; + Value maxScore = -VALUE_INFINITE; + int movesSearched = 0; - Value tbValue = VALUE_TB - ply; + for (size_t i = 0; i < moves.size(); ++i) { + Move move = moves[i]; - Value value = wdl < -drawScore ? -tbValue : wdl > drawScore ? tbValue : VALUE_DRAW + 2 * wdl * drawScore; - TTFlag b = wdl < -drawScore ? TTFlag::UPPERBOUND : wdl > drawScore ? TTFlag::LOWERBOUND : TTFlag::EXACT; - if (b == TTFlag::EXACT || (b == TTFlag::LOWERBOUND ? value >= beta : value <= alpha)) { - tt.store(hash, Move::none(), value_to_tt(value, ply), std::min(MAX_PLY - 1, depth + 6), b); - return value; - } + bool isCapture = board.isCapture(move); + bool givesCheck = board.givesCheck(move) != CheckType::NO_CHECK; - if (b == TTFlag::LOWERBOUND) - alpha = std::max(alpha, value); - } - } + // Futility pruning at shallow depths + if (!inCheck && !isCapture && !givesCheck && depth <= 2 && ply > 0 && + movesSearched > 0) { + Value margin = Value(128 + 128 * depth); + if (staticEval + margin <= alpha && + std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) + continue; } - Movelist moves; - board.legals(moves); - if (!moves.size()) { - session.pv[ply][0] = Move::none(); - return board.checkers() ? -MATE(ply) : 0; - } - movepick::orderMoves(board, moves, ttMove, ply, session, prevMove); - - // Internal Iterative Deepening (IID): get a TT move when we don't have one - if (depth >= 8 && ttMove == Move::none() && !inCheck && alpha != beta - 1) { - int d = std::max(2, depth - 2 - depth / 4); - doSearch(board, d, alpha, beta, session, ply, prevMove); - if (TTEntry *e = search::tt.lookup(hash)) - ttMove = Move(e->getMove()); + // Late move pruning at very shallow depths + if (!inCheck && !isCapture && !givesCheck && depth <= 2 && + movesSearched > 3 + 2 * depth && + std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) + continue; + + // SEE pruning for losing captures at shallow depths + if (!inCheck && isCapture && !givesCheck && depth <= 2 && + movesSearched > 0 && move.type_of() != PROMOTION && + std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) { + if (movepick::see(board, move) < 0) + continue; } - // Singular Extension: extend TT move when it dominates all others - int singularExt = 0; - if (depth >= 12 && ttMove.is_ok() && !inCheck && ply > 0 && entry && entry->getDepth() >= depth - 4 && - entry->getFlag() != TTFlag::UPPERBOUND && alpha != beta - 1 && !is_win(beta)) { - Value sBeta = std::max(staticEval - 2 * depth, Value(-VALUE_MATE)); - int r = std::max(2, depth / 4); - if (moves.size() <= 5) { - bool singular = true; - for (size_t si = 0; si < moves.size() && singular; ++si) { - if (moves[si] == ttMove) - continue; - board.doMove(moves[si]); - Value v = doSearch(board, r, sBeta - 1, sBeta, session, ply + 1, moves[si]); - board.undoMove(); - if (v == VALUE_NONE) { - board.undoMove(); - singular = false; - break; - } - v = -v; - if (v >= sBeta) - singular = false; - } - if (singular) - singularExt = 1; - } + // LMR reduction + int reduction = 0; + if (movesSearched >= 2 && depth >= 3) { + if (!isCapture && !givesCheck) { + reduction = 1 + movesSearched / 5 + depth / 7; + int history = + session.historyHeuristic[(int)move.from()][(int)move.to()]; + if (history > 0) + reduction--; + else if (history < 0) + reduction++; + if (move == session.killerMoves[ply][0] || + move == session.killerMoves[ply][1]) + reduction--; + if (prevMove.is_ok() && + move == session.counterMoves[prevMove.from_to()]) + reduction--; + if (staticEval + 50 < alphaOrig) + reduction++; + else if (staticEval - 50 >= alphaOrig) + reduction--; + } else if (movesSearched >= 6) { + reduction = 1 + movesSearched / 8; + } + reduction = std::clamp(reduction, 1, depth - 2); } - Value maxScore = -VALUE_INFINITE; - int movesSearched = 0; - - for (size_t i = 0; i < moves.size(); ++i) { - Move move = moves[i]; - - bool isCapture = board.isCapture(move); - bool givesCheck = board.givesCheck(move) != CheckType::NO_CHECK; - - // Futility pruning at shallow depths - if (!inCheck && !isCapture && !givesCheck && depth <= 2 && ply > 0 && movesSearched > 0) { - Value margin = Value(128 + 128 * depth); - if (staticEval + margin <= alpha && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) - continue; - } - - // Late move pruning at very shallow depths - if (!inCheck && !isCapture && !givesCheck && depth <= 2 && movesSearched > 3 + 2 * depth && - std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) - continue; + int ext = 0; + if (singularExt && movesSearched == 0) + ext = 1; + if (ext == 0 && moves.size() == 1) + ext = 1; + if (ext == 0 && isCapture && movesSearched == 0) + ext = 1; - // SEE pruning for losing captures at shallow depths - if (!inCheck && isCapture && !givesCheck && depth <= 2 && movesSearched > 0 && move.type_of() != PROMOTION && - std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) { - if (movepick::see(board, move) < 0) - continue; - } + board.doMove(move); - // LMR reduction - int reduction = 0; - if (movesSearched >= 2 && depth >= 3) { - if (!isCapture && !givesCheck) { - reduction = 1 + movesSearched / 5 + depth / 7; - int history = session.historyHeuristic[(int)move.from()][(int)move.to()]; - if (history > 0) - reduction--; - else if (history < 0) - reduction++; - if (move == session.killerMoves[ply][0] || move == session.killerMoves[ply][1]) - reduction--; - if (prevMove.is_ok() && move == session.counterMoves[prevMove.from_to()]) - reduction--; - if (staticEval + 50 < alphaOrig) - reduction++; - else if (staticEval - 50 >= alphaOrig) - reduction--; - } else if (movesSearched >= 6) { - reduction = 1 + movesSearched / 8; - } - reduction = std::clamp(reduction, 1, depth - 2); - } - - int ext = 0; - if (singularExt && movesSearched == 0) - ext = 1; - if (ext == 0 && moves.size() == 1) - ext = 1; - if (ext == 0 && isCapture && movesSearched == 0) - ext = 1; - - board.doMove(move); - - Value score; - - if (movesSearched == 0 || reduction == 0) { - score = doSearch(board, depth - 1 + ext, -beta, -alpha, session, ply + 1, move); - if (score == VALUE_NONE) { - board.undoMove(); - score = -VALUE_INFINITE; - break; - } - score = -score; - } else { - int d = depth - 1 - reduction + ext; - score = doSearch(board, d, -alpha - 1, -alpha, session, ply + 1, move); - if (score == VALUE_NONE) { - board.undoMove(); - score = -VALUE_INFINITE; - break; - } - score = -score; - if (score > alpha && reduction) { - session.lmrResearches++; - score = doSearch(board, depth - 1 + ext, -beta, -alpha, session, ply + 1, move); - if (score == VALUE_NONE) { - board.undoMove(); - score = -VALUE_INFINITE; - break; - } - score = -score; - } - } + Value score; + if (movesSearched == 0 || reduction == 0) { + score = doSearch(board, depth - 1 + ext, -beta, -alpha, session, ply + 1, + move); + if (score == VALUE_NONE) { board.undoMove(); - movesSearched++; - - if (score > maxScore) { - maxScore = score; - update_pv(session.pv[ply], move, session.pv[ply + 1]); + score = -VALUE_INFINITE; + break; + } + score = -score; + } else { + int d = depth - 1 - reduction + ext; + score = doSearch(board, d, -alpha - 1, -alpha, session, ply + 1, move); + if (score == VALUE_NONE) { + board.undoMove(); + score = -VALUE_INFINITE; + break; + } + score = -score; + if (score > alpha && reduction) { + session.lmrResearches++; + score = doSearch(board, depth - 1 + ext, -beta, -alpha, session, + ply + 1, move); + if (score == VALUE_NONE) { + board.undoMove(); + score = -VALUE_INFINITE; + break; } + score = -score; + } + } - if (score > alpha) { - alpha = score; - - if (!isCapture) { - int bonus = depth * depth; - if (is_win(score)) - bonus += 4 * depth * depth; - session.historyHeuristic[(int)move.from()][(int)move.to()] = - std::clamp(session.historyHeuristic[(int)move.from()][(int)move.to()] + bonus, -16384, 16384); - } - } + board.undoMove(); + movesSearched++; - if (alpha >= beta) { - if (!isCapture) { - if (session.killerMoves[ply][0] != move) { - session.killerMoves[ply][1] = session.killerMoves[ply][0]; - session.killerMoves[ply][0] = move; - } - if (prevMove.is_ok()) - session.counterMoves[prevMove.from_to()] = move; - } - break; - } + if (score > maxScore) { + maxScore = score; + update_pv(session.pv[ply], move, session.pv[ply + 1]); + } - if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || - stopSearch.load(std::memory_order_relaxed)) - return VALUE_NONE; + if (score > alpha) { + alpha = score; + + if (!isCapture) { + int bonus = depth * depth; + if (is_win(score)) + bonus += 4 * depth * depth; + session.historyHeuristic[(int)move.from()][(int)move.to()] = std::clamp( + session.historyHeuristic[(int)move.from()][(int)move.to()] + bonus, + -16384, 16384); + } } - if (maxScore != -VALUE_INFINITE) { - TTFlag flag = maxScore >= beta ? TTFlag::LOWERBOUND : maxScore <= alphaOrig ? TTFlag::UPPERBOUND : TTFlag::EXACT; - tt.store(hash, session.pv[ply][0], value_to_tt(maxScore, ply), depth, flag); + if (alpha >= beta) { + if (!isCapture) { + if (session.killerMoves[ply][0] != move) { + session.killerMoves[ply][1] = session.killerMoves[ply][0]; + session.killerMoves[ply][0] = move; + } + if (prevMove.is_ok()) + session.counterMoves[prevMove.from_to()] = move; + } + break; } - return maxScore; + + if (((session.nodes & 2047) == 0 && + session.tm.elapsed() >= session.tm.optimum()) || + stopSearch.load(std::memory_order_relaxed)) + return VALUE_NONE; + } + if (maxScore != -VALUE_INFINITE) { + TTFlag flag = maxScore >= beta ? TTFlag::LOWERBOUND + : maxScore <= alphaOrig ? TTFlag::UPPERBOUND + : TTFlag::EXACT; + + tt.store(hash, session.pv[ply][0], value_to_tt(maxScore, ply), depth, flag); + } + return maxScore; } std::string extract_pv(const chess::Board &root, int maxPly) { - std::string pv; - chess::Board pos = root; - uint64_t cycle[64]{}; - for (int ply = 0; ply < maxPly; ply++) { - TTEntry *e = search::tt.lookup(pos.hash()); - if (!e) - break; - chess::Move m(e->getMove()); - if (!m.is_ok()) - break; - uint64_t h = pos.hash(); - int idx = (h >> 6) & 0x3F; - uint64_t bit = 1ULL << (h & 0x3F); - if (cycle[idx] & bit) - break; - cycle[idx] |= bit; - chess::Movelist ml; - pos.legals(ml); - bool legal = false; - for (size_t i = 0; i < ml.size(); i++) - if (ml[i] == m) { - legal = true; - break; - } - if (!legal) - break; - pv += chess::uci::moveToUci(m, root.chess960()) + " "; - if (ply + 1 >= maxPly) - break; - pos.doMove(m); - } - return pv; + std::string pv; + chess::Board pos = root; + uint64_t cycle[64]{}; + for (int ply = 0; ply < maxPly; ply++) { + TTEntry *e = search::tt.lookup(pos.hash()); + if (!e) + break; + chess::Move m(e->getMove()); + if (!m.is_ok()) + break; + uint64_t h = pos.hash(); + int idx = (h >> 6) & 0x3F; + uint64_t bit = 1ULL << (h & 0x3F); + if (cycle[idx] & bit) + break; + cycle[idx] |= bit; + chess::Movelist ml; + pos.legals(ml); + bool legal = false; + for (size_t i = 0; i < ml.size(); i++) + if (ml[i] == m) { + legal = true; + break; + } + if (!legal) + break; + pv += chess::uci::moveToUci(m, root.chess960()) + " "; + if (ply + 1 >= maxPly) + break; + pos.doMove(m); + } + return pv; } void search(const chess::Board &board, const timeman::LimitsType timecontrol) { - stopSearch = false; - tt.newSearch(); - static double originalTimeAdjust = -1; - Session session; - session.tc = timecontrol; - session.tm.init(session.tc, board.side_to_move(), 0, originalTimeAdjust); + stopSearch = false; + tt.newSearch(); + static double originalTimeAdjust = -1; + Session session; + session.tc = timecontrol; + session.tm.init(session.tc, board.side_to_move(), 0, originalTimeAdjust); + session.lastLogTime = session.tm.elapsed(); + session.ogcolor = board.side_to_move(); + chess::Move lastPV[MAX_PLY]{}; + Value prevScore = VALUE_NONE; + + for (int i = 1; i <= timecontrol.depth; i++) { session.lastLogTime = session.tm.elapsed(); - session.ogcolor = board.side_to_move(); - chess::Move lastPV[MAX_PLY]{}; - Value prevScore = VALUE_NONE; - - for (int i = 1; i <= timecontrol.depth; i++) { - session.lastLogTime = session.tm.elapsed(); - session.depth = i; - for (int _ = 0; _ < 64; _++) - for (int j = 0; j < 64; j++) - session.historyHeuristic[_][j] /= 2; - auto board_ = board; - Value score_; - - if (i >= 3 && prevScore != VALUE_NONE && !is_win(prevScore) && !is_loss(prevScore)) { - Value delta = Value(20 + i * 5); - Value alpha0 = std::max(prevScore - delta, -VALUE_INFINITE); - Value beta0 = std::min(prevScore + delta, VALUE_INFINITE); - - score_ = doSearch(board_, i, alpha0, beta0, session); - - if (score_ != VALUE_NONE && score_ <= alpha0) { - score_ = doSearch(board_, i, -VALUE_INFINITE, beta0, session); - if (score_ != VALUE_NONE && score_ <= alpha0) - score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); - } else if (score_ != VALUE_NONE && score_ >= beta0) { - score_ = doSearch(board_, i, alpha0, VALUE_INFINITE, session); - if (score_ != VALUE_NONE && score_ >= beta0) - score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); - } - } else { - score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + session.depth = i; + for (int _ = 0; _ < 64; _++) + for (int j = 0; j < 64; j++) + session.historyHeuristic[_][j] /= 2; + auto board_ = board; + Value score_; + + if (i >= 3 && prevScore != VALUE_NONE && !is_win(prevScore) && + !is_loss(prevScore)) { + Value delta = Value(20 + i * 5); + Value alpha0 = std::max(prevScore - delta, -VALUE_INFINITE); + Value beta0 = std::min(prevScore + delta, VALUE_INFINITE); + + score_ = doSearch(board_, i, alpha0, beta0, session); + + if (score_ != VALUE_NONE && score_ <= alpha0) { + score_ = doSearch(board_, i, -VALUE_INFINITE, beta0, session); + if (score_ != VALUE_NONE && score_ <= alpha0) + score_ = + doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + } else if (score_ != VALUE_NONE && score_ >= beta0) { + score_ = doSearch(board_, i, alpha0, VALUE_INFINITE, session); + if (score_ != VALUE_NONE && score_ >= beta0) + score_ = + doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + } + } else { + score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + } + prevScore = score_; + if (session.tm.elapsed() >= session.tm.optimum() || + session.tm.elapsed() >= session.tm.maximum() || + stopSearch.load(std::memory_order_relaxed) || score_ == VALUE_NONE) + break; + InfoFull info{}; + info.depth = i; + info.selDepth = session.seldepth; + info.hashfull = tt.hashfull(); + info.nodes = session.nodes; + info.nps = session.nodes * 1000 / + std::max(session.tm.elapsed(), (timeman::TimePoint)1); + info.timeMs = session.tm.elapsed(); + info.tbHits = session.tbHits; + info.multiPV = 1; + info.score = score_; + TTEntry *entry = tt.lookup(board.hash()); + if (entry) + switch (entry->getFlag()) { + case LOWERBOUND: + info.bound = "lowerbound"; + break; + case UPPERBOUND: + info.bound = "upperbound"; + break; + default: + break; + } + info.pv = extract_pv(board, 2 * i + 4); + // Save first move from PV for bestmove output + std::string pvStr = info.pv; + size_t sp = pvStr.find(' '); + std::string firstMove = + (sp == std::string::npos) ? pvStr : pvStr.substr(0, sp); + if (!firstMove.empty()) + lastPV[0] = chess::Move(chess::uci::uciToMove(board, firstMove).raw()); + std::stringstream ss; + ss << "qnodes " << session.qnodes << " lmrResearches " + << session.lmrResearches << " ttHits " << session.ttHits << " ttCutoffs " + << session.ttCutoffs << " nullCutoffs " << session.nullCutoffs; + info.extrainfo = ss.str(); + report(info); + } + if (lastPV[0].is_ok()) + report(chess::uci::moveToUci(lastPV[0], board.chess960())); + else { + std::cerr << "info string Warning: Did not search\n"; + TTEntry *entry = tt.lookup(board.hash()); + if (entry && entry->getMove() != Move::none().raw()) + report(chess::uci::moveToUci(Move(entry->getMove()), board.chess960())); + else { + Movelist moves; + board.legals(moves); + + if (moves.size()) { + Board board_ = board; + Move best = moves[0]; + Value bestScore = -VALUE_INFINITE; + Session tmpSession{}; + for (Move move : moves) { + board_.doMove(move); + Value score = + -qsearch(board_, -VALUE_INFINITE, VALUE_INFINITE, tmpSession, 0); + if (score > bestScore) { + bestScore = score; + best = move; + } + board_.undoMove(); } - prevScore = score_; - if (session.tm.elapsed() >= session.tm.optimum() || session.tm.elapsed() >= session.tm.maximum() || - stopSearch.load(std::memory_order_relaxed) || score_ == VALUE_NONE) - break; + InfoFull info{}; - info.depth = i; - info.selDepth = session.seldepth; - info.hashfull = tt.hashfull(); - info.nodes = session.nodes; - info.nps = session.nodes * 1000 / std::max(session.tm.elapsed(), (timeman::TimePoint)1); - info.timeMs = session.tm.elapsed(); - info.tbHits = session.tbHits; + info.depth = 1; + info.nodes = 1; + info.score = 0; info.multiPV = 1; - info.score = score_; - TTEntry *entry = tt.lookup(board.hash()); - if (entry) - switch (entry->getFlag()) { - case LOWERBOUND: - info.bound = "lowerbound"; - break; - case UPPERBOUND: - info.bound = "upperbound"; - break; - default: - break; - } - info.pv = extract_pv(board, 2 * i + 4); - // Save first move from PV for bestmove output - std::string pvStr = info.pv; - size_t sp = pvStr.find(' '); - std::string firstMove = (sp == std::string::npos) ? pvStr : pvStr.substr(0, sp); - if (!firstMove.empty()) - lastPV[0] = chess::Move(chess::uci::uciToMove(board, firstMove).raw()); - std::stringstream ss; - ss << "qnodes " << session.qnodes << " lmrResearches " << session.lmrResearches << " ttHits " << session.ttHits - << " ttCutoffs " << session.ttCutoffs << " nullCutoffs " << session.nullCutoffs; - info.extrainfo = ss.str(); + info.pv = std::string(chess::uci::moveToUci(best, board.chess960())); report(info); + + report(chess::uci::moveToUci(best, board.chess960())); + } else { + report("0000"); + } } - if (lastPV[0].is_ok()) - report(chess::uci::moveToUci(lastPV[0], board.chess960())); - else { - std::cerr << "info string Warning: Did not search\n"; - TTEntry *entry = tt.lookup(board.hash()); - if (entry && entry->getMove() != Move::none().raw()) - report(chess::uci::moveToUci(Move(entry->getMove()), board.chess960())); - else { - Movelist moves; - board.legals(moves); - - if (moves.size()) { - Board board_ = board; - Move best = moves[0]; - Value bestScore = -VALUE_INFINITE; - Session tmpSession{}; - for (Move move : moves) { - board_.doMove(move); - Value score = -qsearch(board_, -VALUE_INFINITE, VALUE_INFINITE, tmpSession, 0); - if (score > bestScore) { - bestScore = score; - best = move; - } - board_.undoMove(); - } - - InfoFull info{}; - info.depth = 1; - info.nodes = 1; - info.score = 0; - info.multiPV = 1; - info.pv = std::string(chess::uci::moveToUci(best, board.chess960())); - report(info); - - report(chess::uci::moveToUci(best, board.chess960())); - } else { - report("0000"); - } - } - } + } } } // namespace engine::search \ No newline at end of file diff --git a/search.h b/search.h index be1c304..dbbe5e0 100644 --- a/search.h +++ b/search.h @@ -5,18 +5,18 @@ #include namespace engine::search { struct Session { - timeman::TimeManagement tm; - timeman::LimitsType tc; - int seldepth = 0; - uint64_t nodes = 0, qnodes = 0, lmrResearches = 0; - uint64_t tbHits = 0, ttHits = 0, ttCutoffs = 0, nullCutoffs = 0; - chess::Move pv[MAX_PLY][MAX_PLY]; - Value historyHeuristic[chess::SQUARE_NB][chess::SQUARE_NB]{}; - chess::Move killerMoves[MAX_PLY][2]; - chess::Move counterMoves[4096]; - timeman::TimePoint lastLogTime; - int depth = 0; - chess::Color ogcolor; + timeman::TimeManagement tm; + timeman::LimitsType tc; + int seldepth = 0; + uint64_t nodes = 0, qnodes = 0, lmrResearches = 0; + uint64_t tbHits = 0, ttHits = 0, ttCutoffs = 0, nullCutoffs = 0; + chess::Move pv[MAX_PLY][MAX_PLY]; + Value historyHeuristic[chess::SQUARE_NB][chess::SQUARE_NB]{}; + chess::Move killerMoves[MAX_PLY][2]; + chess::Move counterMoves[4096]; + timeman::TimePoint lastLogTime; + int depth = 0; + chess::Color ogcolor; }; void stop(); void search(const chess::Board &, const timeman::LimitsType); diff --git a/tb.cpp b/tb.cpp index 61d0efa..27e98d3 100644 --- a/tb.cpp +++ b/tb.cpp @@ -9,28 +9,30 @@ namespace engine::tb { tbprobe::syzygy::Tablebase tablebase; namespace { -std::size_t unique_table_count(const std::unordered_map &tables) { - std::unordered_set uniqueTables; +std::size_t unique_table_count( + const std::unordered_map &tables) { + std::unordered_set uniqueTables; - for (const auto &[_, table] : tables) - if (table != nullptr) - uniqueTables.insert(table); + for (const auto &[_, table] : tables) + if (table != nullptr) + uniqueTables.insert(table); - return uniqueTables.size(); + return uniqueTables.size(); } } // namespace void init(const std::string &path) { - tablebase.close(); + tablebase.close(); - if (path.empty()) { - std::cout << "info string Found 0 WDL and 0 DTZ tablebase files\n"; - return; - } + if (path.empty()) { + std::cout << "info string Found 0 WDL and 0 DTZ tablebase files\n"; + return; + } - tbprobe::syzygy::initialize(); - tablebase.add_directory(path, true, true); - std::cout << "info string Found " << wdl_count() << " WDL and " << dtz_count() << " DTZ tablebase files\n"; + tbprobe::syzygy::initialize(); + tablebase.add_directory(path, true, true); + std::cout << "info string Found " << wdl_count() << " WDL and " << dtz_count() + << " DTZ tablebase files\n"; } std::size_t wdl_count() { return unique_table_count(tablebase.wdl); } @@ -38,18 +40,18 @@ std::size_t wdl_count() { return unique_table_count(tablebase.wdl); } std::size_t dtz_count() { return unique_table_count(tablebase.dtz); } int probe_wdl(chess::Board &board) { - try { - return *tablebase.get_wdl(board, TB_ERROR); - } catch (const std::exception &) { - return TB_ERROR; - } + try { + return *tablebase.get_wdl(board, TB_ERROR); + } catch (const std::exception &) { + return TB_ERROR; + } } int probe_dtz(chess::Board &board) { - try { - return *tablebase.get_dtz(board, TB_ERROR); - } catch (const std::exception &) { - return TB_ERROR; - } + try { + return *tablebase.get_dtz(board, TB_ERROR); + } catch (const std::exception &) { + return TB_ERROR; + } } } // namespace engine::tb diff --git a/timeman.cpp b/timeman.cpp index 3bd6469..b47bad9 100644 --- a/timeman.cpp +++ b/timeman.cpp @@ -18,67 +18,77 @@ void TimeManagement::clear() {} // the bounds of time allowed for the current game ply. We currently support: // 1) x basetime (+ z increment) // 2) x moves in y seconds (+ z increment) -void TimeManagement::init(LimitsType &limits, chess::Color us, int ply, double &originalTimeAdjust) { +void TimeManagement::init(LimitsType &limits, chess::Color us, int ply, + double &originalTimeAdjust) { - // If we have no time, we don't need to fully initialize TM. - // startTime is used by movetime and useNodesTime is used in elapsed calls. - startTime = limits.startTime; - if (limits.movetime != 0 && limits.time[us] == 0) { - optimumTime = maximumTime = TimePoint(limits.movetime); - return; - } - if (limits.time[us] == 0 && limits.movetime == 0) { - optimumTime = maximumTime = INFINITE_TIME; - return; - } - // optScale is a percentage of available time to use for the current move. - // maxScale is a multiplier applied to optimumTime. - double optScale, maxScale; + // If we have no time, we don't need to fully initialize TM. + // startTime is used by movetime and useNodesTime is used in elapsed calls. + startTime = limits.startTime; + if (limits.movetime != 0 && limits.time[us] == 0) { + optimumTime = maximumTime = TimePoint(limits.movetime); + return; + } + if (limits.time[us] == 0 && limits.movetime == 0) { + optimumTime = maximumTime = INFINITE_TIME; + return; + } + // optScale is a percentage of available time to use for the current move. + // maxScale is a multiplier applied to optimumTime. + double optScale, maxScale; - // These numbers are used where multiplications, divisions or comparisons - // with constants are involved. - const TimePoint time = limits.time[us]; - const int moveOverhead = options["Move Overhead"]; - // Maximum move horizon - int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051; + // These numbers are used where multiplications, divisions or comparisons + // with constants are involved. + const TimePoint time = limits.time[us]; + const int moveOverhead = options["Move Overhead"]; + // Maximum move horizon + int centiMTG = + limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051; - // If less than one second, gradually reduce mtg - if (time < 1000) - centiMTG = int(time * 5.051); + // If less than one second, gradually reduce mtg + if (time < 1000) + centiMTG = int(time * 5.051); - // Make sure timeLeft is > 0 since we may use it as a divisor - TimePoint timeLeft = - std::max(TimePoint(1), time + (limits.inc[us] * (centiMTG - 100) - moveOverhead * (200 + centiMTG)) / 100); + // Make sure timeLeft is > 0 since we may use it as a divisor + TimePoint timeLeft = + std::max(TimePoint(1), time + (limits.inc[us] * (centiMTG - 100) - + moveOverhead * (200 + centiMTG)) / + 100); - // x basetime (+ z increment) - // If there is a healthy increment, timeLeft can exceed the actual available - // game time for the current move, so also cap to a percentage of available - // game time. - if (limits.movestogo == 0) { - // Extra time according to timeLeft - if (originalTimeAdjust < 0) - originalTimeAdjust = 0.3128 * std::log10(timeLeft) - 0.4354; + // x basetime (+ z increment) + // If there is a healthy increment, timeLeft can exceed the actual available + // game time for the current move, so also cap to a percentage of available + // game time. + if (limits.movestogo == 0) { + // Extra time according to timeLeft + if (originalTimeAdjust < 0) + originalTimeAdjust = 0.3128 * std::log10(timeLeft) - 0.4354; - // Calculate time constants based on current time left. - double logTimeInSec = std::log10(time / 1000.0); - double optConstant = std::min(0.0032116 + 0.000321123 * logTimeInSec, 0.00508017); - double maxConstant = std::max(3.3977 + 3.03950 * logTimeInSec, 2.94761); + // Calculate time constants based on current time left. + double logTimeInSec = std::log10(time / 1000.0); + double optConstant = + std::min(0.0032116 + 0.000321123 * logTimeInSec, 0.00508017); + double maxConstant = std::max(3.3977 + 3.03950 * logTimeInSec, 2.94761); - optScale = std::min(0.0121431 + std::pow(ply + 2.94693, 0.461073) * optConstant, 0.213035 * time / timeLeft) * - originalTimeAdjust; + optScale = + std::min(0.0121431 + std::pow(ply + 2.94693, 0.461073) * optConstant, + 0.213035 * time / timeLeft) * + originalTimeAdjust; - maxScale = std::min(6.67704, maxConstant + ply / 11.9847); - } + maxScale = std::min(6.67704, maxConstant + ply / 11.9847); + } - // x moves in y seconds (+ z increment) - else { - optScale = std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), 0.88 * time / timeLeft); - maxScale = 1.3 + 0.11 * (centiMTG / 100.0); - } + // x moves in y seconds (+ z increment) + else { + optScale = std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), + 0.88 * time / timeLeft); + maxScale = 1.3 + 0.11 * (centiMTG / 100.0); + } - // Limit the maximum possible time for this move - optimumTime = TimePoint(optScale * timeLeft); - maximumTime = TimePoint(std::min(0.825179 * time - moveOverhead, maxScale * optimumTime)) - 10; + // Limit the maximum possible time for this move + optimumTime = TimePoint(optScale * timeLeft); + maximumTime = TimePoint(std::min(0.825179 * time - moveOverhead, + maxScale * optimumTime)) - + 10; } } // namespace engine::timeman diff --git a/timeman.h b/timeman.h index 6a09fdb..ca90fe2 100644 --- a/timeman.h +++ b/timeman.h @@ -8,41 +8,47 @@ constexpr TimePoint INFINITE_TIME = 864000000; // LimitsType struct stores information sent by the caller about the analysis // required. inline TimePoint now() { - return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count(); } struct LimitsType { - // Init explicitly due to broken value-initialization of non POD in MSVC - LimitsType() { - time[chess::WHITE] = time[chess::BLACK] = inc[chess::WHITE] = inc[chess::BLACK] = movetime = TimePoint(0); - movestogo = mate = perft = infinite = 0; - depth = 64; - nodes = 0; - ponderMode = false; - } + // Init explicitly due to broken value-initialization of non POD in MSVC + LimitsType() { + time[chess::WHITE] = time[chess::BLACK] = inc[chess::WHITE] = + inc[chess::BLACK] = movetime = TimePoint(0); + movestogo = mate = perft = infinite = 0; + depth = 64; + nodes = 0; + ponderMode = false; + } - bool use_time_management() const { return time[chess::WHITE] || time[chess::BLACK]; } + bool use_time_management() const { + return time[chess::WHITE] || time[chess::BLACK]; + } - std::vector searchmoves; - TimePoint time[chess::COLOR_NB], inc[chess::COLOR_NB], movetime, startTime; - int movestogo, depth, mate, perft, infinite; - uint64_t nodes; - bool ponderMode; + std::vector searchmoves; + TimePoint time[chess::COLOR_NB], inc[chess::COLOR_NB], movetime, startTime; + int movestogo, depth, mate, perft, infinite; + uint64_t nodes; + bool ponderMode; }; class TimeManagement { - public: - void init(LimitsType &limits, chess::Color us, int ply, double &originalTimeAdjust); +public: + void init(LimitsType &limits, chess::Color us, int ply, + double &originalTimeAdjust); - TimePoint optimum() const; - TimePoint maximum() const; - TimePoint elapsed() const { return elapsed_time(); } - TimePoint elapsed_time() const { return now() - startTime; }; + TimePoint optimum() const; + TimePoint maximum() const; + TimePoint elapsed() const { return elapsed_time(); } + TimePoint elapsed_time() const { return now() - startTime; }; - void clear(); + void clear(); - private: - TimePoint startTime; - TimePoint optimumTime; - TimePoint maximumTime; +private: + TimePoint startTime; + TimePoint optimumTime; + TimePoint maximumTime; }; } // namespace engine::timeman \ No newline at end of file diff --git a/tt.cpp b/tt.cpp index 768b2cd..9bbe83e 100644 --- a/tt.cpp +++ b/tt.cpp @@ -6,69 +6,70 @@ #endif using namespace engine; static inline uint64_t index_for_hash(uint64_t hash, uint64_t buckets) { - if (buckets == 0) - return 0; + if (buckets == 0) + return 0; #if defined(_MSC_VER) - // MSVC: use _umul128 to get high 64 bits of 128-bit product - unsigned long long high = 0; - (void)_umul128((unsigned long long)hash, (unsigned long long)buckets, &high); - return (uint64_t)high; + // MSVC: use _umul128 to get high 64 bits of 128-bit product + unsigned long long high = 0; + (void)_umul128((unsigned long long)hash, (unsigned long long)buckets, &high); + return (uint64_t)high; #elif defined(__SIZEOF_INT128__) - // GCC/Clang: use __uint128_t - __uint128_t prod = (__uint128_t)hash * (__uint128_t)buckets; - return (uint64_t)(prod >> 64); + // GCC/Clang: use __uint128_t + __uint128_t prod = (__uint128_t)hash * (__uint128_t)buckets; + return (uint64_t)(prod >> 64); #else - uint64_t aL = uint32_t(hash), aH = a >> 32; - uint64_t bL = uint32_t(buckets), bH = b >> 32; - uint64_t c1 = (aL * bL) >> 32; - uint64_t c2 = aH * bL + c1; - uint64_t c3 = aL * bH + uint32_t(c2); - return aH * bH + (c2 >> 32) + (c3 >> 32); + uint64_t aL = uint32_t(hash), aH = a >> 32; + uint64_t bL = uint32_t(buckets), bH = b >> 32; + uint64_t c1 = (aL * bL) >> 32; + uint64_t c2 = aH * bL + c1; + uint64_t c3 = aL * bH + uint32_t(c2); + return aH * bH + (c2 >> 32) + (c3 >> 32); #endif } void TranspositionTable::newSearch() { time++; } -void TranspositionTable::store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, TTFlag flag) { - // 2 entries per bucket - if (buckets == 0) - return; +void TranspositionTable::store(uint64_t hash, chess::Move best, int16_t score, + int8_t depth, TTFlag flag) { + // 2 entries per bucket + if (buckets == 0) + return; - uint64_t index = index_for_hash(hash, buckets); + uint64_t index = index_for_hash(hash, buckets); - TTEntry &e0 = table[2 * index], &e1 = table[2 * index + 1]; - // Store the entry - for (TTEntry *e : { &e0, &e1 }) { - if (e->key == hash || e->getDepth() < depth) { - e->key = hash; - e->setPackedFields(score, depth, flag, best.raw(), time); + TTEntry &e0 = table[2 * index], &e1 = table[2 * index + 1]; + // Store the entry + for (TTEntry *e : {&e0, &e1}) { + if (e->key == hash || e->getDepth() < depth) { + e->key = hash; + e->setPackedFields(score, depth, flag, best.raw(), time); - return; - } + return; } - // If we get here, we need to evict an entry - // Find the oldest entry - TTEntry *oldest = (e0.timestamp() < e1.timestamp()) ? &e0 : &e1; - // Evict it - oldest->key = hash; - oldest->setPackedFields(score, depth, flag, best.raw(), time); + } + // If we get here, we need to evict an entry + // Find the oldest entry + TTEntry *oldest = (e0.timestamp() < e1.timestamp()) ? &e0 : &e1; + // Evict it + oldest->key = hash; + oldest->setPackedFields(score, depth, flag, best.raw(), time); } TTEntry *TranspositionTable::lookup(uint64_t hash) { - if (buckets == 0) - return nullptr; + if (buckets == 0) + return nullptr; - uint64_t bucket = index_for_hash(hash, buckets); + uint64_t bucket = index_for_hash(hash, buckets); - TTEntry &e0 = table[2 * bucket]; - TTEntry &e1 = table[2 * bucket + 1]; + TTEntry &e0 = table[2 * bucket]; + TTEntry &e1 = table[2 * bucket + 1]; - if (e0.key == hash) - return &e0; + if (e0.key == hash) + return &e0; - if (e1.key == hash) - return &e1; + if (e1.key == hash) + return &e1; - return nullptr; + return nullptr; } \ No newline at end of file diff --git a/tt.h b/tt.h index e648370..b1eb094 100644 --- a/tt.h +++ b/tt.h @@ -6,138 +6,165 @@ namespace engine { enum TTFlag : uint8_t { EXACT = 0, LOWERBOUND = 1, UPPERBOUND = 2 }; struct TTEntry { - uint64_t key; - uint64_t pack; // 16-bit score, 8-bit depth, 3-bit flags, 16-bit move, 21 bits - // for generation - - // bit layout constants - static constexpr unsigned SCORE_SHIFT = 0; - static constexpr unsigned SCORE_BITS = 16; - static constexpr uint64_t SCORE_MASK = ((uint64_t(1) << SCORE_BITS) - 1) << SCORE_SHIFT; - - static constexpr unsigned DEPTH_SHIFT = 16; - static constexpr unsigned DEPTH_BITS = 8; - static constexpr uint64_t DEPTH_MASK = ((uint64_t(1) << DEPTH_BITS) - 1) << DEPTH_SHIFT; - - static constexpr unsigned FLAG_SHIFT = 24; - static constexpr unsigned FLAG_BITS = 3; - static constexpr uint64_t FLAG_MASK = ((uint64_t(1) << FLAG_BITS) - 1) << FLAG_SHIFT; - - static constexpr unsigned MOVE_SHIFT = 27; - static constexpr unsigned MOVE_BITS = 16; - static constexpr uint64_t MOVE_MASK = ((uint64_t(1) << MOVE_BITS) - 1) << MOVE_SHIFT; - - static constexpr unsigned GEN_SHIFT = 43; - static constexpr unsigned GEN_BITS = 21; - static constexpr uint64_t GEN_MASK = ((uint64_t(1) << GEN_BITS) - 1) << GEN_SHIFT; - - // getters - inline int16_t getScore() const noexcept { return static_cast((pack & SCORE_MASK) >> SCORE_SHIFT); } - - inline uint8_t getDepth() const noexcept { return static_cast((pack & DEPTH_MASK) >> DEPTH_SHIFT); } - - inline TTFlag getFlag() const noexcept { return static_cast((pack & FLAG_MASK) >> FLAG_SHIFT); } - - inline uint16_t getMove() const noexcept { return static_cast((pack & MOVE_MASK) >> MOVE_SHIFT); } - - inline uint32_t getGeneration() const noexcept { return static_cast((pack & GEN_MASK) >> GEN_SHIFT); } - - // setters - inline void setScore(int16_t score) noexcept { - // preserve two's complement by casting through uint16_t - const uint64_t v = (static_cast(static_cast(score)) << SCORE_SHIFT) & SCORE_MASK; - pack = (pack & ~SCORE_MASK) | v; - } - - inline void setDepth(uint8_t depth) noexcept { - const uint64_t v = (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; - pack = (pack & ~DEPTH_MASK) | v; - } - - inline void setFlag(TTFlag flag) noexcept { - const uint64_t v = (static_cast(static_cast(flag)) << FLAG_SHIFT) & FLAG_MASK; - pack = (pack & ~FLAG_MASK) | v; - } - - inline void setMove(uint16_t move) noexcept { - const uint64_t v = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; - pack = (pack & ~MOVE_MASK) | v; - } - - inline void setGeneration(uint32_t gen) noexcept { - const uint64_t v = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; - pack = (pack & ~GEN_MASK) | v; - } - - // convenience: set all packed fields at once - inline void setPackedFields(int16_t score, uint8_t depth, TTFlag flag, uint16_t move, uint32_t gen) noexcept { - const uint64_t s = (static_cast(static_cast(score)) << SCORE_SHIFT) & SCORE_MASK; - const uint64_t d = (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; - const uint64_t f = (static_cast(static_cast(flag)) << FLAG_SHIFT) & FLAG_MASK; - const uint64_t m = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; - const uint64_t g = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; - pack = s | d | f | m | g; - } - - inline uint32_t timestamp() const noexcept { return getGeneration(); } + uint64_t key; + uint64_t pack; // 16-bit score, 8-bit depth, 3-bit flags, 16-bit move, 21 bits + // for generation + + // bit layout constants + static constexpr unsigned SCORE_SHIFT = 0; + static constexpr unsigned SCORE_BITS = 16; + static constexpr uint64_t SCORE_MASK = ((uint64_t(1) << SCORE_BITS) - 1) + << SCORE_SHIFT; + + static constexpr unsigned DEPTH_SHIFT = 16; + static constexpr unsigned DEPTH_BITS = 8; + static constexpr uint64_t DEPTH_MASK = ((uint64_t(1) << DEPTH_BITS) - 1) + << DEPTH_SHIFT; + + static constexpr unsigned FLAG_SHIFT = 24; + static constexpr unsigned FLAG_BITS = 3; + static constexpr uint64_t FLAG_MASK = ((uint64_t(1) << FLAG_BITS) - 1) + << FLAG_SHIFT; + + static constexpr unsigned MOVE_SHIFT = 27; + static constexpr unsigned MOVE_BITS = 16; + static constexpr uint64_t MOVE_MASK = ((uint64_t(1) << MOVE_BITS) - 1) + << MOVE_SHIFT; + + static constexpr unsigned GEN_SHIFT = 43; + static constexpr unsigned GEN_BITS = 21; + static constexpr uint64_t GEN_MASK = ((uint64_t(1) << GEN_BITS) - 1) + << GEN_SHIFT; + + // getters + inline int16_t getScore() const noexcept { + return static_cast((pack & SCORE_MASK) >> SCORE_SHIFT); + } + + inline uint8_t getDepth() const noexcept { + return static_cast((pack & DEPTH_MASK) >> DEPTH_SHIFT); + } + + inline TTFlag getFlag() const noexcept { + return static_cast((pack & FLAG_MASK) >> FLAG_SHIFT); + } + + inline uint16_t getMove() const noexcept { + return static_cast((pack & MOVE_MASK) >> MOVE_SHIFT); + } + + inline uint32_t getGeneration() const noexcept { + return static_cast((pack & GEN_MASK) >> GEN_SHIFT); + } + + // setters + inline void setScore(int16_t score) noexcept { + // preserve two's complement by casting through uint16_t + const uint64_t v = + (static_cast(static_cast(score)) << SCORE_SHIFT) & + SCORE_MASK; + pack = (pack & ~SCORE_MASK) | v; + } + + inline void setDepth(uint8_t depth) noexcept { + const uint64_t v = + (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; + pack = (pack & ~DEPTH_MASK) | v; + } + + inline void setFlag(TTFlag flag) noexcept { + const uint64_t v = + (static_cast(static_cast(flag)) << FLAG_SHIFT) & + FLAG_MASK; + pack = (pack & ~FLAG_MASK) | v; + } + + inline void setMove(uint16_t move) noexcept { + const uint64_t v = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; + pack = (pack & ~MOVE_MASK) | v; + } + + inline void setGeneration(uint32_t gen) noexcept { + const uint64_t v = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; + pack = (pack & ~GEN_MASK) | v; + } + + // convenience: set all packed fields at once + inline void setPackedFields(int16_t score, uint8_t depth, TTFlag flag, + uint16_t move, uint32_t gen) noexcept { + const uint64_t s = + (static_cast(static_cast(score)) << SCORE_SHIFT) & + SCORE_MASK; + const uint64_t d = + (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; + const uint64_t f = + (static_cast(static_cast(flag)) << FLAG_SHIFT) & + FLAG_MASK; + const uint64_t m = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; + const uint64_t g = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; + pack = s | d | f | m | g; + } + + inline uint32_t timestamp() const noexcept { return getGeneration(); } }; class TranspositionTable { - TTEntry *table; - int buckets; // number of buckets (pairs) - uint32_t time; - - public: - size_t size; // total number of TTEntry elements (must be even) - TranspositionTable() : table(nullptr), buckets(0), time(0), size(0) {} - - TranspositionTable(size_t sizeInMB) : time(0) { - size = sizeInMB * 1048576LL / sizeof(TTEntry); - if (size % 2 != 0) - size--; // Ensure even size - buckets = size / 2; - table = new TTEntry[size]; - clear(); + TTEntry *table; + int buckets; // number of buckets (pairs) + uint32_t time; + +public: + size_t size; // total number of TTEntry elements (must be even) + TranspositionTable() : table(nullptr), buckets(0), time(0), size(0) {} + + TranspositionTable(size_t sizeInMB) : time(0) { + size = sizeInMB * 1048576LL / sizeof(TTEntry); + if (size % 2 != 0) + size--; // Ensure even size + buckets = size / 2; + table = new TTEntry[size]; + clear(); + } + + ~TranspositionTable() { delete[] table; } + + void resize(int sizeInMB) { + int new_size = sizeInMB * 1048576 / sizeof(TTEntry); + if (new_size % 2 != 0) + new_size--; + + TTEntry *new_table = new (std::nothrow) TTEntry[new_size](); + if (!new_table) { + throw std::bad_alloc(); } - ~TranspositionTable() { delete[] table; } - - void resize(int sizeInMB) { - int new_size = sizeInMB * 1048576 / sizeof(TTEntry); - if (new_size % 2 != 0) - new_size--; + delete[] table; + table = new_table; + size = new_size; + buckets = size / 2; + } - TTEntry *new_table = new (std::nothrow) TTEntry[new_size](); - if (!new_table) { - throw std::bad_alloc(); - } - - delete[] table; - table = new_table; - size = new_size; - buckets = size / 2; - } + void newSearch(); + void store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, + TTFlag flag); - void newSearch(); - void store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, TTFlag flag); + inline void clear() { std::fill_n(table, size, TTEntry{}); } - inline void clear() { std::fill_n(table, size, TTEntry{}); } + TTEntry *lookup(uint64_t hash); - TTEntry *lookup(uint64_t hash); + // Returns hash usage as percentage [0..100] based on occupied buckets. + inline int hashfull() const noexcept { + if (!table || buckets <= 0) + return 0; - // Returns hash usage as percentage [0..100] based on occupied buckets. - inline int hashfull() const noexcept { - if (!table || buckets <= 0) - return 0; - - int used = 0; - for (int i = 0; i < buckets; ++i) { - const TTEntry &a = table[2 * i]; - const TTEntry &b = table[2 * i + 1]; - if (a.key != 0 || b.key != 0) - ++used; - } - - return (used * 1000LL) / buckets; + int used = 0; + for (int i = 0; i < buckets; ++i) { + const TTEntry &a = table[2 * i]; + const TTEntry &b = table[2 * i + 1]; + if (a.key != 0 || b.key != 0) + ++used; } + + return (used * 1000LL) / buckets; + } }; } // namespace engine diff --git a/tune.cpp b/tune.cpp index 94077ec..d29fed6 100644 --- a/tune.cpp +++ b/tune.cpp @@ -39,61 +39,65 @@ std::map TuneResults; std::optional on_tune(const Option &o) { - if (!Tune::update_on_last || LastOption == &o) - Tune::read_options(); + if (!Tune::update_on_last || LastOption == &o) + Tune::read_options(); - return std::nullopt; + return std::nullopt; } } // namespace -void Tune::make_option(OptionsMap *opts, const string &n, int v, const SetRange &r) { +void Tune::make_option(OptionsMap *opts, const string &n, int v, + const SetRange &r) { - // Do not generate option when there is nothing to tune (ie. min = max) - if (r(v).first == r(v).second) - return; + // Do not generate option when there is nothing to tune (ie. min = max) + if (r(v).first == r(v).second) + return; - if (TuneResults.count(n)) - v = TuneResults[n]; + if (TuneResults.count(n)) + v = TuneResults[n]; - opts->add(n, Option(v, r(v).first, r(v).second, on_tune)); - LastOption = &((*opts)[n]); + opts->add(n, Option(v, r(v).first, r(v).second, on_tune)); + LastOption = &((*opts)[n]); - // Print formatted parameters, ready to be copy-pasted in Fishtest - std::cout << n << "," // + // Print formatted parameters, ready to be copy-pasted in Fishtest + std::cout << n << "," // #ifdef OPENBENCH_SUPPORT - // or OpenBench - << "int" << "," + // or OpenBench + << "int" << "," #endif - << v << "," // - << r(v).first << "," // - << r(v).second << "," // - << (r(v).second - r(v).first) / 20.0 << "," // - << "0.0020" << std::endl; + << v << "," // + << r(v).first << "," // + << r(v).second << "," // + << (r(v).second - r(v).first) / 20.0 << "," // + << "0.0020" << std::endl; } string Tune::next(string &names, bool pop) { - string name; + string name; - do { - string token = names.substr(0, names.find(',')); + do { + string token = names.substr(0, names.find(',')); - if (pop) - names.erase(0, token.size() + 1); + if (pop) + names.erase(0, token.size() + 1); - std::stringstream ws(token); - name += (ws >> token, token); // Remove trailing whitespace + std::stringstream ws(token); + name += (ws >> token, token); // Remove trailing whitespace - } while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')')); + } while (std::count(name.begin(), name.end(), '(') - + std::count(name.begin(), name.end(), ')')); - return name; + return name; } -template <> void Tune::Entry::init_option() { make_option(options, name, value, range); } +template <> void Tune::Entry::init_option() { + make_option(options, name, value, range); +} template <> void Tune::Entry::read_option() { - if (options->count(name)) - value = int((*options)[name]); + if (options->count(name)) + value = int((*options)[name]); } // Instead of a variable here we have a PostUpdate function: just call it diff --git a/tune.h b/tune.h index e337d11..d6a0022 100644 --- a/tune.h +++ b/tune.h @@ -34,15 +34,17 @@ using Range = std::pair; // Option's min-max values using RangeFun = Range(int); // Default Range function, to calculate Option's min-max values -inline Range default_range(int v) { return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); } +inline Range default_range(int v) { + return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); +} struct SetRange { - explicit SetRange(RangeFun f) : fun(f) {} - SetRange(int min, int max) : fun(nullptr), range(min, max) {} - Range operator()(int v) const { return fun ? fun(v) : range; } + explicit SetRange(RangeFun f) : fun(f) {} + SetRange(int min, int max) : fun(nullptr), range(min, max) {} + Range operator()(int v) const { return fun ? fun(v) : range; } - RangeFun *fun; - Range range; + RangeFun *fun; + Range range; }; #define SetDefaultRange SetRange(default_range) @@ -76,93 +78,105 @@ struct SetRange { class Tune { - using PostUpdate = void(); // Post-update function - - Tune() { read_results(); } - Tune(const Tune &) = delete; - void operator=(const Tune &) = delete; - void read_results(); - - static Tune &instance() { - static Tune t; - return t; - } // Singleton - - // Use polymorphism to accommodate Entry of different types in the same vector - struct EntryBase { - virtual ~EntryBase() = default; - virtual void init_option() = 0; - virtual void read_option() = 0; - }; - - template struct Entry : public EntryBase { - - static_assert(!std::is_const_v, "Parameter cannot be const!"); - - static_assert(std::is_same_v || std::is_same_v, "Parameter type not supported!"); - - Entry(const std::string &n, T &v, const SetRange &r) : name(n), value(v), range(r) {} - void operator=(const Entry &) = delete; // Because 'value' is a reference - void init_option() override; - void read_option() override; - - std::string name; - T &value; - SetRange range; - }; - - // Our facility to fill the container, each Entry corresponds to a parameter - // to tune. We use variadic templates to deal with an unspecified number of - // entries, each one of a possible different type. - static std::string next(std::string &names, bool pop = true); - - int add(const SetRange &, std::string &&) { return 0; } - - template int add(const SetRange &range, std::string &&names, T &value, Args &&...args) { - list.push_back(std::unique_ptr(new Entry(next(names), value, range))); - return add(range, std::move(names), args...); - } - - // Template specialization for arrays: recursively handle multi-dimensional - // arrays - template - int add(const SetRange &range, std::string &&names, T (&value)[N], Args &&...args) { - for (size_t i = 0; i < N; i++) - add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]); - return add(range, std::move(names), args...); - } - - // Template specialization for SetRange - template int add(const SetRange &, std::string &&names, SetRange &value, Args &&...args) { - return add(value, (next(names), std::move(names)), args...); - } - - static void make_option(OptionsMap *options, const std::string &n, int v, const SetRange &r); - - std::vector> list; - - public: - template static int add(const std::string &names, Args &&...args) { - return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), - args...); // Remove trailing parenthesis - } - static void init(OptionsMap &o) { - options = &o; - for (auto &e : instance().list) - e->init_option(); - read_options(); - } // Deferred, due to UCIEngine::Options access - static void read_options() { - for (auto &e : instance().list) - e->read_option(); - } - - static bool update_on_last; - static OptionsMap *options; + using PostUpdate = void(); // Post-update function + + Tune() { read_results(); } + Tune(const Tune &) = delete; + void operator=(const Tune &) = delete; + void read_results(); + + static Tune &instance() { + static Tune t; + return t; + } // Singleton + + // Use polymorphism to accommodate Entry of different types in the same vector + struct EntryBase { + virtual ~EntryBase() = default; + virtual void init_option() = 0; + virtual void read_option() = 0; + }; + + template struct Entry : public EntryBase { + + static_assert(!std::is_const_v, "Parameter cannot be const!"); + + static_assert(std::is_same_v || std::is_same_v, + "Parameter type not supported!"); + + Entry(const std::string &n, T &v, const SetRange &r) + : name(n), value(v), range(r) {} + void operator=(const Entry &) = delete; // Because 'value' is a reference + void init_option() override; + void read_option() override; + + std::string name; + T &value; + SetRange range; + }; + + // Our facility to fill the container, each Entry corresponds to a parameter + // to tune. We use variadic templates to deal with an unspecified number of + // entries, each one of a possible different type. + static std::string next(std::string &names, bool pop = true); + + int add(const SetRange &, std::string &&) { return 0; } + + template + int add(const SetRange &range, std::string &&names, T &value, + Args &&...args) { + list.push_back( + std::unique_ptr(new Entry(next(names), value, range))); + return add(range, std::move(names), args...); + } + + // Template specialization for arrays: recursively handle multi-dimensional + // arrays + template + int add(const SetRange &range, std::string &&names, T (&value)[N], + Args &&...args) { + for (size_t i = 0; i < N; i++) + add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", + value[i]); + return add(range, std::move(names), args...); + } + + // Template specialization for SetRange + template + int add(const SetRange &, std::string &&names, SetRange &value, + Args &&...args) { + return add(value, (next(names), std::move(names)), args...); + } + + static void make_option(OptionsMap *options, const std::string &n, int v, + const SetRange &r); + + std::vector> list; + +public: + template + static int add(const std::string &names, Args &&...args) { + return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), + args...); // Remove trailing parenthesis + } + static void init(OptionsMap &o) { + options = &o; + for (auto &e : instance().list) + e->init_option(); + read_options(); + } // Deferred, due to UCIEngine::Options access + static void read_options() { + for (auto &e : instance().list) + e->read_option(); + } + + static bool update_on_last; + static OptionsMap *options; }; template constexpr void tune_check_args(Args &&...) { - static_assert((!std::is_fundamental_v && ...), "TUNE macro arguments wrong"); + static_assert((!std::is_fundamental_v && ...), + "TUNE macro arguments wrong"); } // Some macro magic :-) we define a dummy int variable that the compiler @@ -170,11 +184,11 @@ template constexpr void tune_check_args(Args &&...) { #define STRINGIFY(x) #x #define UNIQUE2(x, y) x##y #define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ -#define TUNE(...) \ - int UNIQUE(p, __LINE__) = []() -> int { \ - tune_check_args(__VA_ARGS__); \ - return engine::Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__); \ - }(); +#define TUNE(...) \ + int UNIQUE(p, __LINE__) = []() -> int { \ + tune_check_args(__VA_ARGS__); \ + return engine::Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__); \ + }(); #define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true diff --git a/uci.cpp b/uci.cpp index 230bd52..7cc5888 100644 --- a/uci.cpp +++ b/uci.cpp @@ -18,214 +18,222 @@ std::thread searchThread; namespace { std::string strip_optional_quotes(std::string fen) { - auto notSpace = [](unsigned char c) { return !std::isspace(c); }; + auto notSpace = [](unsigned char c) { return !std::isspace(c); }; - fen.erase(fen.begin(), std::find_if(fen.begin(), fen.end(), notSpace)); - fen.erase(std::find_if(fen.rbegin(), fen.rend(), notSpace).base(), fen.end()); + fen.erase(fen.begin(), std::find_if(fen.begin(), fen.end(), notSpace)); + fen.erase(std::find_if(fen.rbegin(), fen.rend(), notSpace).base(), fen.end()); - if (fen.size() >= 2 && fen.front() == '"' && fen.back() == '"') - return fen.substr(1, fen.size() - 2); + if (fen.size() >= 2 && fen.front() == '"' && fen.back() == '"') + return fen.substr(1, fen.size() - 2); - if (!fen.empty() && fen.front() == '"') - fen.erase(fen.begin()); + if (!fen.empty() && fen.front() == '"') + fen.erase(fen.begin()); - if (!fen.empty() && fen.back() == '"') - fen.pop_back(); + if (!fen.empty() && fen.back() == '"') + fen.pop_back(); - return fen; + return fen; } } // namespace void engine::stop() { - search::stop(); - if (searchThread.joinable()) { - searchThread.join(); - } + search::stop(); + if (searchThread.joinable()) { + searchThread.join(); + } } void handlePosition(std::istringstream &is) { - stop(); - std::string token, fen; - - is >> token; - - if (token == "startpos") { - fen = chess::Position::START_FEN; - is >> token; // Consume the "moves" token, if any - } else if (token == "fen") - while (is >> token && token != "moves") - fen += token + " "; - else - return; - + stop(); + std::string token, fen; + + is >> token; + + if (token == "startpos") { + fen = chess::Position::START_FEN; + is >> token; // Consume the "moves" token, if any + } else if (token == "fen") + while (is >> token && token != "moves") + fen += token + " "; + else + return; + + try { + pos.setFEN(strip_optional_quotes(fen)); + } catch (const std::exception &e) { + std::cerr << "info string Invalid FEN: " << e.what() << std::endl; + return; + } + + while (is >> token) { try { - pos.setFEN(strip_optional_quotes(fen)); + pos.push_uci(token); } catch (const std::exception &e) { - std::cerr << "info string Invalid FEN: " << e.what() << std::endl; - return; - } - - while (is >> token) { - try { - pos.push_uci(token); - } catch (const std::exception &e) { - std::cerr << "info string Invalid move " << token << ": " << e.what() << std::endl; - return; - } + std::cerr << "info string Invalid move " << token << ": " << e.what() + << std::endl; + return; } + } } timeman::LimitsType parse_limits(std::istream &is) { - timeman::LimitsType limits; - std::string token; + timeman::LimitsType limits; + std::string token; + + limits.startTime = timeman::now(); // The search starts as early as possible + + while (is >> token) + if (token == "searchmoves") // Needs to be the last command on the line + while (is >> token) { + std::transform(token.begin(), token.end(), token.begin(), + [](auto c) { return std::tolower(c); }); + limits.searchmoves.push_back(token); + } + else if (token == "wtime") + is >> limits.time[chess::WHITE]; + else if (token == "btime") + is >> limits.time[chess::BLACK]; + else if (token == "winc") + is >> limits.inc[chess::WHITE]; + else if (token == "binc") + is >> limits.inc[chess::BLACK]; + else if (token == "movestogo") + is >> limits.movestogo; + else if (token == "depth") + is >> limits.depth; + else if (token == "nodes") + is >> limits.nodes; + else if (token == "movetime") + is >> limits.movetime; + else if (token == "mate") + is >> limits.mate; + else if (token == "perft") + is >> limits.perft; + else if (token == "infinite") + limits.infinite = 1; + else if (token == "ponder") { + } - limits.startTime = timeman::now(); // The search starts as early as possible - - while (is >> token) - if (token == "searchmoves") // Needs to be the last command on the line - while (is >> token) { - std::transform(token.begin(), token.end(), token.begin(), [](auto c) { return std::tolower(c); }); - limits.searchmoves.push_back(token); - } - else if (token == "wtime") - is >> limits.time[chess::WHITE]; - else if (token == "btime") - is >> limits.time[chess::BLACK]; - else if (token == "winc") - is >> limits.inc[chess::WHITE]; - else if (token == "binc") - is >> limits.inc[chess::BLACK]; - else if (token == "movestogo") - is >> limits.movestogo; - else if (token == "depth") - is >> limits.depth; - else if (token == "nodes") - is >> limits.nodes; - else if (token == "movetime") - is >> limits.movetime; - else if (token == "mate") - is >> limits.mate; - else if (token == "perft") - is >> limits.perft; - else if (token == "infinite") - limits.infinite = 1; - else if (token == "ponder") { - } - - return limits; + return limits; } void handleGo(std::istringstream &ss) { - stop(); - chess::Position copy = pos; + stop(); + chess::Position copy = pos; - searchThread = std::thread([copy, ss = std::move(ss)]() mutable { search::search(copy, parse_limits(ss)); }); + searchThread = std::thread([copy, ss = std::move(ss)]() mutable { + search::search(copy, parse_limits(ss)); + }); } template struct overload : Ts... { - using Ts::operator()...; + using Ts::operator()...; }; template overload(Ts...) -> overload; std::string engine::format_score(const Score &s) { - const auto format = - overload{ [](Score::Mate mate) -> std::string { - auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2; - return std::string("mate ") + std::to_string(m); - }, - [](Score::Tablebase tb) -> std::string { - constexpr int TB_CP = 20000; - return std::string("cp ") + std::to_string((tb.win ? TB_CP - tb.plies : -TB_CP - tb.plies)); - }, - [](Score::InternalUnits units) -> std::string { return std::string("cp ") + std::to_string(units.value); } }; - - return s.visit(format); + const auto format = overload{ + [](Score::Mate mate) -> std::string { + auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2; + return std::string("mate ") + std::to_string(m); + }, + [](Score::Tablebase tb) -> std::string { + constexpr int TB_CP = 20000; + return std::string("cp ") + + std::to_string((tb.win ? TB_CP - tb.plies : -TB_CP - tb.plies)); + }, + [](Score::InternalUnits units) -> std::string { + return std::string("cp ") + std::to_string(units.value); + }}; + + return s.visit(format); } void engine::report(const InfoShort &info) { - std::cout << "info depth " << info.depth << " score " << format_score(info.score) << std::endl; + std::cout << "info depth " << info.depth << " score " + << format_score(info.score) << std::endl; } void engine::report(const InfoFull &info, bool showWDL) { - std::stringstream ss; - - ss << "info"; - ss << " depth " << info.depth // - << " seldepth " << info.selDepth // - << " multipv " << info.multiPV // - << " score " << format_score(info.score); // - - if (!info.bound.empty()) - ss << " " << info.bound; - - if (showWDL) - ss << " wdl " << info.wdl; - - ss << " nodes " << info.nodes // - << " nps " << info.nps // - << " hashfull " << info.hashfull // - << " tbhits " << info.tbHits // - << " time " << info.timeMs // - << " pv " << info.pv; // - if (!info.extrainfo.empty()) - ss << "\ninfo string extras " << info.extrainfo; - std::cout << ss.str() << std::endl; + std::stringstream ss; + + ss << "info"; + ss << " depth " << info.depth // + << " seldepth " << info.selDepth // + << " multipv " << info.multiPV // + << " score " << format_score(info.score); // + + if (!info.bound.empty()) + ss << " " << info.bound; + + if (showWDL) + ss << " wdl " << info.wdl; + + ss << " nodes " << info.nodes // + << " nps " << info.nps // + << " hashfull " << info.hashfull // + << " tbhits " << info.tbHits // + << " time " << info.timeMs // + << " pv " << info.pv; // + if (!info.extrainfo.empty()) + ss << "\ninfo string extras " << info.extrainfo; + std::cout << ss.str() << std::endl; } void engine::report(const InfoIteration &info) { - std::stringstream ss; + std::stringstream ss; - ss << "info"; - ss << " depth " << info.depth // - << " currmove " << info.currmove // - << " currmovenumber " << info.currmovenumber; // + ss << "info"; + ss << " depth " << info.depth // + << " currmove " << info.currmove // + << " currmovenumber " << info.currmovenumber; // - std::cout << ss.str() << std::endl; + std::cout << ss.str() << std::endl; } void engine::report(std::string_view bestmove) { - std::cout << "bestmove " << bestmove; - std::cout << std::endl; + std::cout << "bestmove " << bestmove; + std::cout << std::endl; } void engine::loop() { - std::string line; - pos.setFEN(chess::Position::START_FEN); - - while (std::getline(std::cin, line)) { - std::istringstream ss(line); - std::string token; - stop(); - while (ss >> token) { - if (token == "uci") { - std::cout << "id name cppchess_engine\n"; - std::cout << "id author winapiadmin\n"; - std::cout << options << '\n'; - std::cout << "uciok\n"; - break; - } else if (token == "isready") { - std::cout << "readyok\n"; - break; - } else if (token == "position") { - handlePosition(ss); - break; - } else if (token == "go") { - handleGo(ss); - break; // rest belongs to go - } else if (token == "ucinewgame") { - search::tt.clear(); - break; - } else if (token == "stop") { - break; - } else if (token == "quit") { - return; - } else if (token == "setoption") { - options.setoption(ss); - break; - } else if (token == "visualize" || token == "d") { - std::cout << pos << std::endl; - break; - } else if (token == "eval") { - std::cout << eval::eval(pos) << std::endl; - break; - } - } - } + std::string line; + pos.setFEN(chess::Position::START_FEN); + + while (std::getline(std::cin, line)) { + std::istringstream ss(line); + std::string token; stop(); + while (ss >> token) { + if (token == "uci") { + std::cout << "id name cppchess_engine\n"; + std::cout << "id author winapiadmin\n"; + std::cout << options << '\n'; + std::cout << "uciok\n"; + break; + } else if (token == "isready") { + std::cout << "readyok\n"; + break; + } else if (token == "position") { + handlePosition(ss); + break; + } else if (token == "go") { + handleGo(ss); + break; // rest belongs to go + } else if (token == "ucinewgame") { + search::tt.clear(); + break; + } else if (token == "stop") { + break; + } else if (token == "quit") { + return; + } else if (token == "setoption") { + options.setoption(ss); + break; + } else if (token == "visualize" || token == "d") { + std::cout << pos << std::endl; + break; + } else if (token == "eval") { + std::cout << eval::eval(pos) << std::endl; + break; + } + } + } + stop(); } diff --git a/uci.h b/uci.h index 18d5e17..8212b1a 100644 --- a/uci.h +++ b/uci.h @@ -4,28 +4,28 @@ #include namespace engine { struct InfoShort { - int depth; - Score score; + int depth; + Score score; }; struct InfoFull : InfoShort { - int selDepth; - size_t multiPV; - std::string_view wdl; - std::string_view bound; - size_t timeMs; - size_t nodes; - size_t nps; - size_t tbHits; - std::string pv; - std::string extrainfo; - int hashfull; + int selDepth; + size_t multiPV; + std::string_view wdl; + std::string_view bound; + size_t timeMs; + size_t nodes; + size_t nps; + size_t tbHits; + std::string pv; + std::string extrainfo; + int hashfull; }; struct InfoIteration { - int depth; - std::string currmove; - size_t currmovenumber; + int depth; + std::string currmove; + size_t currmovenumber; }; std::string format_score(const Score &s); void report(const InfoFull &info, bool showWDL = false); diff --git a/ucioption.cpp b/ucioption.cpp index 3d62ca3..68fc9e6 100644 --- a/ucioption.cpp +++ b/ucioption.cpp @@ -28,91 +28,102 @@ namespace engine { -bool CaseInsensitiveLess::operator()(const std::string &s1, const std::string &s2) const { +bool CaseInsensitiveLess::operator()(const std::string &s1, + const std::string &s2) const { - return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), [](char c1, char c2) { - return std::tolower(c1) < std::tolower(c2); - }); + return std::lexicographical_compare( + s1.begin(), s1.end(), s2.begin(), s2.end(), + [](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); }); } -void OptionsMap::add_info_listener(InfoListener &&message_func) { info = std::move(message_func); } +void OptionsMap::add_info_listener(InfoListener &&message_func) { + info = std::move(message_func); +} void OptionsMap::setoption(std::istringstream &is) { - std::string token, name, value; + std::string token, name, value; - is >> token; // Consume the "name" token + is >> token; // Consume the "name" token - // Read the option name (can contain spaces) - while (is >> token && token != "value") - name += (name.empty() ? "" : " ") + token; + // Read the option name (can contain spaces) + while (is >> token && token != "value") + name += (name.empty() ? "" : " ") + token; - // Read the option value (can contain spaces) - while (is >> token) - value += (value.empty() ? "" : " ") + token; + // Read the option value (can contain spaces) + while (is >> token) + value += (value.empty() ? "" : " ") + token; - if (options_map.count(name)) - options_map[name] = value; - else - std::cerr << "No such option: " << name << std::endl; + if (options_map.count(name)) + options_map[name] = value; + else + std::cerr << "No such option: " << name << std::endl; } const Option &OptionsMap::operator[](const std::string &name) const { - auto it = options_map.find(name); - assert(it != options_map.end()); - return it->second; + auto it = options_map.find(name); + assert(it != options_map.end()); + return it->second; } // Inits options and assigns idx in the correct printing order void OptionsMap::add(const std::string &name, const Option &option) { - if (!options_map.count(name)) { - static size_t insert_order = 0; + if (!options_map.count(name)) { + static size_t insert_order = 0; - options_map[name] = option; + options_map[name] = option; - options_map[name].parent = this; - options_map[name].idx = insert_order++; - } else { - std::cerr << "Option \"" << name << "\" was already added!" << std::endl; - std::exit(EXIT_FAILURE); - } + options_map[name].parent = this; + options_map[name].idx = insert_order++; + } else { + std::cerr << "Option \"" << name << "\" was already added!" << std::endl; + std::exit(EXIT_FAILURE); + } } -std::size_t OptionsMap::count(const std::string &name) const { return options_map.count(name); } +std::size_t OptionsMap::count(const std::string &name) const { + return options_map.count(name); +} Option::Option(const OptionsMap *map) : parent(map) {} -Option::Option(const char *v, OnChange f) : type("string"), min(0), max(0), on_change(std::move(f)) { - defaultValue = currentValue = v; +Option::Option(const char *v, OnChange f) + : type("string"), min(0), max(0), on_change(std::move(f)) { + defaultValue = currentValue = v; } -Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(std::move(f)) { - defaultValue = currentValue = (v ? "true" : "false"); +Option::Option(bool v, OnChange f) + : type("check"), min(0), max(0), on_change(std::move(f)) { + defaultValue = currentValue = (v ? "true" : "false"); } -Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(std::move(f)) {} +Option::Option(OnChange f) + : type("button"), min(0), max(0), on_change(std::move(f)) {} -Option::Option(int v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(std::move(f)) { - defaultValue = currentValue = std::to_string(v); +Option::Option(int v, int minv, int maxv, OnChange f) + : type("spin"), min(minv), max(maxv), on_change(std::move(f)) { + defaultValue = currentValue = std::to_string(v); } -Option::Option(const char *v, const char *cur, OnChange f) : type("combo"), min(0), max(0), on_change(std::move(f)) { - defaultValue = v; - currentValue = cur; +Option::Option(const char *v, const char *cur, OnChange f) + : type("combo"), min(0), max(0), on_change(std::move(f)) { + defaultValue = v; + currentValue = cur; } Option::operator int() const { - assert(type == "check" || type == "spin"); - return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); + assert(type == "check" || type == "spin"); + return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); } Option::operator std::string() const { - assert(type == "string"); - return currentValue; + assert(type == "string"); + return currentValue; } bool Option::operator==(const char *s) const { - assert(type == "combo"); - return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue); + assert(type == "combo"); + return !CaseInsensitiveLess()(currentValue, s) && + !CaseInsensitiveLess()(s, currentValue); } bool Option::operator!=(const char *s) const { return !(*this == s); } @@ -122,58 +133,61 @@ bool Option::operator!=(const char *s) const { return !(*this == s); } // from the user by console window, so let's check the bounds anyway. Option &Option::operator=(const std::string &v) { - assert(!type.empty()); - - if ((type != "button" && type != "string" && v.empty()) || (type == "check" && v != "true" && v != "false") || - (type == "spin" && (std::stoi(v) < min || std::stoi(v) > max))) - return *this; - - if (type == "combo") { - OptionsMap comboMap; // To have case insensitive compare - std::string token; - std::istringstream ss(defaultValue); - while (ss >> token) - comboMap.add(token, Option()); - if (!comboMap.count(v) || v == "var") - return *this; - } - - if (type == "string") - currentValue = v == "" ? "" : v; - else if (type != "button") - currentValue = v; - - if (on_change) { - const auto ret = on_change(*this); - - if (ret && parent != nullptr && parent->info != nullptr) - parent->info(ret); - } + assert(!type.empty()); + if ((type != "button" && type != "string" && v.empty()) || + (type == "check" && v != "true" && v != "false") || + (type == "spin" && (std::stoi(v) < min || std::stoi(v) > max))) return *this; + + if (type == "combo") { + OptionsMap comboMap; // To have case insensitive compare + std::string token; + std::istringstream ss(defaultValue); + while (ss >> token) + comboMap.add(token, Option()); + if (!comboMap.count(v) || v == "var") + return *this; + } + + if (type == "string") + currentValue = v == "" ? "" : v; + else if (type != "button") + currentValue = v; + + if (on_change) { + const auto ret = on_change(*this); + + if (ret && parent != nullptr && parent->info != nullptr) + parent->info(ret); + } + + return *this; } std::ostream &operator<<(std::ostream &os, const OptionsMap &om) { - for (size_t idx = 0; idx < om.options_map.size(); ++idx) - for (const auto &it : om.options_map) - if (it.second.idx == idx) { - const Option &o = it.second; - os << "\noption name " << it.first << " type " << o.type; + for (size_t idx = 0; idx < om.options_map.size(); ++idx) + for (const auto &it : om.options_map) + if (it.second.idx == idx) { + const Option &o = it.second; + os << "\noption name " << it.first << " type " << o.type; - if (o.type == "check" || o.type == "combo") - os << " default " << o.defaultValue; + if (o.type == "check" || o.type == "combo") + os << " default " << o.defaultValue; - else if (o.type == "string") { - std::string defaultValue = o.defaultValue.empty() ? "" : o.defaultValue; - os << " default " << defaultValue; - } + else if (o.type == "string") { + std::string defaultValue = + o.defaultValue.empty() ? "" : o.defaultValue; + os << " default " << defaultValue; + } - else if (o.type == "spin") - os << " default " << stoi(o.defaultValue) << " min " << o.min << " max " << o.max; + else if (o.type == "spin") + os << " default " << stoi(o.defaultValue) << " min " << o.min + << " max " << o.max; - break; - } + break; + } - return os; + return os; } } // namespace engine diff --git a/ucioption.h b/ucioption.h index 813b4c7..012bc22 100644 --- a/ucioption.h +++ b/ucioption.h @@ -30,76 +30,76 @@ namespace engine { // Define a custom comparator, because the UCI options should be // case-insensitive struct CaseInsensitiveLess { - bool operator()(const std::string &, const std::string &) const; + bool operator()(const std::string &, const std::string &) const; }; class OptionsMap; // The Option class implements each option as specified by the UCI protocol class Option { - public: - using OnChange = std::function(const Option &)>; - - Option(const OptionsMap *); - Option(OnChange = nullptr); - Option(bool v, OnChange = nullptr); - Option(const char *v, OnChange = nullptr); - Option(int v, int minv, int maxv, OnChange = nullptr); - Option(const char *v, const char *cur, OnChange = nullptr); - - Option &operator=(const std::string &); - operator int() const; - operator std::string() const; - bool operator==(const char *) const; - bool operator!=(const char *) const; - - friend std::ostream &operator<<(std::ostream &, const OptionsMap &); - - int operator<<(const Option &) = delete; - - private: - friend class OptionsMap; - friend class Engine; - friend class Tune; - - std::string defaultValue, currentValue, type; - int min, max; - size_t idx; - OnChange on_change; - const OptionsMap *parent = nullptr; +public: + using OnChange = std::function(const Option &)>; + + Option(const OptionsMap *); + Option(OnChange = nullptr); + Option(bool v, OnChange = nullptr); + Option(const char *v, OnChange = nullptr); + Option(int v, int minv, int maxv, OnChange = nullptr); + Option(const char *v, const char *cur, OnChange = nullptr); + + Option &operator=(const std::string &); + operator int() const; + operator std::string() const; + bool operator==(const char *) const; + bool operator!=(const char *) const; + + friend std::ostream &operator<<(std::ostream &, const OptionsMap &); + + int operator<<(const Option &) = delete; + +private: + friend class OptionsMap; + friend class Engine; + friend class Tune; + + std::string defaultValue, currentValue, type; + int min, max; + size_t idx; + OnChange on_change; + const OptionsMap *parent = nullptr; }; class OptionsMap { - public: - using InfoListener = std::function)>; +public: + using InfoListener = std::function)>; - OptionsMap() = default; - OptionsMap(const OptionsMap &) = delete; - OptionsMap(OptionsMap &&) = delete; - OptionsMap &operator=(const OptionsMap &) = delete; - OptionsMap &operator=(OptionsMap &&) = delete; + OptionsMap() = default; + OptionsMap(const OptionsMap &) = delete; + OptionsMap(OptionsMap &&) = delete; + OptionsMap &operator=(const OptionsMap &) = delete; + OptionsMap &operator=(OptionsMap &&) = delete; - void add_info_listener(InfoListener &&); + void add_info_listener(InfoListener &&); - void setoption(std::istringstream &); + void setoption(std::istringstream &); - const Option &operator[](const std::string &) const; + const Option &operator[](const std::string &) const; - void add(const std::string &, const Option &option); + void add(const std::string &, const Option &option); - std::size_t count(const std::string &) const; + std::size_t count(const std::string &) const; - private: - friend class Engine; - friend class Option; +private: + friend class Engine; + friend class Option; - friend std::ostream &operator<<(std::ostream &, const OptionsMap &); + friend std::ostream &operator<<(std::ostream &, const OptionsMap &); - // The options container is defined as a std::map - using OptionsStore = std::map; + // The options container is defined as a std::map + using OptionsStore = std::map; - OptionsStore options_map; - InfoListener info; + OptionsStore options_map; + InfoListener info; }; } // namespace engine From 01fcb3c77de5ec10b5a41b49bc91ad8fc86f7cef Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 9 Jun 2026 22:55:46 +0700 Subject: [PATCH 12/29] fix regression:)))) it's significantly stronger as internally needed --- .gitignore | 3 +- CMakeLists.txt | 2 +- eval.cpp | 942 +++++++++++++++++++-------------------- eval.h | 22 +- main.cpp | 36 +- movepick.cpp | 156 ++++--- movepick.h | 3 +- score.cpp | 20 +- score.h | 40 +- search.cpp | 1164 +++++++++++++++++++++++------------------------- search.h | 24 +- tb.cpp | 50 +-- timeman.cpp | 112 +++-- timeman.h | 58 ++- tt.cpp | 89 ++-- tt.h | 273 +++++------- tune.cpp | 68 ++- tune.h | 211 +++++---- uci.cpp | 343 +++++++------- uci.h | 32 +- ucioption.cpp | 192 ++++---- ucioption.h | 102 ++--- 22 files changed, 1904 insertions(+), 2038 deletions(-) diff --git a/.gitignore b/.gitignore index ec84a85..a31ee69 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ !.gitignore !CMakeLists.txt !.github/ -!.github/** \ No newline at end of file +!.github/** +!.clang-format \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 251515c..da09fe3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ FetchContent_Declare( ) set(BUILD_GAVIOTA OFF CACHE BOOL "Enable Gaviota TB probing" FORCE) FetchContent_MakeAvailable(chesslib tbprobe) -add_executable(engine "main.cpp" "timeman.cpp" "timeman.h" "eval.h" "eval.cpp" "tune.h" "ucioption.h" "tune.cpp" "ucioption.cpp" "tt.h" "tt.cpp" "uci.cpp" "uci.h" "search.h" "search.cpp" "score.h" "score.cpp" "movepick.h" "movepick.cpp" "tb.h" "tb.cpp") +add_executable(engine "main.cpp" "timeman.cpp" "timeman.h" "eval.h" "eval.cpp" "tune.h" "ucioption.h" "tune.cpp" "ucioption.cpp" "tt.h" "tt.cpp" "uci.cpp" "uci.h" "search.h" "search.cpp" "score.h" "score.cpp" "movepick.h" "movepick.cpp" "tb.h" "tb.cpp" "Weights.h") target_link_libraries(engine PRIVATE chesslib syzygy_probe) target_include_directories(engine PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${chesslib_SOURCE_DIR}) execute_process( diff --git a/eval.cpp b/eval.cpp index 868ac06..29932c8 100644 --- a/eval.cpp +++ b/eval.cpp @@ -1,4 +1,5 @@ #include "eval.h" +#include "Weights.h" #include "tune.h" #include #include @@ -8,534 +9,509 @@ using namespace engine::eval; namespace engine::eval { static Bitboard passedMask[2][64]; struct PassedMaskInit { - PassedMaskInit() { - for (int sq = 0; sq < 64; sq++) { - File f = file_of(Square(sq)); - Rank r = rank_of(Square(sq)); - for (int adjF = std::max(0, (int)f - 1); adjF <= std::min(7, (int)f + 1); - adjF++) { - for (int r2 = (int)r + 1; r2 <= 7; r2++) - passedMask[WHITE][sq] |= - attacks::MASK_FILE[adjF] & attacks::MASK_RANK[r2]; - for (int r2 = (int)r - 1; r2 >= 0; r2--) - passedMask[BLACK][sq] |= - attacks::MASK_FILE[adjF] & attacks::MASK_RANK[r2]; - } + PassedMaskInit() { + for (int sq = 0; sq < 64; sq++) { + File f = file_of(Square(sq)); + Rank r = rank_of(Square(sq)); + for (int adjF = std::max(0, (int)f - 1); adjF <= std::min(7, (int)f + 1); adjF++) { + for (int r2 = (int)r + 1; r2 <= 7; r2++) + passedMask[WHITE][sq] |= attacks::MASK_FILE[adjF] & attacks::MASK_RANK[r2]; + for (int r2 = (int)r - 1; r2 >= 0; r2--) + passedMask[BLACK][sq] |= attacks::MASK_FILE[adjF] & attacks::MASK_RANK[r2]; + } + } } - } }; static PassedMaskInit passedMaskInit; -//clang-format off -Value tempo = 50; -Value PawnValue = 72; -Value KnightValue = 372; -Value BishopValue = 202; -Value RookValue = 324; -Value QueenValue = 652; -Value fianchettoBonus = 44; -Value trappedBishopPenalty = 29; -Value centerWeight = 33; -Value mopUpKingDistWeight = 24; -Value mopUpEdgeDistWeight = 38; -Value spaceWeight = 73; -Value bishopPairMg = 94; -Value bishopPairEg = 14; -Value rookOpenFileMg = 34; -Value rookOpenFileEg = 29; -Value rookSemiOpenFileMg = 25; -Value rookSemiOpenFileEg = 14; -Value doubledPawnMg = 60; -Value doubledPawnEg = 63; -Value isolatedPawnMg = 77; -Value isolatedPawnEg = 23; -Value kingShelterBaseMg = 16; -Value kingShelterBaseEg = 6; -Value kingShelterDecayMg = 5; -Value kingShelterDecayEg = 10; -Value kqkDistWeight = 28; -Value kqkEdgeWeight = 45; -Value krkDistWeight = 4; -Value krkEdgeWeight = 17; -Value kpkWeight = 17; -Value mgMobilityCnt[7][8] = {{92, -46, 6, -99, -98, -35, 74, -28}, - {-81, 43, 0, -100, -68, -86, 47, 29}, - {51, 71, 88, -78, 17, -56, 99, 5}, - {-59, -78, -88, 22, 2, 86, -77, -1}, - {-55, -98, 93, 70, 27, -54, -100, -83}, - {13, 1, -81, -7, -42, -95, -68, -99}, - {-37, 43, 8, -100, -99, 36, 91, -42}}; -Value egMobilityCnt[7][8] = {{-75, -98, 51, 26, -10, 68, 2, 10}, - {-65, -80, 95, 79, -43, -16, 100, -99}, - {63, -25, -74, -41, 100, -37, 4, -85}, - {-66, 40, 41, 46, -65, -75, 29, 78}, - {12, -53, -91, 23, -100, -66, 40, 73}, - {-28, 5, -46, 25, -13, 11, 87, 33}, - {-89, -4, 28, -27, 7, -1, 26, -98}}; -Value kingTropismMg[7] = {-29, 17, 7, -15, -49, -14, -1}; -Value kingTropismEg[7] = {-41, 0, -31, -41, 19, 36, 50}; -Value passedBonusMg[8] = {313, 227, 386, 163, 109, 284, 220, 363}; -Value passedBonusEg[8] = {380, 350, 394, 388, 149, 394, 190, 316}; -Value mg_pawn_table[56] = { - 114, -495, 41, -368, -477, 412, -158, -276, -129, -129, -237, 437, - -158, 121, -2, -160, 163, 459, 425, -298, 52, -296, -496, 333, - -390, -473, -314, -319, -357, 348, 127, -347, 175, -346, -398, 155, - 362, 477, -147, -418, -477, 311, 191, -489, -414, -352, 479, -89, - 499, 265, -325, -291, -55, 491, -499, 470}; -Value mg_knight_table[64] = { - 443, 51, 110, 497, 243, 173, 224, 109, -498, 280, -499, 185, 58, - 494, 408, -33, -395, 45, -15, 208, 245, -13, 378, 359, -489, 352, - 96, -152, 422, -414, -153, 304, -483, -113, 346, 1, 104, -185, 141, - -483, 405, -500, -117, 158, -306, -145, 465, 458, 257, -344, -107, 26, - 334, 369, -338, 352, -412, -223, 69, 244, -156, 335, -368, 359}; -Value mg_bishop_table[64] = { - -258, -250, 404, 459, 322, 66, 411, 227, -93, -52, -352, 13, 450, - -441, 392, -139, 315, 263, 210, 299, -397, 133, 371, 440, 272, 221, - -76, -125, -13, 483, 135, 182, -351, 203, 101, 298, 352, 277, -478, - 488, -62, 42, -144, 496, -396, 195, -414, 11, 409, 116, -373, 386, - 377, 47, 493, 0, -302, 312, 81, 459, 350, -187, -265, 153}; -Value mg_rook_table[64] = { - -27, -134, -499, 159, 77, -60, 266, 171, 77, -364, -338, -315, -123, - 482, -341, 412, 213, 132, 134, 254, 354, -40, -145, -364, 249, 497, - -490, 25, -191, -245, 104, 428, 417, 49, 499, -426, -261, 466, 80, - -61, -457, 75, 95, 15, 270, -250, -171, 165, 186, 92, 401, 482, - -223, -70, 384, 316, -189, 13, 185, -110, 207, -307, -257, 175}; -Value mg_king_table[64] = { - -498, 290, 205, 125, -483, -313, -372, -330, 302, 312, -171, -115, 403, - 43, -259, 212, -264, -396, -329, 260, -288, 374, 454, 499, -124, -366, - -335, 371, 161, -361, 368, 102, -454, -133, -52, 139, -102, -418, 380, - 499, -108, 340, -352, 326, 415, 2, 150, -314, 430, -161, -399, -99, - 8, -292, -81, 79, -491, 309, -160, -208, -173, -283, -313, -62}; -Value mg_queen_table[64] = { - -301, -182, 487, 58, 270, 273, 177, -207, -175, 246, 266, 332, -416, - -363, 54, -202, 261, -423, -97, 280, -251, -265, 79, 493, 332, -133, - 499, -375, -251, 210, 139, -248, 424, 193, -36, -279, 429, 62, 379, - 173, 446, 0, -370, 426, -462, 287, 165, -499, 94, -425, 143, -409, - 144, 495, 111, 452, 78, 157, -143, -478, 243, -97, -489, 112}; -Value eg_pawn_table[56] = { - -273, -6, 100, 320, -127, 163, -33, 79, 235, 334, 47, 435, - -149, -1, -155, -277, -25, 220, 340, -396, 401, 446, -30, 424, - 472, -385, 486, -91, 99, -275, 340, -330, -125, 150, -121, -30, - -207, -244, -277, 411, 301, -298, 77, -313, -141, -357, 386, 255, - -495, 80, -500, 400, -450, 130, -111, -28}; -Value eg_knight_table[64] = { - 371, -385, 222, -192, 139, 46, -343, 59, 366, -360, 248, 384, -63, - -425, 225, 414, -333, 396, -421, -99, 10, 367, -368, 500, -500, 270, - 41, 168, 154, 327, -161, 457, -368, 79, -265, -457, 94, -294, -234, - 348, 233, -471, -360, 380, -228, 3, 314, 415, -327, 200, -91, 251, - -390, -294, -187, -335, -29, 177, -341, 197, -37, 143, 217, 384}; -Value eg_bishop_table[64] = { - 31, 57, -369, -271, 267, -495, -141, -276, 239, 405, 217, 34, -391, - -36, 122, -427, -35, 406, 286, 52, 497, 162, 262, -147, -251, -470, - -258, -171, -63, -203, 296, 497, 90, 444, 422, -241, -499, 62, -312, - 299, -345, 309, 168, 142, 161, -170, 138, -116, 359, -45, 214, 191, - -360, 163, 433, 386, 182, -181, 348, 258, 415, 375, -233, 214}; -Value eg_rook_table[64] = { - -227, 492, 17, -205, 495, 12, 207, -246, 195, -22, 312, - 16, -124, -259, 405, -494, -262, -344, -218, -12, 8, 388, - 306, 316, 376, 455, -139, -194, 41, 158, 283, 476, 469, - 94, 63, -498, -414, 0, -292, -373, -270, 256, -162, -385, - 453, -220, 162, -53, -221, -435, -426, -222, 235, 116, 182, - 409, -327, -486, 80, 109, -455, -247, 106, -307}; -Value eg_king_table[64] = { - -319, -481, 495, -290, 1, 426, -347, -112, 423, -89, 500, -432, 54, - -223, 148, 118, -292, -351, -205, 195, 377, -185, -296, -176, -73, 247, - -491, -411, -232, 106, -295, 326, 60, 500, -488, 218, 207, -409, 91, - -390, 35, -249, 111, -179, -169, -182, -372, -330, 425, 455, 457, 500, - -494, -442, 24, 170, 367, 353, -297, 97, -137, 125, 221, -32}; -Value eg_queen_table[64] = { - -326, 119, 358, 91, -311, -289, 373, 329, 44, -83, 162, -470, -470, - -2, 45, -486, 438, 39, -493, 371, 493, -278, 338, -416, 360, 499, - 357, -145, -500, 398, -210, -199, 218, -22, -367, 69, 144, 488, -55, - -71, 499, -36, -262, -351, -407, 291, -497, -412, -164, -46, 186, 323, - -56, -416, -129, 394, 71, -343, 183, -360, 91, -295, -180, 88}; -//clang-format on -Value *mgPst[] = {nullptr, mg_pawn_table, mg_knight_table, - mg_bishop_table, mg_rook_table, mg_queen_table, - mg_king_table}; +Value *mgPst[] = { nullptr, mg_pawn_table, mg_knight_table, mg_bishop_table, mg_rook_table, mg_queen_table, mg_king_table }; -Value *egPst[] = {nullptr, eg_pawn_table, eg_knight_table, - eg_bishop_table, eg_rook_table, eg_queen_table, - eg_king_table}; +Value *egPst[] = { nullptr, eg_pawn_table, eg_knight_table, eg_bishop_table, eg_rook_table, eg_queen_table, eg_king_table }; // tuning slop here -TUNE(SetRange(0, 50), tempo, SetRange(50, 200), PawnValue, SetRange(200, 500), - KnightValue, SetRange(200, 500), BishopValue, SetRange(300, 700), - RookValue, SetRange(600, 1400), QueenValue); -TUNE(SetRange(-100, 100), mgMobilityCnt, egMobilityCnt); -TUNE(SetRange(0, 50), fianchettoBonus, SetRange(0, 150), trappedBishopPenalty); -TUNE(SetRange(-50, 50), kingTropismMg, kingTropismEg); -TUNE(SetRange(0, 50), centerWeight, SetRange(0, 30), mopUpKingDistWeight, - SetRange(0, 50), mopUpEdgeDistWeight, SetRange(0, 100), spaceWeight); -TUNE(SetRange(0, 100), bishopPairMg, SetRange(0, 100), bishopPairEg); -TUNE(SetRange(0, 50), rookOpenFileMg, SetRange(0, 50), rookOpenFileEg, - SetRange(0, 50), rookSemiOpenFileMg, SetRange(0, 50), rookSemiOpenFileEg); -TUNE(SetRange(0, 100), doubledPawnMg, SetRange(0, 100), doubledPawnEg, - SetRange(0, 100), isolatedPawnMg, SetRange(0, 100), isolatedPawnEg); -TUNE(SetRange(0, 400), passedBonusMg, passedBonusEg); -TUNE(SetRange(0, 30), kingShelterBaseMg, SetRange(0, 30), kingShelterBaseEg, - SetRange(0, 10), kingShelterDecayMg, SetRange(0, 10), kingShelterDecayEg); -TUNE(SetRange(-500, 500), mg_pawn_table, mg_knight_table, mg_bishop_table, - mg_rook_table, mg_king_table, mg_queen_table, eg_pawn_table, - eg_knight_table, eg_bishop_table, eg_rook_table, eg_king_table, - eg_queen_table); -TUNE(SetRange(0, 50), kqkDistWeight, SetRange(0, 50), kqkEdgeWeight, - SetRange(0, 50), krkDistWeight, SetRange(0, 50), krkEdgeWeight, - SetRange(0, 50), kpkWeight); +TUNE(SetRange(5, 30), + tempo, + SetRange(80, 120), + PawnValue, + SetRange(280, 370), + KnightValue, + SetRange(300, 400), + BishopValue, + SetRange(450, 550), + RookValue, + SetRange(800, 1000), + QueenValue); +TUNE(SetRange(-10, 10), mgMobilityCnt, egMobilityCnt); +TUNE(SetRange(0, 30), fianchettoBonus, SetRange(0, 100), trappedBishopPenalty); +TUNE(SetRange(-20, 20), kingTropismMg, kingTropismEg); +TUNE(SetRange(0, 30), + centerWeight, + SetRange(0, 20), + mopUpKingDistWeight, + SetRange(0, 20), + mopUpEdgeDistWeight, + SetRange(1, 30), + spaceWeight); +TUNE(SetRange(0, 50), bishopPairMg, SetRange(0, 50), bishopPairEg); +TUNE(SetRange(0, 30), + rookOpenFileMg, + SetRange(0, 30), + rookOpenFileEg, + SetRange(0, 20), + rookSemiOpenFileMg, + SetRange(0, 20), + rookSemiOpenFileEg); +TUNE(SetRange(0, 30), + doubledPawnMg, + SetRange(0, 30), + doubledPawnEg, + SetRange(0, 40), + isolatedPawnMg, + SetRange(0, 40), + isolatedPawnEg); +TUNE(SetRange(0, 200), passedBonusMg[1],passedBonusMg[2],passedBonusMg[3],passedBonusMg[4],passedBonusMg[5],passedBonusMg[6], passedBonusEg[1],passedBonusEg[2],passedBonusEg[3],passedBonusEg[4],passedBonusEg[5],passedBonusEg[6]); +TUNE(SetRange(1, 30), + kingShelterBaseMg, + SetRange(1, 30), + kingShelterBaseEg, + SetRange(1, 10), + kingShelterDecayMg, + SetRange(1, 10), + kingShelterDecayEg); +TUNE(SetRange(-25, 25), + mg_knight_table, + mg_bishop_table, + mg_rook_table, + mg_king_table, + mg_queen_table, + eg_knight_table, + eg_bishop_table, + eg_rook_table, + eg_king_table, + eg_queen_table, + mg_pawn_table[8], + eg_pawn_table[8], + mg_pawn_table[9], + eg_pawn_table[9], + mg_pawn_table[10], + eg_pawn_table[10], + mg_pawn_table[11], + eg_pawn_table[11], + mg_pawn_table[12], + eg_pawn_table[12], + mg_pawn_table[13], + eg_pawn_table[13], + mg_pawn_table[14], + eg_pawn_table[14], + mg_pawn_table[15], + eg_pawn_table[15], + mg_pawn_table[16], + eg_pawn_table[16], + mg_pawn_table[17], + eg_pawn_table[17], + mg_pawn_table[18], + eg_pawn_table[18], + mg_pawn_table[19], + eg_pawn_table[19], + mg_pawn_table[20], + eg_pawn_table[20], + mg_pawn_table[21], + eg_pawn_table[21], + mg_pawn_table[22], + eg_pawn_table[22], + mg_pawn_table[23], + eg_pawn_table[23], + mg_pawn_table[24], + eg_pawn_table[24], + mg_pawn_table[25], + eg_pawn_table[25], + mg_pawn_table[26], + eg_pawn_table[26], + mg_pawn_table[27], + eg_pawn_table[27], + mg_pawn_table[28], + eg_pawn_table[28], + mg_pawn_table[29], + eg_pawn_table[29], + mg_pawn_table[30], + eg_pawn_table[30], + mg_pawn_table[31], + eg_pawn_table[31], + mg_pawn_table[32], + eg_pawn_table[32], + mg_pawn_table[33], + eg_pawn_table[33], + mg_pawn_table[34], + eg_pawn_table[34], + mg_pawn_table[35], + eg_pawn_table[35], + mg_pawn_table[36], + eg_pawn_table[36], + mg_pawn_table[37], + eg_pawn_table[37], + mg_pawn_table[38], + eg_pawn_table[38], + mg_pawn_table[39], + eg_pawn_table[39], + mg_pawn_table[40], + eg_pawn_table[40], + mg_pawn_table[41], + eg_pawn_table[41], + mg_pawn_table[42], + eg_pawn_table[42], + mg_pawn_table[43], + eg_pawn_table[43], + mg_pawn_table[44], + eg_pawn_table[44], + mg_pawn_table[45], + eg_pawn_table[45], + mg_pawn_table[46], + eg_pawn_table[46], + mg_pawn_table[47], + eg_pawn_table[47], + mg_pawn_table[48], + eg_pawn_table[48], + mg_pawn_table[49], + eg_pawn_table[49], + mg_pawn_table[50], + eg_pawn_table[50], + mg_pawn_table[51], + eg_pawn_table[51], + mg_pawn_table[52], + eg_pawn_table[52], + mg_pawn_table[53], + eg_pawn_table[53], + mg_pawn_table[54], + eg_pawn_table[54], + mg_pawn_table[55], + eg_pawn_table[55]); +TUNE(SetRange(0, 30), + kqkDistWeight, + SetRange(0, 20), + kqkEdgeWeight, + SetRange(0, 20), + krkDistWeight, + SetRange(0, 20), + krkEdgeWeight, + SetRange(0, 30), + kpkWeight); Value eval(const chess::Board &board) { - constexpr int KnightPhase = 1; - constexpr int BishopPhase = 1; - constexpr int RookPhase = 2; - constexpr int QueenPhase = 4; - constexpr int TotalPhase = - KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; - const int sign = board.side_to_move() == WHITE ? 1 : -1; - int mgScore = 0; - int egScore = 0; - int phase = 0; - { - Bitboard pinMask = board.pin_mask(); - Bitboard occ = board.occ(), occ2 = occ; - while (occ) { - Square sq = Square(pop_lsb(occ)); - auto p = board.at(sq); - Color pc = color_of(p); - int _sign = pc == WHITE ? 1 : -1; - Square _sq = _sign == 1 ? sq : square_mirror(sq); - auto pt = piece_of(p); - if (pt == NO_PIECE_TYPE) - continue; - mgScore += _sign * mgPst[pt][_sq]; - egScore += _sign * egPst[pt][_sq]; - mgScore += _sign * piece_value(pt); - egScore += _sign * piece_value(pt); - if (pt == KNIGHT) - phase += KnightPhase; - else if (pt == BISHOP) - phase += BishopPhase; - else if (pt == ROOK) - phase += RookPhase; - else if (pt == QUEEN) - phase += QueenPhase; - Bitboard attacks = 0; - if (pt == KNIGHT) - attacks = chess::attacks::knight(sq); - else if (pt == BISHOP) - attacks = chess::attacks::bishop(sq, occ2); - else if (pt == ROOK) - attacks = chess::attacks::rook(sq, occ2); - else if (pt == QUEEN) - attacks = chess::attacks::queen(sq, occ2); - attacks &= ~board.us(pc); - int mobility = popcount(attacks); - int clampedMobility = std::min(mobility, 7); - if ((1ULL << sq) & pinMask) - clampedMobility = std::min(clampedMobility / 4, 7); - mgScore += _sign * mgMobilityCnt[pt][clampedMobility]; - egScore += _sign * egMobilityCnt[pt][clampedMobility]; - if (pt != PAWN && pt != KING) { - int kd = square_distance(sq, board.kingSq(~pc)); - mgScore += _sign * (7 - kd) * kingTropismMg[pt]; - egScore += _sign * (7 - kd) * kingTropismEg[pt]; - } + constexpr int KnightPhase = 1; + constexpr int BishopPhase = 1; + constexpr int RookPhase = 2; + constexpr int QueenPhase = 4; + constexpr int TotalPhase = KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; + const int sign = board.side_to_move() == WHITE ? 1 : -1; + int mgScore = 0; + int egScore = 0; + int phase = 0; + { + Bitboard pinMask = board.pin_mask(); + Bitboard occ = board.occ(), occ2 = occ; + while (occ) { + Square sq = Square(pop_lsb(occ)); + auto p = board.at(sq); + Color pc = color_of(p); + int _sign = pc == WHITE ? 1 : -1; + Square _sq = _sign == 1 ? sq : square_mirror(sq); + auto pt = piece_of(p); + if (pt == NO_PIECE_TYPE) + continue; + mgScore += _sign * mgPst[pt][_sq]; + egScore += _sign * egPst[pt][_sq]; + mgScore += _sign * piece_value(pt); + egScore += _sign * piece_value(pt); + if (pt == KNIGHT) + phase += KnightPhase; + else if (pt == BISHOP) + phase += BishopPhase; + else if (pt == ROOK) + phase += RookPhase; + else if (pt == QUEEN) + phase += QueenPhase; + Bitboard attacks = 0; + if (pt == KNIGHT) + attacks = chess::attacks::knight(sq); + else if (pt == BISHOP) + attacks = chess::attacks::bishop(sq, occ2); + else if (pt == ROOK) + attacks = chess::attacks::rook(sq, occ2); + else if (pt == QUEEN) + attacks = chess::attacks::queen(sq, occ2); + attacks &= ~board.us(pc); + int mobility = popcount(attacks); + int clampedMobility = std::min(mobility, 7); + if ((1ULL << sq) & pinMask) + clampedMobility = std::min(clampedMobility / 4, 7); + mgScore += _sign * mgMobilityCnt[pt][clampedMobility]; + egScore += _sign * egMobilityCnt[pt][clampedMobility]; + if (pt != PAWN && pt != KING) { + int kd = square_distance(sq, board.kingSq(~pc)); + mgScore += _sign * (7 - kd) * kingTropismMg[pt]; + egScore += _sign * (7 - kd) * kingTropismEg[pt]; + } + } } - } - // Bishop pair bonus - for (Color c : {WHITE, BLACK}) { - if (board.count(BISHOP, c) >= 2) { - int s = (c == WHITE) ? 1 : -1; - mgScore += s * bishopPairMg; - egScore += s * bishopPairEg; + // Bishop pair bonus + for (Color c : { WHITE, BLACK }) { + if (board.count(BISHOP, c) >= 2) { + int s = (c == WHITE) ? 1 : -1; + mgScore += s * bishopPairMg; + egScore += s * bishopPairEg; + } } - } - // Fianchetto bonus - for (Color c : {WHITE, BLACK}) { - int s = (c == WHITE) ? 1 : -1; - Square fianchettoSq[2] = {relative_square(c, SQ_B2), - relative_square(c, SQ_G2)}; - Square pawnSq[2] = {relative_square(c, SQ_B3), relative_square(c, SQ_G3)}; - for (int i = 0; i < 2; i++) { - if (board.at(fianchettoSq[i]) == BISHOP && - board.at(fianchettoSq[i]) == c && - board.at(pawnSq[i]) == PAWN && - board.at(pawnSq[i]) == c) { - mgScore += s * fianchettoBonus; - egScore += s * fianchettoBonus; - } + // Fianchetto bonus + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Square fianchettoSq[2] = { relative_square(c, SQ_B2), relative_square(c, SQ_G2) }; + Square pawnSq[2] = { relative_square(c, SQ_B3), relative_square(c, SQ_G3) }; + for (int i = 0; i < 2; i++) { + if (board.at(fianchettoSq[i]) == BISHOP && board.at(fianchettoSq[i]) == c && + board.at(pawnSq[i]) == PAWN && board.at(pawnSq[i]) == c) { + mgScore += s * fianchettoBonus; + egScore += s * fianchettoBonus; + } + } } - } - // Trapped bishop penalty - for (Color c : {WHITE, BLACK}) { - int s = (c == WHITE) ? 1 : -1; - Square trapSq[2] = {relative_square(c, SQ_A2), relative_square(c, SQ_H2)}; - Square kingAdj[2] = {relative_square(c, SQ_B1), relative_square(c, SQ_G1)}; - for (int i = 0; i < 2; i++) { - if (board.at(trapSq[i]) == BISHOP && - board.at(trapSq[i]) == c && board.kingSq(c) == kingAdj[i]) { - mgScore -= s * trappedBishopPenalty; - egScore -= s * trappedBishopPenalty; - } + // Trapped bishop penalty + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Square trapSq[2] = { relative_square(c, SQ_A2), relative_square(c, SQ_H2) }; + Square kingAdj[2] = { relative_square(c, SQ_B1), relative_square(c, SQ_G1) }; + for (int i = 0; i < 2; i++) { + if (board.at(trapSq[i]) == BISHOP && board.at(trapSq[i]) == c && board.kingSq(c) == kingAdj[i]) { + mgScore -= s * trappedBishopPenalty; + egScore -= s * trappedBishopPenalty; + } + } } - } - // Rook on open/semi-open file - for (Color c : {WHITE, BLACK}) { - int s = (c == WHITE) ? 1 : -1; - Bitboard rooks = board.pieces(ROOK, c); - while (rooks) { - Square sq = Square(pop_lsb(rooks)); - File f = file_of(sq); - Bitboard fileMask = attacks::MASK_FILE[f]; - bool hasOwnPawn = (board.pieces(PAWN, c) & fileMask) != 0; - bool hasEnemyPawn = (board.pieces(PAWN, ~c) & fileMask) != 0; - if (!hasOwnPawn && !hasEnemyPawn) { - mgScore += s * rookOpenFileMg; - egScore += s * rookOpenFileEg; - } else if (!hasOwnPawn) { - mgScore += s * rookSemiOpenFileMg; - egScore += s * rookSemiOpenFileEg; - } + // Rook on open/semi-open file + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Bitboard rooks = board.pieces(ROOK, c); + while (rooks) { + Square sq = Square(pop_lsb(rooks)); + File f = file_of(sq); + Bitboard fileMask = attacks::MASK_FILE[f]; + bool hasOwnPawn = (board.pieces(PAWN, c) & fileMask) != 0; + bool hasEnemyPawn = (board.pieces(PAWN, ~c) & fileMask) != 0; + if (!hasOwnPawn && !hasEnemyPawn) { + mgScore += s * rookOpenFileMg; + egScore += s * rookOpenFileEg; + } else if (!hasOwnPawn) { + mgScore += s * rookSemiOpenFileMg; + egScore += s * rookSemiOpenFileEg; + } + } } - } - // Precompute pawn data - Bitboard pawnBB[2] = {board.pieces(PAWN, WHITE), board.pieces(PAWN, BLACK)}; - Bitboard pawnAtks[2] = {attacks::pawn(pawnBB[WHITE]), - attacks::pawn(pawnBB[BLACK])}; + // Precompute pawn data + Bitboard pawnBB[2] = { board.pieces(PAWN, WHITE), board.pieces(PAWN, BLACK) }; + Bitboard pawnAtks[2] = { attacks::pawn(pawnBB[WHITE]), attacks::pawn(pawnBB[BLACK]) }; - // Pawn structure: doubled, isolated, passed in one pass per color - for (Color c : {WHITE, BLACK}) { - int s = (c == WHITE) ? 1 : -1; - Bitboard pawns = pawnBB[c]; - Bitboard enemyPawns = pawnBB[~c]; + // Pawn structure: doubled, isolated, passed in one pass per color + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Bitboard pawns = pawnBB[c]; + Bitboard enemyPawns = pawnBB[~c]; - bool fileHasPawn[8] = {false}; - int fileCount[8] = {0}; - Bitboard tmp = pawns; - while (tmp) { - Square sq = Square(pop_lsb(tmp)); - File f = file_of(sq); - fileCount[f]++; - fileHasPawn[f] = true; - } + bool fileHasPawn[8] = { false }; + int fileCount[8] = { 0 }; + Bitboard tmp = pawns; + while (tmp) { + Square sq = Square(pop_lsb(tmp)); + File f = file_of(sq); + fileCount[f]++; + fileHasPawn[f] = true; + } - for (int f = 0; f < 8; f++) - if (fileCount[f] >= 2) { - mgScore -= s * (fileCount[f] - 1) * doubledPawnMg; - egScore -= s * (fileCount[f] - 1) * doubledPawnEg; - } + for (int f = 0; f < 8; f++) + if (fileCount[f] >= 2) { + mgScore -= s * (fileCount[f] - 1) * doubledPawnMg; + egScore -= s * (fileCount[f] - 1) * doubledPawnEg; + } - tmp = pawns; - while (tmp) { - Square sq = Square(pop_lsb(tmp)); - File f = file_of(sq); - Rank relRank = relative_rank(c, sq); + tmp = pawns; + while (tmp) { + Square sq = Square(pop_lsb(tmp)); + File f = file_of(sq); + Rank relRank = relative_rank(c, sq); - bool isolated = true; - if (f > FILE_A && fileHasPawn[f - 1]) - isolated = false; - if (f < FILE_H && fileHasPawn[f + 1]) - isolated = false; - if (isolated) { - mgScore -= s * isolatedPawnMg; - egScore -= s * isolatedPawnEg; - } + bool isolated = true; + if (f > FILE_A && fileHasPawn[f - 1]) + isolated = false; + if (f < FILE_H && fileHasPawn[f + 1]) + isolated = false; + if (isolated) { + mgScore -= s * isolatedPawnMg; + egScore -= s * isolatedPawnEg; + } - if (relRank >= RANK_2 && (passedMask[c][sq] & enemyPawns) == 0) { - mgScore += s * passedBonusMg[relRank]; - egScore += s * passedBonusEg[relRank]; - } + if (relRank >= RANK_2 && (passedMask[c][sq] & enemyPawns) == 0) { + mgScore += s * passedBonusMg[relRank]; + egScore += s * passedBonusEg[relRank]; + } + } } - } - // Center control (using precomputed pawn attacks) - { - Bitboard centerMask = - (1ULL << SQ_D4) | (1ULL << SQ_E4) | (1ULL << SQ_D5) | (1ULL << SQ_E5); - int wc = popcount(pawnAtks[WHITE] & centerMask); - int bc = popcount(pawnAtks[BLACK] & centerMask); - mgScore += (wc - bc) * centerWeight; - egScore += (wc - bc) * centerWeight; - } + // Center control (using precomputed pawn attacks) + { + Bitboard centerMask = (1ULL << SQ_D4) | (1ULL << SQ_E4) | (1ULL << SQ_D5) | (1ULL << SQ_E5); + int wc = popcount(pawnAtks[WHITE] & centerMask); + int bc = popcount(pawnAtks[BLACK] & centerMask); + mgScore += (wc - bc) * centerWeight; + egScore += (wc - bc) * centerWeight; + } - // Space evaluation - for (Color c : {WHITE, BLACK}) { - int s = (c == WHITE) ? 1 : -1; - Bitboard safe = ~pawnAtks[~c]; - Bitboard enemyCamp = c == WHITE - ? (attacks::MASK_RANK[4] | attacks::MASK_RANK[5] | - attacks::MASK_RANK[6]) - : (attacks::MASK_RANK[3] | attacks::MASK_RANK[2] | - attacks::MASK_RANK[1]); - int space = popcount(pawnAtks[c] & safe & enemyCamp); - mgScore += s * space * spaceWeight; - egScore += s * space * spaceWeight; - } + // Space evaluation + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Bitboard safe = ~pawnAtks[~c]; + Bitboard enemyCamp = c == WHITE ? (attacks::MASK_RANK[4] | attacks::MASK_RANK[5] | attacks::MASK_RANK[6]) + : (attacks::MASK_RANK[3] | attacks::MASK_RANK[2] | attacks::MASK_RANK[1]); + int space = popcount(pawnAtks[c] & safe & enemyCamp); + mgScore += s * space * spaceWeight; + egScore += s * space * spaceWeight; + } - // King safety: pawn shelter - for (Color c : {WHITE, BLACK}) { - int s = (c == WHITE) ? 1 : -1; - Square kingSq = board.kingSq(c); - File kf = file_of(kingSq); - Rank kr = rank_of(kingSq); - Bitboard pawns = board.pieces(PAWN, c); - int shelterMg = 0, shelterEg = 0; - int startF = std::max(0, (int)kf - 1); - int endF = std::min(7, (int)kf + 1); - for (int adjF = startF; adjF <= endF; adjF++) { - Bitboard fileMask = attacks::MASK_FILE[adjF]; - if (c == WHITE) { - for (int r = (int)kr + 1; r <= std::min(7, (int)kr + 3); r++) { - if (pawns & (fileMask & attacks::MASK_RANK[r])) { - shelterMg += - kingShelterBaseMg - (r - (int)kr - 1) * kingShelterDecayMg; - shelterEg += - kingShelterBaseEg - (r - (int)kr - 1) * kingShelterDecayEg; - } - } - } else { - for (int r = (int)kr - 1; r >= std::max(0, (int)kr - 3); r--) { - if (pawns & (fileMask & attacks::MASK_RANK[r])) { - shelterMg += - kingShelterBaseMg - ((int)kr - r - 1) * kingShelterDecayMg; - shelterEg += - kingShelterBaseEg - ((int)kr - r - 1) * kingShelterDecayEg; - } + // King safety: pawn shelter + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Square kingSq = board.kingSq(c); + File kf = file_of(kingSq); + Rank kr = rank_of(kingSq); + Bitboard pawns = board.pieces(PAWN, c); + int shelterMg = 0, shelterEg = 0; + int startF = std::max(0, (int)kf - 1); + int endF = std::min(7, (int)kf + 1); + for (int adjF = startF; adjF <= endF; adjF++) { + Bitboard fileMask = attacks::MASK_FILE[adjF]; + if (c == WHITE) { + for (int r = (int)kr + 1; r <= std::min(7, (int)kr + 3); r++) { + if (pawns & (fileMask & attacks::MASK_RANK[r])) { + shelterMg += kingShelterBaseMg - (r - (int)kr - 1) * kingShelterDecayMg; + shelterEg += kingShelterBaseEg - (r - (int)kr - 1) * kingShelterDecayEg; + } + } + } else { + for (int r = (int)kr - 1; r >= std::max(0, (int)kr - 3); r--) { + if (pawns & (fileMask & attacks::MASK_RANK[r])) { + shelterMg += kingShelterBaseMg - ((int)kr - r - 1) * kingShelterDecayMg; + shelterEg += kingShelterBaseEg - ((int)kr - r - 1) * kingShelterDecayEg; + } + } + } } - } + mgScore += s * shelterMg; + egScore += s * shelterEg; } - mgScore += s * shelterMg; - egScore += s * shelterEg; - } - // Endgame bonuses + mop-up in one pass per color - for (Color c : {WHITE, BLACK}) { - int s = (c == WHITE) ? 1 : -1; - int oppCount = popcount(board.occ(~c)); - Square myKing = board.kingSq(c); - Square oppKing = board.kingSq(~c); + // Endgame bonuses + mop-up in one pass per color + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + int oppCount = popcount(board.occ(~c)); + Square myKing = board.kingSq(c); + Square oppKing = board.kingSq(~c); - // KQK: queen vs lone king - if (board.count(QUEEN, c) >= 1 && oppCount == 1) { - int kingDist = - std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), - std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); - File ef = file_of(oppKing); - Rank er = rank_of(oppKing); - int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), - std::min((int)er, 7 - (int)er)); - mgScore += s * ((14 - kingDist) * kqkDistWeight + - (7 - edgeDist) * kqkEdgeWeight); - egScore += s * ((14 - kingDist) * kqkDistWeight + - (7 - edgeDist) * kqkEdgeWeight); - } + // KQK: queen vs lone king + if (board.count(QUEEN, c) >= 1 && oppCount == 1) { + int kingDist = std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), + std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); + File ef = file_of(oppKing); + Rank er = rank_of(oppKing); + int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), std::min((int)er, 7 - (int)er)); + mgScore += s * ((14 - kingDist) * kqkDistWeight + (7 - edgeDist) * kqkEdgeWeight); + egScore += s * ((14 - kingDist) * kqkDistWeight + (7 - edgeDist) * kqkEdgeWeight); + } - // KRK: rook vs lone king - if (board.count(ROOK, c) >= 1 && oppCount == 1 && - board.count(QUEEN, c) == 0 && board.count(BISHOP, c) == 0 && - board.count(KNIGHT, c) == 0 && board.count(PAWN, c) == 0) { - int kingDist = - std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), - std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); - File ef = file_of(oppKing); - Rank er = rank_of(oppKing); - int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), - std::min((int)er, 7 - (int)er)); - mgScore += s * ((14 - kingDist) * krkDistWeight + - (7 - edgeDist) * krkEdgeWeight); - egScore += s * ((14 - kingDist) * krkDistWeight + - (7 - edgeDist) * krkEdgeWeight); - } + // KRK: rook vs lone king + if (board.count(ROOK, c) >= 1 && oppCount == 1 && board.count(QUEEN, c) == 0 && board.count(BISHOP, c) == 0 && + board.count(KNIGHT, c) == 0 && board.count(PAWN, c) == 0) { + int kingDist = std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), + std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); + File ef = file_of(oppKing); + Rank er = rank_of(oppKing); + int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), std::min((int)er, 7 - (int)er)); + mgScore += s * ((14 - kingDist) * krkDistWeight + (7 - edgeDist) * krkEdgeWeight); + egScore += s * ((14 - kingDist) * krkDistWeight + (7 - edgeDist) * krkEdgeWeight); + } - // KPK: king + pawn(s) vs lone king - if (board.count(PAWN, c) >= 1 && oppCount == 1 && - board.count(QUEEN, c) == 0 && board.count(ROOK, c) == 0 && - board.count(BISHOP, c) == 0 && board.count(KNIGHT, c) == 0) { - Bitboard ps = pawnBB[c]; - while (ps) { - Square psq = Square(pop_lsb(ps)); - Rank pr = relative_rank(c, psq); - if (pr < RANK_4) - continue; + // KPK: king + pawn(s) vs lone king + if (board.count(PAWN, c) >= 1 && oppCount == 1 && board.count(QUEEN, c) == 0 && board.count(ROOK, c) == 0 && + board.count(BISHOP, c) == 0 && board.count(KNIGHT, c) == 0) { + Bitboard ps = pawnBB[c]; + while (ps) { + Square psq = Square(pop_lsb(ps)); + Rank pr = relative_rank(c, psq); + if (pr < RANK_4) + continue; - File pf = file_of(psq); - Square promSq = make_sq(pf, c == WHITE ? RANK_8 : RANK_1); - int pawnDist = - (c == WHITE) ? (7 - (int)rank_of(psq)) : (int)rank_of(psq); - int pkDist = - std::max(std::abs((int)file_of(oppKing) - (int)pf), - std::abs((int)rank_of(oppKing) - (int)rank_of(promSq))); - int mkDist = - std::max(std::abs((int)file_of(myKing) - (int)file_of(psq)), - std::abs((int)rank_of(myKing) - (int)rank_of(psq))); + File pf = file_of(psq); + Square promSq = make_sq(pf, c == WHITE ? RANK_8 : RANK_1); + int pawnDist = (c == WHITE) ? (7 - (int)rank_of(psq)) : (int)rank_of(psq); + int pkDist = + std::max(std::abs((int)file_of(oppKing) - (int)pf), std::abs((int)rank_of(oppKing) - (int)rank_of(promSq))); + int mkDist = std::max(std::abs((int)file_of(myKing) - (int)file_of(psq)), + std::abs((int)rank_of(myKing) - (int)rank_of(psq))); - bool winning = pkDist > pawnDist || mkDist <= pawnDist + 1 || - (pr >= RANK_6 && file_of(myKing) == pf && - ((c == WHITE && rank_of(myKing) >= RANK_6) || - (c == BLACK && rank_of(myKing) <= RANK_3))); - if ((pf == FILE_A || pf == FILE_H) && oppKing == promSq) - winning = false; + bool winning = pkDist > pawnDist || mkDist <= pawnDist + 1 || + (pr >= RANK_6 && file_of(myKing) == pf && + ((c == WHITE && rank_of(myKing) >= RANK_6) || (c == BLACK && rank_of(myKing) <= RANK_3))); + if ((pf == FILE_A || pf == FILE_H) && oppKing == promSq) + winning = false; - if (winning) { - mgScore += s * kpkWeight * pawnDist; - egScore += s * kpkWeight * pawnDist; + if (winning) { + mgScore += s * kpkWeight * pawnDist; + egScore += s * kpkWeight * pawnDist; + } + } } - } - } - // Mop-up: general endgame drive - Bitboard ourMat = board.occ(c) & ~board.pieces(PAWN) & ~board.pieces(KING); - Bitboard theirMat = - board.occ(~c) & ~board.pieces(PAWN) & ~board.pieces(KING); - if (ourMat && !theirMat && oppCount <= 2) { - int kingDist = - std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), - std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); - File ef = file_of(oppKing); - Rank er = rank_of(oppKing); - int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), - std::min((int)er, 7 - (int)er)); - mgScore += s * ((14 - kingDist) * mopUpKingDistWeight + - (7 - edgeDist) * mopUpEdgeDistWeight); - egScore += s * ((14 - kingDist) * mopUpKingDistWeight + - (7 - edgeDist) * mopUpEdgeDistWeight); + // Mop-up: general endgame drive + Bitboard ourMat = board.occ(c) & ~board.pieces(PAWN) & ~board.pieces(KING); + Bitboard theirMat = board.occ(~c) & ~board.pieces(PAWN) & ~board.pieces(KING); + if (ourMat && !theirMat && oppCount <= 2) { + int kingDist = std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), + std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); + File ef = file_of(oppKing); + Rank er = rank_of(oppKing); + int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), std::min((int)er, 7 - (int)er)); + mgScore += s * ((14 - kingDist) * mopUpKingDistWeight + (7 - edgeDist) * mopUpEdgeDistWeight); + egScore += s * ((14 - kingDist) * mopUpKingDistWeight + (7 - edgeDist) * mopUpEdgeDistWeight); + } } - } - // Draw detection: score 0 for positions where neither side can force a win - int totalPieces = popcount(board.occ()); - int pawnCount = board.count(); + // Draw detection: score 0 for positions where neither side can force a win + int totalPieces = popcount(board.occ()); + int pawnCount = board.count(); - // KBKB same-colored bishops (no pawns) - drawn - if (totalPieces == 4 && pawnCount == 0 && board.count() == 2 && - board.count() == 0 && board.count() == 0 && - board.count() == 0 && board.count(BISHOP, WHITE) == 1 && - board.count(BISHOP, BLACK) == 1) { - Bitboard wbBB = board.pieces(BISHOP, WHITE); - Bitboard bbBB = board.pieces(BISHOP, BLACK); - Square wb = Square(pop_lsb(wbBB)); - Square bb = Square(pop_lsb(bbBB)); - if (square_color(wb) == square_color(bb)) - return 0; - } + // KBKB same-colored bishops (no pawns) - drawn + if (totalPieces == 4 && pawnCount == 0 && board.count() == 2 && board.count() == 0 && + board.count() == 0 && board.count() == 0 && board.count(BISHOP, WHITE) == 1 && + board.count(BISHOP, BLACK) == 1) { + Bitboard wbBB = board.pieces(BISHOP, WHITE); + Bitboard bbBB = board.pieces(BISHOP, BLACK); + Square wb = Square(pop_lsb(wbBB)); + Square bb = Square(pop_lsb(bbBB)); + if (square_color(wb) == square_color(bb)) + return 0; + } - // KNNK (no pawns) - drawn - if (totalPieces == 4 && pawnCount == 0 && board.count() == 2 && - board.count() == 0 && board.count() == 0 && - board.count() == 0) - return 0; + // KNNK (no pawns) - drawn + if (totalPieces == 4 && pawnCount == 0 && board.count() == 2 && board.count() == 0 && + board.count() == 0 && board.count() == 0) + return 0; - phase = (phase * 256 + TotalPhase / 2) / TotalPhase; - Value finalScore = - (((mgScore * phase) + (egScore * (256 - phase))) * sign) / 256 + tempo; - return finalScore; + phase = (phase * 256 + TotalPhase / 2) / TotalPhase; + Value finalScore = (((mgScore * phase) + (egScore * (256 - phase))) * sign) / 256 + tempo; + return finalScore; } Value piece_value(PieceType pt) { - Value pieces[] = {0, PawnValue, KnightValue, BishopValue, RookValue, - QueenValue, 0}; - return pieces[pt]; + Value pieces[] = { 0, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, 0 }; + return pieces[pt]; } } // namespace engine::eval diff --git a/eval.h b/eval.h index 74c1e20..8ed77bd 100644 --- a/eval.h +++ b/eval.h @@ -1,5 +1,6 @@ #pragma once #include +#include using Value = int; namespace engine { @@ -20,15 +21,24 @@ constexpr bool is_valid(Value value) { return value != VALUE_NONE; } constexpr bool is_win(Value value) { return value >= VALUE_TB_WIN_IN_MAX_PLY; } -constexpr bool is_loss(Value value) { - return value <= VALUE_TB_LOSS_IN_MAX_PLY; +constexpr bool is_loss(Value value) { return value <= VALUE_TB_LOSS_IN_MAX_PLY; } + +constexpr bool is_decisive(Value value) { return is_win(value) || is_loss(value); } +constexpr bool is_mate(Value value) { + assert(is_valid(value)); + return value >= VALUE_MATE_IN_MAX_PLY; } -constexpr bool is_decisive(Value value) { - return is_win(value) || is_loss(value); +constexpr bool is_mated(Value value) { + assert(is_valid(value)); + return value <= VALUE_MATED_IN_MAX_PLY; } -constexpr Value MATE(int i) { return VALUE_MATE - i; } -constexpr Value MATE_DISTANCE(int i) { return VALUE_MATE - (i < 0 ? -i : i); } + +constexpr bool is_mate_or_mated(Value value) { return is_mate(value) || is_mated(value); } + +constexpr Value mate_in(int ply) { return VALUE_MATE - ply; } + +constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; } namespace eval { Value eval(const chess::Board &board); Value piece_value(chess::PieceType pt); diff --git a/main.cpp b/main.cpp index 6607fd0..f9290bd 100644 --- a/main.cpp +++ b/main.cpp @@ -9,24 +9,24 @@ using namespace engine; #define STR(x) STR_HELPER(x) int main() { - std::cout << std::unitbuf; - std::cout << "cppchess_engine version " << BUILD_VERSION << '\n'; - options.add("Move Overhead", Option(10, 0, 1000)); - options.add("Hash", Option(16, 1, 1 << 25, [](const Option &o) { - search::tt.resize(int(o)); - return std::nullopt; - })); + std::cout << std::unitbuf; + std::cout << "cppchess_engine version " << BUILD_VERSION << '\n'; + options.add("Move Overhead", Option(10, 0, 1000)); + options.add("Hash", Option(16, 1, 1 << 25, [](const Option &o) { + search::tt.resize(int(o)); + return std::nullopt; + })); - options.add("Clear Hash", Option(+[](const Option &) { - search::tt.clear(); + options.add("Clear Hash", Option(+[](const Option &) { + search::tt.clear(); - return std::nullopt; - })); - // work in progress - options.add("SyzygyPath", Option("", [](const Option &o) { - tb::init(std::string(o)); - return std::nullopt; - })); - Tune::init(options); - loop(); + return std::nullopt; + })); + // work in progress + options.add("SyzygyPath", Option("", [](const Option &o) { + tb::init(std::string(o)); + return std::nullopt; + })); + Tune::init(options); + loop(); } diff --git a/movepick.cpp b/movepick.cpp index 66b2eb0..da896c3 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -7,92 +7,86 @@ using engine::eval::piece_value; namespace engine::movepick { static Bitboard att(PieceType pt, Square sq, Bitboard occ) { - switch (pt) { - case BISHOP: - return attacks::bishop(sq, occ); - case ROOK: - return attacks::rook(sq, occ); - case QUEEN: - return attacks::queen(sq, occ); - default: - return 0; - } + switch (pt) { + case BISHOP: + return attacks::bishop(sq, occ); + case ROOK: + return attacks::rook(sq, occ); + case QUEEN: + return attacks::queen(sq, occ); + default: + return 0; + } } Value see(Board &board, Move move) { - if (move.type_of() == EN_PASSANT) - return piece_value(PAWN); - PieceType captured = board.at(move.to()); - if (captured == NO_PIECE_TYPE) - return 0; - PieceType attacker = board.at(move.from()); - Bitboard occ = board.occ() ^ (1ULL << move.from()); - Bitboard attackers = board.attackers(WHITE, move.to(), occ) | - board.attackers(BLACK, move.to(), occ); - Value gain[32]; - int d = 0; - gain[d] = piece_value(captured); - Color stm = ~board.side_to_move(); - while (++d < 32) { - gain[d] = piece_value(attacker) - gain[d - 1]; - if (gain[d] < 0) - break; - attackers &= occ; - Bitboard stmAttackers = attackers & board.occ(stm); - if (!stmAttackers) - break; - Square sq = (Square)pop_lsb(stmAttackers); - attacker = board.at(sq); - if (attacker == NO_PIECE_TYPE) - break; - if (attacker == BISHOP || attacker == ROOK || attacker == QUEEN) - attackers |= att(attacker, move.to(), occ); - occ ^= (1ULL << sq); - stm = ~stm; - } - while (--d) - gain[d - 1] = -gain[d]; - return gain[0]; + if (move.type_of() == EN_PASSANT) + return piece_value(PAWN); + PieceType captured = board.at(move.to()); + if (captured == NO_PIECE_TYPE) + return 0; + PieceType attacker = board.at(move.from()); + Bitboard occ = board.occ() ^ (1ULL << move.from()); + Bitboard attackers = board.attackers(WHITE, move.to(), occ) | board.attackers(BLACK, move.to(), occ); + Value gain[32]; + int d = 0; + gain[d] = piece_value(captured); + Color stm = ~board.side_to_move(); + while (++d < 32) { + gain[d] = piece_value(attacker) - gain[d - 1]; + if (gain[d] < 0) + break; + attackers &= occ; + Bitboard stmAttackers = attackers & board.occ(stm); + if (!stmAttackers) + break; + Square sq = (Square)pop_lsb(stmAttackers); + attacker = board.at(sq); + if (attacker == NO_PIECE_TYPE) + break; + if (attacker == BISHOP || attacker == ROOK || attacker == QUEEN) + attackers |= att(attacker, move.to(), occ); + occ ^= (1ULL << sq); + stm = ~stm; + } + while (--d) + gain[d - 1] = -gain[d]; + return gain[0]; } -void orderMoves(Board &board, Movelist &moves, Move ttMove, int ply, - const engine::search::Session &session, Move prevMove) { - Value scores[300]; - size_t n = moves.size(); - for (size_t i = 0; i < n; ++i) { - Move move = moves[i]; - if (move == ttMove) - scores[i] = 10000; - else if (board.isCapture(move)) { - Value s = see(board, move); - Value capturedVal = move.type_of() == EN_PASSANT - ? piece_value(PAWN) - : piece_value(board.at(move.to())); - Value attackerVal = piece_value(board.at(move.from())); - scores[i] = (s >= -50 ? 9000 : 4000) + std::max(s, Value(-50)) + - (capturedVal * 10 - attackerVal) / 100; - } else if (move == session.killerMoves[ply][0]) - scores[i] = 8500; - else if (move == session.killerMoves[ply][1]) - scores[i] = 8000; - else if (prevMove.is_ok() && - move == session.counterMoves[prevMove.from_to()]) - scores[i] = 7500; - else if (board.givesCheck(move) != CheckType::NO_CHECK) - scores[i] = 7000; - else - scores[i] = session.historyHeuristic[move.from()][move.to()]; - } - size_t limit = std::min(n, size_t(12)); - for (size_t i = 0; i + 1 < limit; ++i) { - size_t best = i; - for (size_t j = i + 1; j < n; ++j) - if (scores[j] > scores[best]) - best = j; - if (best != i) { - std::swap(moves[i], moves[best]); - std::swap(scores[i], scores[best]); +void orderMoves(Board &board, Movelist &moves, Move ttMove, int ply, const engine::search::Session &session, Move prevMove) { + Value scores[300]; + size_t n = moves.size(); + for (size_t i = 0; i < n; ++i) { + Move move = moves[i]; + if (move == ttMove) + scores[i] = 10000; + else if (board.isCapture(move)) { + Value s = see(board, move); + Value capturedVal = move.type_of() == EN_PASSANT ? piece_value(PAWN) : piece_value(board.at(move.to())); + Value attackerVal = piece_value(board.at(move.from())); + scores[i] = (s >= -50 ? 9000 : 4000) + std::max(s, Value(-50)) + (capturedVal * 10 - attackerVal) / 100; + } else if (move == session.killerMoves[ply][0]) + scores[i] = 8500; + else if (move == session.killerMoves[ply][1]) + scores[i] = 8000; + else if (prevMove.is_ok() && move == session.counterMoves[prevMove.from_to()]) + scores[i] = 7500; + else if (board.givesCheck(move) != CheckType::NO_CHECK) + scores[i] = 7000; + else + scores[i] = session.historyHeuristic[move.from()][move.to()]; + } + size_t limit = std::min(n, size_t(12)); + for (size_t i = 0; i + 1 < limit; ++i) { + size_t best = i; + for (size_t j = i + 1; j < n; ++j) + if (scores[j] > scores[best]) + best = j; + if (best != i) { + std::swap(moves[i], moves[best]); + std::swap(scores[i], scores[best]); + } } - } } } // namespace engine::movepick \ No newline at end of file diff --git a/movepick.h b/movepick.h index 9798927..c7728ff 100644 --- a/movepick.h +++ b/movepick.h @@ -5,7 +5,6 @@ namespace engine::search { struct Session; } // namespace engine::search namespace engine::movepick { -void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int, - const engine::search::Session &, chess::Move); +void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int, const engine::search::Session &, chess::Move); Value see(chess::Board &, chess::Move); } // namespace engine::movepick diff --git a/score.cpp b/score.cpp index 2d07d9e..ca3a1c2 100644 --- a/score.cpp +++ b/score.cpp @@ -3,16 +3,16 @@ #include namespace engine { Score::Score(Value v) { - assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); - if (!is_decisive(v)) { - score = InternalUnits{v}; - } else if (std::abs(v) <= VALUE_TB) { - auto distance = VALUE_TB - std::abs(v); - score = (v > 0) ? Tablebase{distance, true} : Tablebase{-distance, false}; - } else { - auto distance = VALUE_MATE - std::abs(v); - score = (v > 0) ? Mate{distance} : Mate{-distance}; - } + if (!is_decisive(v)) { + score = InternalUnits{ v }; + } else if (std::abs(v) <= VALUE_TB) { + auto distance = VALUE_TB - std::abs(v); + score = (v > 0) ? Tablebase{ distance, true } : Tablebase{ -distance, false }; + } else { + auto distance = VALUE_MATE - std::abs(v); + score = (v > 0) ? Mate{ distance } : Mate{ -distance }; + } } } // namespace engine diff --git a/score.h b/score.h index 2b7b0e1..34d01bd 100644 --- a/score.h +++ b/score.h @@ -25,35 +25,31 @@ namespace engine { class Score { -public: - struct Mate { - int plies; - }; + public: + struct Mate { + int plies; + }; - struct Tablebase { - int plies; - bool win; - }; + struct Tablebase { + int plies; + bool win; + }; - struct InternalUnits { - int value; - }; + struct InternalUnits { + int value; + }; - Score() = default; - Score(Value v); + Score() = default; + Score(Value v); - template bool is() const { - return std::holds_alternative(score); - } + template bool is() const { return std::holds_alternative(score); } - template T get() const { return std::get(score); } + template T get() const { return std::get(score); } - template decltype(auto) visit(F &&f) const { - return std::visit(std::forward(f), score); - } + template decltype(auto) visit(F &&f) const { return std::visit(std::forward(f), score); } -private: - std::variant score; + private: + std::variant score; }; } // namespace engine diff --git a/search.cpp b/search.cpp index f15eb1b..ca6bf8e 100644 --- a/search.cpp +++ b/search.cpp @@ -13,669 +13,635 @@ using namespace chess; namespace engine::search { TranspositionTable tt(16); -std::atomic stopSearch{false}; +std::atomic stopSearch{ false }; void stop() { stopSearch.store(true, std::memory_order_relaxed); } bool isStopped() { return stopSearch.load(std::memory_order_relaxed); } namespace { void update_pv(Move *pv, Move move, const Move *childPv) { - for (*pv++ = move; childPv && *childPv != Move::none();) - *pv++ = *childPv++; - *pv = Move::none(); -} -Value value_to_tt(Value v, int ply) { - return is_win(v) ? v + ply : is_loss(v) ? v - ply : v; + for (*pv++ = move; childPv && *childPv != Move::none();) + *pv++ = *childPv++; + *pv = Move::none(); } +// Adjusts a mate or TB score from "plies to mate from the root" to +// "plies to mate from the current position". Standard scores are unchanged. +// The function is called before storing a value in the transposition table. +Value value_to_tt(Value v, int ply) { return is_win(v) ? v + ply : is_loss(v) ? v - ply : v; } + +// Inverse of value_to_tt(): it adjusts a mate or TB score from the transposition +// table (which refers to the plies to mate/be mated from current position) to +// "plies to mate/be mated (TB win/loss) from the root". However, to avoid +// potentially false mate or TB scores related to the 50 moves rule and the +// graph history interaction, we return the highest non-TB score instead. Value value_from_tt(Value v, int ply, int r50c) { - if (!is_valid(v)) - return VALUE_NONE; - if (is_win(v)) { - if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c) - return VALUE_TB_WIN_IN_MAX_PLY - 1; - if (VALUE_TB - v > 100 - r50c) - return VALUE_TB_WIN_IN_MAX_PLY - 1; - return v - ply; - } - if (is_loss(v)) { - if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c) - return VALUE_TB_LOSS_IN_MAX_PLY + 1; - if (VALUE_TB + v > 100 - r50c) - return VALUE_TB_LOSS_IN_MAX_PLY + 1; - return v + ply; - } - return v; -} -} // namespace -Value qsearch(Board &board, Value alpha, Value beta, search::Session &session, - int ply) { - session.nodes++; - session.qnodes++; - session.seldepth = std::max(session.seldepth, ply); - if (((session.nodes & 2047) == 0 && - session.tm.elapsed() >= session.tm.optimum()) || - stopSearch.load(std::memory_order_relaxed)) - return VALUE_NONE; - - bool inCheck = board.checkers(); - - if (ply >= MAX_PLY - 1) - return inCheck ? -MATE(ply) : eval::eval(board); - - Move ttMove = Move::none(); - TTEntry *entry = search::tt.lookup(board.hash()); - Value alphaOrig = alpha; - if (entry) { - session.ttHits++; - Value ttScore = value_from_tt(entry->getScore(), ply, board.rule50_count()); - - if (entry->getFlag() == TTFlag::EXACT) { - session.ttCutoffs++; - return ttScore; - } - if (entry->getFlag() == TTFlag::LOWERBOUND && ttScore >= beta) { - session.ttCutoffs++; - return ttScore; - } - if (entry->getFlag() == TTFlag::UPPERBOUND && ttScore <= alpha) { - session.ttCutoffs++; - return ttScore; - } - if (entry->getFlag() == TTFlag::LOWERBOUND) - alpha = std::max(alpha, ttScore); - else if (entry->getFlag() == TTFlag::UPPERBOUND) - beta = std::min(beta, ttScore); - if (alpha >= beta) { - session.ttCutoffs++; - return ttScore; + + if (!is_valid(v)) + return VALUE_NONE; + + // handle TB win or better + if (is_win(v)) + { + // Downgrade a potentially false mate score + if (is_mate(v) && VALUE_MATE - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; + + // Downgrade a potentially false TB score. + if (VALUE_TB - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; + + return v - ply; } - ttMove = Move(entry->getMove()); - } - Movelist moves; + // handle TB loss or worse + if (is_loss(v)) + { + // Downgrade a potentially false mate score. + if (is_mated(v) && VALUE_MATE + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; - if (inCheck) - board.legals(moves); - else - board.legals(moves); - Value best = -VALUE_INFINITE; - Value standPat = VALUE_NONE; + // Downgrade a potentially false TB score. + if (VALUE_TB + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; - if (!inCheck) { - standPat = eval::eval(board); + return v + ply; + } + + return v; +} +} // namespace +Value qsearch(Board &board, Value alpha, Value beta, search::Session &session, int ply) { + session.nodes++; + session.qnodes++; + session.seldepth = std::max(session.seldepth, ply); + if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || + stopSearch.load(std::memory_order_relaxed)) + return VALUE_NONE; - if (standPat >= beta) - return standPat; + bool inCheck = board.is_check(); + if (board.is_draw(3) || board.is_insufficient_material()) + return VALUE_DRAW; - if (standPat > alpha) - alpha = standPat; + Move ttMove = Move::none(); + TTEntry *entry = search::tt.lookup(board.hash()); + Value alphaOrig = alpha; + if (entry) { + session.ttHits++; + Value ttScore = value_from_tt(entry->getScore(), ply, board.rule50_count()); - best = standPat; - } + if (entry->getFlag() == EXACT) { + session.ttCutoffs++; + return ttScore; + } + if (entry->getFlag() == LOWERBOUND && ttScore >= beta) { + session.ttCutoffs++; + return ttScore; + } + if (entry->getFlag() == UPPERBOUND && ttScore <= alpha) { + session.ttCutoffs++; + return ttScore; + } + if (entry->getFlag() == LOWERBOUND) + alpha = std::max(alpha, ttScore); + else if (entry->getFlag() == UPPERBOUND) + beta = std::min(beta, ttScore); + if (alpha >= beta) { + session.ttCutoffs++; + return ttScore; + } + ttMove = Move(entry->getMove()); + } - if (!moves.size()) - return inCheck ? -MATE(ply) : best; + Movelist moves; - movepick::orderMoves(board, moves, ttMove, ply, session, Move::none()); - int movesSearched = 0; - for (Move move : moves) { - bool isCapture = board.isCapture(move); - bool givesCheck = board.givesCheck(move) != CheckType::NO_CHECK; + if (inCheck) + board.legals(moves); + else + board.legals(moves); + Value best = -VALUE_INFINITE; + Value standPat = VALUE_NONE; if (!inCheck) { - if (!isCapture && !givesCheck) - continue; - if (isCapture && !givesCheck && move.type_of() != PROMOTION) { - Value capturedValue = - move.type_of() == EN_PASSANT - ? eval::piece_value(PAWN) - : eval::piece_value(board.at(move.to())); - if (standPat + capturedValue + 200 < alpha) - continue; - if (movepick::see(board, move) < 0) - continue; - } - } else { - int plyFromDepth = ply - session.depth; - int maxUncond = plyFromDepth < 1 ? 6 : plyFromDepth < 4 ? 3 : 0; - if (movesSearched >= maxUncond && movesSearched >= 1) { - if (!isCapture && !givesCheck) - continue; - if (isCapture && !givesCheck && move.type_of() != PROMOTION && - movepick::see(board, move) < 0) - continue; - } + standPat = eval::eval(board); + + if (standPat >= beta) + return standPat; + + if (standPat > alpha) + alpha = standPat; + + best = standPat; } - board.doMove(move); + if (!moves.size()) + return inCheck ? mated_in(ply) : best; + if (ply >= MAX_PLY - 1) + return best; + + movepick::orderMoves(board, moves, ttMove, ply, session, Move::none()); + int movesSearched = 0; + for (Move move : moves) { + bool isCapture = board.isCapture(move); + bool givesCheck = board.givesCheck(move) != CheckType::NO_CHECK; + + if (!is_loss(best)) { + if (!isCapture && !givesCheck) + continue; + if (isCapture && !givesCheck && move.type_of() != PROMOTION) { + Value capturedValue = + move.type_of() == EN_PASSANT ? eval::piece_value(PAWN) : eval::piece_value(board.at(move.to())); + if (standPat + capturedValue + 200 < alpha) + continue; + if (movepick::see(board, move) < 0) + continue; + } + } + + board.doMove(move); - Value score = -qsearch(board, -beta, -alpha, session, ply + 1); + Value score = -qsearch(board, -beta, -alpha, session, ply + 1); - board.undoMove(); + board.undoMove(); - if (score == VALUE_NONE) - return VALUE_NONE; + if (score == VALUE_NONE) + return VALUE_NONE; + + if (score > best) { + ttMove = move; + best = score; + } + if (score > alpha) + alpha = score; + if (alpha >= beta) + break; - if (score > best) { - ttMove = move; - best = score; + movesSearched++; } - if (score > alpha) - alpha = score; - if (alpha >= beta) - break; - - movesSearched++; - } - TTFlag flag = best >= beta ? TTFlag::LOWERBOUND - : best <= alphaOrig ? TTFlag::UPPERBOUND - : TTFlag::EXACT; - tt.store(board.hash(), ttMove, value_to_tt(best, ply), 0, flag); - return best; + TTFlag flag = best >= beta ? LOWERBOUND : best <= alphaOrig ? UPPERBOUND : EXACT; + tt.store(board.hash(), ttMove, value_to_tt(best, ply), 0, flag); + return best; } -Value doSearch(Board &board, int depth, Value alpha, Value beta, - search::Session &session, int ply = 0, - Move prevMove = Move::none()) { - session.nodes++; - session.seldepth = std::max(session.seldepth, ply); - if (ply >= MAX_PLY - 1) - return board.checkers() ? -MATE(ply) : eval::eval(board); - if (depth <= 0) - return qsearch(board, alpha, beta, session, ply); - if (((session.nodes & 2047) == 0 && - session.tm.elapsed() >= session.tm.optimum()) || - stopSearch.load(std::memory_order_relaxed)) - return VALUE_NONE; - - bool inCheck = board.checkers(); - - alpha = std::max(-MATE(ply), alpha); - beta = std::min(MATE(ply + 1), beta); - if (alpha >= beta) - return alpha; - - session.pv[ply][0] = Move::none(); - if (board.is_draw(2) || board.is_insufficient_material()) - return 0; - - Value alphaOrig = alpha; - uint64_t hash = board.hash(); - Move ttMove = Move::none(); - Value staticEval = eval::eval(board); - - TTEntry *entry = search::tt.lookup(hash); - if (entry) { - if (entry->getDepth() >= depth) { - session.ttHits++; - Value ttScore = - value_from_tt(entry->getScore(), ply, board.rule50_count()); - TTFlag flag = entry->getFlag(); - - if (flag == TTFlag::EXACT && ply > 0) { - session.ttCutoffs++; - session.pv[ply][0] = Move(entry->getMove()); - session.pv[ply][1] = Move::none(); - return ttScore; - } - if (flag == TTFlag::LOWERBOUND && ttScore >= beta && ply > 0) { - session.ttCutoffs++; - session.pv[ply][0] = Move(entry->getMove()); - session.pv[ply][1] = Move::none(); - return ttScore; - } - if (flag == TTFlag::UPPERBOUND && ttScore <= alpha && ply > 0) { - session.ttCutoffs++; - session.pv[ply][0] = Move(entry->getMove()); - session.pv[ply][1] = Move::none(); - return ttScore; - } - } - ttMove = Move(entry->getMove()); - } - - // Reverse futility pruning: if eval is well above beta, prune - if (!inCheck && ply > 0 && depth <= 3 && staticEval - 150 * depth >= beta && - !is_win(beta) && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) - return staticEval; - - // Razoring: if eval is far below alpha, try qsearch to verify - if (depth <= 2 && !inCheck && ply > 0 && !is_win(alpha)) { - Value razorMargin = Value(256 + 100 * depth); - if (staticEval + razorMargin < alpha) { - Value v = qsearch(board, alpha - 1, alpha, session, ply); - if (v == VALUE_NONE) +Value doSearch( + Board &board, int depth, Value alpha, Value beta, search::Session &session, int ply = 0, Move prevMove = Move::none()) { + session.nodes++; + session.seldepth = std::max(session.seldepth, ply); + if (ply >= MAX_PLY - 1) + return eval::eval(board); + if (depth <= 0) + return qsearch(board, alpha, beta, session, ply); + if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || + stopSearch.load(std::memory_order_relaxed)) return VALUE_NONE; - if (v < alpha && std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) - return v; + + bool inCheck = board.is_check(); + + alpha = std::max(mated_in(ply), alpha); + beta = std::min(mate_in(ply + 1), beta); + if (alpha >= beta) + return alpha; + + session.pv[ply][0] = Move::none(); + if (board.is_draw(3) || board.is_insufficient_material()) + return VALUE_DRAW; + + Value alphaOrig = alpha; + uint64_t hash = board.hash(); + Move ttMove = Move::none(); + Value staticEval = eval::eval(board); + + TTEntry *entry = search::tt.lookup(hash); + if (entry) { + if (entry->getDepth() >= depth) { + session.ttHits++; + Value ttScore = value_from_tt(entry->getScore(), ply, board.rule50_count()); + TTFlag flag = entry->getFlag(); + + if (flag == EXACT && ply > 0) { + session.ttCutoffs++; + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); + return ttScore; + } + if (flag == LOWERBOUND && ttScore >= beta && ply > 0) { + session.ttCutoffs++; + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); + return ttScore; + } + if (flag == UPPERBOUND && ttScore <= alpha && ply > 0) { + session.ttCutoffs++; + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); + return ttScore; + } + } + ttMove = Move(entry->getMove()); } - } - - // Null move pruning (skip when a mate threat is possible) - if (depth >= 3 && !inCheck && ply > 0 && staticEval >= beta && - !is_win(beta)) { - int R = 2 + depth / 6 + std::min(2, depth / 10); - board.doNullMove(); - Value score = doSearch(board, depth - 1 - R, -beta, -beta + 1, session, - ply + 1, Move::none()); - board.undoMove(); - if (score == VALUE_NONE) - return VALUE_NONE; - score = -score; - if (score >= beta) { - session.nullCutoffs++; - return score; + + // Reverse futility pruning: if eval is well above beta, prune + if (!inCheck && ply > 0 && depth <= 3 && staticEval - 150 * depth >= beta && !is_win(beta) && + std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) + return staticEval; + + // Razoring: if eval is far below alpha, try qsearch to verify + if (depth <= 2 && !inCheck && ply > 0 && !is_win(alpha)) { + Value razorMargin = Value(256 + 100 * depth); + if (staticEval + razorMargin < alpha) { + Value v = qsearch(board, alpha - 1, alpha, session, ply); + if (v == VALUE_NONE) + return VALUE_NONE; + if (v < alpha && std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) + return v; + } } - } - - // ProbCut: if eval is well above beta, verify with reduced-depth search - if (depth >= 5 && !inCheck && !is_win(beta) && - std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) { - Value probCutMargin = Value(200 + 100 * (depth - 5)); - if (staticEval >= beta + probCutMargin) { - Value v = - doSearch(board, depth - 2, beta - 1, beta, session, ply, prevMove); - if (v == VALUE_NONE) - return VALUE_NONE; - if (v >= beta) - return v; + + // Null move pruning (skip when a mate threat is possible) + if (depth >= 3 && !inCheck && ply > 0 && staticEval >= beta && !is_win(beta)) { + int R = 2 + depth / 6;// + std::min(2, depth / 10); + board.doNullMove(); + Value score = doSearch(board, depth - 1 - R, -beta, -beta + 1, session, ply + 1, Move::none()); + board.undoMove(); + if (score == VALUE_NONE) + return VALUE_NONE; + score = -score; + if (score >= beta) { + session.nullCutoffs++; + return score; + } } - } - - // Existing static null-move / futility pruning - if (!inCheck && staticEval < alpha - 512 - 293 * depth * depth) { - Value value = qsearch(board, alpha - 1, alpha, session, ply); - if (value == VALUE_NONE) - return VALUE_NONE; - if (value < alpha && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) - return value; - } - - // Tablebase probing (unchanged) - if (ply != 0) { - if (popcount(board.occ()) <= 7 && board.castlingRights() == NO_CASTLING) { - int wdl = engine::tb::probe_wdl(board); - if (wdl != engine::tb::TB_ERROR) { - session.tbHits++; - - int drawScore = 1; - - Value tbValue = VALUE_TB - ply; - - Value value = wdl < -drawScore ? -tbValue - : wdl > drawScore ? tbValue - : VALUE_DRAW + 2 * wdl * drawScore; - TTFlag b = wdl < -drawScore ? TTFlag::UPPERBOUND - : wdl > drawScore ? TTFlag::LOWERBOUND - : TTFlag::EXACT; - if (b == TTFlag::EXACT || - (b == TTFlag::LOWERBOUND ? value >= beta : value <= alpha)) { - tt.store(hash, Move::none(), value_to_tt(value, ply), - std::min(MAX_PLY - 1, depth + 6), b); - return value; + + // ProbCut: if eval is well above beta, verify with reduced-depth search + if (depth >= 5 && !inCheck && !is_win(beta) && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) { + Value probCutMargin = Value(200 + 100 * (depth - 5)); + if (staticEval >= beta + probCutMargin) { + Value v = doSearch(board, depth - 2, beta - 1, beta, session, ply, prevMove); + if (v == VALUE_NONE) + return VALUE_NONE; + if (v >= beta) + return v; } + } - if (b == TTFlag::LOWERBOUND) - alpha = std::max(alpha, value); - } + // Existing static null-move / futility pruning + if (!inCheck && staticEval < alpha - 512 - 293 * depth * depth) { + Value value = qsearch(board, alpha - 1, alpha, session, ply); + if (value == VALUE_NONE) + return VALUE_NONE; + if (value < alpha && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) + return value; } - } - Movelist moves; - board.legals(moves); - if (!moves.size()) { - session.pv[ply][0] = Move::none(); - return board.checkers() ? -MATE(ply) : 0; - } - movepick::orderMoves(board, moves, ttMove, ply, session, prevMove); - - // Internal Iterative Deepening (IID): get a TT move when we don't have one - if (depth >= 8 && ttMove == Move::none() && !inCheck && alpha != beta - 1) { - int d = std::max(2, depth - 2 - depth / 4); - doSearch(board, d, alpha, beta, session, ply, prevMove); - if (TTEntry *e = search::tt.lookup(hash)) - ttMove = Move(e->getMove()); - } - - // Singular Extension: extend TT move when it dominates all others - int singularExt = 0; - if (depth >= 12 && ttMove.is_ok() && !inCheck && ply > 0 && entry && - entry->getDepth() >= depth - 4 && - entry->getFlag() != TTFlag::UPPERBOUND && alpha != beta - 1 && - !is_win(beta)) { - Value sBeta = std::max(staticEval - 2 * depth, Value(-VALUE_MATE)); - int r = std::max(2, depth / 4); - if (moves.size() <= 5) { - bool singular = true; - for (size_t si = 0; si < moves.size() && singular; ++si) { - if (moves[si] == ttMove) - continue; - board.doMove(moves[si]); - Value v = - doSearch(board, r, sBeta - 1, sBeta, session, ply + 1, moves[si]); - board.undoMove(); - if (v == VALUE_NONE) { - board.undoMove(); - singular = false; - break; + // Tablebase probing (unchanged) + if (ply != 0) { + if (popcount(board.occ()) <= 7 && board.castlingRights() == NO_CASTLING) { + int wdl = engine::tb::probe_wdl(board); + if (wdl != engine::tb::TB_ERROR) { + session.tbHits++; + + int drawScore = 1; + + Value tbValue = VALUE_TB - ply; + + Value value = wdl < -drawScore ? -tbValue : wdl > drawScore ? tbValue : VALUE_DRAW + 2 * wdl * drawScore; + TTFlag b = wdl < -drawScore ? UPPERBOUND : wdl > drawScore ? LOWERBOUND : EXACT; + if (b == EXACT || (b == LOWERBOUND ? value >= beta : value <= alpha)) { + tt.store(hash, Move::none(), value_to_tt(value, ply), std::min(MAX_PLY - 1, depth + 6), b); + return value; + } + + if (b == LOWERBOUND) + alpha = std::max(alpha, value); + if (alpha >= beta) + return alpha; + } } - v = -v; - if (v >= sBeta) - singular = false; - } - if (singular) - singularExt = 1; } - } - Value maxScore = -VALUE_INFINITE; - int movesSearched = 0; + // Internal Iterative Deepening (IID): get a TT move when we don't have one + if (depth >= 8 && ttMove == Move::none() && !inCheck && alpha != beta - 1) { + int d = std::max(2, depth - 2 - depth / 4); + Value v=doSearch(board, d, alpha, beta, session, ply, prevMove); + if (v==VALUE_NONE) return VALUE_NONE; + if (TTEntry *e = search::tt.lookup(hash)) + ttMove = Move(e->getMove()); + } + Movelist moves; + board.legals(moves); + if (!moves.size()) { + session.pv[ply][0] = Move::none(); + return board.checkers() ? mated_in(ply) : 0; + } + movepick::orderMoves(board, moves, ttMove, ply, session, prevMove); + + // Singular Extension: extend TT move when it dominates all others + int singularExt = 0; + if (depth >= 12 && ttMove.is_ok() && !inCheck && ply > 0 && entry && entry->getDepth() >= depth - 4 && + entry->getFlag() != UPPERBOUND && alpha != beta - 1 && !is_win(beta)) { + Value sBeta = std::max(staticEval - 2 * depth, Value(-VALUE_MATE)); + int r = std::max(2, depth / 4); + if (moves.size() <= 5) { + bool singular = true; + for (size_t si = 0; si < moves.size() && singular; ++si) { + if (moves[si] == ttMove) + continue; + board.doMove(moves[si]); + Value v = doSearch(board, r, -sBeta, -sBeta + 1, session, ply + 1, moves[si]); + board.undoMove(); + if (v == VALUE_NONE) { + singular = false; + break; + } + v = -v; + if (v >= sBeta) + singular = false; + } + if (singular) + singularExt = 1; + } + } - for (size_t i = 0; i < moves.size(); ++i) { - Move move = moves[i]; - bool isCapture = board.isCapture(move); - bool givesCheck = board.givesCheck(move) != CheckType::NO_CHECK; + Value maxScore = -VALUE_INFINITE; + int movesSearched = 0; - // Futility pruning at shallow depths - if (!inCheck && !isCapture && !givesCheck && depth <= 2 && ply > 0 && - movesSearched > 0) { - Value margin = Value(128 + 128 * depth); - if (staticEval + margin <= alpha && - std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) - continue; - } + for (size_t i = 0; i < moves.size(); ++i) { + Move move = moves[i]; - // Late move pruning at very shallow depths - if (!inCheck && !isCapture && !givesCheck && depth <= 2 && - movesSearched > 3 + 2 * depth && - std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) - continue; - - // SEE pruning for losing captures at shallow depths - if (!inCheck && isCapture && !givesCheck && depth <= 2 && - movesSearched > 0 && move.type_of() != PROMOTION && - std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) { - if (movepick::see(board, move) < 0) - continue; - } + bool isCapture = board.isCapture(move); + bool givesCheck = board.givesCheck(move) != CheckType::NO_CHECK; - // LMR reduction - int reduction = 0; - if (movesSearched >= 2 && depth >= 3) { - if (!isCapture && !givesCheck) { - reduction = 1 + movesSearched / 5 + depth / 7; - int history = - session.historyHeuristic[(int)move.from()][(int)move.to()]; - if (history > 0) - reduction--; - else if (history < 0) - reduction++; - if (move == session.killerMoves[ply][0] || - move == session.killerMoves[ply][1]) - reduction--; - if (prevMove.is_ok() && - move == session.counterMoves[prevMove.from_to()]) - reduction--; - if (staticEval + 50 < alphaOrig) - reduction++; - else if (staticEval - 50 >= alphaOrig) - reduction--; - } else if (movesSearched >= 6) { - reduction = 1 + movesSearched / 8; - } - reduction = std::clamp(reduction, 1, depth - 2); - } + // Futility pruning at shallow depths + if (!inCheck && !isCapture && !givesCheck && depth <= 2 && ply > 0 && movesSearched > 0) { + Value margin = Value(128 + 128 * depth); + if (staticEval + margin <= alpha && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) + continue; + } - int ext = 0; - if (singularExt && movesSearched == 0) - ext = 1; - if (ext == 0 && moves.size() == 1) - ext = 1; - if (ext == 0 && isCapture && movesSearched == 0) - ext = 1; + // Late move pruning at very shallow depths + if (!inCheck && !isCapture && !givesCheck && depth <= 2 && movesSearched > 3 + 2 * depth && + std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) + continue; + + // SEE pruning for losing captures at shallow depths + if (!inCheck && isCapture && !givesCheck && depth <= 2 && movesSearched > 0 && move.type_of() != PROMOTION && + std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) { + if (movepick::see(board, move) < 0) + continue; + } - board.doMove(move); + // LMR reduction + int reduction = 0; + if (movesSearched >= 2 && depth >= 3) { + if (!isCapture && !givesCheck) { + reduction = 1 + movesSearched / 5 + depth / 7; + int history = session.historyHeuristic[(int)move.from()][(int)move.to()]; + if (history > 0) + reduction--; + else if (history < 0) + reduction++; + if (move == session.killerMoves[ply][0] || move == session.killerMoves[ply][1]) + reduction--; + if (prevMove.is_ok() && move == session.counterMoves[prevMove.from_to()]) + reduction--; + if (staticEval + 50 < alphaOrig) + reduction++; + else if (staticEval - 50 >= alphaOrig) + reduction--; + } else if (movesSearched >= 6) { + reduction = 1 + movesSearched / 8; + } + reduction = std::clamp(reduction, 1, depth - 2); + } - Value score; + int ext = 0; + if (singularExt && movesSearched == 0) + ext = 1; + if (ext == 0 && moves.size() == 1) + ext = 1; + if (ext == 0 && isCapture && movesSearched == 0) + ext = 1; + + board.doMove(move); + + Value score; + + if (movesSearched == 0 || reduction == 0) { + score = doSearch(board, depth - 1 + ext, -beta, -alpha, session, ply + 1, move); + if (score == VALUE_NONE) { + board.undoMove(); + return VALUE_NONE; + } + score = -score; + } else { + int d = depth - 1 - reduction + ext; + score = doSearch(board, d, -alpha - 1, -alpha, session, ply + 1, move); + if (score == VALUE_NONE) { + board.undoMove(); + return VALUE_NONE; + } + score = -score; + if (score > alpha && reduction) { + session.lmrResearches++; + score = doSearch(board, depth - 1 + ext, -beta, -alpha, session, ply + 1, move); + if (score == VALUE_NONE) { + board.undoMove(); + return VALUE_NONE; + } + score = -score; + } + } - if (movesSearched == 0 || reduction == 0) { - score = doSearch(board, depth - 1 + ext, -beta, -alpha, session, ply + 1, - move); - if (score == VALUE_NONE) { - board.undoMove(); - score = -VALUE_INFINITE; - break; - } - score = -score; - } else { - int d = depth - 1 - reduction + ext; - score = doSearch(board, d, -alpha - 1, -alpha, session, ply + 1, move); - if (score == VALUE_NONE) { board.undoMove(); - score = -VALUE_INFINITE; - break; - } - score = -score; - if (score > alpha && reduction) { - session.lmrResearches++; - score = doSearch(board, depth - 1 + ext, -beta, -alpha, session, - ply + 1, move); - if (score == VALUE_NONE) { - board.undoMove(); - score = -VALUE_INFINITE; - break; - } - score = -score; - } - } + movesSearched++; - board.undoMove(); - movesSearched++; + if (score > maxScore) { + maxScore = score; + update_pv(session.pv[ply], move, session.pv[ply + 1]); + } - if (score > maxScore) { - maxScore = score; - update_pv(session.pv[ply], move, session.pv[ply + 1]); - } + if (score > alpha) { + alpha = score; - if (score > alpha) { - alpha = score; - - if (!isCapture) { - int bonus = depth * depth; - if (is_win(score)) - bonus += 4 * depth * depth; - session.historyHeuristic[(int)move.from()][(int)move.to()] = std::clamp( - session.historyHeuristic[(int)move.from()][(int)move.to()] + bonus, - -16384, 16384); - } - } + if (!isCapture) { + int bonus = depth * depth; + if (is_win(score)) + bonus += 4 * depth * depth; + session.historyHeuristic[(int)move.from()][(int)move.to()] = + std::clamp(session.historyHeuristic[(int)move.from()][(int)move.to()] + bonus, -16384, 16384); + } + } - if (alpha >= beta) { - if (!isCapture) { - if (session.killerMoves[ply][0] != move) { - session.killerMoves[ply][1] = session.killerMoves[ply][0]; - session.killerMoves[ply][0] = move; + if (alpha >= beta) { + if (!isCapture) { + if (session.killerMoves[ply][0] != move) { + session.killerMoves[ply][1] = session.killerMoves[ply][0]; + session.killerMoves[ply][0] = move; + } + if (prevMove.is_ok()) + session.counterMoves[prevMove.from_to()] = move; + } + break; } - if (prevMove.is_ok()) - session.counterMoves[prevMove.from_to()] = move; - } - break; + + if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || + stopSearch.load(std::memory_order_relaxed)) + return VALUE_NONE; } + if (maxScore != -VALUE_INFINITE) { + TTFlag flag = maxScore >= beta ? LOWERBOUND : maxScore <= alphaOrig ? UPPERBOUND : EXACT; - if (((session.nodes & 2047) == 0 && - session.tm.elapsed() >= session.tm.optimum()) || - stopSearch.load(std::memory_order_relaxed)) - return VALUE_NONE; - } - if (maxScore != -VALUE_INFINITE) { - TTFlag flag = maxScore >= beta ? TTFlag::LOWERBOUND - : maxScore <= alphaOrig ? TTFlag::UPPERBOUND - : TTFlag::EXACT; - - tt.store(hash, session.pv[ply][0], value_to_tt(maxScore, ply), depth, flag); - } - return maxScore; + tt.store(hash, session.pv[ply][0], value_to_tt(maxScore, ply), depth, flag); + } + return maxScore; } std::string extract_pv(const chess::Board &root, int maxPly) { - std::string pv; - chess::Board pos = root; - uint64_t cycle[64]{}; - for (int ply = 0; ply < maxPly; ply++) { - TTEntry *e = search::tt.lookup(pos.hash()); - if (!e) - break; - chess::Move m(e->getMove()); - if (!m.is_ok()) - break; - uint64_t h = pos.hash(); - int idx = (h >> 6) & 0x3F; - uint64_t bit = 1ULL << (h & 0x3F); - if (cycle[idx] & bit) - break; - cycle[idx] |= bit; - chess::Movelist ml; - pos.legals(ml); - bool legal = false; - for (size_t i = 0; i < ml.size(); i++) - if (ml[i] == m) { - legal = true; - break; - } - if (!legal) - break; - pv += chess::uci::moveToUci(m, root.chess960()) + " "; - if (ply + 1 >= maxPly) - break; - pos.doMove(m); - } - return pv; + std::string pv; + chess::Board pos = root; + for (int ply = 0; ply < maxPly; ply++) { + if (pos.is_draw(3)) + break; + TTEntry *e = search::tt.lookup(pos.hash()); + if (!e) + break; + chess::Move m(e->getMove()); + if (!m.is_ok()) + break; + uint64_t h = pos.hash(); + chess::Movelist ml; + pos.legals(ml); + bool legal = false; + for (size_t i = 0; i < ml.size(); i++) + if (ml[i] == m) { + legal = true; + break; + } + if (!legal) + break; + pv += chess::uci::moveToUci(m, root.chess960()) + " "; + if (ply + 1 >= maxPly) + break; + pos.doMove(m); + } + return pv; } void search(const chess::Board &board, const timeman::LimitsType timecontrol) { - stopSearch = false; - tt.newSearch(); - static double originalTimeAdjust = -1; - Session session; - session.tc = timecontrol; - session.tm.init(session.tc, board.side_to_move(), 0, originalTimeAdjust); - session.lastLogTime = session.tm.elapsed(); - session.ogcolor = board.side_to_move(); - chess::Move lastPV[MAX_PLY]{}; - Value prevScore = VALUE_NONE; - - for (int i = 1; i <= timecontrol.depth; i++) { + stopSearch = false; + tt.newSearch(); + static double originalTimeAdjust = -1; + Session session; + session.tc = timecontrol; + session.tm.init(session.tc, board.side_to_move(), 0, originalTimeAdjust); session.lastLogTime = session.tm.elapsed(); - session.depth = i; - for (int _ = 0; _ < 64; _++) - for (int j = 0; j < 64; j++) - session.historyHeuristic[_][j] /= 2; - auto board_ = board; - Value score_; - - if (i >= 3 && prevScore != VALUE_NONE && !is_win(prevScore) && - !is_loss(prevScore)) { - Value delta = Value(20 + i * 5); - Value alpha0 = std::max(prevScore - delta, -VALUE_INFINITE); - Value beta0 = std::min(prevScore + delta, VALUE_INFINITE); - - score_ = doSearch(board_, i, alpha0, beta0, session); - - if (score_ != VALUE_NONE && score_ <= alpha0) { - score_ = doSearch(board_, i, -VALUE_INFINITE, beta0, session); - if (score_ != VALUE_NONE && score_ <= alpha0) - score_ = - doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); - } else if (score_ != VALUE_NONE && score_ >= beta0) { - score_ = doSearch(board_, i, alpha0, VALUE_INFINITE, session); - if (score_ != VALUE_NONE && score_ >= beta0) - score_ = - doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); - } - } else { - score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); - } - prevScore = score_; - if (session.tm.elapsed() >= session.tm.optimum() || - session.tm.elapsed() >= session.tm.maximum() || - stopSearch.load(std::memory_order_relaxed) || score_ == VALUE_NONE) - break; - InfoFull info{}; - info.depth = i; - info.selDepth = session.seldepth; - info.hashfull = tt.hashfull(); - info.nodes = session.nodes; - info.nps = session.nodes * 1000 / - std::max(session.tm.elapsed(), (timeman::TimePoint)1); - info.timeMs = session.tm.elapsed(); - info.tbHits = session.tbHits; - info.multiPV = 1; - info.score = score_; - TTEntry *entry = tt.lookup(board.hash()); - if (entry) - switch (entry->getFlag()) { - case LOWERBOUND: - info.bound = "lowerbound"; - break; - case UPPERBOUND: - info.bound = "upperbound"; - break; - default: - break; - } - info.pv = extract_pv(board, 2 * i + 4); - // Save first move from PV for bestmove output - std::string pvStr = info.pv; - size_t sp = pvStr.find(' '); - std::string firstMove = - (sp == std::string::npos) ? pvStr : pvStr.substr(0, sp); - if (!firstMove.empty()) - lastPV[0] = chess::Move(chess::uci::uciToMove(board, firstMove).raw()); - std::stringstream ss; - ss << "qnodes " << session.qnodes << " lmrResearches " - << session.lmrResearches << " ttHits " << session.ttHits << " ttCutoffs " - << session.ttCutoffs << " nullCutoffs " << session.nullCutoffs; - info.extrainfo = ss.str(); - report(info); - } - if (lastPV[0].is_ok()) - report(chess::uci::moveToUci(lastPV[0], board.chess960())); - else { - std::cerr << "info string Warning: Did not search\n"; - TTEntry *entry = tt.lookup(board.hash()); - if (entry && entry->getMove() != Move::none().raw()) - report(chess::uci::moveToUci(Move(entry->getMove()), board.chess960())); - else { - Movelist moves; - board.legals(moves); - - if (moves.size()) { - Board board_ = board; - Move best = moves[0]; - Value bestScore = -VALUE_INFINITE; - Session tmpSession{}; - for (Move move : moves) { - board_.doMove(move); - Value score = - -qsearch(board_, -VALUE_INFINITE, VALUE_INFINITE, tmpSession, 0); - if (score > bestScore) { - bestScore = score; - best = move; - } - board_.undoMove(); + session.ogcolor = board.side_to_move(); + chess::Move lastPV[MAX_PLY]{}; + Value prevScore = VALUE_NONE; + + for (int i = 1; i <= timecontrol.depth; i++) { + session.lastLogTime = session.tm.elapsed(); + session.depth = i; + for (int _ = 0; _ < 64; _++) + for (int j = 0; j < 64; j++) + session.historyHeuristic[_][j] /= 2; + auto board_ = board; + Value score_; + + if (i >= 3 && prevScore != VALUE_NONE && !is_win(prevScore) && !is_loss(prevScore)) { + Value delta = Value(20 + i * 5); + Value alpha0 = std::max(prevScore - delta, -VALUE_INFINITE); + Value beta0 = std::min(prevScore + delta, VALUE_INFINITE); + + score_ = doSearch(board_, i, alpha0, beta0, session); + + if (score_ != VALUE_NONE && score_ <= alpha0) { + score_ = doSearch(board_, i, -VALUE_INFINITE, beta0, session); + if (score_ != VALUE_NONE && score_ <= alpha0) + score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + } else if (score_ != VALUE_NONE && score_ >= beta0) { + score_ = doSearch(board_, i, alpha0, VALUE_INFINITE, session); + if (score_ != VALUE_NONE && score_ >= beta0) + score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + } + } else { + score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); } - + prevScore = score_; + if (session.tm.elapsed() >= session.tm.optimum() || session.tm.elapsed() >= session.tm.maximum() || + stopSearch.load(std::memory_order_relaxed) || score_ == VALUE_NONE) + break; InfoFull info{}; - info.depth = 1; - info.nodes = 1; - info.score = 0; + info.depth = i; + info.selDepth = session.seldepth; + info.hashfull = tt.hashfull(); + info.nodes = session.nodes; + info.nps = session.nodes * 1000 / std::max(session.tm.elapsed(), (timeman::TimePoint)1); + info.timeMs = session.tm.elapsed(); + info.tbHits = session.tbHits; info.multiPV = 1; - info.pv = std::string(chess::uci::moveToUci(best, board.chess960())); + info.score = score_; + TTEntry *entry = tt.lookup(board.hash()); + if (entry) + switch (entry->getFlag()) { + case LOWERBOUND: + info.bound = "lowerbound"; + break; + case UPPERBOUND: + info.bound = "upperbound"; + break; + default: + break; + } + info.pv = extract_pv(board, 2 * i + 4); + // Save first move from PV for bestmove output + std::string pvStr = info.pv; + size_t sp = pvStr.find(' '); + std::string firstMove = (sp == std::string::npos) ? pvStr : pvStr.substr(0, sp); + if (!firstMove.empty()) + lastPV[0] = chess::Move(chess::uci::uciToMove(board, firstMove).raw()); + std::stringstream ss; + ss << "qnodes " << session.qnodes << " lmrResearches " << session.lmrResearches << " ttHits " << session.ttHits + << " ttCutoffs " << session.ttCutoffs << " nullCutoffs " << session.nullCutoffs; + info.extrainfo = ss.str(); report(info); - - report(chess::uci::moveToUci(best, board.chess960())); - } else { - report("0000"); - } } - } + if (lastPV[0].is_ok()) + report(chess::uci::moveToUci(lastPV[0], board.chess960())); + else { + std::cerr << "info string Warning: Did not search\n"; + TTEntry *entry = tt.lookup(board.hash()); + if (entry && entry->getMove() != Move::none().raw()) + report(chess::uci::moveToUci(Move(entry->getMove()), board.chess960())); + else { + Movelist moves; + board.legals(moves); + + if (moves.size()) { + Board board_ = board; + Move best = moves[0]; + Value bestScore = -VALUE_INFINITE; + Session tmpSession{}; + for (Move move : moves) { + board_.doMove(move); + Value score = -qsearch(board_, -VALUE_INFINITE, VALUE_INFINITE, tmpSession, 0); + if (score > bestScore) { + bestScore = score; + best = move; + } + board_.undoMove(); + } + + InfoFull info{}; + info.depth = 1; + info.nodes = 1; + info.score = 0; + info.multiPV = 1; + info.pv = std::string(chess::uci::moveToUci(best, board.chess960())); + report(info); + + report(chess::uci::moveToUci(best, board.chess960())); + } else { + report("0000"); + } + } + } } } // namespace engine::search \ No newline at end of file diff --git a/search.h b/search.h index dbbe5e0..be1c304 100644 --- a/search.h +++ b/search.h @@ -5,18 +5,18 @@ #include namespace engine::search { struct Session { - timeman::TimeManagement tm; - timeman::LimitsType tc; - int seldepth = 0; - uint64_t nodes = 0, qnodes = 0, lmrResearches = 0; - uint64_t tbHits = 0, ttHits = 0, ttCutoffs = 0, nullCutoffs = 0; - chess::Move pv[MAX_PLY][MAX_PLY]; - Value historyHeuristic[chess::SQUARE_NB][chess::SQUARE_NB]{}; - chess::Move killerMoves[MAX_PLY][2]; - chess::Move counterMoves[4096]; - timeman::TimePoint lastLogTime; - int depth = 0; - chess::Color ogcolor; + timeman::TimeManagement tm; + timeman::LimitsType tc; + int seldepth = 0; + uint64_t nodes = 0, qnodes = 0, lmrResearches = 0; + uint64_t tbHits = 0, ttHits = 0, ttCutoffs = 0, nullCutoffs = 0; + chess::Move pv[MAX_PLY][MAX_PLY]; + Value historyHeuristic[chess::SQUARE_NB][chess::SQUARE_NB]{}; + chess::Move killerMoves[MAX_PLY][2]; + chess::Move counterMoves[4096]; + timeman::TimePoint lastLogTime; + int depth = 0; + chess::Color ogcolor; }; void stop(); void search(const chess::Board &, const timeman::LimitsType); diff --git a/tb.cpp b/tb.cpp index 27e98d3..61d0efa 100644 --- a/tb.cpp +++ b/tb.cpp @@ -9,30 +9,28 @@ namespace engine::tb { tbprobe::syzygy::Tablebase tablebase; namespace { -std::size_t unique_table_count( - const std::unordered_map &tables) { - std::unordered_set uniqueTables; +std::size_t unique_table_count(const std::unordered_map &tables) { + std::unordered_set uniqueTables; - for (const auto &[_, table] : tables) - if (table != nullptr) - uniqueTables.insert(table); + for (const auto &[_, table] : tables) + if (table != nullptr) + uniqueTables.insert(table); - return uniqueTables.size(); + return uniqueTables.size(); } } // namespace void init(const std::string &path) { - tablebase.close(); + tablebase.close(); - if (path.empty()) { - std::cout << "info string Found 0 WDL and 0 DTZ tablebase files\n"; - return; - } + if (path.empty()) { + std::cout << "info string Found 0 WDL and 0 DTZ tablebase files\n"; + return; + } - tbprobe::syzygy::initialize(); - tablebase.add_directory(path, true, true); - std::cout << "info string Found " << wdl_count() << " WDL and " << dtz_count() - << " DTZ tablebase files\n"; + tbprobe::syzygy::initialize(); + tablebase.add_directory(path, true, true); + std::cout << "info string Found " << wdl_count() << " WDL and " << dtz_count() << " DTZ tablebase files\n"; } std::size_t wdl_count() { return unique_table_count(tablebase.wdl); } @@ -40,18 +38,18 @@ std::size_t wdl_count() { return unique_table_count(tablebase.wdl); } std::size_t dtz_count() { return unique_table_count(tablebase.dtz); } int probe_wdl(chess::Board &board) { - try { - return *tablebase.get_wdl(board, TB_ERROR); - } catch (const std::exception &) { - return TB_ERROR; - } + try { + return *tablebase.get_wdl(board, TB_ERROR); + } catch (const std::exception &) { + return TB_ERROR; + } } int probe_dtz(chess::Board &board) { - try { - return *tablebase.get_dtz(board, TB_ERROR); - } catch (const std::exception &) { - return TB_ERROR; - } + try { + return *tablebase.get_dtz(board, TB_ERROR); + } catch (const std::exception &) { + return TB_ERROR; + } } } // namespace engine::tb diff --git a/timeman.cpp b/timeman.cpp index b47bad9..3bd6469 100644 --- a/timeman.cpp +++ b/timeman.cpp @@ -18,77 +18,67 @@ void TimeManagement::clear() {} // the bounds of time allowed for the current game ply. We currently support: // 1) x basetime (+ z increment) // 2) x moves in y seconds (+ z increment) -void TimeManagement::init(LimitsType &limits, chess::Color us, int ply, - double &originalTimeAdjust) { +void TimeManagement::init(LimitsType &limits, chess::Color us, int ply, double &originalTimeAdjust) { - // If we have no time, we don't need to fully initialize TM. - // startTime is used by movetime and useNodesTime is used in elapsed calls. - startTime = limits.startTime; - if (limits.movetime != 0 && limits.time[us] == 0) { - optimumTime = maximumTime = TimePoint(limits.movetime); - return; - } - if (limits.time[us] == 0 && limits.movetime == 0) { - optimumTime = maximumTime = INFINITE_TIME; - return; - } - // optScale is a percentage of available time to use for the current move. - // maxScale is a multiplier applied to optimumTime. - double optScale, maxScale; + // If we have no time, we don't need to fully initialize TM. + // startTime is used by movetime and useNodesTime is used in elapsed calls. + startTime = limits.startTime; + if (limits.movetime != 0 && limits.time[us] == 0) { + optimumTime = maximumTime = TimePoint(limits.movetime); + return; + } + if (limits.time[us] == 0 && limits.movetime == 0) { + optimumTime = maximumTime = INFINITE_TIME; + return; + } + // optScale is a percentage of available time to use for the current move. + // maxScale is a multiplier applied to optimumTime. + double optScale, maxScale; - // These numbers are used where multiplications, divisions or comparisons - // with constants are involved. - const TimePoint time = limits.time[us]; - const int moveOverhead = options["Move Overhead"]; - // Maximum move horizon - int centiMTG = - limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051; + // These numbers are used where multiplications, divisions or comparisons + // with constants are involved. + const TimePoint time = limits.time[us]; + const int moveOverhead = options["Move Overhead"]; + // Maximum move horizon + int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051; - // If less than one second, gradually reduce mtg - if (time < 1000) - centiMTG = int(time * 5.051); + // If less than one second, gradually reduce mtg + if (time < 1000) + centiMTG = int(time * 5.051); - // Make sure timeLeft is > 0 since we may use it as a divisor - TimePoint timeLeft = - std::max(TimePoint(1), time + (limits.inc[us] * (centiMTG - 100) - - moveOverhead * (200 + centiMTG)) / - 100); + // Make sure timeLeft is > 0 since we may use it as a divisor + TimePoint timeLeft = + std::max(TimePoint(1), time + (limits.inc[us] * (centiMTG - 100) - moveOverhead * (200 + centiMTG)) / 100); - // x basetime (+ z increment) - // If there is a healthy increment, timeLeft can exceed the actual available - // game time for the current move, so also cap to a percentage of available - // game time. - if (limits.movestogo == 0) { - // Extra time according to timeLeft - if (originalTimeAdjust < 0) - originalTimeAdjust = 0.3128 * std::log10(timeLeft) - 0.4354; + // x basetime (+ z increment) + // If there is a healthy increment, timeLeft can exceed the actual available + // game time for the current move, so also cap to a percentage of available + // game time. + if (limits.movestogo == 0) { + // Extra time according to timeLeft + if (originalTimeAdjust < 0) + originalTimeAdjust = 0.3128 * std::log10(timeLeft) - 0.4354; - // Calculate time constants based on current time left. - double logTimeInSec = std::log10(time / 1000.0); - double optConstant = - std::min(0.0032116 + 0.000321123 * logTimeInSec, 0.00508017); - double maxConstant = std::max(3.3977 + 3.03950 * logTimeInSec, 2.94761); + // Calculate time constants based on current time left. + double logTimeInSec = std::log10(time / 1000.0); + double optConstant = std::min(0.0032116 + 0.000321123 * logTimeInSec, 0.00508017); + double maxConstant = std::max(3.3977 + 3.03950 * logTimeInSec, 2.94761); - optScale = - std::min(0.0121431 + std::pow(ply + 2.94693, 0.461073) * optConstant, - 0.213035 * time / timeLeft) * - originalTimeAdjust; + optScale = std::min(0.0121431 + std::pow(ply + 2.94693, 0.461073) * optConstant, 0.213035 * time / timeLeft) * + originalTimeAdjust; - maxScale = std::min(6.67704, maxConstant + ply / 11.9847); - } + maxScale = std::min(6.67704, maxConstant + ply / 11.9847); + } - // x moves in y seconds (+ z increment) - else { - optScale = std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), - 0.88 * time / timeLeft); - maxScale = 1.3 + 0.11 * (centiMTG / 100.0); - } + // x moves in y seconds (+ z increment) + else { + optScale = std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), 0.88 * time / timeLeft); + maxScale = 1.3 + 0.11 * (centiMTG / 100.0); + } - // Limit the maximum possible time for this move - optimumTime = TimePoint(optScale * timeLeft); - maximumTime = TimePoint(std::min(0.825179 * time - moveOverhead, - maxScale * optimumTime)) - - 10; + // Limit the maximum possible time for this move + optimumTime = TimePoint(optScale * timeLeft); + maximumTime = TimePoint(std::min(0.825179 * time - moveOverhead, maxScale * optimumTime)) - 10; } } // namespace engine::timeman diff --git a/timeman.h b/timeman.h index ca90fe2..6a09fdb 100644 --- a/timeman.h +++ b/timeman.h @@ -8,47 +8,41 @@ constexpr TimePoint INFINITE_TIME = 864000000; // LimitsType struct stores information sent by the caller about the analysis // required. inline TimePoint now() { - return std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()) - .count(); + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); } struct LimitsType { - // Init explicitly due to broken value-initialization of non POD in MSVC - LimitsType() { - time[chess::WHITE] = time[chess::BLACK] = inc[chess::WHITE] = - inc[chess::BLACK] = movetime = TimePoint(0); - movestogo = mate = perft = infinite = 0; - depth = 64; - nodes = 0; - ponderMode = false; - } + // Init explicitly due to broken value-initialization of non POD in MSVC + LimitsType() { + time[chess::WHITE] = time[chess::BLACK] = inc[chess::WHITE] = inc[chess::BLACK] = movetime = TimePoint(0); + movestogo = mate = perft = infinite = 0; + depth = 64; + nodes = 0; + ponderMode = false; + } - bool use_time_management() const { - return time[chess::WHITE] || time[chess::BLACK]; - } + bool use_time_management() const { return time[chess::WHITE] || time[chess::BLACK]; } - std::vector searchmoves; - TimePoint time[chess::COLOR_NB], inc[chess::COLOR_NB], movetime, startTime; - int movestogo, depth, mate, perft, infinite; - uint64_t nodes; - bool ponderMode; + std::vector searchmoves; + TimePoint time[chess::COLOR_NB], inc[chess::COLOR_NB], movetime, startTime; + int movestogo, depth, mate, perft, infinite; + uint64_t nodes; + bool ponderMode; }; class TimeManagement { -public: - void init(LimitsType &limits, chess::Color us, int ply, - double &originalTimeAdjust); + public: + void init(LimitsType &limits, chess::Color us, int ply, double &originalTimeAdjust); - TimePoint optimum() const; - TimePoint maximum() const; - TimePoint elapsed() const { return elapsed_time(); } - TimePoint elapsed_time() const { return now() - startTime; }; + TimePoint optimum() const; + TimePoint maximum() const; + TimePoint elapsed() const { return elapsed_time(); } + TimePoint elapsed_time() const { return now() - startTime; }; - void clear(); + void clear(); -private: - TimePoint startTime; - TimePoint optimumTime; - TimePoint maximumTime; + private: + TimePoint startTime; + TimePoint optimumTime; + TimePoint maximumTime; }; } // namespace engine::timeman \ No newline at end of file diff --git a/tt.cpp b/tt.cpp index 9bbe83e..768b2cd 100644 --- a/tt.cpp +++ b/tt.cpp @@ -6,70 +6,69 @@ #endif using namespace engine; static inline uint64_t index_for_hash(uint64_t hash, uint64_t buckets) { - if (buckets == 0) - return 0; + if (buckets == 0) + return 0; #if defined(_MSC_VER) - // MSVC: use _umul128 to get high 64 bits of 128-bit product - unsigned long long high = 0; - (void)_umul128((unsigned long long)hash, (unsigned long long)buckets, &high); - return (uint64_t)high; + // MSVC: use _umul128 to get high 64 bits of 128-bit product + unsigned long long high = 0; + (void)_umul128((unsigned long long)hash, (unsigned long long)buckets, &high); + return (uint64_t)high; #elif defined(__SIZEOF_INT128__) - // GCC/Clang: use __uint128_t - __uint128_t prod = (__uint128_t)hash * (__uint128_t)buckets; - return (uint64_t)(prod >> 64); + // GCC/Clang: use __uint128_t + __uint128_t prod = (__uint128_t)hash * (__uint128_t)buckets; + return (uint64_t)(prod >> 64); #else - uint64_t aL = uint32_t(hash), aH = a >> 32; - uint64_t bL = uint32_t(buckets), bH = b >> 32; - uint64_t c1 = (aL * bL) >> 32; - uint64_t c2 = aH * bL + c1; - uint64_t c3 = aL * bH + uint32_t(c2); - return aH * bH + (c2 >> 32) + (c3 >> 32); + uint64_t aL = uint32_t(hash), aH = a >> 32; + uint64_t bL = uint32_t(buckets), bH = b >> 32; + uint64_t c1 = (aL * bL) >> 32; + uint64_t c2 = aH * bL + c1; + uint64_t c3 = aL * bH + uint32_t(c2); + return aH * bH + (c2 >> 32) + (c3 >> 32); #endif } void TranspositionTable::newSearch() { time++; } -void TranspositionTable::store(uint64_t hash, chess::Move best, int16_t score, - int8_t depth, TTFlag flag) { - // 2 entries per bucket - if (buckets == 0) - return; +void TranspositionTable::store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, TTFlag flag) { + // 2 entries per bucket + if (buckets == 0) + return; - uint64_t index = index_for_hash(hash, buckets); + uint64_t index = index_for_hash(hash, buckets); - TTEntry &e0 = table[2 * index], &e1 = table[2 * index + 1]; - // Store the entry - for (TTEntry *e : {&e0, &e1}) { - if (e->key == hash || e->getDepth() < depth) { - e->key = hash; - e->setPackedFields(score, depth, flag, best.raw(), time); + TTEntry &e0 = table[2 * index], &e1 = table[2 * index + 1]; + // Store the entry + for (TTEntry *e : { &e0, &e1 }) { + if (e->key == hash || e->getDepth() < depth) { + e->key = hash; + e->setPackedFields(score, depth, flag, best.raw(), time); - return; + return; + } } - } - // If we get here, we need to evict an entry - // Find the oldest entry - TTEntry *oldest = (e0.timestamp() < e1.timestamp()) ? &e0 : &e1; - // Evict it - oldest->key = hash; - oldest->setPackedFields(score, depth, flag, best.raw(), time); + // If we get here, we need to evict an entry + // Find the oldest entry + TTEntry *oldest = (e0.timestamp() < e1.timestamp()) ? &e0 : &e1; + // Evict it + oldest->key = hash; + oldest->setPackedFields(score, depth, flag, best.raw(), time); } TTEntry *TranspositionTable::lookup(uint64_t hash) { - if (buckets == 0) - return nullptr; + if (buckets == 0) + return nullptr; - uint64_t bucket = index_for_hash(hash, buckets); + uint64_t bucket = index_for_hash(hash, buckets); - TTEntry &e0 = table[2 * bucket]; - TTEntry &e1 = table[2 * bucket + 1]; + TTEntry &e0 = table[2 * bucket]; + TTEntry &e1 = table[2 * bucket + 1]; - if (e0.key == hash) - return &e0; + if (e0.key == hash) + return &e0; - if (e1.key == hash) - return &e1; + if (e1.key == hash) + return &e1; - return nullptr; + return nullptr; } \ No newline at end of file diff --git a/tt.h b/tt.h index b1eb094..e648370 100644 --- a/tt.h +++ b/tt.h @@ -6,165 +6,138 @@ namespace engine { enum TTFlag : uint8_t { EXACT = 0, LOWERBOUND = 1, UPPERBOUND = 2 }; struct TTEntry { - uint64_t key; - uint64_t pack; // 16-bit score, 8-bit depth, 3-bit flags, 16-bit move, 21 bits - // for generation - - // bit layout constants - static constexpr unsigned SCORE_SHIFT = 0; - static constexpr unsigned SCORE_BITS = 16; - static constexpr uint64_t SCORE_MASK = ((uint64_t(1) << SCORE_BITS) - 1) - << SCORE_SHIFT; - - static constexpr unsigned DEPTH_SHIFT = 16; - static constexpr unsigned DEPTH_BITS = 8; - static constexpr uint64_t DEPTH_MASK = ((uint64_t(1) << DEPTH_BITS) - 1) - << DEPTH_SHIFT; - - static constexpr unsigned FLAG_SHIFT = 24; - static constexpr unsigned FLAG_BITS = 3; - static constexpr uint64_t FLAG_MASK = ((uint64_t(1) << FLAG_BITS) - 1) - << FLAG_SHIFT; - - static constexpr unsigned MOVE_SHIFT = 27; - static constexpr unsigned MOVE_BITS = 16; - static constexpr uint64_t MOVE_MASK = ((uint64_t(1) << MOVE_BITS) - 1) - << MOVE_SHIFT; - - static constexpr unsigned GEN_SHIFT = 43; - static constexpr unsigned GEN_BITS = 21; - static constexpr uint64_t GEN_MASK = ((uint64_t(1) << GEN_BITS) - 1) - << GEN_SHIFT; - - // getters - inline int16_t getScore() const noexcept { - return static_cast((pack & SCORE_MASK) >> SCORE_SHIFT); - } - - inline uint8_t getDepth() const noexcept { - return static_cast((pack & DEPTH_MASK) >> DEPTH_SHIFT); - } - - inline TTFlag getFlag() const noexcept { - return static_cast((pack & FLAG_MASK) >> FLAG_SHIFT); - } - - inline uint16_t getMove() const noexcept { - return static_cast((pack & MOVE_MASK) >> MOVE_SHIFT); - } - - inline uint32_t getGeneration() const noexcept { - return static_cast((pack & GEN_MASK) >> GEN_SHIFT); - } - - // setters - inline void setScore(int16_t score) noexcept { - // preserve two's complement by casting through uint16_t - const uint64_t v = - (static_cast(static_cast(score)) << SCORE_SHIFT) & - SCORE_MASK; - pack = (pack & ~SCORE_MASK) | v; - } - - inline void setDepth(uint8_t depth) noexcept { - const uint64_t v = - (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; - pack = (pack & ~DEPTH_MASK) | v; - } - - inline void setFlag(TTFlag flag) noexcept { - const uint64_t v = - (static_cast(static_cast(flag)) << FLAG_SHIFT) & - FLAG_MASK; - pack = (pack & ~FLAG_MASK) | v; - } - - inline void setMove(uint16_t move) noexcept { - const uint64_t v = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; - pack = (pack & ~MOVE_MASK) | v; - } - - inline void setGeneration(uint32_t gen) noexcept { - const uint64_t v = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; - pack = (pack & ~GEN_MASK) | v; - } - - // convenience: set all packed fields at once - inline void setPackedFields(int16_t score, uint8_t depth, TTFlag flag, - uint16_t move, uint32_t gen) noexcept { - const uint64_t s = - (static_cast(static_cast(score)) << SCORE_SHIFT) & - SCORE_MASK; - const uint64_t d = - (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; - const uint64_t f = - (static_cast(static_cast(flag)) << FLAG_SHIFT) & - FLAG_MASK; - const uint64_t m = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; - const uint64_t g = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; - pack = s | d | f | m | g; - } - - inline uint32_t timestamp() const noexcept { return getGeneration(); } + uint64_t key; + uint64_t pack; // 16-bit score, 8-bit depth, 3-bit flags, 16-bit move, 21 bits + // for generation + + // bit layout constants + static constexpr unsigned SCORE_SHIFT = 0; + static constexpr unsigned SCORE_BITS = 16; + static constexpr uint64_t SCORE_MASK = ((uint64_t(1) << SCORE_BITS) - 1) << SCORE_SHIFT; + + static constexpr unsigned DEPTH_SHIFT = 16; + static constexpr unsigned DEPTH_BITS = 8; + static constexpr uint64_t DEPTH_MASK = ((uint64_t(1) << DEPTH_BITS) - 1) << DEPTH_SHIFT; + + static constexpr unsigned FLAG_SHIFT = 24; + static constexpr unsigned FLAG_BITS = 3; + static constexpr uint64_t FLAG_MASK = ((uint64_t(1) << FLAG_BITS) - 1) << FLAG_SHIFT; + + static constexpr unsigned MOVE_SHIFT = 27; + static constexpr unsigned MOVE_BITS = 16; + static constexpr uint64_t MOVE_MASK = ((uint64_t(1) << MOVE_BITS) - 1) << MOVE_SHIFT; + + static constexpr unsigned GEN_SHIFT = 43; + static constexpr unsigned GEN_BITS = 21; + static constexpr uint64_t GEN_MASK = ((uint64_t(1) << GEN_BITS) - 1) << GEN_SHIFT; + + // getters + inline int16_t getScore() const noexcept { return static_cast((pack & SCORE_MASK) >> SCORE_SHIFT); } + + inline uint8_t getDepth() const noexcept { return static_cast((pack & DEPTH_MASK) >> DEPTH_SHIFT); } + + inline TTFlag getFlag() const noexcept { return static_cast((pack & FLAG_MASK) >> FLAG_SHIFT); } + + inline uint16_t getMove() const noexcept { return static_cast((pack & MOVE_MASK) >> MOVE_SHIFT); } + + inline uint32_t getGeneration() const noexcept { return static_cast((pack & GEN_MASK) >> GEN_SHIFT); } + + // setters + inline void setScore(int16_t score) noexcept { + // preserve two's complement by casting through uint16_t + const uint64_t v = (static_cast(static_cast(score)) << SCORE_SHIFT) & SCORE_MASK; + pack = (pack & ~SCORE_MASK) | v; + } + + inline void setDepth(uint8_t depth) noexcept { + const uint64_t v = (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; + pack = (pack & ~DEPTH_MASK) | v; + } + + inline void setFlag(TTFlag flag) noexcept { + const uint64_t v = (static_cast(static_cast(flag)) << FLAG_SHIFT) & FLAG_MASK; + pack = (pack & ~FLAG_MASK) | v; + } + + inline void setMove(uint16_t move) noexcept { + const uint64_t v = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; + pack = (pack & ~MOVE_MASK) | v; + } + + inline void setGeneration(uint32_t gen) noexcept { + const uint64_t v = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; + pack = (pack & ~GEN_MASK) | v; + } + + // convenience: set all packed fields at once + inline void setPackedFields(int16_t score, uint8_t depth, TTFlag flag, uint16_t move, uint32_t gen) noexcept { + const uint64_t s = (static_cast(static_cast(score)) << SCORE_SHIFT) & SCORE_MASK; + const uint64_t d = (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; + const uint64_t f = (static_cast(static_cast(flag)) << FLAG_SHIFT) & FLAG_MASK; + const uint64_t m = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; + const uint64_t g = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; + pack = s | d | f | m | g; + } + + inline uint32_t timestamp() const noexcept { return getGeneration(); } }; class TranspositionTable { - TTEntry *table; - int buckets; // number of buckets (pairs) - uint32_t time; - -public: - size_t size; // total number of TTEntry elements (must be even) - TranspositionTable() : table(nullptr), buckets(0), time(0), size(0) {} - - TranspositionTable(size_t sizeInMB) : time(0) { - size = sizeInMB * 1048576LL / sizeof(TTEntry); - if (size % 2 != 0) - size--; // Ensure even size - buckets = size / 2; - table = new TTEntry[size]; - clear(); - } - - ~TranspositionTable() { delete[] table; } - - void resize(int sizeInMB) { - int new_size = sizeInMB * 1048576 / sizeof(TTEntry); - if (new_size % 2 != 0) - new_size--; - - TTEntry *new_table = new (std::nothrow) TTEntry[new_size](); - if (!new_table) { - throw std::bad_alloc(); + TTEntry *table; + int buckets; // number of buckets (pairs) + uint32_t time; + + public: + size_t size; // total number of TTEntry elements (must be even) + TranspositionTable() : table(nullptr), buckets(0), time(0), size(0) {} + + TranspositionTable(size_t sizeInMB) : time(0) { + size = sizeInMB * 1048576LL / sizeof(TTEntry); + if (size % 2 != 0) + size--; // Ensure even size + buckets = size / 2; + table = new TTEntry[size]; + clear(); } - delete[] table; - table = new_table; - size = new_size; - buckets = size / 2; - } + ~TranspositionTable() { delete[] table; } + + void resize(int sizeInMB) { + int new_size = sizeInMB * 1048576 / sizeof(TTEntry); + if (new_size % 2 != 0) + new_size--; - void newSearch(); - void store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, - TTFlag flag); + TTEntry *new_table = new (std::nothrow) TTEntry[new_size](); + if (!new_table) { + throw std::bad_alloc(); + } - inline void clear() { std::fill_n(table, size, TTEntry{}); } + delete[] table; + table = new_table; + size = new_size; + buckets = size / 2; + } - TTEntry *lookup(uint64_t hash); + void newSearch(); + void store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, TTFlag flag); - // Returns hash usage as percentage [0..100] based on occupied buckets. - inline int hashfull() const noexcept { - if (!table || buckets <= 0) - return 0; + inline void clear() { std::fill_n(table, size, TTEntry{}); } - int used = 0; - for (int i = 0; i < buckets; ++i) { - const TTEntry &a = table[2 * i]; - const TTEntry &b = table[2 * i + 1]; - if (a.key != 0 || b.key != 0) - ++used; - } + TTEntry *lookup(uint64_t hash); - return (used * 1000LL) / buckets; - } + // Returns hash usage as percentage [0..100] based on occupied buckets. + inline int hashfull() const noexcept { + if (!table || buckets <= 0) + return 0; + + int used = 0; + for (int i = 0; i < buckets; ++i) { + const TTEntry &a = table[2 * i]; + const TTEntry &b = table[2 * i + 1]; + if (a.key != 0 || b.key != 0) + ++used; + } + + return (used * 1000LL) / buckets; + } }; } // namespace engine diff --git a/tune.cpp b/tune.cpp index d29fed6..94077ec 100644 --- a/tune.cpp +++ b/tune.cpp @@ -39,65 +39,61 @@ std::map TuneResults; std::optional on_tune(const Option &o) { - if (!Tune::update_on_last || LastOption == &o) - Tune::read_options(); + if (!Tune::update_on_last || LastOption == &o) + Tune::read_options(); - return std::nullopt; + return std::nullopt; } } // namespace -void Tune::make_option(OptionsMap *opts, const string &n, int v, - const SetRange &r) { +void Tune::make_option(OptionsMap *opts, const string &n, int v, const SetRange &r) { - // Do not generate option when there is nothing to tune (ie. min = max) - if (r(v).first == r(v).second) - return; + // Do not generate option when there is nothing to tune (ie. min = max) + if (r(v).first == r(v).second) + return; - if (TuneResults.count(n)) - v = TuneResults[n]; + if (TuneResults.count(n)) + v = TuneResults[n]; - opts->add(n, Option(v, r(v).first, r(v).second, on_tune)); - LastOption = &((*opts)[n]); + opts->add(n, Option(v, r(v).first, r(v).second, on_tune)); + LastOption = &((*opts)[n]); - // Print formatted parameters, ready to be copy-pasted in Fishtest - std::cout << n << "," // + // Print formatted parameters, ready to be copy-pasted in Fishtest + std::cout << n << "," // #ifdef OPENBENCH_SUPPORT - // or OpenBench - << "int" << "," + // or OpenBench + << "int" << "," #endif - << v << "," // - << r(v).first << "," // - << r(v).second << "," // - << (r(v).second - r(v).first) / 20.0 << "," // - << "0.0020" << std::endl; + << v << "," // + << r(v).first << "," // + << r(v).second << "," // + << (r(v).second - r(v).first) / 20.0 << "," // + << "0.0020" << std::endl; } string Tune::next(string &names, bool pop) { - string name; + string name; - do { - string token = names.substr(0, names.find(',')); + do { + string token = names.substr(0, names.find(',')); - if (pop) - names.erase(0, token.size() + 1); + if (pop) + names.erase(0, token.size() + 1); - std::stringstream ws(token); - name += (ws >> token, token); // Remove trailing whitespace + std::stringstream ws(token); + name += (ws >> token, token); // Remove trailing whitespace - } while (std::count(name.begin(), name.end(), '(') - - std::count(name.begin(), name.end(), ')')); + } while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')')); - return name; + return name; } -template <> void Tune::Entry::init_option() { - make_option(options, name, value, range); -} +template <> void Tune::Entry::init_option() { make_option(options, name, value, range); } template <> void Tune::Entry::read_option() { - if (options->count(name)) - value = int((*options)[name]); + if (options->count(name)) + value = int((*options)[name]); } // Instead of a variable here we have a PostUpdate function: just call it diff --git a/tune.h b/tune.h index d6a0022..b58a3b5 100644 --- a/tune.h +++ b/tune.h @@ -34,17 +34,15 @@ using Range = std::pair; // Option's min-max values using RangeFun = Range(int); // Default Range function, to calculate Option's min-max values -inline Range default_range(int v) { - return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); -} +inline Range default_range(int v) { return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); } struct SetRange { - explicit SetRange(RangeFun f) : fun(f) {} - SetRange(int min, int max) : fun(nullptr), range(min, max) {} - Range operator()(int v) const { return fun ? fun(v) : range; } + explicit SetRange(RangeFun f) : fun(f) {} + SetRange(int min, int max) : fun(nullptr), range(min, max) {} + Range operator()(int v) const { return fun ? fun(v) : range; } - RangeFun *fun; - Range range; + RangeFun *fun; + Range range; }; #define SetDefaultRange SetRange(default_range) @@ -78,105 +76,93 @@ struct SetRange { class Tune { - using PostUpdate = void(); // Post-update function - - Tune() { read_results(); } - Tune(const Tune &) = delete; - void operator=(const Tune &) = delete; - void read_results(); - - static Tune &instance() { - static Tune t; - return t; - } // Singleton - - // Use polymorphism to accommodate Entry of different types in the same vector - struct EntryBase { - virtual ~EntryBase() = default; - virtual void init_option() = 0; - virtual void read_option() = 0; - }; - - template struct Entry : public EntryBase { - - static_assert(!std::is_const_v, "Parameter cannot be const!"); - - static_assert(std::is_same_v || std::is_same_v, - "Parameter type not supported!"); - - Entry(const std::string &n, T &v, const SetRange &r) - : name(n), value(v), range(r) {} - void operator=(const Entry &) = delete; // Because 'value' is a reference - void init_option() override; - void read_option() override; - - std::string name; - T &value; - SetRange range; - }; - - // Our facility to fill the container, each Entry corresponds to a parameter - // to tune. We use variadic templates to deal with an unspecified number of - // entries, each one of a possible different type. - static std::string next(std::string &names, bool pop = true); - - int add(const SetRange &, std::string &&) { return 0; } - - template - int add(const SetRange &range, std::string &&names, T &value, - Args &&...args) { - list.push_back( - std::unique_ptr(new Entry(next(names), value, range))); - return add(range, std::move(names), args...); - } - - // Template specialization for arrays: recursively handle multi-dimensional - // arrays - template - int add(const SetRange &range, std::string &&names, T (&value)[N], - Args &&...args) { - for (size_t i = 0; i < N; i++) - add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", - value[i]); - return add(range, std::move(names), args...); - } - - // Template specialization for SetRange - template - int add(const SetRange &, std::string &&names, SetRange &value, - Args &&...args) { - return add(value, (next(names), std::move(names)), args...); - } - - static void make_option(OptionsMap *options, const std::string &n, int v, - const SetRange &r); - - std::vector> list; - -public: - template - static int add(const std::string &names, Args &&...args) { - return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), - args...); // Remove trailing parenthesis - } - static void init(OptionsMap &o) { - options = &o; - for (auto &e : instance().list) - e->init_option(); - read_options(); - } // Deferred, due to UCIEngine::Options access - static void read_options() { - for (auto &e : instance().list) - e->read_option(); - } - - static bool update_on_last; - static OptionsMap *options; + using PostUpdate = void(); // Post-update function + + Tune() { read_results(); } + Tune(const Tune &) = delete; + void operator=(const Tune &) = delete; + void read_results(); + + static Tune &instance() { + static Tune t; + return t; + } // Singleton + + // Use polymorphism to accommodate Entry of different types in the same vector + struct EntryBase { + virtual ~EntryBase() = default; + virtual void init_option() = 0; + virtual void read_option() = 0; + }; + + template struct Entry : public EntryBase { + + static_assert(!std::is_const_v, "Parameter cannot be const!"); + + static_assert(std::is_same_v || std::is_same_v, "Parameter type not supported!"); + + Entry(const std::string &n, T &v, const SetRange &r) : name(n), value(v), range(r) {} + void operator=(const Entry &) = delete; // Because 'value' is a reference + void init_option() override; + void read_option() override; + + std::string name; + T &value; + SetRange range; + }; + + // Our facility to fill the container, each Entry corresponds to a parameter + // to tune. We use variadic templates to deal with an unspecified number of + // entries, each one of a possible different type. + static std::string next(std::string &names, bool pop = true); + + int add(const SetRange &, std::string &&) { return 0; } + + template int add(const SetRange &range, std::string &&names, T &value, Args &&...args) { + list.push_back(std::unique_ptr(new Entry(next(names), value, range))); + return add(range, std::move(names), args...); + } + + // Template specialization for arrays: recursively handle multi-dimensional + // arrays + template + int add(const SetRange &range, std::string &&names, T (&value)[N], Args &&...args) { + for (size_t i = 0; i < N; i++) + add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]); + return add(range, std::move(names), args...); + } + + // Template specialization for SetRange + template int add(const SetRange &, std::string &&names, SetRange &value, Args &&...args) { + return add(value, (next(names), std::move(names)), args...); + } + + static void make_option(OptionsMap *options, const std::string &n, int v, const SetRange &r); + + std::vector> list; + + public: + template static int add(const std::string &names, Args &&...args) { + return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), + args...); // Remove trailing parenthesis + } + static void init(OptionsMap &o) { + options = &o; + for (auto &e : instance().list) + e->init_option(); + read_options(); + } // Deferred, due to UCIEngine::Options access + static void read_options() { + for (auto &e : instance().list) + e->read_option(); + } + + static bool update_on_last; + static OptionsMap *options; }; template constexpr void tune_check_args(Args &&...) { - static_assert((!std::is_fundamental_v && ...), - "TUNE macro arguments wrong"); + static_assert((!std::is_fundamental_v && ...), "TUNE macro arguments wrong"); } // Some macro magic :-) we define a dummy int variable that the compiler @@ -184,11 +170,18 @@ template constexpr void tune_check_args(Args &&...) { #define STRINGIFY(x) #x #define UNIQUE2(x, y) x##y #define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ -#define TUNE(...) \ - int UNIQUE(p, __LINE__) = []() -> int { \ - tune_check_args(__VA_ARGS__); \ - return engine::Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__); \ - }(); +#ifdef _MSC_VER +// Completely bypass the lambda bug for Microsoft Visual Studio +#define TUNE(...) \ + int UNIQUE(p, __LINE__) = (tune_check_args(__VA_ARGS__), engine::Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__)); +#else +// Keep the original standard lambda format for GCC / Clang (Linux/Mac) +#define TUNE(...) \ + int UNIQUE(p, __LINE__) = []() -> int { \ + tune_check_args(__VA_ARGS__); \ + return engine::Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__); \ + }(); +#endif #define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true diff --git a/uci.cpp b/uci.cpp index 7cc5888..3e2f58d 100644 --- a/uci.cpp +++ b/uci.cpp @@ -18,222 +18,217 @@ std::thread searchThread; namespace { std::string strip_optional_quotes(std::string fen) { - auto notSpace = [](unsigned char c) { return !std::isspace(c); }; + auto notSpace = [](unsigned char c) { return !std::isspace(c); }; - fen.erase(fen.begin(), std::find_if(fen.begin(), fen.end(), notSpace)); - fen.erase(std::find_if(fen.rbegin(), fen.rend(), notSpace).base(), fen.end()); + fen.erase(fen.begin(), std::find_if(fen.begin(), fen.end(), notSpace)); + fen.erase(std::find_if(fen.rbegin(), fen.rend(), notSpace).base(), fen.end()); - if (fen.size() >= 2 && fen.front() == '"' && fen.back() == '"') - return fen.substr(1, fen.size() - 2); + if (fen.size() >= 2 && fen.front() == '"' && fen.back() == '"') + return fen.substr(1, fen.size() - 2); - if (!fen.empty() && fen.front() == '"') - fen.erase(fen.begin()); + if (!fen.empty() && fen.front() == '"') + fen.erase(fen.begin()); - if (!fen.empty() && fen.back() == '"') - fen.pop_back(); + if (!fen.empty() && fen.back() == '"') + fen.pop_back(); - return fen; + return fen; } } // namespace void engine::stop() { - search::stop(); - if (searchThread.joinable()) { - searchThread.join(); - } + search::stop(); + if (searchThread.joinable()) { + searchThread.join(); + } } void handlePosition(std::istringstream &is) { - stop(); - std::string token, fen; - - is >> token; - - if (token == "startpos") { - fen = chess::Position::START_FEN; - is >> token; // Consume the "moves" token, if any - } else if (token == "fen") - while (is >> token && token != "moves") - fen += token + " "; - else - return; - - try { - pos.setFEN(strip_optional_quotes(fen)); - } catch (const std::exception &e) { - std::cerr << "info string Invalid FEN: " << e.what() << std::endl; - return; - } - - while (is >> token) { + stop(); + std::string token, fen; + + is >> token; + + if (token == "startpos") { + fen = chess::Position::START_FEN; + is >> token; // Consume the "moves" token, if any + } else if (token == "fen") + while (is >> token && token != "moves") + fen += token + " "; + else + return; + try { - pos.push_uci(token); + pos.setFEN(strip_optional_quotes(fen)); } catch (const std::exception &e) { - std::cerr << "info string Invalid move " << token << ": " << e.what() - << std::endl; - return; + std::cerr << "info string Invalid FEN: " << e.what() << std::endl; + return; + } + + while (is >> token) { + try { + pos.push_uci(token); + } catch (const std::exception &e) { + std::cerr << "info string Invalid move " << token << ": " << e.what() << std::endl; + return; + } } - } } timeman::LimitsType parse_limits(std::istream &is) { - timeman::LimitsType limits; - std::string token; - - limits.startTime = timeman::now(); // The search starts as early as possible - - while (is >> token) - if (token == "searchmoves") // Needs to be the last command on the line - while (is >> token) { - std::transform(token.begin(), token.end(), token.begin(), - [](auto c) { return std::tolower(c); }); - limits.searchmoves.push_back(token); - } - else if (token == "wtime") - is >> limits.time[chess::WHITE]; - else if (token == "btime") - is >> limits.time[chess::BLACK]; - else if (token == "winc") - is >> limits.inc[chess::WHITE]; - else if (token == "binc") - is >> limits.inc[chess::BLACK]; - else if (token == "movestogo") - is >> limits.movestogo; - else if (token == "depth") - is >> limits.depth; - else if (token == "nodes") - is >> limits.nodes; - else if (token == "movetime") - is >> limits.movetime; - else if (token == "mate") - is >> limits.mate; - else if (token == "perft") - is >> limits.perft; - else if (token == "infinite") - limits.infinite = 1; - else if (token == "ponder") { - } + timeman::LimitsType limits; + std::string token; - return limits; + limits.startTime = timeman::now(); // The search starts as early as possible + + while (is >> token) + if (token == "searchmoves") // Needs to be the last command on the line + while (is >> token) { + std::transform(token.begin(), token.end(), token.begin(), [](auto c) { return std::tolower(c); }); + limits.searchmoves.push_back(token); + } + else if (token == "wtime") + is >> limits.time[chess::WHITE]; + else if (token == "btime") + is >> limits.time[chess::BLACK]; + else if (token == "winc") + is >> limits.inc[chess::WHITE]; + else if (token == "binc") + is >> limits.inc[chess::BLACK]; + else if (token == "movestogo") + is >> limits.movestogo; + else if (token == "depth") + is >> limits.depth; + else if (token == "nodes") + is >> limits.nodes; + else if (token == "movetime") + is >> limits.movetime; + else if (token == "mate") + is >> limits.mate; + else if (token == "perft") + is >> limits.perft; + else if (token == "infinite") + limits.infinite = 1; + else if (token == "ponder") { + } + + return limits; } void handleGo(std::istringstream &ss) { - stop(); - chess::Position copy = pos; + stop(); + chess::Position copy = pos; - searchThread = std::thread([copy, ss = std::move(ss)]() mutable { - search::search(copy, parse_limits(ss)); - }); + searchThread = std::thread([copy, ss = std::move(ss)]() mutable { search::search(copy, parse_limits(ss)); }); } template struct overload : Ts... { - using Ts::operator()...; + using Ts::operator()...; }; template overload(Ts...) -> overload; std::string engine::format_score(const Score &s) { - const auto format = overload{ - [](Score::Mate mate) -> std::string { - auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2; - return std::string("mate ") + std::to_string(m); - }, - [](Score::Tablebase tb) -> std::string { - constexpr int TB_CP = 20000; - return std::string("cp ") + - std::to_string((tb.win ? TB_CP - tb.plies : -TB_CP - tb.plies)); - }, - [](Score::InternalUnits units) -> std::string { - return std::string("cp ") + std::to_string(units.value); - }}; - - return s.visit(format); + const auto format = + overload{ [](Score::Mate mate) -> std::string { + auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2; + return std::string("mate ") + std::to_string(m); + }, + [](Score::Tablebase tb) -> std::string { + constexpr int TB_CP = 20000; + return std::string("cp ") + std::to_string((tb.win ? TB_CP - tb.plies : -TB_CP - tb.plies)); + }, + [](Score::InternalUnits units) -> std::string { return std::string("cp ") + std::to_string(units.value); } }; + + return s.visit(format); } void engine::report(const InfoShort &info) { - std::cout << "info depth " << info.depth << " score " - << format_score(info.score) << std::endl; + std::cout << "info depth " << info.depth << " score " << format_score(info.score) << std::endl; } void engine::report(const InfoFull &info, bool showWDL) { - std::stringstream ss; - - ss << "info"; - ss << " depth " << info.depth // - << " seldepth " << info.selDepth // - << " multipv " << info.multiPV // - << " score " << format_score(info.score); // - - if (!info.bound.empty()) - ss << " " << info.bound; - - if (showWDL) - ss << " wdl " << info.wdl; - - ss << " nodes " << info.nodes // - << " nps " << info.nps // - << " hashfull " << info.hashfull // - << " tbhits " << info.tbHits // - << " time " << info.timeMs // - << " pv " << info.pv; // - if (!info.extrainfo.empty()) - ss << "\ninfo string extras " << info.extrainfo; - std::cout << ss.str() << std::endl; + std::stringstream ss; + + ss << "info"; + ss << " depth " << info.depth // + << " seldepth " << info.selDepth // + << " multipv " << info.multiPV // + << " score " << format_score(info.score); // + + if (!info.bound.empty()) + ss << " " << info.bound; + + if (showWDL) + ss << " wdl " << info.wdl; + + ss << " nodes " << info.nodes // + << " nps " << info.nps // + << " hashfull " << info.hashfull // + << " tbhits " << info.tbHits // + << " time " << info.timeMs // + << " pv " << info.pv; // + if (!info.extrainfo.empty()) + ss << "\ninfo string extras " << info.extrainfo; + std::cout << ss.str() << std::endl; } void engine::report(const InfoIteration &info) { - std::stringstream ss; + std::stringstream ss; - ss << "info"; - ss << " depth " << info.depth // - << " currmove " << info.currmove // - << " currmovenumber " << info.currmovenumber; // + ss << "info"; + ss << " depth " << info.depth // + << " currmove " << info.currmove // + << " currmovenumber " << info.currmovenumber; // - std::cout << ss.str() << std::endl; + std::cout << ss.str() << std::endl; } void engine::report(std::string_view bestmove) { - std::cout << "bestmove " << bestmove; - std::cout << std::endl; + std::cout << "bestmove " << bestmove; + std::cout << std::endl; } void engine::loop() { - std::string line; - pos.setFEN(chess::Position::START_FEN); - - while (std::getline(std::cin, line)) { - std::istringstream ss(line); - std::string token; - stop(); - while (ss >> token) { - if (token == "uci") { - std::cout << "id name cppchess_engine\n"; - std::cout << "id author winapiadmin\n"; - std::cout << options << '\n'; - std::cout << "uciok\n"; - break; - } else if (token == "isready") { - std::cout << "readyok\n"; - break; - } else if (token == "position") { - handlePosition(ss); - break; - } else if (token == "go") { - handleGo(ss); - break; // rest belongs to go - } else if (token == "ucinewgame") { - search::tt.clear(); - break; - } else if (token == "stop") { - break; - } else if (token == "quit") { - return; - } else if (token == "setoption") { - options.setoption(ss); - break; - } else if (token == "visualize" || token == "d") { - std::cout << pos << std::endl; - break; - } else if (token == "eval") { - std::cout << eval::eval(pos) << std::endl; - break; - } + std::string line; + pos.setFEN(chess::Position::START_FEN); + + while (std::getline(std::cin, line)) { + std::istringstream ss(line); + std::string token; + stop(); + while (ss >> token) { + if (token == "uci") { + std::cout << "id name cppchess_engine\n"; + std::cout << "id author winapiadmin\n"; + std::cout << options << '\n'; + std::cout << "uciok\n"; + break; + } else if (token == "isready") { + std::cout << "readyok\n"; + break; + } else if (token == "position") { + handlePosition(ss); + break; + } else if (token == "go") { + handleGo(ss); + break; // rest belongs to go + } else if (token == "ucinewgame") { + search::tt.clear(); + break; + } else if (token == "stop") { + break; + } else if (token == "quit") { + return; + } else if (token == "setoption") { + options.setoption(ss); + break; + } else if (token == "visualize" || token == "d") { + std::cout << pos << std::endl; + break; + } else if (token == "eval") { + int score = eval::eval(pos); + if (pos.side_to_move() != chess::WHITE) + score = -score; + std::cout << score << std::endl; + break; + } + } } - } - stop(); + stop(); } diff --git a/uci.h b/uci.h index 8212b1a..18d5e17 100644 --- a/uci.h +++ b/uci.h @@ -4,28 +4,28 @@ #include namespace engine { struct InfoShort { - int depth; - Score score; + int depth; + Score score; }; struct InfoFull : InfoShort { - int selDepth; - size_t multiPV; - std::string_view wdl; - std::string_view bound; - size_t timeMs; - size_t nodes; - size_t nps; - size_t tbHits; - std::string pv; - std::string extrainfo; - int hashfull; + int selDepth; + size_t multiPV; + std::string_view wdl; + std::string_view bound; + size_t timeMs; + size_t nodes; + size_t nps; + size_t tbHits; + std::string pv; + std::string extrainfo; + int hashfull; }; struct InfoIteration { - int depth; - std::string currmove; - size_t currmovenumber; + int depth; + std::string currmove; + size_t currmovenumber; }; std::string format_score(const Score &s); void report(const InfoFull &info, bool showWDL = false); diff --git a/ucioption.cpp b/ucioption.cpp index 68fc9e6..3d62ca3 100644 --- a/ucioption.cpp +++ b/ucioption.cpp @@ -28,102 +28,91 @@ namespace engine { -bool CaseInsensitiveLess::operator()(const std::string &s1, - const std::string &s2) const { +bool CaseInsensitiveLess::operator()(const std::string &s1, const std::string &s2) const { - return std::lexicographical_compare( - s1.begin(), s1.end(), s2.begin(), s2.end(), - [](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); }); + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), [](char c1, char c2) { + return std::tolower(c1) < std::tolower(c2); + }); } -void OptionsMap::add_info_listener(InfoListener &&message_func) { - info = std::move(message_func); -} +void OptionsMap::add_info_listener(InfoListener &&message_func) { info = std::move(message_func); } void OptionsMap::setoption(std::istringstream &is) { - std::string token, name, value; + std::string token, name, value; - is >> token; // Consume the "name" token + is >> token; // Consume the "name" token - // Read the option name (can contain spaces) - while (is >> token && token != "value") - name += (name.empty() ? "" : " ") + token; + // Read the option name (can contain spaces) + while (is >> token && token != "value") + name += (name.empty() ? "" : " ") + token; - // Read the option value (can contain spaces) - while (is >> token) - value += (value.empty() ? "" : " ") + token; + // Read the option value (can contain spaces) + while (is >> token) + value += (value.empty() ? "" : " ") + token; - if (options_map.count(name)) - options_map[name] = value; - else - std::cerr << "No such option: " << name << std::endl; + if (options_map.count(name)) + options_map[name] = value; + else + std::cerr << "No such option: " << name << std::endl; } const Option &OptionsMap::operator[](const std::string &name) const { - auto it = options_map.find(name); - assert(it != options_map.end()); - return it->second; + auto it = options_map.find(name); + assert(it != options_map.end()); + return it->second; } // Inits options and assigns idx in the correct printing order void OptionsMap::add(const std::string &name, const Option &option) { - if (!options_map.count(name)) { - static size_t insert_order = 0; + if (!options_map.count(name)) { + static size_t insert_order = 0; - options_map[name] = option; + options_map[name] = option; - options_map[name].parent = this; - options_map[name].idx = insert_order++; - } else { - std::cerr << "Option \"" << name << "\" was already added!" << std::endl; - std::exit(EXIT_FAILURE); - } + options_map[name].parent = this; + options_map[name].idx = insert_order++; + } else { + std::cerr << "Option \"" << name << "\" was already added!" << std::endl; + std::exit(EXIT_FAILURE); + } } -std::size_t OptionsMap::count(const std::string &name) const { - return options_map.count(name); -} +std::size_t OptionsMap::count(const std::string &name) const { return options_map.count(name); } Option::Option(const OptionsMap *map) : parent(map) {} -Option::Option(const char *v, OnChange f) - : type("string"), min(0), max(0), on_change(std::move(f)) { - defaultValue = currentValue = v; +Option::Option(const char *v, OnChange f) : type("string"), min(0), max(0), on_change(std::move(f)) { + defaultValue = currentValue = v; } -Option::Option(bool v, OnChange f) - : type("check"), min(0), max(0), on_change(std::move(f)) { - defaultValue = currentValue = (v ? "true" : "false"); +Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(std::move(f)) { + defaultValue = currentValue = (v ? "true" : "false"); } -Option::Option(OnChange f) - : type("button"), min(0), max(0), on_change(std::move(f)) {} +Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(std::move(f)) {} -Option::Option(int v, int minv, int maxv, OnChange f) - : type("spin"), min(minv), max(maxv), on_change(std::move(f)) { - defaultValue = currentValue = std::to_string(v); +Option::Option(int v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(std::move(f)) { + defaultValue = currentValue = std::to_string(v); } -Option::Option(const char *v, const char *cur, OnChange f) - : type("combo"), min(0), max(0), on_change(std::move(f)) { - defaultValue = v; - currentValue = cur; +Option::Option(const char *v, const char *cur, OnChange f) : type("combo"), min(0), max(0), on_change(std::move(f)) { + defaultValue = v; + currentValue = cur; } Option::operator int() const { - assert(type == "check" || type == "spin"); - return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); + assert(type == "check" || type == "spin"); + return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); } Option::operator std::string() const { - assert(type == "string"); - return currentValue; + assert(type == "string"); + return currentValue; } bool Option::operator==(const char *s) const { - assert(type == "combo"); - return !CaseInsensitiveLess()(currentValue, s) && - !CaseInsensitiveLess()(s, currentValue); + assert(type == "combo"); + return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue); } bool Option::operator!=(const char *s) const { return !(*this == s); } @@ -133,61 +122,58 @@ bool Option::operator!=(const char *s) const { return !(*this == s); } // from the user by console window, so let's check the bounds anyway. Option &Option::operator=(const std::string &v) { - assert(!type.empty()); + assert(!type.empty()); - if ((type != "button" && type != "string" && v.empty()) || - (type == "check" && v != "true" && v != "false") || - (type == "spin" && (std::stoi(v) < min || std::stoi(v) > max))) - return *this; + if ((type != "button" && type != "string" && v.empty()) || (type == "check" && v != "true" && v != "false") || + (type == "spin" && (std::stoi(v) < min || std::stoi(v) > max))) + return *this; + + if (type == "combo") { + OptionsMap comboMap; // To have case insensitive compare + std::string token; + std::istringstream ss(defaultValue); + while (ss >> token) + comboMap.add(token, Option()); + if (!comboMap.count(v) || v == "var") + return *this; + } - if (type == "combo") { - OptionsMap comboMap; // To have case insensitive compare - std::string token; - std::istringstream ss(defaultValue); - while (ss >> token) - comboMap.add(token, Option()); - if (!comboMap.count(v) || v == "var") - return *this; - } - - if (type == "string") - currentValue = v == "" ? "" : v; - else if (type != "button") - currentValue = v; - - if (on_change) { - const auto ret = on_change(*this); - - if (ret && parent != nullptr && parent->info != nullptr) - parent->info(ret); - } - - return *this; + if (type == "string") + currentValue = v == "" ? "" : v; + else if (type != "button") + currentValue = v; + + if (on_change) { + const auto ret = on_change(*this); + + if (ret && parent != nullptr && parent->info != nullptr) + parent->info(ret); + } + + return *this; } std::ostream &operator<<(std::ostream &os, const OptionsMap &om) { - for (size_t idx = 0; idx < om.options_map.size(); ++idx) - for (const auto &it : om.options_map) - if (it.second.idx == idx) { - const Option &o = it.second; - os << "\noption name " << it.first << " type " << o.type; + for (size_t idx = 0; idx < om.options_map.size(); ++idx) + for (const auto &it : om.options_map) + if (it.second.idx == idx) { + const Option &o = it.second; + os << "\noption name " << it.first << " type " << o.type; - if (o.type == "check" || o.type == "combo") - os << " default " << o.defaultValue; + if (o.type == "check" || o.type == "combo") + os << " default " << o.defaultValue; - else if (o.type == "string") { - std::string defaultValue = - o.defaultValue.empty() ? "" : o.defaultValue; - os << " default " << defaultValue; - } + else if (o.type == "string") { + std::string defaultValue = o.defaultValue.empty() ? "" : o.defaultValue; + os << " default " << defaultValue; + } - else if (o.type == "spin") - os << " default " << stoi(o.defaultValue) << " min " << o.min - << " max " << o.max; + else if (o.type == "spin") + os << " default " << stoi(o.defaultValue) << " min " << o.min << " max " << o.max; - break; - } + break; + } - return os; + return os; } } // namespace engine diff --git a/ucioption.h b/ucioption.h index 012bc22..813b4c7 100644 --- a/ucioption.h +++ b/ucioption.h @@ -30,76 +30,76 @@ namespace engine { // Define a custom comparator, because the UCI options should be // case-insensitive struct CaseInsensitiveLess { - bool operator()(const std::string &, const std::string &) const; + bool operator()(const std::string &, const std::string &) const; }; class OptionsMap; // The Option class implements each option as specified by the UCI protocol class Option { -public: - using OnChange = std::function(const Option &)>; - - Option(const OptionsMap *); - Option(OnChange = nullptr); - Option(bool v, OnChange = nullptr); - Option(const char *v, OnChange = nullptr); - Option(int v, int minv, int maxv, OnChange = nullptr); - Option(const char *v, const char *cur, OnChange = nullptr); - - Option &operator=(const std::string &); - operator int() const; - operator std::string() const; - bool operator==(const char *) const; - bool operator!=(const char *) const; - - friend std::ostream &operator<<(std::ostream &, const OptionsMap &); - - int operator<<(const Option &) = delete; - -private: - friend class OptionsMap; - friend class Engine; - friend class Tune; - - std::string defaultValue, currentValue, type; - int min, max; - size_t idx; - OnChange on_change; - const OptionsMap *parent = nullptr; + public: + using OnChange = std::function(const Option &)>; + + Option(const OptionsMap *); + Option(OnChange = nullptr); + Option(bool v, OnChange = nullptr); + Option(const char *v, OnChange = nullptr); + Option(int v, int minv, int maxv, OnChange = nullptr); + Option(const char *v, const char *cur, OnChange = nullptr); + + Option &operator=(const std::string &); + operator int() const; + operator std::string() const; + bool operator==(const char *) const; + bool operator!=(const char *) const; + + friend std::ostream &operator<<(std::ostream &, const OptionsMap &); + + int operator<<(const Option &) = delete; + + private: + friend class OptionsMap; + friend class Engine; + friend class Tune; + + std::string defaultValue, currentValue, type; + int min, max; + size_t idx; + OnChange on_change; + const OptionsMap *parent = nullptr; }; class OptionsMap { -public: - using InfoListener = std::function)>; + public: + using InfoListener = std::function)>; - OptionsMap() = default; - OptionsMap(const OptionsMap &) = delete; - OptionsMap(OptionsMap &&) = delete; - OptionsMap &operator=(const OptionsMap &) = delete; - OptionsMap &operator=(OptionsMap &&) = delete; + OptionsMap() = default; + OptionsMap(const OptionsMap &) = delete; + OptionsMap(OptionsMap &&) = delete; + OptionsMap &operator=(const OptionsMap &) = delete; + OptionsMap &operator=(OptionsMap &&) = delete; - void add_info_listener(InfoListener &&); + void add_info_listener(InfoListener &&); - void setoption(std::istringstream &); + void setoption(std::istringstream &); - const Option &operator[](const std::string &) const; + const Option &operator[](const std::string &) const; - void add(const std::string &, const Option &option); + void add(const std::string &, const Option &option); - std::size_t count(const std::string &) const; + std::size_t count(const std::string &) const; -private: - friend class Engine; - friend class Option; + private: + friend class Engine; + friend class Option; - friend std::ostream &operator<<(std::ostream &, const OptionsMap &); + friend std::ostream &operator<<(std::ostream &, const OptionsMap &); - // The options container is defined as a std::map - using OptionsStore = std::map; + // The options container is defined as a std::map + using OptionsStore = std::map; - OptionsStore options_map; - InfoListener info; + OptionsStore options_map; + InfoListener info; }; } // namespace engine From 748a8511720c09dbd59fab50d8fc8f5f772da832 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 9 Jun 2026 22:56:12 +0700 Subject: [PATCH 13/29] weights and standard clang-format --- .clang-format | 16 +++++++++++++++ Weights.h | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 .clang-format create mode 100644 Weights.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5de674c --- /dev/null +++ b/.clang-format @@ -0,0 +1,16 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +ContinuationIndentWidth: 4 +ColumnLimit: 128 +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +BinPackParameters: false +BinPackArguments: false +BraceWrapping: +# AfterInitializerList: true + AfterFunction: true + BeforeElse: false + IndentBraces: false +AlignArrayOfStructures: Right +Cpp11BracedListStyle: false +SpacesInContainerLiterals: false diff --git a/Weights.h b/Weights.h new file mode 100644 index 0000000..1078740 --- /dev/null +++ b/Weights.h @@ -0,0 +1,55 @@ +#ifndef WEIGHTS_H +#define WEIGHTS_H +#include "eval.h" +namespace engine::eval { +Value tempo = 29; +Value PawnValue = 82; +Value KnightValue = 284; +Value BishopValue = 304; +Value RookValue = 461; +Value QueenValue = 901; +Value fianchettoBonus = 24; +Value trappedBishopPenalty = 26; +Value centerWeight = 2; +Value mopUpKingDistWeight = 4; +Value mopUpEdgeDistWeight = 17; +Value spaceWeight = 1; +Value bishopPairMg = 13; +Value bishopPairEg = 23; +Value rookOpenFileMg = 30; +Value rookOpenFileEg = 25; +Value rookSemiOpenFileMg = 20; +Value rookSemiOpenFileEg = 17; +Value doubledPawnMg = 7; +Value doubledPawnEg = 18; +Value isolatedPawnMg = 4; +Value isolatedPawnEg = 14; +Value kingShelterBaseMg = 18; +Value kingShelterBaseEg = 4; +Value kingShelterDecayMg = 4; +Value kingShelterDecayEg = 1; +Value kqkDistWeight = 17; +Value kqkEdgeWeight = 18; +Value krkDistWeight = 1; +Value krkEdgeWeight = 9; +Value kpkWeight = 15; +Value mgMobilityCnt[7][8] = { { 4, -1, 5, -1, -5, 3, 0, -3 }, { -7, -10, 7, -4, -7, 1, -5, -2 }, { -1, -8, -6, -3, 2, -2, 1, 7 }, { -10, -6, 5, -3, 3, 5, -7, 10 }, { -8, -8, -10, -1, -2, 0, 6, 8 }, { -9, -1, -7, 2, -2, -7, -5, 2 }, { -4, -5, -9, -5, -3, -6, 5, -3 } }; +Value egMobilityCnt[7][8] = { { -8, -2, -3, 7, -4, 2, -1, -4 }, { -7, -8, -8, 3, -4, -3, 2, -9 }, { 3, -3, -10, -5, -2, -10, -4, 7 }, { -5, -7, -2, 1, -2, 7, -3, 8 }, { -4, -9, 3, 0, 0, -6, 6, 4 }, { -10, -2, -10, -2, -5, -3, 0, 0 }, { -10, 8, 7, 5, 5, 3, -1, 1 } }; +Value kingTropismMg[7] = { 13, 14, 6, -5, 5, 12, 6 }; +Value kingTropismEg[7] = { -9, -13, 3, 5, -2, 19, 4 }; +Value passedBonusMg[7] = { 0, 4, 13, 73, 131, 165, 39 }; +Value passedBonusEg[7] = { 0, 66, 59, 96, 158, 185, 22 }; +Value mg_knight_table[64] = { 1, -5, 8, -21, -11, -2, -24, -2, 23, 21, 13, 2, 4, -17, -7, 4, -5, -15, -8, 14, 19, -2, 2, -25, -13, 15, -1, -1, 5, -18, 5, 6, -1, -13, 1, 17, -24, -13, 14, 19, -10, 6, 1, 16, -13, -18, 8, 24, 14, -21, -8, -21, -15, -1, -1, 8, -17, 22, -18, 6, -11, -4, -9, -7 }; +Value mg_bishop_table[64] = { 3, 21, -23, 16, 5, -20, 7, -4, 14, -15, 21, -11, 7, 2, -4, 19, -5, -4, 22, 11, 15, 25, 20, -9, -3, -4, 20, 21, 10, 11, -4, -21, 9, 9, 10, 5, -3, 0, 16, 23, -1, 5, -10, -12, 9, -24, -9, -10, 5, -17, -8, 5, 4, -3, 6, -4, 1, 2, 20, -4, 9, 3, -20, -22 }; +Value mg_rook_table[64] = { -10, 20, -13, 18, 12, -3, 13, -25, -11, 3, 10, -4, 7, 13, -24, 5, -3, -7, -19, -3, 4, -6, 13, 13, 11, 20, 16, -6, 14, -15, -16, -15, 3, 7, 14, -10, -7, 20, -15, 10, 25, 1, 3, -14, 22, 5, 1, -12, -20, -13, 22, -9, 11, -2, -15, -11, 22, -14, -10, 5, 0, 9, -20, -13 }; +Value mg_king_table[64] = { 11, -12, -10, -19, 5, -6, -2, -10, 19, 13, -7, -3, -7, 8, -21, -24, -5, 16, 19, 2, -21, -14, 11, -16, -12, -13, -18, 14, -23, -14, 16, -5, -16, 0, -21, -12, -2, 14, -16, 17, 11, -10, 21, 14, -5, -15, 19, 13, 8, -21, -13, 4, 1, 1, 16, 13, -8, 13, 6, -20, -4, 3, 8, -2 }; +Value mg_queen_table[64] = { -2, -2, 4, 11, 9, -2, 0, -5, 14, -2, -11, 14, -13, -11, -3, -21, 7, -14, -4, -6, -3, -16, -4, -11, -2, 9, -16, 6, -6, -12, -14, -7, -4, -22, -12, 6, 21, -4, -6, -5, -6, -7, -4, 2, 13, 11, -8, 14, 18, -5, -25, 22, -14, 18, -3, 9, -13, 21, 15, -21, 13, 7, -9, 3 }; +Value eg_knight_table[64] = { -13, 25, -3, 3, -7, 18, -21, 3, -1, 15, -13, 3, -16, 15, -5, 8, -11, -12, 22, 14, 10, 20, 9, -6, -10, 2, -13, -7, -17, 2, 18, -19, 7, 12, -23, 17, -5, 17, 10, -7, -18, -2, -10, -14, 20, 15, -13, 18, 9, -8, -13, 12, 20, 17, 16, 25, 18, 20, 5, 10, -10, 2, 0, 8 }; +Value eg_bishop_table[64] = { -19, -12, -9, -5, 9, -14, -11, 6, -10, -10, 6, -3, 13, -18, -6, 2, 25, 4, 9, -16, -3, -9, 1, 7, 3, -20, -3, 9, -20, -3, 21, 3, -8, 10, 4, -11, -8, -18, -13, 0, 10, 17, 14, -18, 21, 11, 18, 0, -16, 4, 3, 17, -9, -16, -25, -2, -17, -5, -5, 19, 12, -3, -11, -13 }; +Value eg_rook_table[64] = { -21, -24, 12, -12, -11, 15, -17, -22, -14, -22, -18, -5, -9, -7, 23, -2, -1, 9, 18, 3, 4, -19, 20, -21, -15, -21, 9, -11, -2, 4, -8, 15, -6, 13, -6, -10, -3, 12, 16, -18, -19, -19, 23, 3, -14, -23, 3, 15, 18, -6, -24, 21, 18, 3, -2, 17, 8, 13, -11, -13, 0, -16, -4, -10 }; +Value eg_king_table[64] = { -14, 17, 13, -10, -15, 3, -8, -3, 19, 19, -2, 6, -2, 7, -7, -7, 21, 10, 17, -21, 0, 15, -6, -9, -7, 24, 8, 0, 17, 12, -4, -6, 8, -16, 19, 0, 9, 20, 18, 12, -12, -2, -4, -14, 21, 2, -6, -5, 17, -2, -3, -9, -1, -23, 7, -14, 9, 2, 22, -20, 3, 22, -10, 2 }; +Value eg_queen_table[64] = { -7, -3, 13, 11, 2, 17, 2, -19, 12, -5, -2, -11, 2, -7, 19, 2, -1, 15, 19, 1, 15, 8, -19, -6, -14, -25, -8, 22, 4, -22, -10, 20, 3, -1, -3, -17, 12, -3, -4, -4, -7, 16, -18, -22, -2, -11, 4, -14, -7, -15, -8, 9, 20, 8, -2, -1, 8, 18, 20, -23, -12, -8, 6, -11 }; +Value mg_pawn_table[56] = { 0, 0, 0, 0, 0, 0, 0, 0, -16, 4, 6, -22, -16, -20, 4, -13, 2, -24, -12, -7, -3, -11, -5, 6, -15, -9, 2, -14, 2, -18, -7, -10, 1, 15, -7, -6, 22, -13, -6, 2, -16, -9, 14, -2, 23, -18, 8, 6, -2, -8, -1, -4, -10, -1, 18, -12 }; +Value eg_pawn_table[56] = { 0, 0, 0, 0, 0, 0, 0, 0, -4, 1, -2, -5, -20, 13, -17, -2, 2, 24, 12, -14, -4, 2, -17, -4, 15, -9, 8, 11, -24, -4, -14, -10, -13, -11, -6, -17, -4, 17, -14, -3, -17, -19, -10, -19, -23, 9, 13, -3, -7, -5, -9, 21, 8, -17, 7, 13 }; +} // namespace engine::eval +#endif From 2168635f5622e04342a943f3bfe211748bc02099 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 9 Jun 2026 15:56:59 +0000 Subject: [PATCH 14/29] Apply clang-format --- Weights.h | 75 ++++++++++++++++++++++++++++++++++++++++++++---------- eval.cpp | 14 +++++++++- eval.h | 2 +- search.cpp | 15 +++++------ 4 files changed, 81 insertions(+), 25 deletions(-) diff --git a/Weights.h b/Weights.h index 1078740..dbd330a 100644 --- a/Weights.h +++ b/Weights.h @@ -33,23 +33,70 @@ Value kqkEdgeWeight = 18; Value krkDistWeight = 1; Value krkEdgeWeight = 9; Value kpkWeight = 15; -Value mgMobilityCnt[7][8] = { { 4, -1, 5, -1, -5, 3, 0, -3 }, { -7, -10, 7, -4, -7, 1, -5, -2 }, { -1, -8, -6, -3, 2, -2, 1, 7 }, { -10, -6, 5, -3, 3, 5, -7, 10 }, { -8, -8, -10, -1, -2, 0, 6, 8 }, { -9, -1, -7, 2, -2, -7, -5, 2 }, { -4, -5, -9, -5, -3, -6, 5, -3 } }; -Value egMobilityCnt[7][8] = { { -8, -2, -3, 7, -4, 2, -1, -4 }, { -7, -8, -8, 3, -4, -3, 2, -9 }, { 3, -3, -10, -5, -2, -10, -4, 7 }, { -5, -7, -2, 1, -2, 7, -3, 8 }, { -4, -9, 3, 0, 0, -6, 6, 4 }, { -10, -2, -10, -2, -5, -3, 0, 0 }, { -10, 8, 7, 5, 5, 3, -1, 1 } }; +Value mgMobilityCnt[7][8] = { + { 4, -1, 5, -1, -5, 3, 0, -3 }, + { -7, -10, 7, -4, -7, 1, -5, -2 }, + { -1, -8, -6, -3, 2, -2, 1, 7 }, + { -10, -6, 5, -3, 3, 5, -7, 10 }, + { -8, -8, -10, -1, -2, 0, 6, 8 }, + { -9, -1, -7, 2, -2, -7, -5, 2 }, + { -4, -5, -9, -5, -3, -6, 5, -3 } +}; +Value egMobilityCnt[7][8] = { + { -8, -2, -3, 7, -4, 2, -1, -4 }, + { -7, -8, -8, 3, -4, -3, 2, -9 }, + { 3, -3, -10, -5, -2, -10, -4, 7 }, + { -5, -7, -2, 1, -2, 7, -3, 8 }, + { -4, -9, 3, 0, 0, -6, 6, 4 }, + { -10, -2, -10, -2, -5, -3, 0, 0 }, + { -10, 8, 7, 5, 5, 3, -1, 1 } +}; Value kingTropismMg[7] = { 13, 14, 6, -5, 5, 12, 6 }; Value kingTropismEg[7] = { -9, -13, 3, 5, -2, 19, 4 }; Value passedBonusMg[7] = { 0, 4, 13, 73, 131, 165, 39 }; Value passedBonusEg[7] = { 0, 66, 59, 96, 158, 185, 22 }; -Value mg_knight_table[64] = { 1, -5, 8, -21, -11, -2, -24, -2, 23, 21, 13, 2, 4, -17, -7, 4, -5, -15, -8, 14, 19, -2, 2, -25, -13, 15, -1, -1, 5, -18, 5, 6, -1, -13, 1, 17, -24, -13, 14, 19, -10, 6, 1, 16, -13, -18, 8, 24, 14, -21, -8, -21, -15, -1, -1, 8, -17, 22, -18, 6, -11, -4, -9, -7 }; -Value mg_bishop_table[64] = { 3, 21, -23, 16, 5, -20, 7, -4, 14, -15, 21, -11, 7, 2, -4, 19, -5, -4, 22, 11, 15, 25, 20, -9, -3, -4, 20, 21, 10, 11, -4, -21, 9, 9, 10, 5, -3, 0, 16, 23, -1, 5, -10, -12, 9, -24, -9, -10, 5, -17, -8, 5, 4, -3, 6, -4, 1, 2, 20, -4, 9, 3, -20, -22 }; -Value mg_rook_table[64] = { -10, 20, -13, 18, 12, -3, 13, -25, -11, 3, 10, -4, 7, 13, -24, 5, -3, -7, -19, -3, 4, -6, 13, 13, 11, 20, 16, -6, 14, -15, -16, -15, 3, 7, 14, -10, -7, 20, -15, 10, 25, 1, 3, -14, 22, 5, 1, -12, -20, -13, 22, -9, 11, -2, -15, -11, 22, -14, -10, 5, 0, 9, -20, -13 }; -Value mg_king_table[64] = { 11, -12, -10, -19, 5, -6, -2, -10, 19, 13, -7, -3, -7, 8, -21, -24, -5, 16, 19, 2, -21, -14, 11, -16, -12, -13, -18, 14, -23, -14, 16, -5, -16, 0, -21, -12, -2, 14, -16, 17, 11, -10, 21, 14, -5, -15, 19, 13, 8, -21, -13, 4, 1, 1, 16, 13, -8, 13, 6, -20, -4, 3, 8, -2 }; -Value mg_queen_table[64] = { -2, -2, 4, 11, 9, -2, 0, -5, 14, -2, -11, 14, -13, -11, -3, -21, 7, -14, -4, -6, -3, -16, -4, -11, -2, 9, -16, 6, -6, -12, -14, -7, -4, -22, -12, 6, 21, -4, -6, -5, -6, -7, -4, 2, 13, 11, -8, 14, 18, -5, -25, 22, -14, 18, -3, 9, -13, 21, 15, -21, 13, 7, -9, 3 }; -Value eg_knight_table[64] = { -13, 25, -3, 3, -7, 18, -21, 3, -1, 15, -13, 3, -16, 15, -5, 8, -11, -12, 22, 14, 10, 20, 9, -6, -10, 2, -13, -7, -17, 2, 18, -19, 7, 12, -23, 17, -5, 17, 10, -7, -18, -2, -10, -14, 20, 15, -13, 18, 9, -8, -13, 12, 20, 17, 16, 25, 18, 20, 5, 10, -10, 2, 0, 8 }; -Value eg_bishop_table[64] = { -19, -12, -9, -5, 9, -14, -11, 6, -10, -10, 6, -3, 13, -18, -6, 2, 25, 4, 9, -16, -3, -9, 1, 7, 3, -20, -3, 9, -20, -3, 21, 3, -8, 10, 4, -11, -8, -18, -13, 0, 10, 17, 14, -18, 21, 11, 18, 0, -16, 4, 3, 17, -9, -16, -25, -2, -17, -5, -5, 19, 12, -3, -11, -13 }; -Value eg_rook_table[64] = { -21, -24, 12, -12, -11, 15, -17, -22, -14, -22, -18, -5, -9, -7, 23, -2, -1, 9, 18, 3, 4, -19, 20, -21, -15, -21, 9, -11, -2, 4, -8, 15, -6, 13, -6, -10, -3, 12, 16, -18, -19, -19, 23, 3, -14, -23, 3, 15, 18, -6, -24, 21, 18, 3, -2, 17, 8, 13, -11, -13, 0, -16, -4, -10 }; -Value eg_king_table[64] = { -14, 17, 13, -10, -15, 3, -8, -3, 19, 19, -2, 6, -2, 7, -7, -7, 21, 10, 17, -21, 0, 15, -6, -9, -7, 24, 8, 0, 17, 12, -4, -6, 8, -16, 19, 0, 9, 20, 18, 12, -12, -2, -4, -14, 21, 2, -6, -5, 17, -2, -3, -9, -1, -23, 7, -14, 9, 2, 22, -20, 3, 22, -10, 2 }; -Value eg_queen_table[64] = { -7, -3, 13, 11, 2, 17, 2, -19, 12, -5, -2, -11, 2, -7, 19, 2, -1, 15, 19, 1, 15, 8, -19, -6, -14, -25, -8, 22, 4, -22, -10, 20, 3, -1, -3, -17, 12, -3, -4, -4, -7, 16, -18, -22, -2, -11, 4, -14, -7, -15, -8, 9, 20, 8, -2, -1, 8, 18, 20, -23, -12, -8, 6, -11 }; -Value mg_pawn_table[56] = { 0, 0, 0, 0, 0, 0, 0, 0, -16, 4, 6, -22, -16, -20, 4, -13, 2, -24, -12, -7, -3, -11, -5, 6, -15, -9, 2, -14, 2, -18, -7, -10, 1, 15, -7, -6, 22, -13, -6, 2, -16, -9, 14, -2, 23, -18, 8, 6, -2, -8, -1, -4, -10, -1, 18, -12 }; -Value eg_pawn_table[56] = { 0, 0, 0, 0, 0, 0, 0, 0, -4, 1, -2, -5, -20, 13, -17, -2, 2, 24, 12, -14, -4, 2, -17, -4, 15, -9, 8, 11, -24, -4, -14, -10, -13, -11, -6, -17, -4, 17, -14, -3, -17, -19, -10, -19, -23, 9, 13, -3, -7, -5, -9, 21, 8, -17, 7, 13 }; +Value mg_knight_table[64] = { 1, -5, 8, -21, -11, -2, -24, -2, 23, 21, 13, 2, 4, -17, -7, 4, + -5, -15, -8, 14, 19, -2, 2, -25, -13, 15, -1, -1, 5, -18, 5, 6, + -1, -13, 1, 17, -24, -13, 14, 19, -10, 6, 1, 16, -13, -18, 8, 24, + 14, -21, -8, -21, -15, -1, -1, 8, -17, 22, -18, 6, -11, -4, -9, -7 }; +Value mg_bishop_table[64] = { 3, 21, -23, 16, 5, -20, 7, -4, 14, -15, 21, -11, 7, 2, -4, 19, -5, -4, 22, 11, 15, 25, + 20, -9, -3, -4, 20, 21, 10, 11, -4, -21, 9, 9, 10, 5, -3, 0, 16, 23, -1, 5, -10, -12, + 9, -24, -9, -10, 5, -17, -8, 5, 4, -3, 6, -4, 1, 2, 20, -4, 9, 3, -20, -22 }; +Value mg_rook_table[64] = { -10, 20, -13, 18, 12, -3, 13, -25, -11, 3, 10, -4, 7, 13, -24, 5, + -3, -7, -19, -3, 4, -6, 13, 13, 11, 20, 16, -6, 14, -15, -16, -15, + 3, 7, 14, -10, -7, 20, -15, 10, 25, 1, 3, -14, 22, 5, 1, -12, + -20, -13, 22, -9, 11, -2, -15, -11, 22, -14, -10, 5, 0, 9, -20, -13 }; +Value mg_king_table[64] = { 11, -12, -10, -19, 5, -6, -2, -10, 19, 13, -7, -3, -7, 8, -21, -24, + -5, 16, 19, 2, -21, -14, 11, -16, -12, -13, -18, 14, -23, -14, 16, -5, + -16, 0, -21, -12, -2, 14, -16, 17, 11, -10, 21, 14, -5, -15, 19, 13, + 8, -21, -13, 4, 1, 1, 16, 13, -8, 13, 6, -20, -4, 3, 8, -2 }; +Value mg_queen_table[64] = { -2, -2, 4, 11, 9, -2, 0, -5, 14, -2, -11, 14, -13, -11, -3, -21, 7, -14, -4, -6, -3, -16, + -4, -11, -2, 9, -16, 6, -6, -12, -14, -7, -4, -22, -12, 6, 21, -4, -6, -5, -6, -7, -4, 2, + 13, 11, -8, 14, 18, -5, -25, 22, -14, 18, -3, 9, -13, 21, 15, -21, 13, 7, -9, 3 }; +Value eg_knight_table[64] = { -13, 25, -3, 3, -7, 18, -21, 3, -1, 15, -13, 3, -16, 15, -5, 8, + -11, -12, 22, 14, 10, 20, 9, -6, -10, 2, -13, -7, -17, 2, 18, -19, + 7, 12, -23, 17, -5, 17, 10, -7, -18, -2, -10, -14, 20, 15, -13, 18, + 9, -8, -13, 12, 20, 17, 16, 25, 18, 20, 5, 10, -10, 2, 0, 8 }; +Value eg_bishop_table[64] = { -19, -12, -9, -5, 9, -14, -11, 6, -10, -10, 6, -3, 13, -18, -6, 2, + 25, 4, 9, -16, -3, -9, 1, 7, 3, -20, -3, 9, -20, -3, 21, 3, + -8, 10, 4, -11, -8, -18, -13, 0, 10, 17, 14, -18, 21, 11, 18, 0, + -16, 4, 3, 17, -9, -16, -25, -2, -17, -5, -5, 19, 12, -3, -11, -13 }; +Value eg_rook_table[64] = { -21, -24, 12, -12, -11, 15, -17, -22, -14, -22, -18, -5, -9, -7, 23, -2, + -1, 9, 18, 3, 4, -19, 20, -21, -15, -21, 9, -11, -2, 4, -8, 15, + -6, 13, -6, -10, -3, 12, 16, -18, -19, -19, 23, 3, -14, -23, 3, 15, + 18, -6, -24, 21, 18, 3, -2, 17, 8, 13, -11, -13, 0, -16, -4, -10 }; +Value eg_king_table[64] = { -14, 17, 13, -10, -15, 3, -8, -3, 19, 19, -2, 6, -2, 7, -7, -7, 21, 10, 17, -21, 0, 15, + -6, -9, -7, 24, 8, 0, 17, 12, -4, -6, 8, -16, 19, 0, 9, 20, 18, 12, -12, -2, -4, -14, + 21, 2, -6, -5, 17, -2, -3, -9, -1, -23, 7, -14, 9, 2, 22, -20, 3, 22, -10, 2 }; +Value eg_queen_table[64] = { -7, -3, 13, 11, 2, 17, 2, -19, 12, -5, -2, -11, 2, -7, 19, 2, + -1, 15, 19, 1, 15, 8, -19, -6, -14, -25, -8, 22, 4, -22, -10, 20, + 3, -1, -3, -17, 12, -3, -4, -4, -7, 16, -18, -22, -2, -11, 4, -14, + -7, -15, -8, 9, 20, 8, -2, -1, 8, 18, 20, -23, -12, -8, 6, -11 }; +Value mg_pawn_table[56] = { 0, 0, 0, 0, 0, 0, 0, 0, -16, 4, 6, -22, -16, -20, 4, -13, 2, -24, -12, + -7, -3, -11, -5, 6, -15, -9, 2, -14, 2, -18, -7, -10, 1, 15, -7, -6, 22, -13, + -6, 2, -16, -9, 14, -2, 23, -18, 8, 6, -2, -8, -1, -4, -10, -1, 18, -12 }; +Value eg_pawn_table[56] = { 0, 0, 0, 0, 0, 0, 0, 0, -4, 1, -2, -5, -20, 13, -17, -2, 2, 24, 12, + -14, -4, 2, -17, -4, 15, -9, 8, 11, -24, -4, -14, -10, -13, -11, -6, -17, -4, 17, + -14, -3, -17, -19, -10, -19, -23, 9, 13, -3, -7, -5, -9, 21, 8, -17, 7, 13 }; } // namespace engine::eval #endif diff --git a/eval.cpp b/eval.cpp index 29932c8..b14423c 100644 --- a/eval.cpp +++ b/eval.cpp @@ -69,7 +69,19 @@ TUNE(SetRange(0, 30), isolatedPawnMg, SetRange(0, 40), isolatedPawnEg); -TUNE(SetRange(0, 200), passedBonusMg[1],passedBonusMg[2],passedBonusMg[3],passedBonusMg[4],passedBonusMg[5],passedBonusMg[6], passedBonusEg[1],passedBonusEg[2],passedBonusEg[3],passedBonusEg[4],passedBonusEg[5],passedBonusEg[6]); +TUNE(SetRange(0, 200), + passedBonusMg[1], + passedBonusMg[2], + passedBonusMg[3], + passedBonusMg[4], + passedBonusMg[5], + passedBonusMg[6], + passedBonusEg[1], + passedBonusEg[2], + passedBonusEg[3], + passedBonusEg[4], + passedBonusEg[5], + passedBonusEg[6]); TUNE(SetRange(1, 30), kingShelterBaseMg, SetRange(1, 30), diff --git a/eval.h b/eval.h index 8ed77bd..95de21b 100644 --- a/eval.h +++ b/eval.h @@ -1,6 +1,6 @@ #pragma once -#include #include +#include using Value = int; namespace engine { diff --git a/search.cpp b/search.cpp index ca6bf8e..a768580 100644 --- a/search.cpp +++ b/search.cpp @@ -27,7 +27,6 @@ void update_pv(Move *pv, Move move, const Move *childPv) { // The function is called before storing a value in the transposition table. Value value_to_tt(Value v, int ply) { return is_win(v) ? v + ply : is_loss(v) ? v - ply : v; } - // Inverse of value_to_tt(): it adjusts a mate or TB score from the transposition // table (which refers to the plies to mate/be mated from current position) to // "plies to mate/be mated (TB win/loss) from the root". However, to avoid @@ -39,8 +38,7 @@ Value value_from_tt(Value v, int ply, int r50c) { return VALUE_NONE; // handle TB win or better - if (is_win(v)) - { + if (is_win(v)) { // Downgrade a potentially false mate score if (is_mate(v) && VALUE_MATE - v > 100 - r50c) return VALUE_TB_WIN_IN_MAX_PLY - 1; @@ -53,8 +51,7 @@ Value value_from_tt(Value v, int ply, int r50c) { } // handle TB loss or worse - if (is_loss(v)) - { + if (is_loss(v)) { // Downgrade a potentially false mate score. if (is_mated(v) && VALUE_MATE + v > 100 - r50c) return VALUE_TB_LOSS_IN_MAX_PLY + 1; @@ -256,7 +253,7 @@ Value doSearch( // Null move pruning (skip when a mate threat is possible) if (depth >= 3 && !inCheck && ply > 0 && staticEval >= beta && !is_win(beta)) { - int R = 2 + depth / 6;// + std::min(2, depth / 10); + int R = 2 + depth / 6; // + std::min(2, depth / 10); board.doNullMove(); Value score = doSearch(board, depth - 1 - R, -beta, -beta + 1, session, ply + 1, Move::none()); board.undoMove(); @@ -319,8 +316,9 @@ Value doSearch( // Internal Iterative Deepening (IID): get a TT move when we don't have one if (depth >= 8 && ttMove == Move::none() && !inCheck && alpha != beta - 1) { int d = std::max(2, depth - 2 - depth / 4); - Value v=doSearch(board, d, alpha, beta, session, ply, prevMove); - if (v==VALUE_NONE) return VALUE_NONE; + Value v = doSearch(board, d, alpha, beta, session, ply, prevMove); + if (v == VALUE_NONE) + return VALUE_NONE; if (TTEntry *e = search::tt.lookup(hash)) ttMove = Move(e->getMove()); } @@ -359,7 +357,6 @@ Value doSearch( } } - Value maxScore = -VALUE_INFINITE; int movesSearched = 0; From c1ccc8f34e1c0e87ab1a9321fd48948cfbddfc93 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 9 Jun 2026 23:07:13 +0700 Subject: [PATCH 15/29] commented tunes --- eval.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eval.cpp b/eval.cpp index b14423c..a677039 100644 --- a/eval.cpp +++ b/eval.cpp @@ -29,7 +29,7 @@ Value *mgPst[] = { nullptr, mg_pawn_table, mg_knight_table, mg_bishop_table, mg_ Value *egPst[] = { nullptr, eg_pawn_table, eg_knight_table, eg_bishop_table, eg_rook_table, eg_queen_table, eg_king_table }; // tuning slop here -TUNE(SetRange(5, 30), +/*TUNE(SetRange(5, 30), tempo, SetRange(80, 120), PawnValue, @@ -207,7 +207,7 @@ TUNE(SetRange(0, 30), krkEdgeWeight, SetRange(0, 30), kpkWeight); - +*/ Value eval(const chess::Board &board) { constexpr int KnightPhase = 1; constexpr int BishopPhase = 1; From e2330f11de272dc86c138c6fd25a3492017c758c Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:43:43 +0700 Subject: [PATCH 16/29] tuning --- .github/workflows/games.yml | 17 +- CMakeLists.txt | 16 +- Makefile | 45 ++ Weights.h | 198 +++++--- eval.cpp | 229 ++++++++- eval.h | 11 + movepick.cpp | 85 +++- search.cpp | 76 ++- tune.cpp | 126 +++++ tune.h | 12 + tune_cmd.cpp | 942 ++++++++++++++++++++++++++++++++++++ tune_cmd.h | 5 + uci.cpp | 139 ++++-- 13 files changed, 1721 insertions(+), 180 deletions(-) create mode 100644 Makefile create mode 100644 tune_cmd.cpp create mode 100644 tune_cmd.h diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 0f04925..1b501fd 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -6,7 +6,7 @@ on: base_ref: description: "Base branch, tag, or commit" required: false - default: "HandcraftedEngine" + default: "test_chesslib" rounds: description: "Max rounds for SPRT" required: false @@ -18,7 +18,7 @@ on: output_exec: description: "Executable output file name" required: false - default: "chess_engine" + default: "engine" permissions: contents: write @@ -31,14 +31,14 @@ jobs: steps: - uses: actions/checkout@v4 with: - path: test_chesslib + path: new - name: Install deps run: sudo apt update && sudo apt install -y build-essential cmake - name: Build new engine run: | - cd test_chesslib + cd new mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release @@ -48,7 +48,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: new-engine - path: test_chesslib/build/engine + path: new/build/engine build-base: @@ -58,14 +58,14 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.base_ref }} - path: handcrafted + path: base - name: Install deps run: sudo apt update && sudo apt install -y build-essential cmake - name: Build base engine run: | - cd handcrafted + cd base mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release @@ -75,7 +75,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: base-engine - path: handcrafted/build/${{ github.event.inputs.output_exec }} + path: base/build/${{ github.event.inputs.output_exec }} test: @@ -127,3 +127,4 @@ jobs: path: | games.pgn ratings.txt + results.txt \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index da09fe3..5a54fa9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ FetchContent_Declare( ) set(BUILD_GAVIOTA OFF CACHE BOOL "Enable Gaviota TB probing" FORCE) FetchContent_MakeAvailable(chesslib tbprobe) -add_executable(engine "main.cpp" "timeman.cpp" "timeman.h" "eval.h" "eval.cpp" "tune.h" "ucioption.h" "tune.cpp" "ucioption.cpp" "tt.h" "tt.cpp" "uci.cpp" "uci.h" "search.h" "search.cpp" "score.h" "score.cpp" "movepick.h" "movepick.cpp" "tb.h" "tb.cpp" "Weights.h") +add_executable(engine "main.cpp" "timeman.cpp" "timeman.h" "eval.h" "eval.cpp" "tune.h" "ucioption.h" "tune.cpp" "ucioption.cpp" "tt.h" "tt.cpp" "uci.cpp" "uci.h" "search.h" "search.cpp" "score.h" "score.cpp" "movepick.h" "movepick.cpp" "tb.h" "tb.cpp" "Weights.h" "tune_cmd.h" "tune_cmd.cpp") target_link_libraries(engine PRIVATE chesslib syzygy_probe) target_include_directories(engine PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${chesslib_SOURCE_DIR}) execute_process( @@ -45,5 +45,15 @@ else() endif() message(STATUS "Build Version: ${BUILD_VERSION}") -add_definitions(-DBUILD_VERSION="${BUILD_VERSION}") - +target_compile_definitions(engine PRIVATE BUILD_VERSION="${BUILD_VERSION}") +option(ENGINE_TUNING "enable Texel tuning (requires csv-parser)" OFF) +if (ENGINE_TUNING) + FetchContent_Declare( + csv + GIT_REPOSITORY https://github.com/vincentlaucsb/csv-parser.git + GIT_TAG master + ) + FetchContent_MakeAvailable(csv) + target_link_libraries(engine PRIVATE csv) + target_compile_definitions(engine PRIVATE USE_CSV_PARSER) +endif() diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1233d49 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +# :( openbench and fishtest require make +TARGET = engine + +CXX ?= g++ +CXXFLAGS ?= -std=c++17 -Wall -Wextra +OPTFLAGS ?= -O3 +ifeq ($(LTO), yes) + OPTFLAGS += -flto +endif +ifeq ($(debug),no) + CXXFLAGS += -DNDEBUG +endif +ARCH ?= native + +ifeq ($(ARCH), native) + CXXFLAGS += -march=native -mtune=native +endif +ifeq ($(ARCH), avx2) + CXXFLAGS += -march=haswell -mavx2 -mbmi -mbmi2 -msse4.1 +endif +ifeq ($(ARCH), bmi2) + CXXFLAGS += -mbmi2 +endif +ifeq ($(ARCH), sse41) + CXXFLAGS += -msse4.1 +endif +ifeq ($(ARCH), x86-64) + CXXFLAGS += -march=x86-64 +endif +deps: + test -d deps/chesslib || git clone https://github.com/winapiadmin/chesslib deps/chesslib + test -d deps/tbprobe || git clone https://github.com/winapiadmin/tb_probing_tool deps/tbprobe +SRCS = $(wildcard *.cpp) $(wildcard deps/chesslib/*.cpp) $(wildcard deps/tbprobe/*.cpp) +OBJS = $(SRCS:.cpp=.o) + +all: deps $(TARGET) + +$(TARGET): $(OBJS) + $(CXX) $(CXXFLAGS) $(OPTFLAGS) -o $(TARGET) $(OBJS) + +%.o: %.cpp + $(CXX) $(CXXFLAGS) $(OPTFLAGS) -Ideps/chesslib -Ideps/tbprobe/syzygy -c $< -o $@ + +clean: + rm -f $(OBJS) $(TARGET) diff --git a/Weights.h b/Weights.h index dbd330a..e2b0e6f 100644 --- a/Weights.h +++ b/Weights.h @@ -2,38 +2,38 @@ #define WEIGHTS_H #include "eval.h" namespace engine::eval { -Value tempo = 29; -Value PawnValue = 82; -Value KnightValue = 284; -Value BishopValue = 304; -Value RookValue = 461; -Value QueenValue = 901; -Value fianchettoBonus = 24; -Value trappedBishopPenalty = 26; -Value centerWeight = 2; -Value mopUpKingDistWeight = 4; -Value mopUpEdgeDistWeight = 17; -Value spaceWeight = 1; -Value bishopPairMg = 13; -Value bishopPairEg = 23; -Value rookOpenFileMg = 30; -Value rookOpenFileEg = 25; -Value rookSemiOpenFileMg = 20; -Value rookSemiOpenFileEg = 17; -Value doubledPawnMg = 7; -Value doubledPawnEg = 18; -Value isolatedPawnMg = 4; -Value isolatedPawnEg = 14; -Value kingShelterBaseMg = 18; -Value kingShelterBaseEg = 4; -Value kingShelterDecayMg = 4; -Value kingShelterDecayEg = 1; -Value kqkDistWeight = 17; -Value kqkEdgeWeight = 18; -Value krkDistWeight = 1; -Value krkEdgeWeight = 9; -Value kpkWeight = 15; -Value mgMobilityCnt[7][8] = { +inline Value tempo = 29; +inline Value PawnValue = 82; +inline Value KnightValue = 284; +inline Value BishopValue = 304; +inline Value RookValue = 461; +inline Value QueenValue = 901; +inline Value fianchettoBonus = 24; +inline Value trappedBishopPenalty = 26; +inline Value centerWeight = 2; +inline Value mopUpKingDistWeight = 4; +inline Value mopUpEdgeDistWeight = 17; +inline Value spaceWeight = 1; +inline Value bishopPairMg = 13; +inline Value bishopPairEg = 23; +inline Value rookOpenFileMg = 30; +inline Value rookOpenFileEg = 25; +inline Value rookSemiOpenFileMg = 20; +inline Value rookSemiOpenFileEg = 17; +inline Value doubledPawnMg = 7; +inline Value doubledPawnEg = 18; +inline Value isolatedPawnMg = 4; +inline Value isolatedPawnEg = 14; +inline Value kingShelterBaseMg = 18; +inline Value kingShelterBaseEg = 4; +inline Value kingShelterDecayMg = 4; +inline Value kingShelterDecayEg = 1; +inline Value kqkDistWeight = 17; +inline Value kqkEdgeWeight = 18; +inline Value krkDistWeight = 1; +inline Value krkEdgeWeight = 9; +inline Value kpkWeight = 15; +inline Value mgMobilityCnt[7][8] = { { 4, -1, 5, -1, -5, 3, 0, -3 }, { -7, -10, 7, -4, -7, 1, -5, -2 }, { -1, -8, -6, -3, 2, -2, 1, 7 }, @@ -42,7 +42,7 @@ Value mgMobilityCnt[7][8] = { { -9, -1, -7, 2, -2, -7, -5, 2 }, { -4, -5, -9, -5, -3, -6, 5, -3 } }; -Value egMobilityCnt[7][8] = { +inline Value egMobilityCnt[7][8] = { { -8, -2, -3, 7, -4, 2, -1, -4 }, { -7, -8, -8, 3, -4, -3, 2, -9 }, { 3, -3, -10, -5, -2, -10, -4, 7 }, @@ -51,52 +51,90 @@ Value egMobilityCnt[7][8] = { { -10, -2, -10, -2, -5, -3, 0, 0 }, { -10, 8, 7, 5, 5, 3, -1, 1 } }; -Value kingTropismMg[7] = { 13, 14, 6, -5, 5, 12, 6 }; -Value kingTropismEg[7] = { -9, -13, 3, 5, -2, 19, 4 }; -Value passedBonusMg[7] = { 0, 4, 13, 73, 131, 165, 39 }; -Value passedBonusEg[7] = { 0, 66, 59, 96, 158, 185, 22 }; -Value mg_knight_table[64] = { 1, -5, 8, -21, -11, -2, -24, -2, 23, 21, 13, 2, 4, -17, -7, 4, - -5, -15, -8, 14, 19, -2, 2, -25, -13, 15, -1, -1, 5, -18, 5, 6, - -1, -13, 1, 17, -24, -13, 14, 19, -10, 6, 1, 16, -13, -18, 8, 24, - 14, -21, -8, -21, -15, -1, -1, 8, -17, 22, -18, 6, -11, -4, -9, -7 }; -Value mg_bishop_table[64] = { 3, 21, -23, 16, 5, -20, 7, -4, 14, -15, 21, -11, 7, 2, -4, 19, -5, -4, 22, 11, 15, 25, - 20, -9, -3, -4, 20, 21, 10, 11, -4, -21, 9, 9, 10, 5, -3, 0, 16, 23, -1, 5, -10, -12, - 9, -24, -9, -10, 5, -17, -8, 5, 4, -3, 6, -4, 1, 2, 20, -4, 9, 3, -20, -22 }; -Value mg_rook_table[64] = { -10, 20, -13, 18, 12, -3, 13, -25, -11, 3, 10, -4, 7, 13, -24, 5, - -3, -7, -19, -3, 4, -6, 13, 13, 11, 20, 16, -6, 14, -15, -16, -15, - 3, 7, 14, -10, -7, 20, -15, 10, 25, 1, 3, -14, 22, 5, 1, -12, - -20, -13, 22, -9, 11, -2, -15, -11, 22, -14, -10, 5, 0, 9, -20, -13 }; -Value mg_king_table[64] = { 11, -12, -10, -19, 5, -6, -2, -10, 19, 13, -7, -3, -7, 8, -21, -24, - -5, 16, 19, 2, -21, -14, 11, -16, -12, -13, -18, 14, -23, -14, 16, -5, - -16, 0, -21, -12, -2, 14, -16, 17, 11, -10, 21, 14, -5, -15, 19, 13, - 8, -21, -13, 4, 1, 1, 16, 13, -8, 13, 6, -20, -4, 3, 8, -2 }; -Value mg_queen_table[64] = { -2, -2, 4, 11, 9, -2, 0, -5, 14, -2, -11, 14, -13, -11, -3, -21, 7, -14, -4, -6, -3, -16, - -4, -11, -2, 9, -16, 6, -6, -12, -14, -7, -4, -22, -12, 6, 21, -4, -6, -5, -6, -7, -4, 2, - 13, 11, -8, 14, 18, -5, -25, 22, -14, 18, -3, 9, -13, 21, 15, -21, 13, 7, -9, 3 }; -Value eg_knight_table[64] = { -13, 25, -3, 3, -7, 18, -21, 3, -1, 15, -13, 3, -16, 15, -5, 8, - -11, -12, 22, 14, 10, 20, 9, -6, -10, 2, -13, -7, -17, 2, 18, -19, - 7, 12, -23, 17, -5, 17, 10, -7, -18, -2, -10, -14, 20, 15, -13, 18, - 9, -8, -13, 12, 20, 17, 16, 25, 18, 20, 5, 10, -10, 2, 0, 8 }; -Value eg_bishop_table[64] = { -19, -12, -9, -5, 9, -14, -11, 6, -10, -10, 6, -3, 13, -18, -6, 2, - 25, 4, 9, -16, -3, -9, 1, 7, 3, -20, -3, 9, -20, -3, 21, 3, - -8, 10, 4, -11, -8, -18, -13, 0, 10, 17, 14, -18, 21, 11, 18, 0, - -16, 4, 3, 17, -9, -16, -25, -2, -17, -5, -5, 19, 12, -3, -11, -13 }; -Value eg_rook_table[64] = { -21, -24, 12, -12, -11, 15, -17, -22, -14, -22, -18, -5, -9, -7, 23, -2, - -1, 9, 18, 3, 4, -19, 20, -21, -15, -21, 9, -11, -2, 4, -8, 15, - -6, 13, -6, -10, -3, 12, 16, -18, -19, -19, 23, 3, -14, -23, 3, 15, - 18, -6, -24, 21, 18, 3, -2, 17, 8, 13, -11, -13, 0, -16, -4, -10 }; -Value eg_king_table[64] = { -14, 17, 13, -10, -15, 3, -8, -3, 19, 19, -2, 6, -2, 7, -7, -7, 21, 10, 17, -21, 0, 15, - -6, -9, -7, 24, 8, 0, 17, 12, -4, -6, 8, -16, 19, 0, 9, 20, 18, 12, -12, -2, -4, -14, - 21, 2, -6, -5, 17, -2, -3, -9, -1, -23, 7, -14, 9, 2, 22, -20, 3, 22, -10, 2 }; -Value eg_queen_table[64] = { -7, -3, 13, 11, 2, 17, 2, -19, 12, -5, -2, -11, 2, -7, 19, 2, - -1, 15, 19, 1, 15, 8, -19, -6, -14, -25, -8, 22, 4, -22, -10, 20, - 3, -1, -3, -17, 12, -3, -4, -4, -7, 16, -18, -22, -2, -11, 4, -14, - -7, -15, -8, 9, 20, 8, -2, -1, 8, 18, 20, -23, -12, -8, 6, -11 }; -Value mg_pawn_table[56] = { 0, 0, 0, 0, 0, 0, 0, 0, -16, 4, 6, -22, -16, -20, 4, -13, 2, -24, -12, - -7, -3, -11, -5, 6, -15, -9, 2, -14, 2, -18, -7, -10, 1, 15, -7, -6, 22, -13, - -6, 2, -16, -9, 14, -2, 23, -18, 8, 6, -2, -8, -1, -4, -10, -1, 18, -12 }; -Value eg_pawn_table[56] = { 0, 0, 0, 0, 0, 0, 0, 0, -4, 1, -2, -5, -20, 13, -17, -2, 2, 24, 12, - -14, -4, 2, -17, -4, 15, -9, 8, 11, -24, -4, -14, -10, -13, -11, -6, -17, -4, 17, - -14, -3, -17, -19, -10, -19, -23, 9, 13, -3, -7, -5, -9, 21, 8, -17, 7, 13 }; +inline Value kingTropismMg[7] = { 13, 14, 6, -5, 5, 12, 6 }; +inline Value kingTropismEg[7] = { -9, -13, 3, 5, -2, 19, 4 }; +inline Value passedBonusMg[7] = { 0, 4, 13, 73, 131, 165, 39 }; +inline Value passedBonusEg[7] = { 0, 66, 59, 96, 158, 185, 22 }; +inline Value mg_knight_table[64] = { 1, -5, 8, -21, -11, -2, -24, -2, 23, 21, 13, 2, 4, -17, -7, 4, + -5, -15, -8, 14, 19, -2, 2, -25, -13, 15, -1, -1, 5, -18, 5, 6, + -1, -13, 1, 17, -24, -13, 14, 19, -10, 6, 1, 16, -13, -18, 8, 24, + 14, -21, -8, -21, -15, -1, -1, 8, -17, 22, -18, 6, -11, -4, -9, -7 }; +inline Value mg_bishop_table[64] = { 3, 21, -23, 16, 5, -20, 7, -4, 14, -15, 21, -11, 7, 2, -4, 19, -5, -4, 22, 11, 15, 25, + 20, -9, -3, -4, 20, 21, 10, 11, -4, -21, 9, 9, 10, 5, -3, 0, 16, 23, -1, 5, -10, -12, + 9, -24, -9, -10, 5, -17, -8, 5, 4, -3, 6, -4, 1, 2, 20, -4, 9, 3, -20, -22 }; +inline Value mg_rook_table[64] = { -10, 20, -13, 18, 12, -3, 13, -25, -11, 3, 10, -4, 7, 13, -24, 5, + -3, -7, -19, -3, 4, -6, 13, 13, 11, 20, 16, -6, 14, -15, -16, -15, + 3, 7, 14, -10, -7, 20, -15, 10, 25, 1, 3, -14, 22, 5, 1, -12, + -20, -13, 22, -9, 11, -2, -15, -11, 22, -14, -10, 5, 0, 9, -20, -13 }; +inline Value mg_king_table[64] = { 11, -12, -10, -19, 5, -6, -2, -10, 19, 13, -7, -3, -7, 8, -21, -24, + -5, 16, 19, 2, -21, -14, 11, -16, -12, -13, -18, 14, -23, -14, 16, -5, + -16, 0, -21, -12, -2, 14, -16, 17, 11, -10, 21, 14, -5, -15, 19, 13, + 8, -21, -13, 4, 1, 1, 16, 13, -8, 13, 6, -20, -4, 3, 8, -2 }; +inline Value mg_queen_table[64] = { -2, -2, 4, 11, 9, -2, 0, -5, 14, -2, -11, 14, -13, -11, -3, -21, 7, -14, -4, -6, -3, -16, + -4, -11, -2, 9, -16, 6, -6, -12, -14, -7, -4, -22, -12, 6, 21, -4, -6, -5, -6, -7, -4, 2, + 13, 11, -8, 14, 18, -5, -25, 22, -14, 18, -3, 9, -13, 21, 15, -21, 13, 7, -9, 3 }; +inline Value eg_knight_table[64] = { -13, 25, -3, 3, -7, 18, -21, 3, -1, 15, -13, 3, -16, 15, -5, 8, + -11, -12, 22, 14, 10, 20, 9, -6, -10, 2, -13, -7, -17, 2, 18, -19, + 7, 12, -23, 17, -5, 17, 10, -7, -18, -2, -10, -14, 20, 15, -13, 18, + 9, -8, -13, 12, 20, 17, 16, 25, 18, 20, 5, 10, -10, 2, 0, 8 }; +inline Value eg_bishop_table[64] = { -19, -12, -9, -5, 9, -14, -11, 6, -10, -10, 6, -3, 13, -18, -6, 2, + 25, 4, 9, -16, -3, -9, 1, 7, 3, -20, -3, 9, -20, -3, 21, 3, + -8, 10, 4, -11, -8, -18, -13, 0, 10, 17, 14, -18, 21, 11, 18, 0, + -16, 4, 3, 17, -9, -16, -25, -2, -17, -5, -5, 19, 12, -3, -11, -13 }; +inline Value eg_rook_table[64] = { -21, -24, 12, -12, -11, 15, -17, -22, -14, -22, -18, -5, -9, -7, 23, -2, + -1, 9, 18, 3, 4, -19, 20, -21, -15, -21, 9, -11, -2, 4, -8, 15, + -6, 13, -6, -10, -3, 12, 16, -18, -19, -19, 23, 3, -14, -23, 3, 15, + 18, -6, -24, 21, 18, 3, -2, 17, 8, 13, -11, -13, 0, -16, -4, -10 }; +inline Value eg_king_table[64] = { -14, 17, 13, -10, -15, 3, -8, -3, 19, 19, -2, 6, -2, 7, -7, -7, 21, 10, 17, -21, 0, 15, + -6, -9, -7, 24, 8, 0, 17, 12, -4, -6, 8, -16, 19, 0, 9, 20, 18, 12, -12, -2, -4, -14, + 21, 2, -6, -5, 17, -2, -3, -9, -1, -23, 7, -14, 9, 2, 22, -20, 3, 22, -10, 2 }; +inline Value eg_queen_table[64] = { -7, -3, 13, 11, 2, 17, 2, -19, 12, -5, -2, -11, 2, -7, 19, 2, + -1, 15, 19, 1, 15, 8, -19, -6, -14, -25, -8, 22, 4, -22, -10, 20, + 3, -1, -3, -17, 12, -3, -4, -4, -7, 16, -18, -22, -2, -11, 4, -14, + -7, -15, -8, 9, 20, 8, -2, -1, 8, 18, 20, -23, -12, -8, 6, -11 }; +inline Value mg_pawn_table[56] = { 0, 0, 0, 0, 0, 0, 0, 0, -16, 4, 6, -22, -16, -20, 4, -13, 2, -24, -12, + -7, -3, -11, -5, 6, -15, -9, 2, -14, 2, -18, -7, -10, 1, 15, -7, -6, 22, -13, + -6, 2, -16, -9, 14, -2, 23, -18, 8, 6, -2, -8, -1, -4, -10, -1, 18, -12 }; +inline Value eg_pawn_table[56] = { 0, 0, 0, 0, 0, 0, 0, 0, -4, 1, -2, -5, -20, 13, -17, -2, 2, 24, 12, + -14, -4, 2, -17, -4, 15, -9, 8, 11, -24, -4, -14, -10, -13, -11, -6, -17, -4, 17, + -14, -3, -17, -19, -10, -19, -23, 9, 13, -3, -7, -5, -9, 21, 8, -17, 7, 13 }; +inline Value developedMg = 10; +inline Value developedEg = 5; +inline Value outpostBonusKnight[2] = { 15, 30 }; +inline Value outpostBonusBishop[2] = { 10, 20 }; +inline Value kingProtector[6][2] = { + { 0, 0 }, + { 8, 12 }, + { 5, 10 }, + { 10, 15 }, + { 3, 5 }, + { 0, 0 } +}; +inline Value threatByMinor[7][2] = { + { 0, 0 }, + { 0, 0 }, + { 15, 25 }, + { 15, 25 }, + { 25, 35 }, + { 35, 50 } +}; +inline Value threatByRook[7][2] = { + { 0, 0 }, + { 0, 0 }, + { 10, 15 }, + { 10, 15 }, + { 20, 25 }, + { 25, 35 } +}; +inline Value hangingScore = 60; +inline Value overloadScore = 25; +inline Value threatByRankScore = 10; +inline Value minorImWt = 25; +inline Value bishopImWt = 10; +inline Value rookImWt = 15; +inline Value queenImWt = 40; +inline Value rammedPawnPenalty = 10; +inline Value rookOnSeventhBonus = 20; +inline Value earlyQueenPenalty = 15; } // namespace engine::eval #endif diff --git a/eval.cpp b/eval.cpp index a677039..3e6cdd1 100644 --- a/eval.cpp +++ b/eval.cpp @@ -29,7 +29,7 @@ Value *mgPst[] = { nullptr, mg_pawn_table, mg_knight_table, mg_bishop_table, mg_ Value *egPst[] = { nullptr, eg_pawn_table, eg_knight_table, eg_bishop_table, eg_rook_table, eg_queen_table, eg_king_table }; // tuning slop here -/*TUNE(SetRange(5, 30), +TUNE(SetRange(5, 30), tempo, SetRange(80, 120), PawnValue, @@ -53,6 +53,7 @@ TUNE(SetRange(0, 30), SetRange(1, 30), spaceWeight); TUNE(SetRange(0, 50), bishopPairMg, SetRange(0, 50), bishopPairEg); +TUNE(SetRange(1, 20), developedMg, SetRange(1, 20), developedEg); TUNE(SetRange(0, 30), rookOpenFileMg, SetRange(0, 30), @@ -207,17 +208,63 @@ TUNE(SetRange(0, 30), krkEdgeWeight, SetRange(0, 30), kpkWeight); -*/ -Value eval(const chess::Board &board) { + +TUNE(SetRange(1, 100), outpostBonusKnight, outpostBonusBishop); +TUNE(SetRange(-20, 20), kingProtector); +TUNE(SetRange(-50, 100), + threatByMinor[1], + threatByMinor[2], + threatByMinor[3], + threatByMinor[4], + threatByMinor[5], + threatByMinor[6]); +TUNE(SetRange(-50, 100), threatByRook[1], threatByRook[2], threatByRook[3], threatByRook[4], threatByRook[5], threatByRook[6]); +TUNE(SetRange(1, 100), hangingScore, overloadScore, threatByRankScore); +TUNE(SetRange(10, 100), minorImWt, SetRange(5, 50), bishopImWt, SetRange(10, 100), rookImWt, SetRange(20, 150), queenImWt); +TUNE(SetRange(1, 50), rammedPawnPenalty); +TUNE(SetRange(1, 100), rookOnSeventhBonus); +TUNE(SetRange(1, 50), earlyQueenPenalty); + +EvalComponents eval_components(const chess::Board &board) { constexpr int KnightPhase = 1; constexpr int BishopPhase = 1; constexpr int RookPhase = 2; constexpr int QueenPhase = 4; constexpr int TotalPhase = KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; - const int sign = board.side_to_move() == WHITE ? 1 : -1; int mgScore = 0; int egScore = 0; int phase = 0; + // Precompute pawn attacks (needed for outpost, threats, etc.) + Bitboard pawnBB[2] = { board.pieces(PAWN, WHITE), board.pieces(PAWN, BLACK) }; + Bitboard pawnAtks[2] = { attacks::pawn(pawnBB[WHITE]), attacks::pawn(pawnBB[BLACK]) }; +#if 0 + // Development bonus: penalize undeveloped knights/bishops in middlegame + int devCount[2] = { 0, 0 }; + for (Color c : { WHITE, BLACK }) { + Bitboard homeRank = c == WHITE ? attacks::MASK_RANK[0] : attacks::MASK_RANK[7]; + Bitboard knights = board.pieces(KNIGHT, c); + Bitboard bishops = board.pieces(BISHOP, c); + Bitboard knightsHome = knights & homeRank; + Bitboard bishopsHome = bishops & homeRank; + devCount[c] = popcount(knightsHome) + popcount(bishopsHome); + } + mgScore += (devCount[BLACK] - devCount[WHITE]) * developedMg; + egScore += (devCount[BLACK] - devCount[WHITE]) * developedEg; + + // Early queen development penalty: queen moved but minors still on back rank + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Square qStart = c == WHITE ? SQ_D1 : SQ_D8; + if (!(board.pieces(QUEEN, c) & (1ULL << qStart))) { + Bitboard backRank = c == WHITE ? attacks::MASK_RANK[0] : attacks::MASK_RANK[7]; + int undeveloped = popcount((board.pieces(KNIGHT, c) | board.pieces(BISHOP, c)) & backRank); + if (undeveloped >= 2) { + mgScore -= s * earlyQueenPenalty; + egScore -= s * earlyQueenPenalty; + } + } + } +#endif { Bitboard pinMask = board.pin_mask(); Bitboard occ = board.occ(), occ2 = occ; @@ -263,6 +310,31 @@ Value eval(const chess::Board &board) { mgScore += _sign * (7 - kd) * kingTropismMg[pt]; egScore += _sign * (7 - kd) * kingTropismEg[pt]; } +#if 0 + // King protector: bonus for pieces close to own king + if (pt != PAWN && pt != KING) { + int kdist = square_distance(sq, board.kingSq(pc)); + int ki = std::min(kdist, 5); + mgScore += _sign * kingProtector[pt][0] / (1 + ki); + egScore += _sign * kingProtector[pt][1] / (1 + ki); + } + // Outpost bonus for knights and bishops + if (pt == KNIGHT || pt == BISHOP) { + Rank r = rank_of(sq); + bool onOutpost = (pc == WHITE && r >= RANK_5) || (pc == BLACK && r <= RANK_4); + if (onOutpost && !(pawnAtks[~pc] & (1ULL << sq))) { + bool defended = (pawnAtks[pc] & (1ULL << sq)) != 0; + int idx = defended ? 1 : 0; + if (pt == KNIGHT) { + mgScore += _sign * outpostBonusKnight[idx]; + egScore += _sign * outpostBonusKnight[idx]; + } else { + mgScore += _sign * outpostBonusBishop[idx]; + egScore += _sign * outpostBonusBishop[idx]; + } + } + } +#endif } } // Bishop pair bonus @@ -287,7 +359,7 @@ Value eval(const chess::Board &board) { } } } - +#if 0 // Trapped bishop penalty for (Color c : { WHITE, BLACK }) { int s = (c == WHITE) ? 1 : -1; @@ -300,7 +372,7 @@ Value eval(const chess::Board &board) { } } } - +#endif // Rook on open/semi-open file for (Color c : { WHITE, BLACK }) { int s = (c == WHITE) ? 1 : -1; @@ -320,11 +392,31 @@ Value eval(const chess::Board &board) { } } } - - // Precompute pawn data - Bitboard pawnBB[2] = { board.pieces(PAWN, WHITE), board.pieces(PAWN, BLACK) }; - Bitboard pawnAtks[2] = { attacks::pawn(pawnBB[WHITE]), attacks::pawn(pawnBB[BLACK]) }; - +#if 0 + // Rook on seventh rank bonus (endgame, x-raying >=2 undefended pawns) + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Bitboard rooks = board.pieces(ROOK, c); + while (rooks) { + Square sq = Square(pop_lsb(rooks)); + Rank r7 = c == WHITE ? RANK_7 : RANK_2; + if (rank_of(sq) != r7) + continue; + Bitboard ep = board.pieces(PAWN, ~c) & attacks::MASK_RANK[r7]; + int cnt = 0; + Bitboard tmp = ep; + while (tmp) { + Square psq = Square(pop_lsb(tmp)); + if (!board.is_attacked_by(~c, psq)) + cnt++; + } + if (cnt >= 2) { + mgScore += s * rookOnSeventhBonus / 2; + egScore += s * rookOnSeventhBonus; + } + } + } +#endif // Pawn structure: doubled, isolated, passed in one pass per color for (Color c : { WHITE, BLACK }) { int s = (c == WHITE) ? 1 : -1; @@ -369,7 +461,21 @@ Value eval(const chess::Board &board) { } } } - +#if 0 + // Pawn rams: blocked pawn penalty + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Bitboard pawns = pawnBB[c]; + while (pawns) { + Square sq = Square(pop_lsb(pawns)); + Square forward = sq + pawn_push(c); + if (forward >= SQ_A1 && forward <= SQ_H8 && (pawnBB[~c] & (1ULL << forward))) { + mgScore -= s * rammedPawnPenalty; + egScore -= s * rammedPawnPenalty; + } + } + } +#endif // Center control (using precomputed pawn attacks) { Bitboard centerMask = (1ULL << SQ_D4) | (1ULL << SQ_E4) | (1ULL << SQ_D5) | (1ULL << SQ_E5); @@ -497,6 +603,93 @@ Value eval(const chess::Board &board) { } } +// --- Threat evaluation --- +#if 0 + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Color opp = ~c; + Bitboard ourPieces = board.occ(c) & ~board.pieces(PAWN) & ~board.pieces(KING); + while (ourPieces) { + Square sq = Square(pop_lsb(ourPieces)); + PieceType pt = piece_of(board.at(sq)); + bool attacked = board.is_attacked_by(opp, sq); + if (!attacked) + continue; + bool defended = board.is_attacked_by(c, sq); + Bitboard occ = board.occ(); + int attackers = popcount(board.attackers_mask(opp, sq, occ)); + int defenders = defended ? popcount(board.attackers_mask(c, sq, occ)) : 0; + bool hanging = !defended; + bool overloaded = defenders == 1 && attackers >= 2; + bool attackedByMinor = + (board.attackers_mask(opp, sq, occ) & (board.pieces(KNIGHT, opp) | board.pieces(BISHOP, opp))) != 0; + bool attackedByRook = (board.attackers_mask(opp, sq, occ) & board.pieces(ROOK, opp)) != 0; + + int mgThreat = 0, egThreat = 0; + if (hanging) { + mgThreat += (pt == QUEEN ? hangingScore / 2 : hangingScore); + egThreat += (pt == QUEEN ? hangingScore / 4 : hangingScore / 2); + } + if (overloaded) { + mgThreat += overloadScore; + egThreat += overloadScore / 2; + } + if (attackedByMinor && pt < KING) { + mgThreat += threatByMinor[pt][0]; + egThreat += threatByMinor[pt][1]; + } + if (attackedByRook && pt < KING) { + mgThreat += threatByRook[pt][0]; + egThreat += threatByRook[pt][1]; + } + // Rank-based threat bonus + Rank relRank = relative_rank(c, sq); + mgThreat += threatByRankScore * static_cast(relRank); + egThreat += threatByRankScore * static_cast(relRank); + + mgScore -= s * mgThreat; + egScore -= s * egThreat; + } + } + + // --- Trapped bishop at a7/h7 --- + for (Color c : { WHITE, BLACK }) { + int s = (c == WHITE) ? 1 : -1; + Color opp = ~c; + Square aFileSq = relative_square(c, SQ_A7); + Square hFileSq = relative_square(c, SQ_H7); + Square bPawnSq = relative_square(c, SQ_B6); + Square gPawnSq = relative_square(c, SQ_G6); + // Bishop on a7/h7 trapped by b6/g6 pawn + if (board.at(aFileSq) == BISHOP && board.at(aFileSq) == c) { + if (board.at(bPawnSq) == PAWN && board.at(bPawnSq) == opp && !board.is_attacked_by(c, aFileSq)) { + mgScore -= s * trappedBishopPenalty; + egScore -= s * trappedBishopPenalty; + } + } + if (board.at(hFileSq) == BISHOP && board.at(hFileSq) == c) { + if (board.at(gPawnSq) == PAWN && board.at(gPawnSq) == opp && !board.is_attacked_by(c, hFileSq)) { + mgScore -= s * trappedBishopPenalty; + egScore -= s * trappedBishopPenalty; + } + } + } + // --- Material imbalance --- + { + int pieceCount[2][7] = { { 0 } }; + for (Color c : { WHITE, BLACK }) + for (PieceType pt : { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING }) + pieceCount[c][pt] = board.count(pt, c); + int ourMinors = pieceCount[WHITE][KNIGHT] + pieceCount[WHITE][BISHOP]; + int theirMinors = pieceCount[BLACK][KNIGHT] + pieceCount[BLACK][BISHOP]; + int imbalance = (ourMinors - theirMinors) * minorImWt // minor piece imbalance + + (pieceCount[WHITE][BISHOP] - pieceCount[BLACK][BISHOP]) * bishopImWt // bishop vs knight + + (pieceCount[WHITE][ROOK] - pieceCount[BLACK][ROOK]) * rookImWt // rook imbalance + + (pieceCount[WHITE][QUEEN] - pieceCount[BLACK][QUEEN]) * queenImWt; // queen imbalance + mgScore += imbalance; + egScore += imbalance; + } +#endif // Draw detection: score 0 for positions where neither side can force a win int totalPieces = popcount(board.occ()); int pawnCount = board.count(); @@ -510,17 +703,21 @@ Value eval(const chess::Board &board) { Square wb = Square(pop_lsb(wbBB)); Square bb = Square(pop_lsb(bbBB)); if (square_color(wb) == square_color(bb)) - return 0; + return { 0, 0, 0 }; } // KNNK (no pawns) - drawn if (totalPieces == 4 && pawnCount == 0 && board.count() == 2 && board.count() == 0 && board.count() == 0 && board.count() == 0) - return 0; + return { 0, 0, 0 }; phase = (phase * 256 + TotalPhase / 2) / TotalPhase; - Value finalScore = (((mgScore * phase) + (egScore * (256 - phase))) * sign) / 256 + tempo; - return finalScore; + return { mgScore, egScore, phase }; +} +Value eval(const chess::Board &board) { + const int sign = board.side_to_move() == WHITE ? 1 : -1; + auto [mg, eg, phase] = eval_components(board); + return (((mg * phase) + (eg * (256 - phase))) * sign) / 256 + engine::eval::tempo; } Value piece_value(PieceType pt) { Value pieces[] = { 0, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, 0 }; diff --git a/eval.h b/eval.h index 95de21b..be45ea7 100644 --- a/eval.h +++ b/eval.h @@ -40,7 +40,18 @@ constexpr Value mate_in(int ply) { return VALUE_MATE - ply; } constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; } namespace eval { + +struct EvalComponents { + int mg; + int eg; + int phase; +}; + +extern Value *mgPst[]; +extern Value *egPst[]; + Value eval(const chess::Board &board); +EvalComponents eval_components(const chess::Board &board); Value piece_value(chess::PieceType pt); } // namespace eval } // namespace engine diff --git a/movepick.cpp b/movepick.cpp index da896c3..d174558 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -18,39 +18,86 @@ static Bitboard att(PieceType pt, Square sq, Bitboard occ) { return 0; } } +inline Square least_valuable_attacker(const Position& board, + Bitboard attackers, + Color side) { + Bitboard bb; + + bb = attackers & board.pieces(PAWN, side); + if (bb) return Square(pop_lsb(bb)); + + bb = attackers & board.pieces(KNIGHT, side); + if (bb) return Square(pop_lsb(bb)); + + bb = attackers & board.pieces(BISHOP, side); + if (bb) return Square(pop_lsb(bb)); + + bb = attackers & board.pieces(ROOK, side); + if (bb) return Square(pop_lsb(bb)); + + bb = attackers & board.pieces(QUEEN, side); + if (bb) return Square(pop_lsb(bb)); + + bb = attackers & board.pieces(KING, side); + if (bb) return Square(pop_lsb(bb)); + + return SQ_NONE; +} +Value see(Board& board, Move move) { + Square from = move.from(); + Square to = move.to(); + + PieceType captured = + move.type_of() == EN_PASSANT ? PAWN : board.at(to); -Value see(Board &board, Move move) { - if (move.type_of() == EN_PASSANT) - return piece_value(PAWN); - PieceType captured = board.at(move.to()); if (captured == NO_PIECE_TYPE) return 0; - PieceType attacker = board.at(move.from()); - Bitboard occ = board.occ() ^ (1ULL << move.from()); - Bitboard attackers = board.attackers(WHITE, move.to(), occ) | board.attackers(BLACK, move.to(), occ); + + Bitboard occ = board.occ(); + occ ^= 1ULL<(move.from()); + + gain[0] = piece_value(captured); + Color stm = ~board.side_to_move(); + int d = 0; + while (++d < 32) { + + // Charge the piece that just captured. gain[d] = piece_value(attacker) - gain[d - 1]; + if (gain[d] < 0) break; - attackers &= occ; - Bitboard stmAttackers = attackers & board.occ(stm); + + occ &= attackers; + + Bitboard stmAttackers = attackers & occ & board.occ(stm); if (!stmAttackers) break; - Square sq = (Square)pop_lsb(stmAttackers); + + Square sq = least_valuable_attacker(board, stmAttackers, stm); attacker = board.at(sq); - if (attacker == NO_PIECE_TYPE) - break; - if (attacker == BISHOP || attacker == ROOK || attacker == QUEEN) - attackers |= att(attacker, move.to(), occ); - occ ^= (1ULL << sq); + + occ ^= 1ULL< #include #include +#include using namespace chess; namespace engine::search { TranspositionTable tt(16); @@ -339,6 +340,13 @@ Value doSearch( if (moves.size() <= 5) { bool singular = true; for (size_t si = 0; si < moves.size() && singular; ++si) { + if (ply == 0 && + !session.tc.searchmoves.empty() && + std::find(session.tc.searchmoves.begin(), + session.tc.searchmoves.end(), + chess::uci::moveToUci(moves[si], board.chess960())) + == session.tc.searchmoves.end()) + continue; if (moves[si] == ttMove) continue; board.doMove(moves[si]); @@ -362,6 +370,11 @@ Value doSearch( for (size_t i = 0; i < moves.size(); ++i) { Move move = moves[i]; + if (ply == 0 && !session.tc.searchmoves.empty() && + std::find(session.tc.searchmoves.begin(), + session.tc.searchmoves.end(), + chess::uci::moveToUci(move, board.chess960())) == session.tc.searchmoves.end()) + continue; bool isCapture = board.isCapture(move); bool givesCheck = board.givesCheck(move) != CheckType::NO_CHECK; @@ -416,7 +429,6 @@ Value doSearch( ext = 1; if (ext == 0 && isCapture && movesSearched == 0) ext = 1; - board.doMove(move); Value score; @@ -465,7 +477,11 @@ Value doSearch( session.historyHeuristic[(int)move.from()][(int)move.to()] = std::clamp(session.historyHeuristic[(int)move.from()][(int)move.to()] + bonus, -16384, 16384); } - } + } /*else if (!isCapture && depth > 0) { + int malus = -depth * depth; + session.historyHeuristic[(int)move.from()][(int)move.to()] = + std::clamp(session.historyHeuristic[(int)move.from()][(int)move.to()] + malus, -16384, 16384); + }*/ if (alpha >= beta) { if (!isCapture) { @@ -493,6 +509,8 @@ Value doSearch( std::string extract_pv(const chess::Board &root, int maxPly) { std::string pv; chess::Board pos = root; + std::unordered_set visited; + visited.insert(pos.hash()); for (int ply = 0; ply < maxPly; ply++) { if (pos.is_draw(3)) break; @@ -502,7 +520,6 @@ std::string extract_pv(const chess::Board &root, int maxPly) { chess::Move m(e->getMove()); if (!m.is_ok()) break; - uint64_t h = pos.hash(); chess::Movelist ml; pos.legals(ml); bool legal = false; @@ -517,6 +534,8 @@ std::string extract_pv(const chess::Board &root, int maxPly) { if (ply + 1 >= maxPly) break; pos.doMove(m); + if (!visited.insert(pos.hash()).second) + break; } return pv; } @@ -533,6 +552,26 @@ void search(const chess::Board &board, const timeman::LimitsType timecontrol) { chess::Move lastPV[MAX_PLY]{}; Value prevScore = VALUE_NONE; + if (!session.tc.searchmoves.empty()) { + Movelist legal; + board.legals(legal); + bool any_legal = false; + for (size_t i = 0; i < legal.size() && !any_legal; ++i) + if (std::find(session.tc.searchmoves.begin(), + session.tc.searchmoves.end(), + chess::uci::moveToUci(legal[i], board.chess960())) != session.tc.searchmoves.end()) + any_legal = true; + if (!any_legal) + session.tc.searchmoves.clear(); + } + + { + Movelist legal; + board.legals(legal); + if (legal.size()) + lastPV[0] = legal[0]; + } + for (int i = 1; i <= timecontrol.depth; i++) { session.lastLogTime = session.tm.elapsed(); session.depth = i; @@ -613,10 +652,15 @@ void search(const chess::Board &board, const timeman::LimitsType timecontrol) { if (moves.size()) { Board board_ = board; - Move best = moves[0]; + Move best = Move::none(); Value bestScore = -VALUE_INFINITE; Session tmpSession{}; for (Move move : moves) { + if (!session.tc.searchmoves.empty() && + std::find(session.tc.searchmoves.begin(), + session.tc.searchmoves.end(), + chess::uci::moveToUci(move, board.chess960())) == session.tc.searchmoves.end()) + continue; board_.doMove(move); Value score = -qsearch(board_, -VALUE_INFINITE, VALUE_INFINITE, tmpSession, 0); if (score > bestScore) { @@ -626,19 +670,23 @@ void search(const chess::Board &board, const timeman::LimitsType timecontrol) { board_.undoMove(); } - InfoFull info{}; - info.depth = 1; - info.nodes = 1; - info.score = 0; - info.multiPV = 1; - info.pv = std::string(chess::uci::moveToUci(best, board.chess960())); - report(info); - - report(chess::uci::moveToUci(best, board.chess960())); + if (best.is_ok()) { + InfoFull info{}; + info.depth = 1; + info.nodes = 1; + info.score = 0; + info.multiPV = 1; + info.pv = std::string(chess::uci::moveToUci(best, board.chess960())); + report(info); + + report(chess::uci::moveToUci(best, board.chess960())); + } else { + report("0000"); + } } else { report("0000"); } } } } -} // namespace engine::search \ No newline at end of file +} // namespace engine::search diff --git a/tune.cpp b/tune.cpp index 94077ec..0e46cbb 100644 --- a/tune.cpp +++ b/tune.cpp @@ -19,11 +19,14 @@ #include "tune.h" #include +#include +#include #include #include #include #include #include +#include #include "ucioption.h" @@ -57,6 +60,11 @@ void Tune::make_option(OptionsMap *opts, const string &n, int v, const SetRange opts->add(n, Option(v, r(v).first, r(v).second, on_tune)); LastOption = &((*opts)[n]); + auto &[a, b] = r(v); + if (!(a <= v && v <= b)) { + std::cerr << "wrong bounds, name: " << n << '\n'; + std::exit(1); + } // Print formatted parameters, ready to be copy-pasted in Fishtest std::cout << n << "," // @@ -100,6 +108,124 @@ template <> void Tune::Entry::read_option() { template <> void Tune::Entry::init_option() {} template <> void Tune::Entry::read_option() { value(); } +template <> void Tune::Entry::print_option(std::ostream &) const {} + +template <> void Tune::Entry::print_option(std::ostream &) const {} + +namespace { + +struct EntryInfo { + std::string base; + int value; + std::vector idx; +}; + +EntryInfo parse_entry(const std::string &name, int value) { + EntryInfo info; + size_t bracket = name.find('['); + if (bracket == std::string::npos) { + info.base = name; + info.value = value; + return info; + } + info.base = name.substr(0, bracket); + while (bracket != std::string::npos) { + size_t end = name.find(']', bracket); + if (end == std::string::npos) + break; + info.idx.push_back(std::stoi(name.substr(bracket + 1, end - bracket - 1))); + bracket = name.find('[', end + 1); + } + info.value = value; + return info; +} + +void deduce_shape(const std::vector &entries, std::vector &shape) { + if (entries.empty()) + return; + int ndim = (int)entries[0].idx.size(); + shape.assign(ndim, 0); + for (auto &e : entries) + for (int d = 0; d < ndim; d++) + if (e.idx[d] >= shape[d]) + shape[d] = e.idx[d] + 1; +} + +void print_array(std::ostream &os, const std::vector &flat, const int *shape, int ndim, int dim, int &pos) { + if (dim == ndim - 1) { + os << "{ "; + for (int i = 0; i < shape[dim]; i++) { + if (i) + os << ", "; + os << flat[pos++]; + } + os << " }"; + } else { + os << "{ "; + for (int i = 0; i < shape[dim]; i++) { + if (i) + os << ", "; + print_array(os, flat, shape, ndim, dim + 1, pos); + } + os << " }"; + } +} + +} // namespace + +void Tune::export_weights(std::ostream &os) { + std::vector scalars; + std::map> groups; + + for (auto &e : instance().list) { + auto entry = dynamic_cast *>(e.get()); + if (!entry) + continue; + auto info = parse_entry(entry->name, entry->value); + if (info.idx.empty()) + scalars.push_back(info); + else + groups[info.base].push_back(info); + } + + os << "#ifndef WEIGHTS_H\n"; + os << "#define WEIGHTS_H\n"; + os << "#include \"eval.h\"\n"; + os << "namespace engine::eval {\n"; + + for (auto &e : scalars) + os << "inline Value " << e.base << " = " << e.value << ";\n"; + + for (auto &[base, entries] : groups) { + std::vector shape; + deduce_shape(entries, shape); + int ndim = (int)shape.size(); + // Build flat buffer, zero-initialized, fill from entries + int total = 1; + for (int d = 0; d < ndim; d++) + total *= shape[d]; + std::vector flat(total, 0); + for (auto &e : entries) { + int linear = 0, stride = total; + for (int d = 0; d < ndim; d++) { + stride /= shape[d]; + linear += e.idx[d] * stride; + } + flat[linear] = e.value; + } + os << "inline Value " << base; + for (int d = 0; d < ndim; d++) + os << "[" << shape[d] << "]"; + os << " = "; + int pos = 0; + print_array(os, flat, shape.data(), ndim, 0, pos); + os << ";\n"; + } + + os << "} // namespace engine::eval\n"; + os << "#endif\n"; +} + } // namespace engine // Init options with tuning session results instead of default values. Useful to diff --git a/tune.h b/tune.h index b58a3b5..328d80d 100644 --- a/tune.h +++ b/tune.h @@ -20,6 +20,7 @@ #define TUNE_H_INCLUDED #include +#include #include #include #include // IWYU pragma: keep @@ -88,11 +89,13 @@ class Tune { return t; } // Singleton + public: // Use polymorphism to accommodate Entry of different types in the same vector struct EntryBase { virtual ~EntryBase() = default; virtual void init_option() = 0; virtual void read_option() = 0; + virtual void print_option(std::ostream &) const = 0; }; template struct Entry : public EntryBase { @@ -105,12 +108,16 @@ class Tune { void operator=(const Entry &) = delete; // Because 'value' is a reference void init_option() override; void read_option() override; + void print_option(std::ostream &) const override; std::string name; T &value; SetRange range; }; + static std::vector> &get_list() { return instance().list; } + + private: // Our facility to fill the container, each Entry corresponds to a parameter // to tune. We use variadic templates to deal with an unspecified number of // entries, each one of a possible different type. @@ -152,6 +159,11 @@ class Tune { e->init_option(); read_options(); } // Deferred, due to UCIEngine::Options access + template static void visit_entries(F &&f) { + for (auto &e : instance().list) + f(e.get()); + } + static void export_weights(std::ostream &); static void read_options() { for (auto &e : instance().list) e->read_option(); diff --git a/tune_cmd.cpp b/tune_cmd.cpp new file mode 100644 index 0000000..6f4d65d --- /dev/null +++ b/tune_cmd.cpp @@ -0,0 +1,942 @@ +#include "tune_cmd.h" +#include "Weights.h" +#include "eval.h" +#include "tune.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace engine { + +namespace { + +double sigmoid(double cp, double K = 0.004) { return 1.0 / (1.0 + std::exp(-K * cp)); } + +struct TuneParam { + std::string name; + int *value_ptr; + int lower; + int upper; +}; + +std::vector collect_params() { + std::vector params; + for (auto &e : Tune::get_list()) { + if (auto entry = dynamic_cast *>(e.get())) { + auto bounds = entry->range(entry->value); + params.push_back({ entry->name, &entry->value, bounds.first, bounds.second }); + } + } + return params; +} + +struct TuneData { + std::vector fen_buf; + std::vector offsets; + std::vector results; + + size_t size() const { return results.size(); } + + void add(const std::string &fen, double result) { + offsets.push_back(fen_buf.size()); + fen_buf.insert(fen_buf.end(), fen.begin(), fen.end()); + fen_buf.push_back('\0'); + results.push_back(result); + } + + double result(size_t i) const { return results[i]; } +}; + +double eval_loss(const TuneData &data, const std::vector &indices, int num_threads = 0) { + if (indices.empty()) + return 0.0; + if (num_threads <= 0) + num_threads = (int)std::thread::hardware_concurrency(); + if (num_threads < 1) + num_threads = 1; + + size_t n = indices.size(); + std::vector partial(num_threads, 0.0); + std::vector threads; + + auto worker = [&](int tid, size_t start, size_t end) { + chess::Position pos; + std::string fen; + double sum = 0.0; + for (size_t j = start; j < end; j++) { + size_t idx = indices[j]; + fen.assign(data.fen_buf.data() + data.offsets[idx]); + pos.set_fen(fen); + Value score = eval::eval(pos); + double diff = sigmoid(score) - data.result(idx); + sum += diff * diff; + } + partial[tid] = sum; + }; + + size_t chunk = (n + num_threads - 1) / num_threads; + for (int t = 0; t < num_threads; t++) { + size_t start = t * chunk; + size_t end = std::min(start + chunk, n); + if (start >= n) + break; + threads.emplace_back(worker, t, start, end); + } + + for (auto &t : threads) + t.join(); + + double loss = std::accumulate(partial.begin(), partial.end(), 0.0); + return loss / n; +} + +std::unordered_map build_addr_map(const std::vector ¶ms) { + std::unordered_map map; + map.reserve(params.size()); + for (int i = 0; i < (int)params.size(); i++) + map[params[i].value_ptr] = i; + return map; +} + +// Compute passed-pawn mask for a single square and color +chess::Bitboard passed_pawn_mask(chess::Color c, chess::Square sq) { + chess::Bitboard mask = 0; + chess::File f = chess::file_of(sq); + int startF = std::max(0, (int)f - 1); + int endF = std::min(7, (int)f + 1); + for (int adjF = startF; adjF <= endF; adjF++) { + if (c == chess::WHITE) { + for (int r = (int)chess::rank_of(sq) + 1; r <= 7; r++) + mask |= chess::attacks::MASK_FILE[adjF] & chess::attacks::MASK_RANK[r]; + } else { + for (int r = (int)chess::rank_of(sq) - 1; r >= 0; r--) + mask |= chess::attacks::MASK_FILE[adjF] & chess::attacks::MASK_RANK[r]; + } + } + return mask; +} + +static constexpr int KnightPhase = 1; +static constexpr int BishopPhase = 1; +static constexpr int RookPhase = 2; +static constexpr int QueenPhase = 4; +static constexpr int TotalPhase = KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; + +int compute_game_phase(const chess::Board &board) { + int phase = 0; + chess::Bitboard occ = board.occ(); + while (occ) { + chess::Square sq = chess::Square(chess::pop_lsb(occ)); + auto pt = chess::piece_of(board.at(sq)); + if (pt == chess::KNIGHT) + phase += KnightPhase; + else if (pt == chess::BISHOP) + phase += BishopPhase; + else if (pt == chess::ROOK) + phase += RookPhase; + else if (pt == chess::QUEEN) + phase += QueenPhase; + } + return (phase * 256 + TotalPhase / 2) / TotalPhase; +} + +void accumulate_gradient(const chess::Board &board, + double common_factor, + const std::unordered_map &addr_to_idx, + double phase_mg, + double phase_eg, + double stm_sign, + std::vector &gradient) { + using namespace chess; + using namespace engine::eval; + + Bitboard pawnBB[2] = { board.pieces(PAWN, WHITE), board.pieces(PAWN, BLACK) }; + Bitboard pawnAtks[2] = { attacks::pawn(pawnBB[WHITE]), attacks::pawn(pawnBB[BLACK]) }; + + for (Color c : { WHITE, BLACK }) { + double s = (c == WHITE) ? 1.0 : -1.0; + Square qStart = c == WHITE ? SQ_D1 : SQ_D8; + if (!(board.pieces(QUEEN, c) & (1ULL << qStart))) { + Bitboard backRank = c == WHITE ? attacks::MASK_RANK[0] : attacks::MASK_RANK[7]; + int undeveloped = popcount((board.pieces(KNIGHT, c) | board.pieces(BISHOP, c)) & backRank); + if (undeveloped >= 2) { + auto it = addr_to_idx.find(&earlyQueenPenalty); + if (it != addr_to_idx.end()) + gradient[it->second] += common_factor * (s * stm_sign) * (-undeveloped); + } + } + } + + { + Bitboard pinMask = board.pin_mask(); + Bitboard occ = board.occ(), occ2 = occ; + + while (occ) { + Square sq = Square(pop_lsb(occ)); + auto p = board.at(sq); + Color pc = color_of(p); + double _sign = (pc == WHITE) ? 1.0 : -1.0; + double eff = _sign * stm_sign; + Square _sq = _sign == 1.0 ? sq : square_mirror(sq); + auto pt = piece_of(p); + if (pt == NO_PIECE_TYPE) + continue; + + { + auto *mg_arr = mgPst[pt]; + auto *eg_arr = egPst[pt]; + auto it_mg = addr_to_idx.find(&mg_arr[_sq]); + auto it_eg = addr_to_idx.find(&eg_arr[_sq]); + if (it_mg != addr_to_idx.end()) + gradient[it_mg->second] += common_factor * eff * phase_mg; + if (it_eg != addr_to_idx.end()) + gradient[it_eg->second] += common_factor * eff * phase_eg; + } + + auto add_mat = [&](auto *addr) { + auto it = addr_to_idx.find(addr); + if (it != addr_to_idx.end()) { + gradient[it->second] += common_factor * eff * phase_mg; + gradient[it->second] += common_factor * eff * phase_eg; + } + }; + switch (pt) { + case PAWN: + add_mat(&PawnValue); + break; + case KNIGHT: + add_mat(&KnightValue); + break; + case BISHOP: + add_mat(&BishopValue); + break; + case ROOK: + add_mat(&RookValue); + break; + case QUEEN: + add_mat(&QueenValue); + break; + default: + break; + } + + Bitboard atks = 0; + if (pt == KNIGHT) + atks = chess::attacks::knight(sq); + else if (pt == BISHOP) + atks = chess::attacks::bishop(sq, occ2); + else if (pt == ROOK) + atks = chess::attacks::rook(sq, occ2); + else if (pt == QUEEN) + atks = chess::attacks::queen(sq, occ2); + atks &= ~board.us(pc); + int mobility = popcount(atks); + int cmob = std::min(mobility, 7); + if ((1ULL << sq) & pinMask) + cmob = std::min(cmob / 4, 7); + { + auto it_mg = addr_to_idx.find(&mgMobilityCnt[pt][cmob]); + auto it_eg = addr_to_idx.find(&egMobilityCnt[pt][cmob]); + if (it_mg != addr_to_idx.end()) + gradient[it_mg->second] += common_factor * eff * phase_mg; + if (it_eg != addr_to_idx.end()) + gradient[it_eg->second] += common_factor * eff * phase_eg; + } + + if (pt != PAWN && pt != KING) { + int kd = square_distance(sq, board.kingSq(~pc)); + double coeff = 7 - kd; + auto it_mg = addr_to_idx.find(&kingTropismMg[pt]); + auto it_eg = addr_to_idx.find(&kingTropismEg[pt]); + if (it_mg != addr_to_idx.end()) + gradient[it_mg->second] += common_factor * eff * coeff * phase_mg; + if (it_eg != addr_to_idx.end()) + gradient[it_eg->second] += common_factor * eff * coeff * phase_eg; + } + + if (pt != PAWN && pt != KING) { + int kdist = square_distance(sq, board.kingSq(pc)); + int ki = std::min(kdist, 5); + double coeff = 1.0 / (1 + ki); + auto it_mg = addr_to_idx.find(&kingProtector[pt][0]); + auto it_eg = addr_to_idx.find(&kingProtector[pt][1]); + if (it_mg != addr_to_idx.end()) + gradient[it_mg->second] += common_factor * eff * coeff * phase_mg; + if (it_eg != addr_to_idx.end()) + gradient[it_eg->second] += common_factor * eff * coeff * phase_eg; + } + + if (pt == KNIGHT || pt == BISHOP) { + Rank r = rank_of(sq); + bool onOutpost = (pc == WHITE && r >= RANK_5) || (pc == BLACK && r <= RANK_4); + if (onOutpost && !(pawnAtks[~pc] & (1ULL << sq))) { + bool defended = (pawnAtks[pc] & (1ULL << sq)) != 0; + int idx = defended ? 1 : 0; + auto *arr = (pt == KNIGHT) ? outpostBonusKnight : outpostBonusBishop; + auto it = addr_to_idx.find(&arr[idx]); + if (it != addr_to_idx.end()) { + gradient[it->second] += common_factor * eff * phase_mg; + gradient[it->second] += common_factor * eff * phase_eg; + } + } + } + } + } + + for (Color c : { WHITE, BLACK }) { + double s = (c == WHITE) ? 1.0 : -1.0; + if (board.count(BISHOP, c) >= 2) { + double eff = s * stm_sign; + auto it_mg = addr_to_idx.find(&bishopPairMg); + auto it_eg = addr_to_idx.find(&bishopPairEg); + if (it_mg != addr_to_idx.end()) + gradient[it_mg->second] += common_factor * eff; + if (it_eg != addr_to_idx.end()) + gradient[it_eg->second] += common_factor * eff; + } + } + + for (Color c : { WHITE, BLACK }) { + double s = (c == WHITE) ? 1.0 : -1.0; + Square fSq[2] = { relative_square(c, SQ_B2), relative_square(c, SQ_G2) }; + Square pSq[2] = { relative_square(c, SQ_B3), relative_square(c, SQ_G3) }; + for (int i = 0; i < 2; i++) { + if (board.at(fSq[i]) == BISHOP && board.at(fSq[i]) == c && board.at(pSq[i]) == PAWN && + board.at(pSq[i]) == c) { + auto it = addr_to_idx.find(&fianchettoBonus); + if (it != addr_to_idx.end()) + gradient[it->second] += common_factor * (s * stm_sign); + } + } + } + + for (Color c : { WHITE, BLACK }) { + double s = (c == WHITE) ? 1.0 : -1.0; + Square trapSq[2] = { relative_square(c, SQ_A2), relative_square(c, SQ_H2) }; + Square kingAdj[2] = { relative_square(c, SQ_B1), relative_square(c, SQ_G1) }; + for (int i = 0; i < 2; i++) { + if (board.at(trapSq[i]) == BISHOP && board.at(trapSq[i]) == c && board.kingSq(c) == kingAdj[i]) { + auto it = addr_to_idx.find(&trappedBishopPenalty); + if (it != addr_to_idx.end()) + gradient[it->second] += common_factor * (-s * stm_sign); + } + } + } + for (Color c : { WHITE, BLACK }) { + double s = (c == WHITE) ? 1.0 : -1.0; + Color opp = ~c; + Square sqs[2] = { relative_square(c, SQ_A7), relative_square(c, SQ_H7) }; + Square pSqs[2] = { relative_square(c, SQ_B6), relative_square(c, SQ_G6) }; + for (int i = 0; i < 2; i++) { + if (board.at(sqs[i]) == BISHOP && board.at(sqs[i]) == c) { + if (board.at(pSqs[i]) == PAWN && board.at(pSqs[i]) == opp && + !board.is_attacked_by(c, sqs[i])) { + auto it = addr_to_idx.find(&trappedBishopPenalty); + if (it != addr_to_idx.end()) + gradient[it->second] += common_factor * (-s * stm_sign); + } + } + } + } + + for (Color c : { WHITE, BLACK }) { + double s = (c == WHITE) ? 1.0 : -1.0; + Bitboard rooks = board.pieces(ROOK, c); + while (rooks) { + Square sq = Square(pop_lsb(rooks)); + File f = file_of(sq); + Bitboard fileMask = attacks::MASK_FILE[f]; + bool hasOwn = (board.pieces(PAWN, c) & fileMask) != 0; + bool hasEnemy = (board.pieces(PAWN, ~c) & fileMask) != 0; + double eff = s * stm_sign; + if (!hasOwn && !hasEnemy) { + auto it_mg = addr_to_idx.find(&rookOpenFileMg); + auto it_eg = addr_to_idx.find(&rookOpenFileEg); + if (it_mg != addr_to_idx.end()) + gradient[it_mg->second] += common_factor * eff; + if (it_eg != addr_to_idx.end()) + gradient[it_eg->second] += common_factor * eff; + } else if (!hasOwn) { + auto it_mg = addr_to_idx.find(&rookSemiOpenFileMg); + auto it_eg = addr_to_idx.find(&rookSemiOpenFileEg); + if (it_mg != addr_to_idx.end()) + gradient[it_mg->second] += common_factor * eff; + if (it_eg != addr_to_idx.end()) + gradient[it_eg->second] += common_factor * eff; + } + } + } + + for (Color c : { WHITE, BLACK }) { + double s = (c == WHITE) ? 1.0 : -1.0; + Bitboard rooks = board.pieces(ROOK, c); + while (rooks) { + Square sq = Square(pop_lsb(rooks)); + Rank r7 = c == WHITE ? RANK_7 : RANK_2; + if (rank_of(sq) != r7) + continue; + Bitboard ep = board.pieces(PAWN, ~c) & attacks::MASK_RANK[r7]; + int cnt = 0; + Bitboard tmp = ep; + while (tmp) { + Square psq = Square(pop_lsb(tmp)); + if (!board.is_attacked_by(~c, psq)) + cnt++; + } + if (cnt >= 2) { + double eff = s * stm_sign; + auto it = addr_to_idx.find(&rookOnSeventhBonus); + if (it != addr_to_idx.end()) { + gradient[it->second] += common_factor * eff * 0.5 * phase_mg; + gradient[it->second] += common_factor * eff * phase_eg; + } + } + } + } + + for (Color c : { WHITE, BLACK }) { + double s = (c == WHITE) ? 1.0 : -1.0; + double eff = s * stm_sign; + Bitboard pawns = pawnBB[c]; + Bitboard enemyPawns = pawnBB[~c]; + bool fileHasPawn[8] = { false }; + int fileCount[8] = { 0 }; + Bitboard tmp = pawns; + while (tmp) { + Square sq = Square(pop_lsb(tmp)); + fileCount[file_of(sq)]++; + fileHasPawn[file_of(sq)] = true; + } + + for (int f = 0; f < 8; f++) + if (fileCount[f] >= 2) { + double coeff = -(fileCount[f] - 1); + auto it_mg = addr_to_idx.find(&doubledPawnMg); + auto it_eg = addr_to_idx.find(&doubledPawnEg); + if (it_mg != addr_to_idx.end()) + gradient[it_mg->second] += common_factor * eff * coeff; + if (it_eg != addr_to_idx.end()) + gradient[it_eg->second] += common_factor * eff * coeff; + } + + tmp = pawns; + while (tmp) { + Square sq = Square(pop_lsb(tmp)); + File f = file_of(sq); + Rank relRank = relative_rank(c, sq); + + bool isolated = true; + if (f > FILE_A && fileHasPawn[f - 1]) + isolated = false; + if (f < FILE_H && fileHasPawn[f + 1]) + isolated = false; + if (isolated) { + auto it_mg = addr_to_idx.find(&isolatedPawnMg); + auto it_eg = addr_to_idx.find(&isolatedPawnEg); + if (it_mg != addr_to_idx.end()) + gradient[it_mg->second] += common_factor * eff * (-1.0); + if (it_eg != addr_to_idx.end()) + gradient[it_eg->second] += common_factor * eff * (-1.0); + } + + if (relRank >= RANK_2 && (passed_pawn_mask(c, sq) & enemyPawns) == 0) { + auto it_mg = addr_to_idx.find(&passedBonusMg[relRank]); + auto it_eg = addr_to_idx.find(&passedBonusEg[relRank]); + if (it_mg != addr_to_idx.end()) + gradient[it_mg->second] += common_factor * eff; + if (it_eg != addr_to_idx.end()) + gradient[it_eg->second] += common_factor * eff; + } + } + } + + for (Color c : { WHITE, BLACK }) { + double s = (c == WHITE) ? 1.0 : -1.0; + Bitboard pawns = pawnBB[c]; + while (pawns) { + Square sq = Square(pop_lsb(pawns)); + Square forward = sq + pawn_push(c); + if (forward >= SQ_A1 && forward <= SQ_H8 && (pawnBB[~c] & (1ULL << forward))) { + auto it = addr_to_idx.find(&rammedPawnPenalty); + if (it != addr_to_idx.end()) + gradient[it->second] += common_factor * (-s * stm_sign); + } + } + } + + { + Bitboard centerMask = (1ULL << SQ_D4) | (1ULL << SQ_E4) | (1ULL << SQ_D5) | (1ULL << SQ_E5); + int wc = popcount(pawnAtks[WHITE] & centerMask); + int bc = popcount(pawnAtks[BLACK] & centerMask); + auto it = addr_to_idx.find(¢erWeight); + if (it != addr_to_idx.end()) + gradient[it->second] += common_factor * stm_sign * (wc - bc); + } + + for (Color c : { WHITE, BLACK }) { + double s = (c == WHITE) ? 1.0 : -1.0; + Bitboard safe = ~pawnAtks[~c]; + Bitboard camp = c == WHITE ? (attacks::MASK_RANK[4] | attacks::MASK_RANK[5] | attacks::MASK_RANK[6]) + : (attacks::MASK_RANK[3] | attacks::MASK_RANK[2] | attacks::MASK_RANK[1]); + int space = popcount(pawnAtks[c] & safe & camp); + auto it = addr_to_idx.find(&spaceWeight); + if (it != addr_to_idx.end()) + gradient[it->second] += common_factor * (s * stm_sign) * space; + } + + for (Color c : { WHITE, BLACK }) { + double s = (c == WHITE) ? 1.0 : -1.0; + Square kingSq = board.kingSq(c); + File kf = file_of(kingSq); + Rank kr = rank_of(kingSq); + Bitboard pawns = board.pieces(PAWN, c); + int startF = std::max(0, (int)kf - 1); + int endF = std::min(7, (int)kf + 1); + double eff_base = s * stm_sign; + for (int adjF = startF; adjF <= endF; adjF++) { + Bitboard fileMask = attacks::MASK_FILE[adjF]; + auto check_rank = [&](int r) { + if (!(pawns & (fileMask & attacks::MASK_RANK[r]))) + return; + double dist = (c == WHITE) ? (r - (int)kr - 1) : ((int)kr - r - 1); + auto it_bmg = addr_to_idx.find(&kingShelterBaseMg); + auto it_beg = addr_to_idx.find(&kingShelterBaseEg); + auto it_dmg = addr_to_idx.find(&kingShelterDecayMg); + auto it_deg = addr_to_idx.find(&kingShelterDecayEg); + if (it_bmg != addr_to_idx.end()) + gradient[it_bmg->second] += common_factor * eff_base; + if (it_beg != addr_to_idx.end()) + gradient[it_beg->second] += common_factor * eff_base; + if (it_dmg != addr_to_idx.end()) + gradient[it_dmg->second] += common_factor * eff_base * (-dist); + if (it_deg != addr_to_idx.end()) + gradient[it_deg->second] += common_factor * eff_base * (-dist); + }; + if (c == WHITE) { + for (int r = (int)kr + 1; r <= std::min(7, (int)kr + 3); r++) + check_rank(r); + } else { + for (int r = (int)kr - 1; r >= std::max(0, (int)kr - 3); r--) + check_rank(r); + } + } + } + + for (Color c : { WHITE, BLACK }) { + double s = (c == WHITE) ? 1.0 : -1.0; + double eff = s * stm_sign; + int oppCount = popcount(board.occ(~c)); + Square oppKing = board.kingSq(~c); + File ef = file_of(oppKing); + Rank er = rank_of(oppKing); + int edgeDist = std::min(std::min((int)ef, 7 - (int)ef), std::min((int)er, 7 - (int)er)); + + if (board.count(QUEEN, c) >= 1 && oppCount == 1) { + Square myKing = board.kingSq(c); + int kd = std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), + std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); + auto it1 = addr_to_idx.find(&kqkDistWeight); + auto it2 = addr_to_idx.find(&kqkEdgeWeight); + if (it1 != addr_to_idx.end()) + gradient[it1->second] += common_factor * eff * (14 - kd); + if (it2 != addr_to_idx.end()) + gradient[it2->second] += common_factor * eff * (7 - edgeDist); + } + + if (board.count(ROOK, c) >= 1 && oppCount == 1 && board.count(QUEEN, c) == 0 && board.count(BISHOP, c) == 0 && + board.count(KNIGHT, c) == 0 && board.count(PAWN, c) == 0) { + Square myKing = board.kingSq(c); + int kd = std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), + std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); + auto it1 = addr_to_idx.find(&krkDistWeight); + auto it2 = addr_to_idx.find(&krkEdgeWeight); + if (it1 != addr_to_idx.end()) + gradient[it1->second] += common_factor * eff * (14 - kd); + if (it2 != addr_to_idx.end()) + gradient[it2->second] += common_factor * eff * (7 - edgeDist); + } + + if (board.count(PAWN, c) >= 1 && oppCount == 1 && board.count(QUEEN, c) == 0 && board.count(ROOK, c) == 0 && + board.count(BISHOP, c) == 0 && board.count(KNIGHT, c) == 0) { + Bitboard ps = pawnBB[c]; + while (ps) { + Square psq = Square(pop_lsb(ps)); + Rank pr = relative_rank(c, psq); + if (pr < RANK_4) + continue; + File pf = file_of(psq); + Square promSq = make_sq(pf, c == WHITE ? RANK_8 : RANK_1); + Square myKing = board.kingSq(c); + int pawnDist = (c == WHITE) ? (7 - (int)rank_of(psq)) : (int)rank_of(psq); + int pkDist = + std::max(std::abs((int)file_of(oppKing) - (int)pf), std::abs((int)rank_of(oppKing) - (int)rank_of(promSq))); + int mkDist = std::max(std::abs((int)file_of(myKing) - (int)file_of(psq)), + std::abs((int)rank_of(myKing) - (int)rank_of(psq))); + bool winning = pkDist > pawnDist || mkDist <= pawnDist + 1 || + (pr >= RANK_6 && file_of(myKing) == pf && + ((c == WHITE && rank_of(myKing) >= RANK_6) || (c == BLACK && rank_of(myKing) <= RANK_3))); + if ((pf == FILE_A || pf == FILE_H) && oppKing == promSq) + winning = false; + if (winning) { + auto it = addr_to_idx.find(&kpkWeight); + if (it != addr_to_idx.end()) + gradient[it->second] += common_factor * eff * pawnDist; + } + } + } + + Bitboard ourMat = board.occ(c) & ~board.pieces(PAWN) & ~board.pieces(KING); + Bitboard theirMat = board.occ(~c) & ~board.pieces(PAWN) & ~board.pieces(KING); + if (ourMat && !theirMat && oppCount <= 2) { + Square myKing = board.kingSq(c); + int kd = std::max(std::abs((int)file_of(myKing) - (int)file_of(oppKing)), + std::abs((int)rank_of(myKing) - (int)rank_of(oppKing))); + auto it1 = addr_to_idx.find(&mopUpKingDistWeight); + auto it2 = addr_to_idx.find(&mopUpEdgeDistWeight); + if (it1 != addr_to_idx.end()) + gradient[it1->second] += common_factor * eff * (14 - kd); + if (it2 != addr_to_idx.end()) + gradient[it2->second] += common_factor * eff * (7 - edgeDist); + } + } + + for (Color c : { WHITE, BLACK }) { + double s = (c == WHITE) ? 1.0 : -1.0; + Color opp = ~c; + Bitboard ourPieces = board.occ(c) & ~board.pieces(PAWN) & ~board.pieces(KING); + while (ourPieces) { + Square sq = Square(pop_lsb(ourPieces)); + PieceType pt = piece_of(board.at(sq)); + bool attacked = board.is_attacked_by(opp, sq); + if (!attacked) + continue; + bool defended = board.is_attacked_by(c, sq); + Bitboard occ = board.occ(); + int attackers = popcount(board.attackers_mask(opp, sq, occ)); + int defenders = defended ? popcount(board.attackers_mask(c, sq, occ)) : 0; + bool hanging = !defended; + bool overloaded = defenders == 1 && attackers >= 2; + bool attackedByMinor = + (board.attackers_mask(opp, sq, occ) & (board.pieces(KNIGHT, opp) | board.pieces(BISHOP, opp))) != 0; + bool attackedByRook = (board.attackers_mask(opp, sq, occ) & board.pieces(ROOK, opp)) != 0; + + double eff_threat = -s * stm_sign; + + if (hanging) { + double mg_hf = (pt == QUEEN) ? 0.5 : 1.0; + double eg_hf = (pt == QUEEN) ? 0.25 : 0.5; + auto it = addr_to_idx.find(&hangingScore); + if (it != addr_to_idx.end()) { + gradient[it->second] += common_factor * eff_threat * mg_hf * phase_mg; + gradient[it->second] += common_factor * eff_threat * eg_hf * phase_eg; + } + } + + if (overloaded) { + auto it = addr_to_idx.find(&overloadScore); + if (it != addr_to_idx.end()) { + gradient[it->second] += common_factor * eff_threat * phase_mg; + gradient[it->second] += common_factor * eff_threat * 0.5 * phase_eg; + } + } + + if (attackedByMinor) { + auto it_mg = addr_to_idx.find(&threatByMinor[pt][0]); + auto it_eg = addr_to_idx.find(&threatByMinor[pt][1]); + if (it_mg != addr_to_idx.end()) + gradient[it_mg->second] += common_factor * eff_threat; + if (it_eg != addr_to_idx.end()) + gradient[it_eg->second] += common_factor * eff_threat; + } + + if (attackedByRook) { + auto it_mg = addr_to_idx.find(&threatByRook[pt][0]); + auto it_eg = addr_to_idx.find(&threatByRook[pt][1]); + if (it_mg != addr_to_idx.end()) + gradient[it_mg->second] += common_factor * eff_threat; + if (it_eg != addr_to_idx.end()) + gradient[it_eg->second] += common_factor * eff_threat; + } + + Rank relRank = relative_rank(c, sq); + auto it = addr_to_idx.find(&threatByRankScore); + if (it != addr_to_idx.end()) { + gradient[it->second] += common_factor * eff_threat * relRank * phase_mg; + gradient[it->second] += common_factor * eff_threat * relRank * phase_eg; + } + } + } + + { + int pc[2][7] = { { 0 } }; + for (Color c : { WHITE, BLACK }) + for (PieceType pt : { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING }) + pc[c][pt] = board.count(pt, c); + int ourM = pc[WHITE][KNIGHT] + pc[WHITE][BISHOP]; + int theirM = pc[BLACK][KNIGHT] + pc[BLACK][BISHOP]; + auto add_imb = [&](auto *addr, int coeff) { + auto it = addr_to_idx.find(addr); + if (it != addr_to_idx.end()) + gradient[it->second] += common_factor * stm_sign * coeff; + }; + add_imb(&minorImWt, ourM - theirM); + add_imb(&bishopImWt, pc[WHITE][BISHOP] - pc[BLACK][BISHOP]); + add_imb(&rookImWt, pc[WHITE][ROOK] - pc[BLACK][ROOK]); + add_imb(&queenImWt, pc[WHITE][QUEEN] - pc[BLACK][QUEEN]); + } +} + +void texel_tune(TuneData &all, + const std::vector &train_idx, + const std::vector &holdout_idx, + std::vector ¶ms, + int iterations, + const std::string &weights_header) { + auto addr_map = build_addr_map(params); + int dim = (int)params.size(); + int num_threads = (int)std::thread::hardware_concurrency(); + if (num_threads < 1) + num_threads = 1; + size_t n = train_idx.size(); + + double base_loss = eval_loss(all, train_idx); + std::cout << "info string texel_tune: dim=" << dim << " positions=" << n << " initial_loss=" << base_loss * train_idx.size() + << std::endl; + + double lr = 1.0; + + std::vector best_x(dim); + for (int i = 0; i < dim; i++) + best_x[i] = *params[i].value_ptr; + double best_loss = base_loss; + + for (int iter = 0; iter < iterations; iter++) { + auto t0 = std::chrono::steady_clock::now(); + + std::vector gradient(dim, 0.0); + std::vector threads; + std::vector partial_loss(num_threads, 0.0); + std::vector> partial_grad(num_threads, std::vector(dim, 0.0)); + size_t chunk = (n + num_threads - 1) / num_threads; + + auto worker = [&](int tid, size_t start, size_t end) { + chess::Position pos; + std::string fen; + double ploss = 0.0; + auto &pg = partial_grad[tid]; + + for (size_t j = start; j < end; j++) { + size_t idx = train_idx[j]; + fen.assign(all.fen_buf.data() + all.offsets[idx]); + pos.set_fen(fen); + + auto comp = eval::eval_components(pos); + const int sign = pos.side_to_move() == chess::WHITE ? 1 : -1; + Value score = (((comp.mg * comp.phase) + (comp.eg * (256 - comp.phase))) * sign) / 256 + eval::tempo; + + double sig = sigmoid(score); + double error = sig - all.result(idx); + ploss += error * error; + + // Avoid division by zero in gradient + if (std::abs(error) < 1e-12) + continue; + + double sig_deriv = 0.004 * sig * (1.0 - sig); + double common_factor = 2.0 * error * sig_deriv; + double phase_mg = comp.phase / 256.0; + double phase_eg = (256 - comp.phase) / 256.0; + double stm_sign_val = (pos.side_to_move() == chess::WHITE) ? 1.0 : -1.0; + + accumulate_gradient(pos, common_factor, addr_map, phase_mg, phase_eg, stm_sign_val, pg); + } + partial_loss[tid] = ploss; + }; + + for (int t = 0; t < num_threads; t++) { + size_t start = t * chunk; + size_t end = std::min(start + chunk, n); + if (start >= n) + break; + threads.emplace_back(worker, t, start, end); + } + for (auto &t : threads) + t.join(); + + double loss_sum = std::accumulate(partial_loss.begin(), partial_loss.end(), 0.0); + double train_loss = loss_sum / n; + + // Accumulate gradients from all threads + for (int i = 0; i < dim; i++) { + gradient[i] = 0.0; + for (int t = 0; t < num_threads; t++) { + gradient[i] += partial_grad[t][i]; + } + } + + std::vector old_x(dim); + for (int i = 0; i < dim; i++) + old_x[i] = *params[i].value_ptr; + + // Gradient clipping to prevent exploding gradients + double max_abs_grad = 0.0; + for (int i = 0; i < dim; i++) { + // Clip gradient magnitude + if (std::abs(gradient[i]) > 1000.0) { + gradient[i] = (gradient[i] > 0) ? 1000.0 : -1000.0; + } + max_abs_grad = std::max(max_abs_grad, std::abs(gradient[i])); + } + if (max_abs_grad < 1e-12) + max_abs_grad = 1.0; + + double step_size = lr; + double new_loss = train_loss; + int halvings = 0; + + for (int attempt = 0; attempt < 12; attempt++) { + double scale = step_size / max_abs_grad; + bool any_change = false; + + // Store current parameter values before making changes + std::vector current_values(dim); + for (int i = 0; i < dim; i++) { + current_values[i] = *params[i].value_ptr; + } + + // Apply gradient step + for (int i = 0; i < dim; i++) { + double nv = old_x[i] - scale * gradient[i]; + nv = std::clamp(nv, (double)params[i].lower, (double)params[i].upper); + int vi = int(std::round(nv)); + if (vi != *params[i].value_ptr) + any_change = true; + *params[i].value_ptr = vi; + } + + if (!any_change) { + // No parameter changed, restore and break + for (int i = 0; i < dim; i++) { + *params[i].value_ptr = current_values[i]; + } + break; + } + + // Evaluate new loss + new_loss = eval_loss(all, train_idx); + + if (new_loss < train_loss) { + // Improvement found, keep changes and update learning rate + lr = step_size; + break; + } else { + // No improvement, restore parameters and try smaller step + for (int i = 0; i < dim; i++) { + *params[i].value_ptr = current_values[i]; + } + step_size *= 0.5; + halvings++; + } + } + + double hold_loss = eval_loss(all, holdout_idx); + + if (new_loss < best_loss) { + best_loss = new_loss; + for (int i = 0; i < dim; i++) + best_x[i] = *params[i].value_ptr; + } + + auto t1 = std::chrono::steady_clock::now(); + double elapsed = std::chrono::duration_cast(t1 - t0).count() / 1000.0; + + std::cout << "info string iter=" << iter << " train=" << train_loss << " hold=" << hold_loss << " lr=" << lr + << " halvings=" << halvings << " max_grad=" << max_abs_grad << " time=" << elapsed << "s" << std::endl; + + if (new_loss >= best_loss && halvings > 0 && step_size < 1.0 / 128.0) { + std::cout << "info string no further improvement, stopping" << std::endl; + break; + } + } + + for (int i = 0; i < dim; i++) + *params[i].value_ptr = int(std::round(best_x[i])); + + // Export the best weights found + std::fstream file(weights_header, std::ios::out); + Tune::export_weights(file); +} + +} // namespace + +void tune_command(const std::string &csv_path, int iterations, int max_pos, const std::string &weights_header) { + auto start_time = std::chrono::steady_clock::now(); + + csv::CSVReader reader(csv_path); + TuneData all; + std::random_device rd; + std::mt19937_64 load_rng(rd()); + size_t seen = 0; + for (auto &row : reader) { + auto fen = row["fen"].get<>(); + auto result = row["result"].get(); + seen++; + if (all.size() < (size_t)max_pos) { + all.add(fen, result); + } else { + std::uniform_int_distribution dist(0, seen - 1); + size_t j = dist(load_rng); + if (j < all.size()) { + all.offsets[j] = all.fen_buf.size(); + all.fen_buf.insert(all.fen_buf.end(), fen.begin(), fen.end()); + all.fen_buf.push_back('\0'); + all.results[j] = result; + } + } + } + std::cout << "info string loaded " << all.size() << " positions via reservoir sampling (seen " << seen << ")" << std::endl; + + size_t n = all.size(); + size_t holdout_size = std::min(size_t(2000), n / 5); + + std::vector idx(n); + for (size_t i = 0; i < n; i++) + idx[i] = i; + std::mt19937 rng(rd()); + std::shuffle(idx.begin(), idx.end(), rng); + + std::vector holdout_idx(idx.begin(), idx.begin() + holdout_size); + std::vector train_idx(idx.begin() + holdout_size, idx.end()); + + auto params = collect_params(); + int dim = (int)params.size(); + + std::cout << "info string loaded " << n << " positions, dim=" << dim << " train=" << train_idx.size() + << " holdout=" << holdout_idx.size() << std::endl; + + double base_train = eval_loss(all, train_idx); + double base_hold = eval_loss(all, holdout_idx); + std::cout << "info string baseline train=" << base_train << " holdout=" << base_hold << std::endl; + + texel_tune(all, train_idx, holdout_idx, params, iterations, weights_header); + + double final_hold = eval_loss(all, holdout_idx); + auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start_time).count(); + + std::cout << "info string tune done in " << elapsed << "s, best holdout=" << final_hold * holdout_size + << " (baseline=" << base_hold * holdout_size << ")" << std::endl; + std::fstream file(weights_header, std::ios::out); + Tune::export_weights(file); +} + +} // namespace engine diff --git a/tune_cmd.h b/tune_cmd.h new file mode 100644 index 0000000..bade97c --- /dev/null +++ b/tune_cmd.h @@ -0,0 +1,5 @@ +#pragma once +#include +namespace engine { +void tune_command(const std::string &csv_path, int iterations, int max_pos, const std::string &weights_header); +} // namespace engine diff --git a/uci.cpp b/uci.cpp index 3e2f58d..7f0bf75 100644 --- a/uci.cpp +++ b/uci.cpp @@ -2,16 +2,22 @@ #include "eval.h" #include "search.h" #include "timeman.h" +#include "tune.h" +#ifdef USE_CSV_PARSER +#include "tune_cmd.h" +#endif #include "ucioption.h" #include #include #include +#include #include #include #include #include #include using namespace engine; +bool quit{ false }; chess::Position pos; OptionsMap engine::options; std::thread searchThread; @@ -108,6 +114,7 @@ timeman::LimitsType parse_limits(std::istream &is) { else if (token == "infinite") limits.infinite = 1; else if (token == "ponder") { + limits.ponderMode = true; } return limits; @@ -115,6 +122,8 @@ timeman::LimitsType parse_limits(std::istream &is) { void handleGo(std::istringstream &ss) { stop(); + if (searchThread.joinable()) + searchThread.join(); chess::Position copy = pos; searchThread = std::thread([copy, ss = std::move(ss)]() mutable { search::search(copy, parse_limits(ss)); }); @@ -184,51 +193,101 @@ void engine::report(std::string_view bestmove) { std::cout << "bestmove " << bestmove; std::cout << std::endl; } +void execCmd(const std::string &line) { + std::istringstream ss(line); + std::string token; + + while (ss >> token) { + if (token == "uci") { + std::cout << "id name cppchess_engine\n"; + std::cout << "id author winapiadmin\n"; + std::cout << options << '\n'; + std::cout << "uciok\n"; + break; + } else if (token == "isready") { + std::cout << "readyok\n"; + break; + } else if (token == "position") { + handlePosition(ss); + break; + } else if (token == "go") { + handleGo(ss); + break; // rest belongs to go + } else if (token == "ucinewgame") { + search::tt.clear(); + break; + } else if (token == "stop") { + break; + } else if (token == "quit") { + quit = true; + return; + } else if (token == "setoption") { + options.setoption(ss); + break; + } else if (token == "visualize" || token == "d") { + std::cout << pos << std::endl; + break; + } else if (token == "eval") { + handlePosition(ss); // auto-handle it if any + int score = eval::eval(pos); + if (pos.side_to_move() != chess::WHITE) + score = -score; + std::cout << score << std::endl; + break; + } else if (token == "export_weights") { + std::string weights_header = "Weights.h"; + ss >> weights_header; + std::fstream file(weights_header, std::ios::out); + Tune::export_weights(file); + std::cout << "Dumped weights to " << weights_header << '\n'; + break; + } else if (token == "tune") { +#ifdef USE_CSV_PARSER + std::string csv_path, out_file = "Weights.h"; + int iters = 50, max_pos = 20000; + ss >> csv_path >> iters >> max_pos; + tune_command(csv_path, iters, max_pos, out_file); +#else + std::cout << "info string engine built without tuning support" << std::endl; +#endif + break; + } else if (token == "evalbatch") { + char c; + bool first = true; + while (ss >> c) { + if (c != '"') + continue; + std::string fen; + while (ss.get(c) && c != '"') + fen += c; + if (!first) + std::cout << ' '; + first = false; + try { + chess::Position bp; + bp.setFEN(fen); + int sc = eval::eval(bp); + if (bp.side_to_move() != chess::WHITE) + sc = -sc; + std::cout << sc; + } catch (...) { + std::cout << "0"; + } + } + std::cout << std::endl; + break; + } + } +} void engine::loop() { std::string line; pos.setFEN(chess::Position::START_FEN); - while (std::getline(std::cin, line)) { - std::istringstream ss(line); - std::string token; + while (!quit && std::getline(std::cin, line)) { stop(); - while (ss >> token) { - if (token == "uci") { - std::cout << "id name cppchess_engine\n"; - std::cout << "id author winapiadmin\n"; - std::cout << options << '\n'; - std::cout << "uciok\n"; - break; - } else if (token == "isready") { - std::cout << "readyok\n"; - break; - } else if (token == "position") { - handlePosition(ss); - break; - } else if (token == "go") { - handleGo(ss); - break; // rest belongs to go - } else if (token == "ucinewgame") { - search::tt.clear(); - break; - } else if (token == "stop") { - break; - } else if (token == "quit") { - return; - } else if (token == "setoption") { - options.setoption(ss); - break; - } else if (token == "visualize" || token == "d") { - std::cout << pos << std::endl; - break; - } else if (token == "eval") { - int score = eval::eval(pos); - if (pos.side_to_move() != chess::WHITE) - score = -score; - std::cout << score << std::endl; - break; - } - } + execCmd(line); } stop(); + if (searchThread.joinable()) + searchThread.join(); } From 17d8bf44a4b1202b320eb553e5a458b8e6ea0c5e Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 7 Jul 2026 14:58:00 +0700 Subject: [PATCH 17/29] timeman improve: use ply --- search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search.cpp b/search.cpp index 32fde8a..5d50356 100644 --- a/search.cpp +++ b/search.cpp @@ -546,7 +546,7 @@ void search(const chess::Board &board, const timeman::LimitsType timecontrol) { static double originalTimeAdjust = -1; Session session; session.tc = timecontrol; - session.tm.init(session.tc, board.side_to_move(), 0, originalTimeAdjust); + session.tm.init(session.tc, board.side_to_move(), board.ply(), originalTimeAdjust); session.lastLogTime = session.tm.elapsed(); session.ogcolor = board.side_to_move(); chess::Move lastPV[MAX_PLY]{}; From 97d854153f292c4c47f4bfcb4639e057437f7989 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 7 Jul 2026 08:02:05 +0000 Subject: [PATCH 18/29] Apply clang-format --- Weights.h | 21 ++++++++++++--------- movepick.cpp | 41 ++++++++++++++++++++--------------------- search.cpp | 6 ++---- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Weights.h b/Weights.h index e2b0e6f..cb4853f 100644 --- a/Weights.h +++ b/Weights.h @@ -59,9 +59,10 @@ inline Value mg_knight_table[64] = { 1, -5, 8, -21, -11, -2, -24, -2, 23, -5, -15, -8, 14, 19, -2, 2, -25, -13, 15, -1, -1, 5, -18, 5, 6, -1, -13, 1, 17, -24, -13, 14, 19, -10, 6, 1, 16, -13, -18, 8, 24, 14, -21, -8, -21, -15, -1, -1, 8, -17, 22, -18, 6, -11, -4, -9, -7 }; -inline Value mg_bishop_table[64] = { 3, 21, -23, 16, 5, -20, 7, -4, 14, -15, 21, -11, 7, 2, -4, 19, -5, -4, 22, 11, 15, 25, - 20, -9, -3, -4, 20, 21, 10, 11, -4, -21, 9, 9, 10, 5, -3, 0, 16, 23, -1, 5, -10, -12, - 9, -24, -9, -10, 5, -17, -8, 5, 4, -3, 6, -4, 1, 2, 20, -4, 9, 3, -20, -22 }; +inline Value mg_bishop_table[64] = { 3, 21, -23, 16, 5, -20, 7, -4, 14, -15, 21, -11, 7, 2, -4, 19, + -5, -4, 22, 11, 15, 25, 20, -9, -3, -4, 20, 21, 10, 11, -4, -21, + 9, 9, 10, 5, -3, 0, 16, 23, -1, 5, -10, -12, 9, -24, -9, -10, + 5, -17, -8, 5, 4, -3, 6, -4, 1, 2, 20, -4, 9, 3, -20, -22 }; inline Value mg_rook_table[64] = { -10, 20, -13, 18, 12, -3, 13, -25, -11, 3, 10, -4, 7, 13, -24, 5, -3, -7, -19, -3, 4, -6, 13, 13, 11, 20, 16, -6, 14, -15, -16, -15, 3, 7, 14, -10, -7, 20, -15, 10, 25, 1, 3, -14, 22, 5, 1, -12, @@ -70,9 +71,10 @@ inline Value mg_king_table[64] = { 11, -12, -10, -19, 5, -6, -2, -10, 19, -5, 16, 19, 2, -21, -14, 11, -16, -12, -13, -18, 14, -23, -14, 16, -5, -16, 0, -21, -12, -2, 14, -16, 17, 11, -10, 21, 14, -5, -15, 19, 13, 8, -21, -13, 4, 1, 1, 16, 13, -8, 13, 6, -20, -4, 3, 8, -2 }; -inline Value mg_queen_table[64] = { -2, -2, 4, 11, 9, -2, 0, -5, 14, -2, -11, 14, -13, -11, -3, -21, 7, -14, -4, -6, -3, -16, - -4, -11, -2, 9, -16, 6, -6, -12, -14, -7, -4, -22, -12, 6, 21, -4, -6, -5, -6, -7, -4, 2, - 13, 11, -8, 14, 18, -5, -25, 22, -14, 18, -3, 9, -13, 21, 15, -21, 13, 7, -9, 3 }; +inline Value mg_queen_table[64] = { -2, -2, 4, 11, 9, -2, 0, -5, 14, -2, -11, 14, -13, -11, -3, -21, + 7, -14, -4, -6, -3, -16, -4, -11, -2, 9, -16, 6, -6, -12, -14, -7, + -4, -22, -12, 6, 21, -4, -6, -5, -6, -7, -4, 2, 13, 11, -8, 14, + 18, -5, -25, 22, -14, 18, -3, 9, -13, 21, 15, -21, 13, 7, -9, 3 }; inline Value eg_knight_table[64] = { -13, 25, -3, 3, -7, 18, -21, 3, -1, 15, -13, 3, -16, 15, -5, 8, -11, -12, 22, 14, 10, 20, 9, -6, -10, 2, -13, -7, -17, 2, 18, -19, 7, 12, -23, 17, -5, 17, 10, -7, -18, -2, -10, -14, 20, 15, -13, 18, @@ -85,9 +87,10 @@ inline Value eg_rook_table[64] = { -21, -24, 12, -12, -11, 15, -17, -22, -14, -1, 9, 18, 3, 4, -19, 20, -21, -15, -21, 9, -11, -2, 4, -8, 15, -6, 13, -6, -10, -3, 12, 16, -18, -19, -19, 23, 3, -14, -23, 3, 15, 18, -6, -24, 21, 18, 3, -2, 17, 8, 13, -11, -13, 0, -16, -4, -10 }; -inline Value eg_king_table[64] = { -14, 17, 13, -10, -15, 3, -8, -3, 19, 19, -2, 6, -2, 7, -7, -7, 21, 10, 17, -21, 0, 15, - -6, -9, -7, 24, 8, 0, 17, 12, -4, -6, 8, -16, 19, 0, 9, 20, 18, 12, -12, -2, -4, -14, - 21, 2, -6, -5, 17, -2, -3, -9, -1, -23, 7, -14, 9, 2, 22, -20, 3, 22, -10, 2 }; +inline Value eg_king_table[64] = { -14, 17, 13, -10, -15, 3, -8, -3, 19, 19, -2, 6, -2, 7, -7, -7, + 21, 10, 17, -21, 0, 15, -6, -9, -7, 24, 8, 0, 17, 12, -4, -6, + 8, -16, 19, 0, 9, 20, 18, 12, -12, -2, -4, -14, 21, 2, -6, -5, + 17, -2, -3, -9, -1, -23, 7, -14, 9, 2, 22, -20, 3, 22, -10, 2 }; inline Value eg_queen_table[64] = { -7, -3, 13, 11, 2, 17, 2, -19, 12, -5, -2, -11, 2, -7, 19, 2, -1, 15, 19, 1, 15, 8, -19, -6, -14, -25, -8, 22, 4, -22, -10, 20, 3, -1, -3, -17, 12, -3, -4, -4, -7, 16, -18, -22, -2, -11, 4, -14, diff --git a/movepick.cpp b/movepick.cpp index d174558..9a7e968 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -18,47 +18,48 @@ static Bitboard att(PieceType pt, Square sq, Bitboard occ) { return 0; } } -inline Square least_valuable_attacker(const Position& board, - Bitboard attackers, - Color side) { +inline Square least_valuable_attacker(const Position &board, Bitboard attackers, Color side) { Bitboard bb; bb = attackers & board.pieces(PAWN, side); - if (bb) return Square(pop_lsb(bb)); + if (bb) + return Square(pop_lsb(bb)); bb = attackers & board.pieces(KNIGHT, side); - if (bb) return Square(pop_lsb(bb)); + if (bb) + return Square(pop_lsb(bb)); bb = attackers & board.pieces(BISHOP, side); - if (bb) return Square(pop_lsb(bb)); + if (bb) + return Square(pop_lsb(bb)); bb = attackers & board.pieces(ROOK, side); - if (bb) return Square(pop_lsb(bb)); + if (bb) + return Square(pop_lsb(bb)); bb = attackers & board.pieces(QUEEN, side); - if (bb) return Square(pop_lsb(bb)); + if (bb) + return Square(pop_lsb(bb)); bb = attackers & board.pieces(KING, side); - if (bb) return Square(pop_lsb(bb)); + if (bb) + return Square(pop_lsb(bb)); return SQ_NONE; } -Value see(Board& board, Move move) { +Value see(Board &board, Move move) { Square from = move.from(); - Square to = move.to(); + Square to = move.to(); - PieceType captured = - move.type_of() == EN_PASSANT ? PAWN : board.at(to); + PieceType captured = move.type_of() == EN_PASSANT ? PAWN : board.at(to); if (captured == NO_PIECE_TYPE) return 0; Bitboard occ = board.occ(); - occ ^= 1ULL<(move.from()); @@ -85,12 +86,10 @@ Value see(Board& board, Move move) { Square sq = least_valuable_attacker(board, stmAttackers, stm); attacker = board.at(sq); - occ ^= 1ULL< Date: Tue, 7 Jul 2026 15:05:29 +0700 Subject: [PATCH 19/29] Update tune.cpp --- tune.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tune.cpp b/tune.cpp index 0e46cbb..72bc0b2 100644 --- a/tune.cpp +++ b/tune.cpp @@ -60,7 +60,7 @@ void Tune::make_option(OptionsMap *opts, const string &n, int v, const SetRange opts->add(n, Option(v, r(v).first, r(v).second, on_tune)); LastOption = &((*opts)[n]); - auto &[a, b] = r(v); + auto [a, b] = r(v); if (!(a <= v && v <= b)) { std::cerr << "wrong bounds, name: " << n << '\n'; std::exit(1); @@ -72,10 +72,10 @@ void Tune::make_option(OptionsMap *opts, const string &n, int v, const SetRange // or OpenBench << "int" << "," #endif - << v << "," // - << r(v).first << "," // - << r(v).second << "," // - << (r(v).second - r(v).first) / 20.0 << "," // + << v << "," // + << a << "," // + << b << "," // + << (b - a) / 20.0 << "," // << "0.0020" << std::endl; } From d0aa0b873a9e83a65ce8496265b845c9bd0ed663 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 7 Jul 2026 08:05:57 +0000 Subject: [PATCH 20/29] Apply clang-format --- tune.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tune.cpp b/tune.cpp index 72bc0b2..694e334 100644 --- a/tune.cpp +++ b/tune.cpp @@ -72,10 +72,10 @@ void Tune::make_option(OptionsMap *opts, const string &n, int v, const SetRange // or OpenBench << "int" << "," #endif - << v << "," // - << a << "," // - << b << "," // - << (b - a) / 20.0 << "," // + << v << "," // + << a << "," // + << b << "," // + << (b - a) / 20.0 << "," // << "0.0020" << std::endl; } From 7d4d48ace837f23271991531f177e497a286a3d1 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:09:34 +0700 Subject: [PATCH 21/29] Update CMakeLists.txt --- CMakeLists.txt | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a54fa9..c2792d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,8 +17,28 @@ FetchContent_Declare( GIT_TAG main ) set(BUILD_GAVIOTA OFF CACHE BOOL "Enable Gaviota TB probing" FORCE) +option(ENGINE_TUNING "enable Texel tuning (requires csv-parser)" OFF) FetchContent_MakeAvailable(chesslib tbprobe) -add_executable(engine "main.cpp" "timeman.cpp" "timeman.h" "eval.h" "eval.cpp" "tune.h" "ucioption.h" "tune.cpp" "ucioption.cpp" "tt.h" "tt.cpp" "uci.cpp" "uci.h" "search.h" "search.cpp" "score.h" "score.cpp" "movepick.h" "movepick.cpp" "tb.h" "tb.cpp" "Weights.h" "tune_cmd.h" "tune_cmd.cpp") +set(SOURCES + "main.cpp" + "timeman.cpp" + "eval.cpp" + "tune.cpp" + "ucioption.cpp" + "tt.cpp" + "uci.cpp" + "search.cpp" + "score.cpp" + "movepick.cpp" + "tb.cpp" +) + +if(ENGINE_TUNING) + list(APPEND SOURCES "tune_cmd.cpp") +endif() + +add_executable(engine ${SOURCES}) + target_link_libraries(engine PRIVATE chesslib syzygy_probe) target_include_directories(engine PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${chesslib_SOURCE_DIR}) execute_process( @@ -46,7 +66,6 @@ endif() message(STATUS "Build Version: ${BUILD_VERSION}") target_compile_definitions(engine PRIVATE BUILD_VERSION="${BUILD_VERSION}") -option(ENGINE_TUNING "enable Texel tuning (requires csv-parser)" OFF) if (ENGINE_TUNING) FetchContent_Declare( csv From 259e89e0642f984276b90fde8fd69ecfe5110e16 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 7 Jul 2026 21:56:38 +0700 Subject: [PATCH 22/29] Update games.yml --- .github/workflows/games.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 1b501fd..e40aa7f 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -12,9 +12,9 @@ on: required: false default: "6000" tc: - description: "Time control (e.g. 60+0.5 or 10+0.1)" + description: "Time control (e.g. 60+0.6 or 10+0.1)" required: false - default: "60+0.5" + default: "60+0.6" output_exec: description: "Executable output file name" required: false @@ -115,7 +115,7 @@ jobs: -rounds ${{ github.event.inputs.rounds }} \ -concurrency $(nproc) \ -pgnout notation=san nodes=true file=games.pgn \ - -sprt elo0=0 elo1=2 alpha=0.05 beta=0.05 | tee results.txt + -sprt elo0=0 elo1=5 alpha=0.05 beta=0.05 | tee results.txt ./ordo-linux64 -o ratings.txt -- games.pgn sed -n '/Results of new vs base/,/^--------------------------------------------------$/p' results.txt >> ratings.txt @@ -127,4 +127,4 @@ jobs: path: | games.pgn ratings.txt - results.txt \ No newline at end of file + results.txt From 3faf5fad3670dd7a4ea4f354c4a951c482f739f6 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 8 Jul 2026 07:27:11 +0700 Subject: [PATCH 23/29] eol norm and Board -> Position --- Makefile | 3 ++- eval.cpp | 7 ++++--- eval.h | 4 ++-- movepick.cpp | 4 ++-- movepick.h | 4 ++-- search.cpp | 37 +++++++++++++++++++++++++++---------- search.h | 2 +- tb.cpp | 4 ++-- tb.h | 4 ++-- tt.h | 2 +- tune_cmd.cpp | 4 ++-- 11 files changed, 47 insertions(+), 28 deletions(-) diff --git a/Makefile b/Makefile index 1233d49..0886893 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,8 @@ endif deps: test -d deps/chesslib || git clone https://github.com/winapiadmin/chesslib deps/chesslib test -d deps/tbprobe || git clone https://github.com/winapiadmin/tb_probing_tool deps/tbprobe -SRCS = $(wildcard *.cpp) $(wildcard deps/chesslib/*.cpp) $(wildcard deps/tbprobe/*.cpp) +# Tuning is not required on Makefile, use CMake. +SRCS = $(filter-out tuning_cmd.cpp, $(wildcard *.cpp)) $(wildcard deps/chesslib/*.cpp) $(wildcard deps/tbprobe/*.cpp) OBJS = $(SRCS:.cpp=.o) all: deps $(TARGET) diff --git a/eval.cpp b/eval.cpp index 3e6cdd1..4b2762a 100644 --- a/eval.cpp +++ b/eval.cpp @@ -225,7 +225,7 @@ TUNE(SetRange(1, 50), rammedPawnPenalty); TUNE(SetRange(1, 100), rookOnSeventhBonus); TUNE(SetRange(1, 50), earlyQueenPenalty); -EvalComponents eval_components(const chess::Board &board) { +EvalComponents eval_components(const chess::Position &board) { constexpr int KnightPhase = 1; constexpr int BishopPhase = 1; constexpr int RookPhase = 2; @@ -260,7 +260,8 @@ EvalComponents eval_components(const chess::Board &board) { int undeveloped = popcount((board.pieces(KNIGHT, c) | board.pieces(BISHOP, c)) & backRank); if (undeveloped >= 2) { mgScore -= s * earlyQueenPenalty; - egScore -= s * earlyQueenPenalty; + //disabled intentionally in endgames + //egScore -= s * earlyQueenPenalty; } } } @@ -714,7 +715,7 @@ EvalComponents eval_components(const chess::Board &board) { phase = (phase * 256 + TotalPhase / 2) / TotalPhase; return { mgScore, egScore, phase }; } -Value eval(const chess::Board &board) { +Value eval(const chess::Position &board) { const int sign = board.side_to_move() == WHITE ? 1 : -1; auto [mg, eg, phase] = eval_components(board); return (((mg * phase) + (eg * (256 - phase))) * sign) / 256 + engine::eval::tempo; diff --git a/eval.h b/eval.h index be45ea7..aecdc5a 100644 --- a/eval.h +++ b/eval.h @@ -50,8 +50,8 @@ struct EvalComponents { extern Value *mgPst[]; extern Value *egPst[]; -Value eval(const chess::Board &board); -EvalComponents eval_components(const chess::Board &board); +Value eval(const chess::Position &board); +EvalComponents eval_components(const chess::Position &board); Value piece_value(chess::PieceType pt); } // namespace eval } // namespace engine diff --git a/movepick.cpp b/movepick.cpp index 9a7e968..e9f00c5 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -47,7 +47,7 @@ inline Square least_valuable_attacker(const Position &board, Bitboard attackers, return SQ_NONE; } -Value see(Board &board, Move move) { +Value see(Position &board, Move move) { Square from = move.from(); Square to = move.to(); @@ -100,7 +100,7 @@ Value see(Board &board, Move move) { return gain[0]; } -void orderMoves(Board &board, Movelist &moves, Move ttMove, int ply, const engine::search::Session &session, Move prevMove) { +void orderMoves(Position &board, Movelist &moves, Move ttMove, int ply, const engine::search::Session &session, Move prevMove) { Value scores[300]; size_t n = moves.size(); for (size_t i = 0; i < n; ++i) { diff --git a/movepick.h b/movepick.h index c7728ff..91396be 100644 --- a/movepick.h +++ b/movepick.h @@ -5,6 +5,6 @@ namespace engine::search { struct Session; } // namespace engine::search namespace engine::movepick { -void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int, const engine::search::Session &, chess::Move); -Value see(chess::Board &, chess::Move); +void orderMoves(chess::Position &, chess::Movelist &, chess::Move, int, const engine::search::Session &, chess::Move); +Value see(chess::Position &, chess::Move); } // namespace engine::movepick diff --git a/search.cpp b/search.cpp index 1bc155c..18c5b09 100644 --- a/search.cpp +++ b/search.cpp @@ -67,7 +67,7 @@ Value value_from_tt(Value v, int ply, int r50c) { return v; } } // namespace -Value qsearch(Board &board, Value alpha, Value beta, search::Session &session, int ply) { +Value qsearch(Position &board, Value alpha, Value beta, search::Session &session, int ply) { session.nodes++; session.qnodes++; session.seldepth = std::max(session.seldepth, ply); @@ -179,7 +179,7 @@ Value qsearch(Board &board, Value alpha, Value beta, search::Session &session, i return best; } Value doSearch( - Board &board, int depth, Value alpha, Value beta, search::Session &session, int ply = 0, Move prevMove = Move::none()) { + Position &board, int depth, Value alpha, Value beta, search::Session &session, int ply = 0, Move prevMove = Move::none()) { session.nodes++; session.seldepth = std::max(session.seldepth, ply); if (ply >= MAX_PLY - 1) @@ -504,9 +504,9 @@ Value doSearch( } return maxScore; } -std::string extract_pv(const chess::Board &root, int maxPly) { +std::string extract_pv(const chess::Position &root, int maxPly) { std::string pv; - chess::Board pos = root; + chess::Position pos = root; std::unordered_set visited; visited.insert(pos.hash()); for (int ply = 0; ply < maxPly; ply++) { @@ -538,7 +538,7 @@ std::string extract_pv(const chess::Board &root, int maxPly) { return pv; } -void search(const chess::Board &board, const timeman::LimitsType timecontrol) { +void search(const chess::Position &board, const timeman::LimitsType timecontrol) { stopSearch = false; tt.newSearch(); static double originalTimeAdjust = -1; @@ -564,10 +564,27 @@ void search(const chess::Board &board, const timeman::LimitsType timecontrol) { } { - Movelist legal; - board.legals(legal); - if (legal.size()) - lastPV[0] = legal[0]; + Movelist moves; + board.legals(moves); + Position board_ = board; + Move best = Move::none(); + Value bestScore = -VALUE_INFINITE; + Session tmpSession{}; + for (Move move : moves) { + if (!session.tc.searchmoves.empty() && + std::find(session.tc.searchmoves.begin(), + session.tc.searchmoves.end(), + chess::uci::moveToUci(move, board.chess960())) == session.tc.searchmoves.end()) + continue; + board_.doMove(move); + Value score = -qsearch(board_, -VALUE_INFINITE, VALUE_INFINITE, tmpSession, 0); + if (score > bestScore) { + bestScore = score; + best = move; + } + board_.undoMove(); + } + lastPV[0]=best; } for (int i = 1; i <= timecontrol.depth; i++) { @@ -649,7 +666,7 @@ void search(const chess::Board &board, const timeman::LimitsType timecontrol) { board.legals(moves); if (moves.size()) { - Board board_ = board; + Position board_ = board; Move best = Move::none(); Value bestScore = -VALUE_INFINITE; Session tmpSession{}; diff --git a/search.h b/search.h index be1c304..ac458c1 100644 --- a/search.h +++ b/search.h @@ -19,7 +19,7 @@ struct Session { chess::Color ogcolor; }; void stop(); -void search(const chess::Board &, const timeman::LimitsType); +void search(const chess::Position &, const timeman::LimitsType); bool isStopped(); extern engine::TranspositionTable tt; } // namespace engine::search diff --git a/tb.cpp b/tb.cpp index 61d0efa..b45f42e 100644 --- a/tb.cpp +++ b/tb.cpp @@ -37,7 +37,7 @@ std::size_t wdl_count() { return unique_table_count(tablebase.wdl); } std::size_t dtz_count() { return unique_table_count(tablebase.dtz); } -int probe_wdl(chess::Board &board) { +int probe_wdl(chess::Position &board) { try { return *tablebase.get_wdl(board, TB_ERROR); } catch (const std::exception &) { @@ -45,7 +45,7 @@ int probe_wdl(chess::Board &board) { } } -int probe_dtz(chess::Board &board) { +int probe_dtz(chess::Position &board) { try { return *tablebase.get_dtz(board, TB_ERROR); } catch (const std::exception &) { diff --git a/tb.h b/tb.h index 4425f2a..de215d0 100644 --- a/tb.h +++ b/tb.h @@ -10,6 +10,6 @@ void init(const std::string &path); constexpr int TB_ERROR = 1000; std::size_t wdl_count(); std::size_t dtz_count(); -int probe_wdl(chess::Board &board); -int probe_dtz(chess::Board &board); +int probe_wdl(chess::Position &board); +int probe_dtz(chess::Position &board); } // namespace engine::tb diff --git a/tt.h b/tt.h index e648370..8ae16c4 100644 --- a/tt.h +++ b/tt.h @@ -102,7 +102,7 @@ class TranspositionTable { ~TranspositionTable() { delete[] table; } void resize(int sizeInMB) { - int new_size = sizeInMB * 1048576 / sizeof(TTEntry); + size_t new_size = sizeInMB * 1048576LL / sizeof(TTEntry); if (new_size % 2 != 0) new_size--; diff --git a/tune_cmd.cpp b/tune_cmd.cpp index 6f4d65d..daaf21d 100644 --- a/tune_cmd.cpp +++ b/tune_cmd.cpp @@ -132,7 +132,7 @@ static constexpr int RookPhase = 2; static constexpr int QueenPhase = 4; static constexpr int TotalPhase = KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; -int compute_game_phase(const chess::Board &board) { +int compute_game_phase(const chess::Position &board) { int phase = 0; chess::Bitboard occ = board.occ(); while (occ) { @@ -150,7 +150,7 @@ int compute_game_phase(const chess::Board &board) { return (phase * 256 + TotalPhase / 2) / TotalPhase; } -void accumulate_gradient(const chess::Board &board, +void accumulate_gradient(const chess::Position &board, double common_factor, const std::unordered_map &addr_to_idx, double phase_mg, From 6dc67fcf9b96b35c1479929417712e7481e35f70 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 8 Jul 2026 00:27:43 +0000 Subject: [PATCH 24/29] Apply clang-format --- search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search.cpp b/search.cpp index 18c5b09..5f269a6 100644 --- a/search.cpp +++ b/search.cpp @@ -584,7 +584,7 @@ void search(const chess::Position &board, const timeman::LimitsType timecontrol) } board_.undoMove(); } - lastPV[0]=best; + lastPV[0] = best; } for (int i = 1; i <= timecontrol.depth; i++) { From fba7bd7687716aa9d1f60ccd4bb1e1937a2d49fb Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 8 Jul 2026 08:05:36 +0700 Subject: [PATCH 25/29] resolved issues --- Makefile | 3 ++- eval.cpp | 4 +++- main.cpp | 9 +++++++-- movepick.cpp | 6 +++--- search.cpp | 15 ++++++++++++--- search.h | 6 +++--- tb.cpp | 10 +++++++--- tt.cpp | 6 +++--- tt.h | 2 +- tune.cpp | 3 ++- tune_cmd.cpp | 30 ++++++++++++++++++++++++------ uci.cpp | 12 ++++++++---- 12 files changed, 75 insertions(+), 31 deletions(-) diff --git a/Makefile b/Makefile index 0886893..f3bd4fa 100644 --- a/Makefile +++ b/Makefile @@ -31,9 +31,10 @@ deps: test -d deps/chesslib || git clone https://github.com/winapiadmin/chesslib deps/chesslib test -d deps/tbprobe || git clone https://github.com/winapiadmin/tb_probing_tool deps/tbprobe # Tuning is not required on Makefile, use CMake. -SRCS = $(filter-out tuning_cmd.cpp, $(wildcard *.cpp)) $(wildcard deps/chesslib/*.cpp) $(wildcard deps/tbprobe/*.cpp) +SRCS = $(filter-out tune_cmd.cpp, $(wildcard *.cpp)) $(wildcard deps/chesslib/*.cpp) $(wildcard deps/tbprobe/*.cpp) OBJS = $(SRCS:.cpp=.o) +.PHONY: all clean deps all: deps $(TARGET) $(TARGET): $(OBJS) diff --git a/eval.cpp b/eval.cpp index 4b2762a..2e2c54d 100644 --- a/eval.cpp +++ b/eval.cpp @@ -712,12 +712,14 @@ EvalComponents eval_components(const chess::Position &board) { board.count() == 0 && board.count() == 0) return { 0, 0, 0 }; - phase = (phase * 256 + TotalPhase / 2) / TotalPhase; + phase = std::min((phase * 256 + TotalPhase / 2) / TotalPhase, 256); return { mgScore, egScore, phase }; } Value eval(const chess::Position &board) { const int sign = board.side_to_move() == WHITE ? 1 : -1; auto [mg, eg, phase] = eval_components(board); + if (mg == 0 && eg == 0) + return 0; return (((mg * phase) + (eg * (256 - phase))) * sign) / 256 + engine::eval::tempo; } Value piece_value(PieceType pt) { diff --git a/main.cpp b/main.cpp index f9290bd..9ae3626 100644 --- a/main.cpp +++ b/main.cpp @@ -4,6 +4,7 @@ #include "uci.h" #include "ucioption.h" #include +#include using namespace engine; #define STR_HELPER(x) #x #define STR(x) STR_HELPER(x) @@ -13,7 +14,11 @@ int main() { std::cout << "cppchess_engine version " << BUILD_VERSION << '\n'; options.add("Move Overhead", Option(10, 0, 1000)); options.add("Hash", Option(16, 1, 1 << 25, [](const Option &o) { - search::tt.resize(int(o)); + try { + search::tt.resize(int(o)); + } catch (std::bad_alloc &) { + std::cerr << "info string Hash resize failed: bad_alloc\n"; + } return std::nullopt; })); @@ -22,11 +27,11 @@ int main() { return std::nullopt; })); - // work in progress options.add("SyzygyPath", Option("", [](const Option &o) { tb::init(std::string(o)); return std::nullopt; })); Tune::init(options); + std::cout.flush(); loop(); } diff --git a/movepick.cpp b/movepick.cpp index e9f00c5..4134810 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -58,6 +58,8 @@ Value see(Position &board, Move move) { Bitboard occ = board.occ(); occ ^= 1ULL << from; + if (move.type_of() == EN_PASSANT) + occ ^= 1ULL << Square(int(to) + (board.side_to_move() == WHITE ? -8 : 8)); Bitboard attackers = board.attackers(WHITE, to, occ) | board.attackers(BLACK, to, occ); @@ -77,9 +79,7 @@ Value see(Position &board, Move move) { if (gain[d] < 0) break; - occ &= attackers; - - Bitboard stmAttackers = attackers & occ & board.occ(stm); + Bitboard stmAttackers = attackers & board.occ(stm); if (!stmAttackers) break; diff --git a/search.cpp b/search.cpp index 18c5b09..9675989 100644 --- a/search.cpp +++ b/search.cpp @@ -72,6 +72,7 @@ Value qsearch(Position &board, Value alpha, Value beta, search::Session &session session.qnodes++; session.seldepth = std::max(session.seldepth, ply); if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || + (session.tc.nodes > 0 && session.nodes >= session.tc.nodes) || stopSearch.load(std::memory_order_relaxed)) return VALUE_NONE; @@ -187,6 +188,7 @@ Value doSearch( if (depth <= 0) return qsearch(board, alpha, beta, session, ply); if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || + (session.tc.nodes > 0 && session.nodes >= session.tc.nodes) || stopSearch.load(std::memory_order_relaxed)) return VALUE_NONE; @@ -417,7 +419,8 @@ Value doSearch( } else if (movesSearched >= 6) { reduction = 1 + movesSearched / 8; } - reduction = std::clamp(reduction, 1, depth - 2); + if (reduction > 0) + reduction = std::clamp(reduction, 1, depth - 2); } int ext = 0; @@ -569,7 +572,10 @@ void search(const chess::Position &board, const timeman::LimitsType timecontrol) Position board_ = board; Move best = Move::none(); Value bestScore = -VALUE_INFINITE; - Session tmpSession{}; + double unusedTimeAdjust = -1; + Session tmpSession; + tmpSession.tc = session.tc; + tmpSession.tm.init(session.tc, board.side_to_move(), board.ply(), unusedTimeAdjust); for (Move move : moves) { if (!session.tc.searchmoves.empty() && std::find(session.tc.searchmoves.begin(), @@ -669,7 +675,10 @@ void search(const chess::Position &board, const timeman::LimitsType timecontrol) Position board_ = board; Move best = Move::none(); Value bestScore = -VALUE_INFINITE; - Session tmpSession{}; + double unusedTimeAdjust = -1; + Session tmpSession; + tmpSession.tc = session.tc; + tmpSession.tm.init(session.tc, board.side_to_move(), board.ply(), unusedTimeAdjust); for (Move move : moves) { if (!session.tc.searchmoves.empty() && std::find(session.tc.searchmoves.begin(), diff --git a/search.h b/search.h index ac458c1..16bb71a 100644 --- a/search.h +++ b/search.h @@ -10,10 +10,10 @@ struct Session { int seldepth = 0; uint64_t nodes = 0, qnodes = 0, lmrResearches = 0; uint64_t tbHits = 0, ttHits = 0, ttCutoffs = 0, nullCutoffs = 0; - chess::Move pv[MAX_PLY][MAX_PLY]; + chess::Move pv[MAX_PLY][MAX_PLY]{}; Value historyHeuristic[chess::SQUARE_NB][chess::SQUARE_NB]{}; - chess::Move killerMoves[MAX_PLY][2]; - chess::Move counterMoves[4096]; + chess::Move killerMoves[MAX_PLY][2]{}; + chess::Move counterMoves[4096]{}; timeman::TimePoint lastLogTime; int depth = 0; chess::Color ogcolor; diff --git a/tb.cpp b/tb.cpp index b45f42e..0904630 100644 --- a/tb.cpp +++ b/tb.cpp @@ -28,9 +28,13 @@ void init(const std::string &path) { return; } - tbprobe::syzygy::initialize(); - tablebase.add_directory(path, true, true); - std::cout << "info string Found " << wdl_count() << " WDL and " << dtz_count() << " DTZ tablebase files\n"; + try { + tbprobe::syzygy::initialize(); + tablebase.add_directory(path, true, true); + std::cout << "info string Found " << wdl_count() << " WDL and " << dtz_count() << " DTZ tablebase files\n"; + } catch (const std::exception &e) { + std::cout << "info string Syzygy init failed: " << e.what() << '\n'; + } } std::size_t wdl_count() { return unique_table_count(tablebase.wdl); } diff --git a/tt.cpp b/tt.cpp index 768b2cd..f95ec6b 100644 --- a/tt.cpp +++ b/tt.cpp @@ -19,8 +19,8 @@ static inline uint64_t index_for_hash(uint64_t hash, uint64_t buckets) { __uint128_t prod = (__uint128_t)hash * (__uint128_t)buckets; return (uint64_t)(prod >> 64); #else - uint64_t aL = uint32_t(hash), aH = a >> 32; - uint64_t bL = uint32_t(buckets), bH = b >> 32; + uint64_t aL = uint32_t(hash), aH = hash >> 32; + uint64_t bL = uint32_t(buckets), bH = buckets >> 32; uint64_t c1 = (aL * bL) >> 32; uint64_t c2 = aH * bL + c1; uint64_t c3 = aL * bH + uint32_t(c2); @@ -56,7 +56,7 @@ void TranspositionTable::store(uint64_t hash, chess::Move best, int16_t score, i } TTEntry *TranspositionTable::lookup(uint64_t hash) { - if (buckets == 0) + if (buckets == 0 || hash == 0) return nullptr; uint64_t bucket = index_for_hash(hash, buckets); diff --git a/tt.h b/tt.h index 8ae16c4..823452b 100644 --- a/tt.h +++ b/tt.h @@ -83,7 +83,7 @@ struct TTEntry { }; class TranspositionTable { TTEntry *table; - int buckets; // number of buckets (pairs) + size_t buckets; // number of buckets (pairs) uint32_t time; public: diff --git a/tune.cpp b/tune.cpp index 694e334..c118521 100644 --- a/tune.cpp +++ b/tune.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -76,7 +77,7 @@ void Tune::make_option(OptionsMap *opts, const string &n, int v, const SetRange << a << "," // << b << "," // << (b - a) / 20.0 << "," // - << "0.0020" << std::endl; + << "0.0020" << '\n'; } string Tune::next(string &names, bool pop) { diff --git a/tune_cmd.cpp b/tune_cmd.cpp index daaf21d..fe5926c 100644 --- a/tune_cmd.cpp +++ b/tune_cmd.cpp @@ -300,9 +300,9 @@ void accumulate_gradient(const chess::Position &board, auto it_mg = addr_to_idx.find(&bishopPairMg); auto it_eg = addr_to_idx.find(&bishopPairEg); if (it_mg != addr_to_idx.end()) - gradient[it_mg->second] += common_factor * eff; + gradient[it_mg->second] += common_factor * eff * phase_mg; if (it_eg != addr_to_idx.end()) - gradient[it_eg->second] += common_factor * eff; + gradient[it_eg->second] += common_factor * eff * phase_eg; } } @@ -851,8 +851,8 @@ void texel_tune(TuneData &all, double hold_loss = eval_loss(all, holdout_idx); - if (new_loss < best_loss) { - best_loss = new_loss; + if (hold_loss < best_loss) { + best_loss = hold_loss; for (int i = 0; i < dim; i++) best_x[i] = *params[i].value_ptr; } @@ -874,12 +874,23 @@ void texel_tune(TuneData &all, // Export the best weights found std::fstream file(weights_header, std::ios::out); - Tune::export_weights(file); + if (file.is_open()) + Tune::export_weights(file); + else + std::cerr << "info string failed to open " << weights_header << " for writing\n"; } } // namespace void tune_command(const std::string &csv_path, int iterations, int max_pos, const std::string &weights_header) { + if (max_pos <= 0) { + std::cerr << "info string max_pos must be positive\n"; + return; + } + if (iterations <= 0) { + std::cerr << "info string iterations must be positive\n"; + return; + } auto start_time = std::chrono::steady_clock::now(); csv::CSVReader reader(csv_path); @@ -904,6 +915,10 @@ void tune_command(const std::string &csv_path, int iterations, int max_pos, cons } } } + if (all.size() == 0) { + std::cerr << "info string no positions loaded from CSV\n"; + return; + } std::cout << "info string loaded " << all.size() << " positions via reservoir sampling (seen " << seen << ")" << std::endl; size_t n = all.size(); @@ -936,7 +951,10 @@ void tune_command(const std::string &csv_path, int iterations, int max_pos, cons std::cout << "info string tune done in " << elapsed << "s, best holdout=" << final_hold * holdout_size << " (baseline=" << base_hold * holdout_size << ")" << std::endl; std::fstream file(weights_header, std::ios::out); - Tune::export_weights(file); + if (file.is_open()) + Tune::export_weights(file); + else + std::cerr << "info string failed to open " << weights_header << " for writing\n"; } } // namespace engine diff --git a/uci.cpp b/uci.cpp index 7f0bf75..230d882 100644 --- a/uci.cpp +++ b/uci.cpp @@ -66,7 +66,7 @@ void handlePosition(std::istringstream &is) { try { pos.setFEN(strip_optional_quotes(fen)); } catch (const std::exception &e) { - std::cerr << "info string Invalid FEN: " << e.what() << std::endl; + std::cout << "info string Invalid FEN: " << e.what() << std::endl; return; } @@ -74,7 +74,7 @@ void handlePosition(std::istringstream &is) { try { pos.push_uci(token); } catch (const std::exception &e) { - std::cerr << "info string Invalid move " << token << ": " << e.what() << std::endl; + std::cout << "info string Invalid move " << token << ": " << e.what() << std::endl; return; } } @@ -238,8 +238,12 @@ void execCmd(const std::string &line) { std::string weights_header = "Weights.h"; ss >> weights_header; std::fstream file(weights_header, std::ios::out); - Tune::export_weights(file); - std::cout << "Dumped weights to " << weights_header << '\n'; + if (file.is_open()) { + Tune::export_weights(file); + std::cout << "info string dumped weights to " << weights_header << '\n'; + } else { + std::cout << "info string failed to open " << weights_header << " for writing\n"; + } break; } else if (token == "tune") { #ifdef USE_CSV_PARSER From dd9c5ab7e694bd70caa3d3fcac3165fc1c8525a5 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 8 Jul 2026 01:06:13 +0000 Subject: [PATCH 26/29] Apply clang-format --- search.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/search.cpp b/search.cpp index cdf1b33..881d596 100644 --- a/search.cpp +++ b/search.cpp @@ -72,8 +72,7 @@ Value qsearch(Position &board, Value alpha, Value beta, search::Session &session session.qnodes++; session.seldepth = std::max(session.seldepth, ply); if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || - (session.tc.nodes > 0 && session.nodes >= session.tc.nodes) || - stopSearch.load(std::memory_order_relaxed)) + (session.tc.nodes > 0 && session.nodes >= session.tc.nodes) || stopSearch.load(std::memory_order_relaxed)) return VALUE_NONE; bool inCheck = board.is_check(); @@ -188,8 +187,7 @@ Value doSearch( if (depth <= 0) return qsearch(board, alpha, beta, session, ply); if (((session.nodes & 2047) == 0 && session.tm.elapsed() >= session.tm.optimum()) || - (session.tc.nodes > 0 && session.nodes >= session.tc.nodes) || - stopSearch.load(std::memory_order_relaxed)) + (session.tc.nodes > 0 && session.nodes >= session.tc.nodes) || stopSearch.load(std::memory_order_relaxed)) return VALUE_NONE; bool inCheck = board.is_check(); From a78e094d1b605ed671d14a60bc8c20106d9c4250 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 8 Jul 2026 09:35:46 +0700 Subject: [PATCH 27/29] -------------------------------------------------- Results of new vs base (10+0.1, NULL, 64MB, UHO_Lichess_4852_v1.epd): Elo: 111.48 +/- 22.41, nElo: 152.89 +/- 28.67 LOS: 100.00 %, DrawRatio: 30.14 %, PairsRatio: 4.05 Games: 564, Wins: 256, Losses: 81, Draws: 227, Points: 369.5 (65.51 %) Ptnml(0-2): [4, 35, 85, 98, 60], WL/DD Ratio: 0.81 LLR: 2.95 (100.3%) (-2.94, 2.94) [0.00, 5.00] -------------------------------------------------- --- Weights.h | 278 +++++++++++++++++++++++++++++++++-------------------- eval.cpp | 6 +- search.cpp | 4 - 3 files changed, 178 insertions(+), 110 deletions(-) diff --git a/Weights.h b/Weights.h index cb4853f..c5b3579 100644 --- a/Weights.h +++ b/Weights.h @@ -2,109 +2,181 @@ #define WEIGHTS_H #include "eval.h" namespace engine::eval { -inline Value tempo = 29; -inline Value PawnValue = 82; -inline Value KnightValue = 284; -inline Value BishopValue = 304; -inline Value RookValue = 461; -inline Value QueenValue = 901; -inline Value fianchettoBonus = 24; -inline Value trappedBishopPenalty = 26; -inline Value centerWeight = 2; -inline Value mopUpKingDistWeight = 4; -inline Value mopUpEdgeDistWeight = 17; -inline Value spaceWeight = 1; -inline Value bishopPairMg = 13; -inline Value bishopPairEg = 23; -inline Value rookOpenFileMg = 30; -inline Value rookOpenFileEg = 25; -inline Value rookSemiOpenFileMg = 20; -inline Value rookSemiOpenFileEg = 17; -inline Value doubledPawnMg = 7; -inline Value doubledPawnEg = 18; -inline Value isolatedPawnMg = 4; -inline Value isolatedPawnEg = 14; -inline Value kingShelterBaseMg = 18; -inline Value kingShelterBaseEg = 4; +inline Value tempo = 20; +inline Value PawnValue = 100; +inline Value KnightValue = 320; +inline Value BishopValue = 330; +inline Value RookValue = 500; +inline Value QueenValue = 900; +inline Value fianchettoBonus = 20; +inline Value trappedBishopPenalty = 60; +inline Value centerWeight = 5; +inline Value mopUpKingDistWeight = 5; +inline Value mopUpEdgeDistWeight = 10; +inline Value spaceWeight = 2; +inline Value bishopPairMg = 25; +inline Value bishopPairEg = 50; +inline Value rookOpenFileMg = 25; +inline Value rookOpenFileEg = 20; +inline Value rookSemiOpenFileMg = 15; +inline Value rookSemiOpenFileEg = 12; +inline Value doubledPawnMg = 10; +inline Value doubledPawnEg = 20; +inline Value isolatedPawnMg = 15; +inline Value isolatedPawnEg = 25; +inline Value kingShelterBaseMg = 20; +inline Value kingShelterBaseEg = 5; inline Value kingShelterDecayMg = 4; inline Value kingShelterDecayEg = 1; -inline Value kqkDistWeight = 17; -inline Value kqkEdgeWeight = 18; -inline Value krkDistWeight = 1; -inline Value krkEdgeWeight = 9; +inline Value kqkDistWeight = 15; +inline Value kqkEdgeWeight = 15; +inline Value krkDistWeight = 5; +inline Value krkEdgeWeight = 10; inline Value kpkWeight = 15; inline Value mgMobilityCnt[7][8] = { - { 4, -1, 5, -1, -5, 3, 0, -3 }, - { -7, -10, 7, -4, -7, 1, -5, -2 }, - { -1, -8, -6, -3, 2, -2, 1, 7 }, - { -10, -6, 5, -3, 3, 5, -7, 10 }, - { -8, -8, -10, -1, -2, 0, 6, 8 }, - { -9, -1, -7, 2, -2, -7, -5, 2 }, - { -4, -5, -9, -5, -3, -6, 5, -3 } + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { -15, -8, -2, 4, 10, 16, 22, 28 }, + { -20,-12, -4, 5, 14, 23, 32, 40 }, + { -25,-15, -5, 5, 15, 25, 35, 45 }, + { -30,-18, -6, 6, 18, 30, 42, 55 }, + { -10, -5, 0, 5, 10, 15, 20, 25 } }; inline Value egMobilityCnt[7][8] = { - { -8, -2, -3, 7, -4, 2, -1, -4 }, - { -7, -8, -8, 3, -4, -3, 2, -9 }, - { 3, -3, -10, -5, -2, -10, -4, 7 }, - { -5, -7, -2, 1, -2, 7, -3, 8 }, - { -4, -9, 3, 0, 0, -6, 6, 4 }, - { -10, -2, -10, -2, -5, -3, 0, 0 }, - { -10, 8, 7, 5, 5, 3, -1, 1 } -}; -inline Value kingTropismMg[7] = { 13, 14, 6, -5, 5, 12, 6 }; -inline Value kingTropismEg[7] = { -9, -13, 3, 5, -2, 19, 4 }; -inline Value passedBonusMg[7] = { 0, 4, 13, 73, 131, 165, 39 }; -inline Value passedBonusEg[7] = { 0, 66, 59, 96, 158, 185, 22 }; -inline Value mg_knight_table[64] = { 1, -5, 8, -21, -11, -2, -24, -2, 23, 21, 13, 2, 4, -17, -7, 4, - -5, -15, -8, 14, 19, -2, 2, -25, -13, 15, -1, -1, 5, -18, 5, 6, - -1, -13, 1, 17, -24, -13, 14, 19, -10, 6, 1, 16, -13, -18, 8, 24, - 14, -21, -8, -21, -15, -1, -1, 8, -17, 22, -18, 6, -11, -4, -9, -7 }; -inline Value mg_bishop_table[64] = { 3, 21, -23, 16, 5, -20, 7, -4, 14, -15, 21, -11, 7, 2, -4, 19, - -5, -4, 22, 11, 15, 25, 20, -9, -3, -4, 20, 21, 10, 11, -4, -21, - 9, 9, 10, 5, -3, 0, 16, 23, -1, 5, -10, -12, 9, -24, -9, -10, - 5, -17, -8, 5, 4, -3, 6, -4, 1, 2, 20, -4, 9, 3, -20, -22 }; -inline Value mg_rook_table[64] = { -10, 20, -13, 18, 12, -3, 13, -25, -11, 3, 10, -4, 7, 13, -24, 5, - -3, -7, -19, -3, 4, -6, 13, 13, 11, 20, 16, -6, 14, -15, -16, -15, - 3, 7, 14, -10, -7, 20, -15, 10, 25, 1, 3, -14, 22, 5, 1, -12, - -20, -13, 22, -9, 11, -2, -15, -11, 22, -14, -10, 5, 0, 9, -20, -13 }; -inline Value mg_king_table[64] = { 11, -12, -10, -19, 5, -6, -2, -10, 19, 13, -7, -3, -7, 8, -21, -24, - -5, 16, 19, 2, -21, -14, 11, -16, -12, -13, -18, 14, -23, -14, 16, -5, - -16, 0, -21, -12, -2, 14, -16, 17, 11, -10, 21, 14, -5, -15, 19, 13, - 8, -21, -13, 4, 1, 1, 16, 13, -8, 13, 6, -20, -4, 3, 8, -2 }; -inline Value mg_queen_table[64] = { -2, -2, 4, 11, 9, -2, 0, -5, 14, -2, -11, 14, -13, -11, -3, -21, - 7, -14, -4, -6, -3, -16, -4, -11, -2, 9, -16, 6, -6, -12, -14, -7, - -4, -22, -12, 6, 21, -4, -6, -5, -6, -7, -4, 2, 13, 11, -8, 14, - 18, -5, -25, 22, -14, 18, -3, 9, -13, 21, 15, -21, 13, 7, -9, 3 }; -inline Value eg_knight_table[64] = { -13, 25, -3, 3, -7, 18, -21, 3, -1, 15, -13, 3, -16, 15, -5, 8, - -11, -12, 22, 14, 10, 20, 9, -6, -10, 2, -13, -7, -17, 2, 18, -19, - 7, 12, -23, 17, -5, 17, 10, -7, -18, -2, -10, -14, 20, 15, -13, 18, - 9, -8, -13, 12, 20, 17, 16, 25, 18, 20, 5, 10, -10, 2, 0, 8 }; -inline Value eg_bishop_table[64] = { -19, -12, -9, -5, 9, -14, -11, 6, -10, -10, 6, -3, 13, -18, -6, 2, - 25, 4, 9, -16, -3, -9, 1, 7, 3, -20, -3, 9, -20, -3, 21, 3, - -8, 10, 4, -11, -8, -18, -13, 0, 10, 17, 14, -18, 21, 11, 18, 0, - -16, 4, 3, 17, -9, -16, -25, -2, -17, -5, -5, 19, 12, -3, -11, -13 }; -inline Value eg_rook_table[64] = { -21, -24, 12, -12, -11, 15, -17, -22, -14, -22, -18, -5, -9, -7, 23, -2, - -1, 9, 18, 3, 4, -19, 20, -21, -15, -21, 9, -11, -2, 4, -8, 15, - -6, 13, -6, -10, -3, 12, 16, -18, -19, -19, 23, 3, -14, -23, 3, 15, - 18, -6, -24, 21, 18, 3, -2, 17, 8, 13, -11, -13, 0, -16, -4, -10 }; -inline Value eg_king_table[64] = { -14, 17, 13, -10, -15, 3, -8, -3, 19, 19, -2, 6, -2, 7, -7, -7, - 21, 10, 17, -21, 0, 15, -6, -9, -7, 24, 8, 0, 17, 12, -4, -6, - 8, -16, 19, 0, 9, 20, 18, 12, -12, -2, -4, -14, 21, 2, -6, -5, - 17, -2, -3, -9, -1, -23, 7, -14, 9, 2, 22, -20, 3, 22, -10, 2 }; -inline Value eg_queen_table[64] = { -7, -3, 13, 11, 2, 17, 2, -19, 12, -5, -2, -11, 2, -7, 19, 2, - -1, 15, 19, 1, 15, 8, -19, -6, -14, -25, -8, 22, 4, -22, -10, 20, - 3, -1, -3, -17, 12, -3, -4, -4, -7, 16, -18, -22, -2, -11, 4, -14, - -7, -15, -8, 9, 20, 8, -2, -1, 8, 18, 20, -23, -12, -8, 6, -11 }; -inline Value mg_pawn_table[56] = { 0, 0, 0, 0, 0, 0, 0, 0, -16, 4, 6, -22, -16, -20, 4, -13, 2, -24, -12, - -7, -3, -11, -5, 6, -15, -9, 2, -14, 2, -18, -7, -10, 1, 15, -7, -6, 22, -13, - -6, 2, -16, -9, 14, -2, 23, -18, 8, 6, -2, -8, -1, -4, -10, -1, 18, -12 }; -inline Value eg_pawn_table[56] = { 0, 0, 0, 0, 0, 0, 0, 0, -4, 1, -2, -5, -20, 13, -17, -2, 2, 24, 12, - -14, -4, 2, -17, -4, 15, -9, 8, 11, -24, -4, -14, -10, -13, -11, -6, -17, -4, 17, - -14, -3, -17, -19, -10, -19, -23, 9, 13, -3, -7, -5, -9, 21, 8, -17, 7, 13 }; -inline Value developedMg = 10; -inline Value developedEg = 5; + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { -20,-12, -4, 5, 14, 23, 32, 40 }, + { -25,-15, -5, 5, 16, 27, 38, 50 }, + { -30,-18, -6, 6, 18, 30, 42, 55 }, + { -35,-22, -8, 8, 22, 35, 48, 60 }, + { -15, -8, -2, 4, 10, 16, 22, 28 } +}; +inline Value kingTropismMg[7] = { 0, 0, 3, 2, 2, 5, 0 }; +inline Value kingTropismEg[7] = { 0, 0, 2, 2, 3, 4, 0 }; +inline Value passedBonusMg[7] = { 0, 5, 15, 40, 80, 140, 200 }; +inline Value passedBonusEg[7] = { 0, 10, 30, 70, 140, 230, 350 }; +inline Value mg_knight_table[64] = { + -50, -40, -30, -30, -30, -30, -40, -50, + -40, -20, 0, 5, 5, 0, -20, -40, + -30, 5, 10, 15, 15, 10, 5, -30, + -30, 0, 15, 20, 20, 15, 0, -30, + -30, 5, 15, 20, 20, 15, 5, -30, + -30, 0, 10, 15, 15, 10, 0, -30, + -40, -20, 0, 0, 0, 0, -20, -40, + -50, -40, -30, -30, -30, -30, -40, -50 +}; +inline Value mg_bishop_table[64] = { + -20, -10, -10, -10, -10, -10, -10, -20, + -10, 0, 0, 0, 0, 0, 0, -10, + -10, 0, 10, 10, 10, 10, 0, -10, + -10, 0, 5, 10, 10, 5, 0, -10, + -10, 5, 5, 10, 10, 5, 5, -10, + -10, 0, 10, 10, 10, 10, 0, -10, + -10, 0, 0, 0, 0, 0, 0, -10, + -20, -10, -10, -10, -10, -10, -10, -20 +}; +inline Value mg_rook_table[64] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5, 5, 5, 5, 5, 5, 0, + 0, 5, 5, 5, 5, 5, 5, 0, + 0, 5, 5, 5, 5, 5, 5, 0, + 0, 5, 5, 5, 5, 5, 5, 0, + 0, 5, 5, 5, 5, 5, 5, 0, + 10, 15, 15, 15, 15, 15, 15, 10, + 0, 0, 0, 0, 0, 0, 0, 0 +}; +inline Value mg_king_table[64] = { + -30, -40, -40, -50, -50, -40, -40, -30, + -30, -40, -40, -50, -50, -40, -40, -30, + -30, -40, -40, -50, -50, -40, -40, -30, + -30, -40, -40, -50, -50, -40, -40, -30, + -20, -30, -30, -40, -40, -30, -30, -20, + -10, -20, -20, -20, -20, -20, -20, -10, + 20, 20, 0, 0, 0, 0, 20, 20, + 20, 30, 10, 0, 0, 10, 30, 20 +}; +inline Value mg_queen_table[64] = { + -20, -10, -10, -5, -5, -10, -10, -20, + -10, 0, 5, 0, 0, 0, 0, -10, + -10, 5, 5, 5, 5, 5, 0, -10, + 0, 0, 5, 5, 5, 5, 0, -5, + -5, 0, 5, 5, 5, 5, 0, -5, + -10, 0, 5, 5, 5, 5, 0, -10, + -10, 0, 0, 0, 0, 0, 0, -10, + -20, -10, -10, -5, -5, -10, -10, -20 +}; +inline Value eg_knight_table[64] = { + -50, -40, -20, -20, -20, -20, -40, -50, + -40, -10, 5, 10, 10, 5, -10, -40, + -30, 10, 20, 25, 25, 20, 10, -30, + -30, 15, 25, 30, 30, 25, 15, -30, + -30, 15, 25, 30, 30, 25, 15, -30, + -30, 10, 20, 25, 25, 20, 10, -30, + -40, -10, 5, 10, 10, 5, -10, -40, + -50, -40, -20, -20, -20, -20, -40, -50 +}; +inline Value eg_bishop_table[64] = { + -10, -5, -5, -5, -5, -5, -5, -10, + -5, 5, 10, 10, 10, 10, 5, -5, + -5, 10, 15, 20, 20, 15, 10, -5, + -5, 10, 20, 25, 25, 20, 10, -5, + -5, 10, 20, 25, 25, 20, 10, -5, + -5, 10, 15, 20, 20, 15, 10, -5, + -5, 5, 10, 10, 10, 10, 5, -5, + -10, -5, -5, -5, -5, -5, -5, -10 +}; +inline Value eg_rook_table[64] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5, 10, 15, 15, 10, 5, 0, + 0, 5, 10, 15, 15, 10, 5, 0, + 0, 5, 10, 15, 15, 10, 5, 0, + 10, 15, 20, 25, 25, 20, 15, 10, + 10, 15, 20, 25, 25, 20, 15, 10, + 20, 25, 30, 35, 35, 30, 25, 20, + 0, 0, 0, 5, 5, 0, 0, 0 +}; +inline Value eg_king_table[64] = { + -50, -40, -30, -20, -20, -30, -40, -50, + -40, -20, 0, 10, 10, 0, -20, -40, + -30, 0, 20, 30, 30, 20, 0, -30, + -20, 10, 30, 40, 40, 30, 10, -20, + -20, 10, 30, 40, 40, 30, 10, -20, + -30, 0, 20, 30, 30, 20, 0, -30, + -40, -20, 0, 10, 10, 0, -20, -40, + -50, -40, -30, -20, -20, -30, -40, -50 +}; +inline Value eg_queen_table[64] = { + -10, -5, -5, 0, 0, -5, -5, -10, + -5, 0, 5, 5, 5, 5, 0, -5, + -5, 5, 10, 15, 15, 10, 5, -5, + -5, 10, 15, 20, 20, 15, 10, -5, + -5, 10, 15, 20, 20, 15, 10, -5, + -5, 5, 10, 15, 15, 10, 5, -5, + -5, 0, 5, 5, 5, 5, 0, -5, + -10, -5, -5, 0, 0, -5, -5, -10 +}; +inline Value mg_pawn_table[56] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + -5, -2, 2, 5, 5, 2, -2, -5, + -10, -5, 10, 20, 20, 10, -5, -10, + -20, -10, 25, 40, 40, 25, -10, -20, + -35, -20, 50, 80, 80, 50, -20, -35, + -55, -40, 90, 130, 130, 90, -40, -55 +}; +inline Value eg_pawn_table[56] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + -5, -2, 2, 5, 5, 2, -2, -5, + -10, -5, 10, 20, 20, 10, -5, -10, + -20, -10, 30, 55, 55, 30, -10, -20, + -35, -20, 70, 110, 110, 70, -20, -35, + -60, -45, 120, 180, 180, 120, -45, -60 +}; +inline Value developedMg = 8; +inline Value developedEg = 4; inline Value outpostBonusKnight[2] = { 15, 30 }; -inline Value outpostBonusBishop[2] = { 10, 20 }; +inline Value outpostBonusBishop[2] = { 10, 25 }; inline Value kingProtector[6][2] = { { 0, 0 }, { 8, 12 }, @@ -129,15 +201,15 @@ inline Value threatByRook[7][2] = { { 20, 25 }, { 25, 35 } }; -inline Value hangingScore = 60; +inline Value hangingScore = 70; inline Value overloadScore = 25; inline Value threatByRankScore = 10; -inline Value minorImWt = 25; -inline Value bishopImWt = 10; -inline Value rookImWt = 15; -inline Value queenImWt = 40; +inline Value minorImWt = 30; +inline Value bishopImWt = 15; +inline Value rookImWt = 20; +inline Value queenImWt = 50; inline Value rammedPawnPenalty = 10; -inline Value rookOnSeventhBonus = 20; -inline Value earlyQueenPenalty = 15; +inline Value rookOnSeventhBonus = 30; +inline Value earlyQueenPenalty = 5; } // namespace engine::eval #endif diff --git a/eval.cpp b/eval.cpp index 2e2c54d..42b2985 100644 --- a/eval.cpp +++ b/eval.cpp @@ -41,7 +41,7 @@ TUNE(SetRange(5, 30), RookValue, SetRange(800, 1000), QueenValue); -TUNE(SetRange(-10, 10), mgMobilityCnt, egMobilityCnt); +TUNE(SetRange(-50, 70), mgMobilityCnt, egMobilityCnt); TUNE(SetRange(0, 30), fianchettoBonus, SetRange(0, 100), trappedBishopPenalty); TUNE(SetRange(-20, 20), kingTropismMg, kingTropismEg); TUNE(SetRange(0, 30), @@ -70,7 +70,7 @@ TUNE(SetRange(0, 30), isolatedPawnMg, SetRange(0, 40), isolatedPawnEg); -TUNE(SetRange(0, 200), +TUNE(SetRange(0, 400), passedBonusMg[1], passedBonusMg[2], passedBonusMg[3], @@ -91,7 +91,7 @@ TUNE(SetRange(1, 30), kingShelterDecayMg, SetRange(1, 10), kingShelterDecayEg); -TUNE(SetRange(-25, 25), +TUNE(SetRange(-70, 200), mg_knight_table, mg_bishop_table, mg_rook_table, diff --git a/search.cpp b/search.cpp index 881d596..ca595a8 100644 --- a/search.cpp +++ b/search.cpp @@ -508,8 +508,6 @@ Value doSearch( std::string extract_pv(const chess::Position &root, int maxPly) { std::string pv; chess::Position pos = root; - std::unordered_set visited; - visited.insert(pos.hash()); for (int ply = 0; ply < maxPly; ply++) { if (pos.is_draw(3)) break; @@ -533,8 +531,6 @@ std::string extract_pv(const chess::Position &root, int maxPly) { if (ply + 1 >= maxPly) break; pos.doMove(m); - if (!visited.insert(pos.hash()).second) - break; } return pv; } From ac259c5b8358c19abb7f933028348cc98bc3ed6b Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 8 Jul 2026 02:36:32 +0000 Subject: [PATCH 28/29] Apply clang-format --- Weights.h | 188 ++++++++++++++++-------------------------------------- 1 file changed, 56 insertions(+), 132 deletions(-) diff --git a/Weights.h b/Weights.h index c5b3579..b20dd65 100644 --- a/Weights.h +++ b/Weights.h @@ -34,145 +34,69 @@ inline Value krkDistWeight = 5; inline Value krkEdgeWeight = 10; inline Value kpkWeight = 15; inline Value mgMobilityCnt[7][8] = { - { 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0 }, - { -15, -8, -2, 4, 10, 16, 22, 28 }, - { -20,-12, -4, 5, 14, 23, 32, 40 }, - { -25,-15, -5, 5, 15, 25, 35, 45 }, - { -30,-18, -6, 6, 18, 30, 42, 55 }, - { -10, -5, 0, 5, 10, 15, 20, 25 } + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { -15, -8, -2, 4, 10, 16, 22, 28 }, + { -20, -12, -4, 5, 14, 23, 32, 40 }, + { -25, -15, -5, 5, 15, 25, 35, 45 }, + { -30, -18, -6, 6, 18, 30, 42, 55 }, + { -10, -5, 0, 5, 10, 15, 20, 25 } }; inline Value egMobilityCnt[7][8] = { - { 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0 }, - { -20,-12, -4, 5, 14, 23, 32, 40 }, - { -25,-15, -5, 5, 16, 27, 38, 50 }, - { -30,-18, -6, 6, 18, 30, 42, 55 }, - { -35,-22, -8, 8, 22, 35, 48, 60 }, - { -15, -8, -2, 4, 10, 16, 22, 28 } + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { -20, -12, -4, 5, 14, 23, 32, 40 }, + { -25, -15, -5, 5, 16, 27, 38, 50 }, + { -30, -18, -6, 6, 18, 30, 42, 55 }, + { -35, -22, -8, 8, 22, 35, 48, 60 }, + { -15, -8, -2, 4, 10, 16, 22, 28 } }; inline Value kingTropismMg[7] = { 0, 0, 3, 2, 2, 5, 0 }; inline Value kingTropismEg[7] = { 0, 0, 2, 2, 3, 4, 0 }; inline Value passedBonusMg[7] = { 0, 5, 15, 40, 80, 140, 200 }; inline Value passedBonusEg[7] = { 0, 10, 30, 70, 140, 230, 350 }; -inline Value mg_knight_table[64] = { - -50, -40, -30, -30, -30, -30, -40, -50, - -40, -20, 0, 5, 5, 0, -20, -40, - -30, 5, 10, 15, 15, 10, 5, -30, - -30, 0, 15, 20, 20, 15, 0, -30, - -30, 5, 15, 20, 20, 15, 5, -30, - -30, 0, 10, 15, 15, 10, 0, -30, - -40, -20, 0, 0, 0, 0, -20, -40, - -50, -40, -30, -30, -30, -30, -40, -50 -}; -inline Value mg_bishop_table[64] = { - -20, -10, -10, -10, -10, -10, -10, -20, - -10, 0, 0, 0, 0, 0, 0, -10, - -10, 0, 10, 10, 10, 10, 0, -10, - -10, 0, 5, 10, 10, 5, 0, -10, - -10, 5, 5, 10, 10, 5, 5, -10, - -10, 0, 10, 10, 10, 10, 0, -10, - -10, 0, 0, 0, 0, 0, 0, -10, - -20, -10, -10, -10, -10, -10, -10, -20 -}; -inline Value mg_rook_table[64] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 5, 5, 5, 5, 5, 5, 0, - 0, 5, 5, 5, 5, 5, 5, 0, - 0, 5, 5, 5, 5, 5, 5, 0, - 0, 5, 5, 5, 5, 5, 5, 0, - 0, 5, 5, 5, 5, 5, 5, 0, - 10, 15, 15, 15, 15, 15, 15, 10, - 0, 0, 0, 0, 0, 0, 0, 0 -}; -inline Value mg_king_table[64] = { - -30, -40, -40, -50, -50, -40, -40, -30, - -30, -40, -40, -50, -50, -40, -40, -30, - -30, -40, -40, -50, -50, -40, -40, -30, - -30, -40, -40, -50, -50, -40, -40, -30, - -20, -30, -30, -40, -40, -30, -30, -20, - -10, -20, -20, -20, -20, -20, -20, -10, - 20, 20, 0, 0, 0, 0, 20, 20, - 20, 30, 10, 0, 0, 10, 30, 20 -}; -inline Value mg_queen_table[64] = { - -20, -10, -10, -5, -5, -10, -10, -20, - -10, 0, 5, 0, 0, 0, 0, -10, - -10, 5, 5, 5, 5, 5, 0, -10, - 0, 0, 5, 5, 5, 5, 0, -5, - -5, 0, 5, 5, 5, 5, 0, -5, - -10, 0, 5, 5, 5, 5, 0, -10, - -10, 0, 0, 0, 0, 0, 0, -10, - -20, -10, -10, -5, -5, -10, -10, -20 -}; -inline Value eg_knight_table[64] = { - -50, -40, -20, -20, -20, -20, -40, -50, - -40, -10, 5, 10, 10, 5, -10, -40, - -30, 10, 20, 25, 25, 20, 10, -30, - -30, 15, 25, 30, 30, 25, 15, -30, - -30, 15, 25, 30, 30, 25, 15, -30, - -30, 10, 20, 25, 25, 20, 10, -30, - -40, -10, 5, 10, 10, 5, -10, -40, - -50, -40, -20, -20, -20, -20, -40, -50 -}; -inline Value eg_bishop_table[64] = { - -10, -5, -5, -5, -5, -5, -5, -10, - -5, 5, 10, 10, 10, 10, 5, -5, - -5, 10, 15, 20, 20, 15, 10, -5, - -5, 10, 20, 25, 25, 20, 10, -5, - -5, 10, 20, 25, 25, 20, 10, -5, - -5, 10, 15, 20, 20, 15, 10, -5, - -5, 5, 10, 10, 10, 10, 5, -5, - -10, -5, -5, -5, -5, -5, -5, -10 -}; -inline Value eg_rook_table[64] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 5, 10, 15, 15, 10, 5, 0, - 0, 5, 10, 15, 15, 10, 5, 0, - 0, 5, 10, 15, 15, 10, 5, 0, - 10, 15, 20, 25, 25, 20, 15, 10, - 10, 15, 20, 25, 25, 20, 15, 10, - 20, 25, 30, 35, 35, 30, 25, 20, - 0, 0, 0, 5, 5, 0, 0, 0 -}; -inline Value eg_king_table[64] = { - -50, -40, -30, -20, -20, -30, -40, -50, - -40, -20, 0, 10, 10, 0, -20, -40, - -30, 0, 20, 30, 30, 20, 0, -30, - -20, 10, 30, 40, 40, 30, 10, -20, - -20, 10, 30, 40, 40, 30, 10, -20, - -30, 0, 20, 30, 30, 20, 0, -30, - -40, -20, 0, 10, 10, 0, -20, -40, - -50, -40, -30, -20, -20, -30, -40, -50 -}; -inline Value eg_queen_table[64] = { - -10, -5, -5, 0, 0, -5, -5, -10, - -5, 0, 5, 5, 5, 5, 0, -5, - -5, 5, 10, 15, 15, 10, 5, -5, - -5, 10, 15, 20, 20, 15, 10, -5, - -5, 10, 15, 20, 20, 15, 10, -5, - -5, 5, 10, 15, 15, 10, 5, -5, - -5, 0, 5, 5, 5, 5, 0, -5, - -10, -5, -5, 0, 0, -5, -5, -10 -}; -inline Value mg_pawn_table[56] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - -5, -2, 2, 5, 5, 2, -2, -5, - -10, -5, 10, 20, 20, 10, -5, -10, - -20, -10, 25, 40, 40, 25, -10, -20, - -35, -20, 50, 80, 80, 50, -20, -35, - -55, -40, 90, 130, 130, 90, -40, -55 -}; -inline Value eg_pawn_table[56] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - -5, -2, 2, 5, 5, 2, -2, -5, - -10, -5, 10, 20, 20, 10, -5, -10, - -20, -10, 30, 55, 55, 30, -10, -20, - -35, -20, 70, 110, 110, 70, -20, -35, - -60, -45, 120, 180, 180, 120, -45, -60 -}; +inline Value mg_knight_table[64] = { -50, -40, -30, -30, -30, -30, -40, -50, -40, -20, 0, 5, 5, 0, -20, -40, + -30, 5, 10, 15, 15, 10, 5, -30, -30, 0, 15, 20, 20, 15, 0, -30, + -30, 5, 15, 20, 20, 15, 5, -30, -30, 0, 10, 15, 15, 10, 0, -30, + -40, -20, 0, 0, 0, 0, -20, -40, -50, -40, -30, -30, -30, -30, -40, -50 }; +inline Value mg_bishop_table[64] = { -20, -10, -10, -10, -10, -10, -10, -20, -10, 0, 0, 0, 0, 0, 0, -10, + -10, 0, 10, 10, 10, 10, 0, -10, -10, 0, 5, 10, 10, 5, 0, -10, + -10, 5, 5, 10, 10, 5, 5, -10, -10, 0, 10, 10, 10, 10, 0, -10, + -10, 0, 0, 0, 0, 0, 0, -10, -20, -10, -10, -10, -10, -10, -10, -20 }; +inline Value mg_rook_table[64] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 0, 0, 5, 5, 5, 5, 5, + 5, 0, 0, 5, 5, 5, 5, 5, 5, 0, 0, 5, 5, 5, 5, 5, 5, 0, 0, 5, 5, 5, + 5, 5, 5, 0, 10, 15, 15, 15, 15, 15, 15, 10, 0, 0, 0, 0, 0, 0, 0, 0 }; +inline Value mg_king_table[64] = { -30, -40, -40, -50, -50, -40, -40, -30, -30, -40, -40, -50, -50, -40, -40, -30, + -30, -40, -40, -50, -50, -40, -40, -30, -30, -40, -40, -50, -50, -40, -40, -30, + -20, -30, -30, -40, -40, -30, -30, -20, -10, -20, -20, -20, -20, -20, -20, -10, + 20, 20, 0, 0, 0, 0, 20, 20, 20, 30, 10, 0, 0, 10, 30, 20 }; +inline Value mg_queen_table[64] = { -20, -10, -10, -5, -5, -10, -10, -20, -10, 0, 5, 0, 0, 0, 0, -10, + -10, 5, 5, 5, 5, 5, 0, -10, 0, 0, 5, 5, 5, 5, 0, -5, + -5, 0, 5, 5, 5, 5, 0, -5, -10, 0, 5, 5, 5, 5, 0, -10, + -10, 0, 0, 0, 0, 0, 0, -10, -20, -10, -10, -5, -5, -10, -10, -20 }; +inline Value eg_knight_table[64] = { -50, -40, -20, -20, -20, -20, -40, -50, -40, -10, 5, 10, 10, 5, -10, -40, + -30, 10, 20, 25, 25, 20, 10, -30, -30, 15, 25, 30, 30, 25, 15, -30, + -30, 15, 25, 30, 30, 25, 15, -30, -30, 10, 20, 25, 25, 20, 10, -30, + -40, -10, 5, 10, 10, 5, -10, -40, -50, -40, -20, -20, -20, -20, -40, -50 }; +inline Value eg_bishop_table[64] = { -10, -5, -5, -5, -5, -5, -5, -10, -5, 5, 10, 10, 10, 10, 5, -5, -5, 10, 15, 20, 20, 15, + 10, -5, -5, 10, 20, 25, 25, 20, 10, -5, -5, 10, 20, 25, 25, 20, 10, -5, -5, 10, 15, 20, + 20, 15, 10, -5, -5, 5, 10, 10, 10, 10, 5, -5, -10, -5, -5, -5, -5, -5, -5, -10 }; +inline Value eg_rook_table[64] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 10, 15, 15, 10, 5, 0, 0, 5, 10, 15, 15, 10, + 5, 0, 0, 5, 10, 15, 15, 10, 5, 0, 10, 15, 20, 25, 25, 20, 15, 10, 10, 15, 20, 25, + 25, 20, 15, 10, 20, 25, 30, 35, 35, 30, 25, 20, 0, 0, 0, 5, 5, 0, 0, 0 }; +inline Value eg_king_table[64] = { -50, -40, -30, -20, -20, -30, -40, -50, -40, -20, 0, 10, 10, 0, -20, -40, + -30, 0, 20, 30, 30, 20, 0, -30, -20, 10, 30, 40, 40, 30, 10, -20, + -20, 10, 30, 40, 40, 30, 10, -20, -30, 0, 20, 30, 30, 20, 0, -30, + -40, -20, 0, 10, 10, 0, -20, -40, -50, -40, -30, -20, -20, -30, -40, -50 }; +inline Value eg_queen_table[64] = { -10, -5, -5, 0, 0, -5, -5, -10, -5, 0, 5, 5, 5, 5, 0, -5, -5, 5, 10, 15, 15, 10, + 5, -5, -5, 10, 15, 20, 20, 15, 10, -5, -5, 10, 15, 20, 20, 15, 10, -5, -5, 5, 10, 15, + 15, 10, 5, -5, -5, 0, 5, 5, 5, 5, 0, -5, -10, -5, -5, 0, 0, -5, -5, -10 }; +inline Value mg_pawn_table[56] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -5, -2, 2, + 5, 5, 2, -2, -5, -10, -5, 10, 20, 20, 10, -5, -10, -20, -10, 25, 40, 40, 25, + -10, -20, -35, -20, 50, 80, 80, 50, -20, -35, -55, -40, 90, 130, 130, 90, -40, -55 }; +inline Value eg_pawn_table[56] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -5, -2, 2, + 5, 5, 2, -2, -5, -10, -5, 10, 20, 20, 10, -5, -10, -20, -10, 30, 55, 55, 30, + -10, -20, -35, -20, 70, 110, 110, 70, -20, -35, -60, -45, 120, 180, 180, 120, -45, -60 }; inline Value developedMg = 8; inline Value developedEg = 4; inline Value outpostBonusKnight[2] = { 15, 30 }; From d9e061aa4984959abff4922e2d52ee09640929d7 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 8 Jul 2026 20:41:09 +0700 Subject: [PATCH 29/29] some diffs --- .github/workflows/games.yml | 10 +++++++++- eval.cpp | 3 ++- search.cpp | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index e40aa7f..8a47060 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -15,6 +15,14 @@ on: description: "Time control (e.g. 60+0.6 or 10+0.1)" required: false default: "60+0.6" + elo0: + description: "elo0" + required: false + default: "0.5" + elo1: + description: "elo1" + required: false + default: "2.5" output_exec: description: "Executable output file name" required: false @@ -115,7 +123,7 @@ jobs: -rounds ${{ github.event.inputs.rounds }} \ -concurrency $(nproc) \ -pgnout notation=san nodes=true file=games.pgn \ - -sprt elo0=0 elo1=5 alpha=0.05 beta=0.05 | tee results.txt + -sprt elo0=${{ github.event.inputs.elo0 }} elo1=${{ github.event.inputs.elo1 }} alpha=0.05 beta=0.05 | tee results.txt ./ordo-linux64 -o ratings.txt -- games.pgn sed -n '/Results of new vs base/,/^--------------------------------------------------$/p' results.txt >> ratings.txt diff --git a/eval.cpp b/eval.cpp index 42b2985..e192ed5 100644 --- a/eval.cpp +++ b/eval.cpp @@ -29,6 +29,7 @@ Value *mgPst[] = { nullptr, mg_pawn_table, mg_knight_table, mg_bishop_table, mg_ Value *egPst[] = { nullptr, eg_pawn_table, eg_knight_table, eg_bishop_table, eg_rook_table, eg_queen_table, eg_king_table }; // tuning slop here +/* TUNE(SetRange(5, 30), tempo, SetRange(80, 120), @@ -224,7 +225,7 @@ TUNE(SetRange(10, 100), minorImWt, SetRange(5, 50), bishopImWt, SetRange(10, 100 TUNE(SetRange(1, 50), rammedPawnPenalty); TUNE(SetRange(1, 100), rookOnSeventhBonus); TUNE(SetRange(1, 50), earlyQueenPenalty); - +*/ EvalComponents eval_components(const chess::Position &board) { constexpr int KnightPhase = 1; constexpr int BishopPhase = 1; diff --git a/search.cpp b/search.cpp index ca595a8..d580188 100644 --- a/search.cpp +++ b/search.cpp @@ -592,7 +592,7 @@ void search(const chess::Position &board, const timeman::LimitsType timecontrol) session.depth = i; for (int _ = 0; _ < 64; _++) for (int j = 0; j < 64; j++) - session.historyHeuristic[_][j] /= 2; + session.historyHeuristic[_][j] = session.historyHeuristic[_][j] * 1 / 2; auto board_ = board; Value score_;