diff --git a/CMakeLists.txt b/CMakeLists.txt index d9eb054..3405d8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,10 +5,11 @@ project(cppchess_engine) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) +set(BUILD_TESTING OFF) FetchContent_Declare( chesslib GIT_REPOSITORY https://github.com/winapiadmin/chesslib.git - GIT_TAG maintaince_1 + 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") diff --git a/eval.cpp b/eval.cpp index a9b3722..861b1e7 100644 --- a/eval.cpp +++ b/eval.cpp @@ -96,13 +96,13 @@ Value eg_king_table[64] = {-74, -35, -18, -18, -11, 15, 4, -17, -12, 17, 14, 23, 16, 7, -9, -27, -11, 4, 13, 14, 4, -5, -17, -53, -34, -21, -11, -28, -14, -24, -43}; -Value *mg_pesto_table[] = { - {0}, mg_pawn_table, mg_knight_table, mg_bishop_table, - mg_rook_table, mg_queen_table, mg_king_table}; +Value *mg_pesto_table[] = {nullptr, mg_pawn_table, mg_knight_table, + mg_bishop_table, mg_rook_table, mg_queen_table, + mg_king_table}; -Value *eg_pesto_table[] = { - {0}, eg_pawn_table, eg_knight_table, eg_bishop_table, - eg_rook_table, eg_queen_table, eg_king_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 eval(const chess::Board &board) { int pieceCount[10] = { diff --git a/eval.h b/eval.h index eb881e6..74c1e20 100644 --- a/eval.h +++ b/eval.h @@ -1,6 +1,4 @@ #pragma once -#include -#include #include using Value = int; namespace engine { diff --git a/score.cpp b/score.cpp index 2fdc2c5..2d07d9e 100644 --- a/score.cpp +++ b/score.cpp @@ -1,5 +1,6 @@ #include "score.h" #include +#include namespace engine { Score::Score(Value v) { assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); @@ -14,4 +15,4 @@ Score::Score(Value v) { score = (v > 0) ? Mate{distance} : Mate{-distance}; } } -} // namespace engine \ No newline at end of file +} // namespace engine diff --git a/search.cpp b/search.cpp index 07b9af5..f05dfd0 100644 --- a/search.cpp +++ b/search.cpp @@ -6,14 +6,12 @@ #include #include #include +#include using namespace chess; namespace engine { TranspositionTable search::tt(16); std::atomic stopSearch{false}; -void search::stop() { - tt.clear(); - stopSearch.store(true, std::memory_order_relaxed); -} +void search::stop() { stopSearch.store(true, std::memory_order_relaxed); } struct Session { timeman::TimeManagement tm; timeman::LimitsType tc; @@ -29,7 +27,11 @@ void update_pv(Move *pv, Move move, const Move *childPv) { } Value qsearch(Board &board, Value alpha, Value beta, Session &session, int ply = 0) { + if (session.tm.elapsed() >= session.tm.optimum() || + stopSearch.load(std::memory_order_relaxed)) + return VALUE_NONE; session.nodes++; + session.seldepth = std::max(session.seldepth, ply); int standPat = eval::eval(board); Value maxScore = standPat; if (maxScore >= beta) @@ -40,8 +42,11 @@ Value qsearch(Board &board, Value alpha, Value beta, Session &session, board.legals(moves); for (Move move : moves) { board.doMove(move); - Value score = -qsearch(board, -beta, -alpha, session, ply + 1); + Value score = qsearch(board, -beta, -alpha, session, ply + 1); board.undoMove(); + if (score == VALUE_NONE) + return VALUE_NONE; + score = -score; if (score >= beta) return score; if (score > maxScore) @@ -51,7 +56,7 @@ Value qsearch(Board &board, Value alpha, Value beta, Session &session, } return maxScore; } -Value doSearch(Board &board, int depth, Value alpha, Value beta, +Value doSearch(Board board, int depth, Value alpha, Value beta, Session &session, int ply = 0) { if (ply >= MAX_PLY - 1) return eval::eval(board); @@ -97,7 +102,7 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, preferred = Move(entry->getMove()); } if (depth == 0) { - return qsearch(board, alpha, beta, session, ply + 1); + return qsearch(board, alpha, beta, session, ply); } Value maxScore = -VALUE_INFINITE; Movelist moves; @@ -107,20 +112,73 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, return board.checkers() ? -MATE(ply) : 0; } movepick::orderMoves(board, moves, preferred, ply); - for (Move move : moves) { + if (bool useNMP = depth >= 3 && !board.checkers() && ply > 0) { + int R = 2 + depth / 6; + board.doNullMove(); + Value score = + doSearch(board, depth - 1 - R, -beta, -beta + 1, session, ply + 1); + + if (score == VALUE_NONE) { + board.undoMove(); + return VALUE_NONE; + } + score = -score; + board.undoMove(); + if (score >= beta) + return score; + } + 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; + + // --- LMR reduction --- + int reduction = 0; + if (i >= 3 && depth >= 3 && !isCapture && !givesCheck) { + reduction = 1 + (int)(i / 6) + (depth / 8); + + // history heuristic: good moves get reduced less + if (movepick::historyHeuristic[(int)move.from()][(int)move.to()] > 0) + reduction--; + + reduction = std::max(0, reduction); + reduction = std::min(reduction, depth - 2); + } board.doMove(move); - Value childScore = - doSearch(board, depth - 1, -beta, -alpha, session, ply + 1); + Value score; - board.undoMove(); + if (i == 0) { + // --- First move: full window (PVS root move) --- + score = -doSearch(board, depth - 1, -beta, -alpha, session, ply + 1); - // ---- ABORT PROPAGATION ---- - if (childScore == VALUE_NONE) - return VALUE_NONE; + if (score == VALUE_NONE) { + board.undoMove(); + return VALUE_NONE; + } + } else { + // --- 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; + } + score = -score; + } + } - Value score = -childScore; + board.undoMove(); if (score > maxScore) { maxScore = score; @@ -129,18 +187,20 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, if (score > alpha) { alpha = score; - if (!board.isCapture(move)) + + if (!isCapture) movepick::historyHeuristic[(int)move.from()][(int)move.to()] += depth * depth; } + if (alpha >= beta) { - if (!board.isCapture(move)) { + // killer moves + if (!isCapture) { if (movepick::killerMoves[ply][0] != move) { movepick::killerMoves[ply][1] = movepick::killerMoves[ply][0]; movepick::killerMoves[ply][0] = move; } } - break; } @@ -165,6 +225,7 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta, } void search::search(const chess::Board &board, const timeman::LimitsType timecontrol) { + stopSearch = false; static double originalTimeAdjust = -1; Session session; session.tc = timecontrol; @@ -178,12 +239,11 @@ void search::search(const chess::Board &board, // since MAX_PLY=64 session.pv[_][j] = Move::none(); } - session.nodes = 0; auto board_ = board; Value score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); if (session.tm.elapsed() >= session.tm.optimum() || - stopSearch.load(std::memory_order_relaxed) || score_ == VALUE_NONE) + stopSearch.load(std::memory_order_relaxed) || abs(score_) == VALUE_NONE) break; InfoFull info{}; info.depth = i; @@ -195,6 +255,18 @@ void search::search(const chess::Board &board, 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()) + " "; @@ -233,7 +305,7 @@ void search::search(const chess::Board &board, info.nodes = 1; info.score = 0; info.multiPV = 1; - info.pv = chess::uci::moveToUci(best, board.chess960()); + info.pv = std::string(chess::uci::moveToUci(best, board.chess960())); report(info); report(chess::uci::moveToUci(best, board.chess960())); diff --git a/tt.cpp b/tt.cpp index bd400ef..f084236 100644 --- a/tt.cpp +++ b/tt.cpp @@ -68,7 +68,7 @@ TTEntry *TranspositionTable::lookup(uint64_t hash) { TTEntry &e0 = table[index], &e1 = table[index + 1]; // Check the entries for (TTEntry *e : {&e0, &e1}) { - if (e->key == hash) + if (e->key == hash && e->getGeneration() == this->time) return e; } return nullptr; diff --git a/tt.h b/tt.h index f2e2496..6c69dc3 100644 --- a/tt.h +++ b/tt.h @@ -37,8 +37,8 @@ struct TTEntry { << GEN_SHIFT; // getters - inline uint16_t getScore() const noexcept { - return static_cast((pack & SCORE_MASK) >> SCORE_SHIFT); + inline int16_t getScore() const noexcept { + return static_cast((pack & SCORE_MASK) >> SCORE_SHIFT); } inline uint8_t getDepth() const noexcept { @@ -176,4 +176,4 @@ class TranspositionTable { return (used * 1000) / buckets; } }; -} // namespace engine \ No newline at end of file +} // namespace engine diff --git a/uci.cpp b/uci.cpp index 4ee298b..5025555 100644 --- a/uci.cpp +++ b/uci.cpp @@ -5,7 +5,9 @@ #include #include #include +#include #include +#include using namespace engine; chess::Position pos; OptionsMap engine::options; @@ -69,7 +71,18 @@ timeman::LimitsType parse_limits(std::istream &is) { return limits; } -void handleGo(std::istringstream &ss) { search::search(pos, parse_limits(ss)); } +std::thread searchThread; + +void handleGo(std::istringstream &ss) { + if (searchThread.joinable()) { + search::stop(); + searchThread.join(); + } + + searchThread = std::thread([ss = std::move(ss)]() mutable { + search::search(pos, parse_limits(ss)); + }); +} template struct overload : Ts... { using Ts::operator()...; }; @@ -159,7 +172,7 @@ void engine::loop() { break; } else if (token == "position") { handlePosition(ss); - break; // rest belongs to position + break; } else if (token == "go") { handleGo(ss); break; // rest belongs to go @@ -168,8 +181,13 @@ void engine::loop() { break; } else if (token == "stop") { search::stop(); + if (searchThread.joinable()) + searchThread.join(); break; } else if (token == "quit") { + search::stop(); + if (searchThread.joinable()) + searchThread.join(); return; } else if (token == "setoption") { options.setoption(ss);