From 8df9e390a71eeb2835eab06ec4b474c8cad63ecd Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 30 Jul 2026 16:40:17 +0200 Subject: improving bot --- src/board/board.cpp | 24 ++++---- src/board/board.hpp | 19 +++--- src/bot.cpp | 173 ++++++++++++++++++++++++++++++++++------------------ src/bot.hpp | 6 +- src/moves.cpp | 7 +-- src/uci.cpp | 2 +- 6 files changed, 141 insertions(+), 90 deletions(-) (limited to 'src') diff --git a/src/board/board.cpp b/src/board/board.cpp index 22db36e..d0cf4c1 100644 --- a/src/board/board.cpp +++ b/src/board/board.cpp @@ -43,10 +43,10 @@ UndoMove MakeMove(Move move, Game *g) { undo.oldState = g->state; - undo.OldwhiteCastleKing = g->whiteCastleKing; - undo.OldwhiteCastleQueen = g->whiteCastleQueen; - undo.OldblackCastleKing = g->blackCastleKing; - undo.OldblackCastleQueen = g->blackCastleQueen; + undo.OldWhiteCastleKing = g->whiteCastleKing; + undo.OldWhiteCastleQueen = g->whiteCastleQueen; + undo.OldBlackCastleKing = g->blackCastleKing; + undo.OldBlackCastleQueen = g->blackCastleQueen; // play move Piece piece = g->pieces[PositionToIndex(move.From)]; @@ -184,7 +184,7 @@ UndoMove MakeMove(Move move, Game *g) { // 3 fold check uint64_t key = GenerateZobristKey(g); - undo.zobristKey = key; + undo.ZobristKey = key; g->ThreeFoldMap[key]++; @@ -218,10 +218,10 @@ void UnMakeMove(UndoMove undo, Game *g) { g->state = undo.oldState; - g->whiteCastleKing = undo.OldwhiteCastleKing; - g->whiteCastleQueen = undo.OldwhiteCastleQueen; - g->blackCastleKing = undo.OldblackCastleKing; - g->blackCastleQueen = undo.OldblackCastleQueen; + g->whiteCastleKing = undo.OldWhiteCastleKing; + g->whiteCastleQueen = undo.OldWhiteCastleQueen; + g->blackCastleKing = undo.OldBlackCastleKing; + g->blackCastleQueen = undo.OldBlackCastleQueen; if (undo.wasCastle) { if (undo.CastledSide) { @@ -247,8 +247,8 @@ void UnMakeMove(UndoMove undo, Game *g) { } } - g->ThreeFoldMap[undo.zobristKey] -= 1; - if (g->ThreeFoldMap[undo.zobristKey] <= 0) { - g->ThreeFoldMap.erase(undo.zobristKey); + g->ThreeFoldMap[undo.ZobristKey] -= 1; + if (g->ThreeFoldMap[undo.ZobristKey] <= 0) { + g->ThreeFoldMap.erase(undo.ZobristKey); }; }; diff --git a/src/board/board.hpp b/src/board/board.hpp index bd8efba..b35773d 100644 --- a/src/board/board.hpp +++ b/src/board/board.hpp @@ -18,12 +18,11 @@ enum GameState { TURN, WHITE_WON, BLACK_WON, - STALEMATE, DRAW, }; struct Piece { - bool color = 0; + bool color = false; PieceType type = NONEPIECE; }; @@ -35,7 +34,7 @@ struct Position { }; struct TranspositionsEntry { int depth; - float Eval; + int Eval; }; struct BitBoards { uint64_t whitePawns; @@ -54,7 +53,7 @@ struct BitBoards { }; struct Game { Piece pieces[64]; - bool turn = 1; // 1 white; 0 black + bool turn = true; // 1 white; 0 black bool whiteCastleKing = false; bool whiteCastleQueen = false; bool blackCastleKing = false; @@ -62,7 +61,7 @@ struct Game { uint8_t halfMoveClock = 0; uint16_t MoveClock = 0; Position enPassant; - bool canEnpassant = 0; + bool canEnpassant = false; GameState state = TURN; std::unordered_map ThreeFoldMap; std::unordered_map *Transpositions = nullptr; @@ -92,14 +91,14 @@ struct UndoMove { bool oldCanEnpassant; Position oldEnPassant; - bool OldwhiteCastleKing; - bool OldwhiteCastleQueen; - bool OldblackCastleKing; - bool OldblackCastleQueen; + bool OldWhiteCastleKing; + bool OldWhiteCastleQueen; + bool OldBlackCastleKing; + bool OldBlackCastleQueen; int oldHalfMoveClock; int oldMoveClock; - uint64_t zobristKey; + uint64_t ZobristKey; GameState oldState; // castling diff --git a/src/bot.cpp b/src/bot.cpp index 9a489dd..f05c303 100644 --- a/src/bot.cpp +++ b/src/bot.cpp @@ -10,16 +10,17 @@ #include #include -const int DEFAULT_DEPTH = 4; +constexpr int DEFAULT_DEPTH = 4; +constexpr int Q_DEPTH_LIMIT = 6; -const int PAWN_VALUE = 100; -const int KNIGHT_VALUE = 320; -const int BISHOP_VALUE = 400; -const int ROOK_VALUE = 500; -const int QUEEN_VALUE = 900; -const int MATE = 10000; +constexpr int PAWN_VALUE = 100; +constexpr int KNIGHT_VALUE = 320; +constexpr int BISHOP_VALUE = 400; +constexpr int ROOK_VALUE = 500; +constexpr int QUEEN_VALUE = 900; +constexpr int MATE = 10000; -const int PAWN_TABLE[64] = { +static int PAWN_TABLE[64] = { 0, 0, 0, 0, 0, 0, 0, 0, // last rank promotes to a quuen 50, 50, 50, 50, 50, 50, 50, 50, // comments to stop formating to ruin it 10, 10, 20, 35, 35, 20, 10, 10, // @@ -30,7 +31,7 @@ const int PAWN_TABLE[64] = { 0, 0, 0, 0, 0, 0, 0, 0 // }; -int KNIGHT_TABLE[64] = { +static int KNIGHT_TABLE[64] = { -50, -40, -30, -30, -30, -30, -40, -50, // -40, -20, 0, 0, 0, 0, -20, -40, // -30, 0, 10, 15, 15, 10, 0, -30, // @@ -41,7 +42,7 @@ int KNIGHT_TABLE[64] = { -50, -40, -30, -30, -30, -30, -40, -50, // }; -int BISHOP_TABLE[64] = { +static int BISHOP_TABLE[64] = { -20, -10, -10, -10, -10, -10, -10, -20, // -10, 5, 0, 0, 0, 0, 5, -10, // -10, 10, 10, 10, 10, 10, 10, -10, // @@ -52,7 +53,7 @@ int BISHOP_TABLE[64] = { -20, -10, -10, -10, -10, -10, -10, -20, // }; -int ROOK_TABLE[64] = { +static int ROOK_TABLE[64] = { 0, 0, 5, 10, 10, 5, 0, 0, // 5, 10, 10, 10, 10, 10, 10, 5, // -5, 0, 0, 0, 0, 0, 0, -5, // @@ -63,7 +64,7 @@ int ROOK_TABLE[64] = { 0, 0, 5, 10, 10, 5, 0, 0, // }; -int QUEEN_TABLE[64] = { +static int QUEEN_TABLE[64] = { -20, -10, -10, -5, -5, -10, -10, -20, // -10, 0, 0, 0, 0, 0, 0, -10, // -10, 0, 5, 5, 5, 5, 0, -10, // @@ -74,7 +75,7 @@ int QUEEN_TABLE[64] = { -20, -10, -10, -5, -5, -10, -10, -20, // }; -int KING_TABLE_EARLY[64] = { +static int KING_TABLE_EARLY[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, // @@ -88,8 +89,8 @@ int KING_TABLE_EARLY[64] = { int ScoreMove(const Game *board, const Move &move) { int score = 0; - Piece moving = board->pieces[PositionToIndex(move.From)]; - Piece captured = board->pieces[PositionToIndex(move.To)]; + const Piece moving = board->pieces[PositionToIndex(move.From)]; + const Piece captured = board->pieces[PositionToIndex(move.To)]; // Captures (MVV-LVA) if (captured.type != NONEPIECE) { @@ -118,32 +119,33 @@ int ScoreMove(const Game *board, const Move &move) { std::vector GetSortedLegalMoves(Game *g) { auto moves = GetLegalMoves(g); - if (moves.size() == 0) { + if (moves.empty()) { return moves; } - std::sort(moves.begin(), moves.end(), [&](const Move &a, const Move &c) { + std::ranges::sort(moves, [&](const Move &a, const Move &c) { return ScoreMove(g, a) > ScoreMove(g, c); }); return moves; } -Move EngineGetBestMove(Game *b, int depth) { - int usedDepth = (depth != -1 ? depth : DEFAULT_DEPTH); - auto moves = GetSortedLegalMoves(b); - if (moves.size() == 0) { +Move EngineGetBestMove(Game *b, const int depth) { + const int usedDepth = (depth != -1 ? depth : DEFAULT_DEPTH); + const auto moves = GetSortedLegalMoves(b); + if (moves.empty()) { std::cout << "Expected a position with legal moves"; - assert(false && "Unhanled error zero legal moves for bot"); + assert(false && "Error zero legal moves for bot"); exit(1); } Move bestMove = moves[0]; - float BestEval = (b->turn ? -INFINITY : INFINITY); - for (Move move : moves) { - UndoMove undo = MakeMove(move, b); - float alpha = -INFINITY; - float beta = INFINITY; + int BestEval = b->turn ? std::numeric_limits::lowest() + : std::numeric_limits::max(); + for (const Move move : moves) { + const UndoMove undo = MakeMove(move, b); + const float alpha = -INFINITY; + const float beta = INFINITY; - float eval = minimax(usedDepth - 1, b, alpha, beta); + const int eval = minimax(usedDepth - 1, b, alpha, beta); UnMakeMove(undo, b); if (b->turn) { @@ -160,11 +162,52 @@ Move EngineGetBestMove(Game *b, int depth) { } return bestMove; } -float minimax(int depth, Game *b, float alpha, float beta) { +int quiescenceSearch(Game *b, int Qdepth) { + int standPat = EvaluateBoardForWhite(b); + if (Qdepth >= Q_DEPTH_LIMIT) { + return standPat; + } + std::vector attackMoves; + for (Move move : GetSortedLegalMoves(b)) { + if (b->pieces[PositionToIndex(move.To)].type != NONEPIECE) { + attackMoves.push_back(move); + } + }; + if (attackMoves.empty()) { + return standPat; + } + + int bestEval = b->turn ? std::numeric_limits::lowest() + : std::numeric_limits::max(); + + if (b->turn) { + for (Move move : attackMoves) { + UndoMove undo = MakeMove(move, b); + + int eval = quiescenceSearch(b, Qdepth + 1); + + UnMakeMove(undo, b); + bestEval = std::max(bestEval, eval); + } + } else { + for (Move move : attackMoves) { + UndoMove undo = MakeMove(move, b); + + int eval = quiescenceSearch(b, Qdepth + 1); + + UnMakeMove(undo, b); + bestEval = std::min(bestEval, eval); + } + } + + return bestEval; +} + +int minimax(int depth, Game *b, float alpha, float beta) { uint64_t gameHash = GenerateZobristKey(b); if (b->ThreeFoldMap[gameHash] >= 2) { - return b->turn ? -500 : 500; + return 0; } if (b->Transpositions->contains(gameHash)) { TranspositionsEntry data = b->Transpositions->at(gameHash); @@ -173,24 +216,29 @@ float minimax(int depth, Game *b, float alpha, float beta) { } } + if (depth <= 0) { + return quiescenceSearch(b, 0); + } auto moves = GetSortedLegalMoves(b); - if (depth == 0 || moves.size() == 0) { - return EvaluateBoardForWhite(b); + if (moves.empty()) { + return quiescenceSearch(b, 0); } bool shouldStore = true; - float bestEval = b->turn ? -INFINITY : INFINITY; + int bestEval = b->turn ? std::numeric_limits::lowest() + : std::numeric_limits::max(); + if (b->turn) { for (Move move : moves) { UndoMove undo = MakeMove(move, b); - float eval = minimax(depth - 1, b, alpha, beta); + int eval = minimax(depth - 1, b, alpha, beta); UnMakeMove(undo, b); bestEval = std::max(bestEval, eval); - alpha = std::max(alpha, bestEval); + alpha = std::max(alpha, static_cast(bestEval)); if (alpha >= beta) { shouldStore = false; @@ -201,12 +249,12 @@ float minimax(int depth, Game *b, float alpha, float beta) { for (Move move : moves) { UndoMove undo = MakeMove(move, b); - float eval = minimax(depth - 1, b, alpha, beta); + int eval = minimax(depth - 1, b, alpha, beta); UnMakeMove(undo, b); bestEval = std::min(bestEval, eval); - beta = std::min(beta, bestEval); + beta = std::min(beta, static_cast(bestEval)); if (alpha >= beta) { shouldStore = false; break; // *snips* @@ -215,24 +263,29 @@ float minimax(int depth, Game *b, float alpha, float beta) { } if (shouldStore) { - b->Transpositions->operator[](gameHash) = {depth, bestEval}; + b->Transpositions->operator[](gameHash) = {.depth = depth, + .Eval = bestEval}; } return bestEval; } -int PSTIndex(int square, bool white) { return white ? square : (56 ^ square); } -static int ForceKingToEdgeBonus(Position enemyKing, Position myKing) { +static int PSTIndex(const int square, const bool white) { + return white ? square : (56 ^ square); +} + +static int ForceKingToEdgeBonus(const Position enemyKing, + const Position myKing) { int bonus = 0; // Push enemy king toward edge - int distToCenter = + const int distToCenter = std::abs(enemyKing.file - 3) + std::abs(enemyKing.rank - 3); bonus += distToCenter * 10; // Bring own king closer - int kingDistance = std::abs(myKing.file - enemyKing.file) + - std::abs(myKing.rank - enemyKing.rank); + const int kingDistance = std::abs(myKing.file - enemyKing.file) + + std::abs(myKing.rank - enemyKing.rank); bonus += (14 - kingDistance) * 5; @@ -242,8 +295,8 @@ static bool IsEndgame(Game *g) { int queens = 0; int rooks = 0; - for (int i = 0; i < 64; i++) { - switch (g->pieces[i].type) { + for (const Piece &piece : g->pieces) { + switch (piece.type) { case QUEEN: queens++; break; @@ -258,11 +311,10 @@ static bool IsEndgame(Game *g) { return queens == 0 || (queens == 2 && rooks <= 1); } -float EvaluateBoardForWhite(Game *g) { - float score = 0; +int EvaluateBoardForWhite(Game *g) { + int score = 0; - GameState state = GetNewGameState(g); - switch (state) { + switch (GetNewGameState(g)) { case WHITE_WON: return MATE; break; @@ -271,13 +323,11 @@ float EvaluateBoardForWhite(Game *g) { break; case TURN: break; - case STALEMATE: - return 0; case DRAW: return 0; } for (int i = 0; i < 64; i++) { - Piece piece = g->pieces[i]; + const Piece piece = g->pieces[i]; if (piece.type == NONEPIECE) continue; @@ -316,15 +366,16 @@ float EvaluateBoardForWhite(Game *g) { } else { score -= value; } - Position whiteKing = FindKing(g, true); - Position blackKing = FindKing(g, false); - - if (IsEndgame(g)) { - score += - ForceKingToEdgeBonus(blackKing, whiteKing); // White attacking black - score -= - ForceKingToEdgeBonus(whiteKing, blackKing); // Black attacking white - } + } + + Position whiteKing = FindKing(g, true); + Position blackKing = FindKing(g, false); + + if (IsEndgame(g)) { + score += + ForceKingToEdgeBonus(blackKing, whiteKing); // White attacking black + score -= + ForceKingToEdgeBonus(whiteKing, blackKing); // Black attacking white } return score; } diff --git a/src/bot.hpp b/src/bot.hpp index 382c9be..0796299 100644 --- a/src/bot.hpp +++ b/src/bot.hpp @@ -4,8 +4,10 @@ #include "board/board.hpp" #include Move EngineGetBestMove(Game *b, int depth); -float EvaluateBoardForWhite(Game *b); -float minimax(int depth, Game *b, float alpha, float beta); + +int EvaluateBoardForWhite(Game *b); + +int minimax(int depth, Game *b, float alpha, float beta); int ScoreMove(const Game *board, const Move &move); std::vector GetSortedLegalMoves(Game *g); #endif /* SRC_BOT_H_ */ diff --git a/src/moves.cpp b/src/moves.cpp index d5ad989..4deafe3 100644 --- a/src/moves.cpp +++ b/src/moves.cpp @@ -164,12 +164,11 @@ std::vector GetLegalMoves(Game *g) { } } - return legalMoves; + return legalMoves; } GameState GetNewGameState(Game *g) { // make sure we dont override game ending states - if (g->state == DRAW || g->state == STALEMATE || g->state == WHITE_WON || - g->state == BLACK_WON) { + if (g->state == DRAW || g->state == WHITE_WON || g->state == BLACK_WON) { return g->state; } if (g->halfMoveClock >= 50) { @@ -186,7 +185,7 @@ GameState GetNewGameState(Game *g) { if (check) { g->state = g->turn ? BLACK_WON : WHITE_WON; } else { - g->state = STALEMATE; + g->state = DRAW; } } return g->state; diff --git a/src/uci.cpp b/src/uci.cpp index ef368b3..ad1e89e 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -31,7 +31,7 @@ bool LoadTable( uint64_t hash; int depth; - float eval; + int eval; while (file >> hash >> depth >> eval) { transpositions->operator[](hash) = {depth, eval}; -- cgit v1.2.3