diff options
| author | Adam <adammegarules1@gmail.com> | 2026-07-31 12:11:58 +0200 |
|---|---|---|
| committer | Adam <adammegarules1@gmail.com> | 2026-07-31 12:11:58 +0200 |
| commit | a550620aa894e218a738b62b9ecd042b03f7858f (patch) | |
| tree | 5d6f459e9db0a6f315c6d1e1e7f54cd1feade724 | |
| parent | 17f245ac325cea859fb39799a9c8b088fc933808 (diff) | |
changes
| -rw-r--r-- | src/board/board.hpp | 23 | ||||
| -rw-r--r-- | src/bot.cpp | 475 | ||||
| -rw-r--r-- | src/bot.hpp | 9 | ||||
| -rw-r--r-- | src/evaluate.cpp | 189 | ||||
| -rw-r--r-- | src/evaluate.hpp | 9 | ||||
| -rw-r--r-- | src/uci.cpp | 40 |
6 files changed, 425 insertions, 320 deletions
diff --git a/src/board/board.hpp b/src/board/board.hpp index 0b92798..d1e379a 100644 --- a/src/board/board.hpp +++ b/src/board/board.hpp @@ -32,9 +32,23 @@ struct Position { bool operator==(const Position &) const = default; }; +struct Move { + Position From; + Position To; + + PieceType promotion = NONEPIECE; + bool operator==(const Move &other) const { + return From == other.From && To == other.To && promotion == other.promotion; + } +}; + +enum Flag { EXACT, LOWERBOUND, UPPERBOUND }; + struct TranspositionsEntry { int depth; int Eval; + Flag flag; + Move bestMove = {}; }; struct Game { Piece pieces[64]; @@ -52,15 +66,6 @@ struct Game { std::unordered_map<uint64_t, TranspositionsEntry> *Transpositions = nullptr; }; -struct Move { - Position From; - Position To; - - PieceType promotion = NONEPIECE; - bool operator==(const Move &other) const { - return From == other.From && To == other.To && promotion == other.promotion; - } -}; struct UndoMove { Piece movedPiece; Piece capturedPiece; diff --git a/src/bot.cpp b/src/bot.cpp index 982a4a4..71c9277 100644 --- a/src/bot.cpp +++ b/src/bot.cpp @@ -1,115 +1,59 @@ #include "bot.hpp" #include "board/board.hpp" +#include "evaluate.hpp" #include "moves.hpp" #include "zobrist/zobrist.hpp" + #include <algorithm> #include <cassert> #include <cmath> #include <cstdint> -#include <cstdlib> #include <iostream> +#include <iterator> +#include <limits> #include <vector> -constexpr int DEFAULT_DEPTH = 4; -constexpr int Q_DEPTH_LIMIT = 3; +constexpr int DEFAULT_DEPTH_LIMIT = 4; +constexpr int Q_DEPTH_LIMIT = 4; -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; -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, // - 5, 5, 10, 30, 30, 10, 5, 5, // - 0, 0, 0, 25, 25, 0, 0, 0, // - 5, -5, -10, 0, 0, -10, -5, 5, // - 5, 10, 10, -20, -20, 10, 10, 5, // - 0, 0, 0, 0, 0, 0, 0, 0 // -}; - -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, // - -30, 5, 15, 20, 20, 15, 5, -30, // - -30, 0, 15, 20, 20, 15, 0, -30, // - -30, 5, 10, 15, 15, 10, 5, -30, // - -40, -20, 0, 5, 5, 0, -20, -40, // - -50, -40, -30, -30, -30, -30, -40, -50, // -}; - -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, // - -10, 0, 10, 15, 15, 10, 0, -10, // - -10, 5, 5, 10, 10, 5, 5, -10, // - -10, 0, 5, 10, 10, 5, 0, -10, // - -10, 0, 0, 0, 0, 0, 0, -10, // - -20, -10, -10, -10, -10, -10, -10, -20, // -}; - -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, // - -5, 0, 0, 5, 5, 0, 0, -5, // - -5, 0, 0, 5, 5, 0, 0, -5, // - -5, 0, 0, 0, 0, 0, 0, -5, // - 5, 10, 10, 10, 10, 10, 10, 5, // - 0, 0, 5, 10, 10, 5, 0, 0, // -}; - -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, // - -5, 0, 5, 5, 5, 5, 0, -5, // - 0, 0, 5, 5, 5, 5, 0, -5, // - -10, 5, 5, 5, 5, 5, 0, -10, // - -10, 0, 5, 0, 0, 0, 0, -10, // - -20, -10, -10, -5, -5, -10, -10, -20, // -}; - -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, // - -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, // -}; +// Material + PST bonuses can never reach this, so anything at/above it is a +// forced mate. Used to stop iterative deepening once a mate is found. +constexpr int MATE_THRESHOLD = MATE - 1000; +constexpr int INF = 100000000; + +static int ScoreMove(const Game *board, const Move &move, + const Move *bestMove) { + // Indexed by PieceType (NONE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING). + static constexpr int PIECE_VALUES[] = { + 0, // NONEPIECE + 100, // PAWN + 320, // KNIGHT + 330, // BISHOP + 500, // ROOK + 900, // QUEEN + 20000, // KING (never captured in legal play) + }; + static_assert(std::size(PIECE_VALUES) == KING + 1); -int ScoreMove(const Game *board, const Move &move) { int score = 0; + // Search the previous iteration's best move first (iterative deepening). + if (bestMove != nullptr && move == *bestMove) { + score += 1000000; + } + const Piece moving = board->pieces[PositionToIndex(move.From)]; const Piece captured = board->pieces[PositionToIndex(move.To)]; - // Captures (MVV-LVA) + // MVV-LVA: value the capture by what we win, penalise by what we spend. if (captured.type != NONEPIECE) { - static const int pieceValue[] = { - 0, // NONE - 20000, // KING (should never happen) - 900, // QUEEN - 500, // ROOK - 330, // BISHOP - 320, // KNIGHT - 100 // PAWN - }; - score += 10000; - score += pieceValue[captured.type] * 10; - score -= pieceValue[moving.type]; + score += PIECE_VALUES[captured.type] * 10; + score -= PIECE_VALUES[moving.type]; } - // Promotions if (move.promotion != NONEPIECE) { score += 8000; } @@ -117,260 +61,261 @@ int ScoreMove(const Game *board, const Move &move) { return score; } -std::vector<Move> GetSortedLegalMoves(Game *g, bool generateQuietMoves) { +static std::vector<Move> GetSortedLegalMoves(Game *g, bool generateQuietMoves, + const Move *bestMove) { auto moves = GetLegalMoves(g, generateQuietMoves); - if (moves.empty()) { - return moves; - } std::ranges::sort(moves, [&](const Move &a, const Move &c) { - return ScoreMove(g, a) > ScoreMove(g, c); + return ScoreMove(g, a, bestMove) > ScoreMove(g, c, bestMove); }); return moves; } -Move EngineGetBestMove(Game *b, const int depth) { - const auto moves = GetSortedLegalMoves(b); - if (moves.empty()) { - std::cout << "Expected a position with legal moves"; - assert(false && "Error zero legal moves for bot"); - exit(1); - } - - Move bestMove = moves[0]; - int BestEval = b->turn ? std::numeric_limits<int>::lowest() - : std::numeric_limits<int>::max(); - for (const Move move : moves) { - const UndoMove undo = MakeMove(move, b); - const float alpha = -INFINITY; - const float beta = INFINITY; +// Quiescence search: only captures, so the eval isn't blind to hanging pieces. +// `standPat` lets the side to move decline every capture. Terminal positions +// are scored with the same `MATE - ply` convention as the main search so that +// mate distances stay consistent (a mate found inside quiescence must not look +// faster than a real mate-in-1). White perspective throughout. +static int quiescenceSearch(Game *b, int qdepth, int alpha, int beta, int ply) { + // Runs GetNewGameState internally, so b->state is up to date afterwards. + const int standPat = EvaluateBoardForWhite(b); - const int eval = - minimax(depth <= 0 ? DEFAULT_DEPTH : depth, b, alpha, beta); - - UnMakeMove(undo, b); - if (b->turn) { - if (eval > BestEval) { - BestEval = eval; - bestMove = move; - } - } else { - if (eval < BestEval) { - BestEval = eval; - bestMove = move; - } - } - } - return bestMove; -} -int quiescenceSearch(Game *b, int Qdepth) { - int standPat = EvaluateBoardForWhite(b); - if (Qdepth >= Q_DEPTH_LIMIT) { - return standPat; + switch (b->state) { + case WHITE_WON: + return MATE - ply; // black is mated + case BLACK_WON: + return -(MATE - ply); // white is mated + case DRAW: + return 0; + case TURN: + break; } - std::vector<Move> attackMoves = GetSortedLegalMoves(b, false); - if (attackMoves.empty()) { + + if (qdepth >= Q_DEPTH_LIMIT) { return standPat; } - int bestScore = b->turn ? std::numeric_limits<int>::lowest() - : std::numeric_limits<int>::max(); + auto moves = GetSortedLegalMoves(b, false, nullptr); if (b->turn) { - for (Move move : attackMoves) { + if (standPat >= beta) { + return beta; + } + alpha = std::max(alpha, standPat); + + for (Move move : moves) { UndoMove undo = MakeMove(move, b); - int eval = quiescenceSearch(b, Qdepth + 1); + int score = quiescenceSearch(b, qdepth + 1, alpha, beta, ply + 1); UnMakeMove(undo, b); - bestScore = std::max(bestScore, eval); + + if (score >= beta) { + return beta; + } + alpha = std::max(alpha, score); } + return alpha; } else { - for (Move move : attackMoves) { + if (standPat <= alpha) { + return alpha; + } + beta = std::min(beta, standPat); + + for (Move move : moves) { UndoMove undo = MakeMove(move, b); - int eval = quiescenceSearch(b, Qdepth + 1); + int score = quiescenceSearch(b, qdepth + 1, alpha, beta, ply + 1); UnMakeMove(undo, b); - bestScore = std::min(bestScore, eval); + + if (score <= alpha) { + return alpha; + } + beta = std::min(beta, score); } + return beta; } - - return bestScore; } +static int minimax(int depth, Game *b, int alpha, int beta, int ply) { + const uint64_t gameHash = GenerateZobristKey(b); -int minimax(int depth, Game *b, float alpha, float beta) { - uint64_t gameHash = GenerateZobristKey(b); - - if (b->ThreeFoldMap[gameHash] >= 2) { - return 0; + auto repIt = b->ThreeFoldMap.find(gameHash); + if (repIt != b->ThreeFoldMap.end() && repIt->second >= 2) { + return 0; // repetition -> draw } - if (b->Transpositions->contains(gameHash)) { - TranspositionsEntry data = b->Transpositions->at(gameHash); - if (data.depth >= depth) { - return data.Eval; + + TranspositionsEntry *entry = nullptr; + if (auto it = b->Transpositions->find(gameHash); + it != b->Transpositions->end()) { + entry = &it->second; + if (entry->depth >= depth) { + if (entry->flag == EXACT) { + return entry->Eval; + } + if (entry->flag == LOWERBOUND) { + alpha = std::max(alpha, entry->Eval); + } + if (entry->flag == UPPERBOUND) { + beta = std::min(beta, entry->Eval); + } + if (alpha >= beta) { + return entry->Eval; + } } } if (depth <= 0) { - return quiescenceSearch(b, 0); + return quiescenceSearch(b, 0, alpha, beta, ply); } - auto moves = GetSortedLegalMoves(b); + + Move ttBestMove = entry != nullptr ? entry->bestMove : Move{}; + std::vector<Move> moves = GetSortedLegalMoves(b, true, &ttBestMove); + if (moves.empty()) { - return EvaluateBoardForWhite(b); + const Position king = FindKing(b, b->turn); + if (IsSquareAttacked(b, king, !b->turn)) { + return b->turn ? -(MATE - ply) : (MATE - ply); // mated + } + return 0; // stalemate } - bool shouldStore = true; + Move bestMove = moves[0]; + const int alphaOrig = alpha; - int bestScore = b->turn ? std::numeric_limits<int>::lowest() - : std::numeric_limits<int>::max(); + if (b->turn) { // white maximizes + int bestScore = std::numeric_limits<int>::lowest(); - if (b->turn) { for (Move move : moves) { UndoMove undo = MakeMove(move, b); - int eval = minimax(depth - 1, b, alpha, beta); + int score = minimax(depth - 1, b, alpha, beta, ply + 1); UnMakeMove(undo, b); - bestScore = std::max(bestScore, eval); - - alpha = std::max(alpha, static_cast<float>(bestScore)); + if (score > bestScore) { + bestScore = score; + bestMove = move; + } + alpha = std::max(alpha, score); if (alpha >= beta) { - shouldStore = false; - break; // *snips* + break; } } - } else { + + const Flag flag = bestScore <= alphaOrig ? UPPERBOUND + : bestScore >= beta ? LOWERBOUND + : EXACT; + (*b->Transpositions)[gameHash] = { + .depth = depth, .Eval = bestScore, .flag = flag, .bestMove = bestMove}; + return bestScore; + } else { // black minimizes + int bestScore = std::numeric_limits<int>::max(); + for (Move move : moves) { UndoMove undo = MakeMove(move, b); - int eval = minimax(depth - 1, b, alpha, beta); + int score = minimax(depth - 1, b, alpha, beta, ply + 1); UnMakeMove(undo, b); - bestScore = std::min(bestScore, eval); - beta = std::min(beta, static_cast<float>(bestScore)); + if (score < bestScore) { + bestScore = score; + bestMove = move; + } + beta = std::min(beta, score); if (alpha >= beta) { - shouldStore = false; - break; // *snips* + break; } } - } - if (shouldStore) { - b->Transpositions->operator[](gameHash) = {.depth = depth, - .Eval = bestScore}; + const Flag flag = bestScore <= alphaOrig ? UPPERBOUND + : bestScore >= beta ? LOWERBOUND + : EXACT; + (*b->Transpositions)[gameHash] = { + .depth = depth, .Eval = bestScore, .flag = flag, .bestMove = bestMove}; + return bestScore; } - return bestScore; } -static int PSTIndex(const int square, const bool white) { - return white ? square : (56 ^ square); -} +struct SearchResult { + Move bestMove; + int score; // white perspective +}; -static int ForceKingToEdgeBonus(const Position enemyKing, - const Position myKing) { - int bonus = 0; +// Searches every root move to `depth` plies and returns the best one. +// `previousBest` is the best move from the previous iteration (used for move +// ordering, the core win of iterative deepening). +static SearchResult SearchDepth(Game *b, int depth, const Move *previousBest) { + auto moves = GetSortedLegalMoves(b, true, previousBest); - // Push enemy king toward edge - const int distToCenter = - std::abs(enemyKing.file - 3) + std::abs(enemyKing.rank - 3); + if (moves.empty()) { + return {{}, EvaluateBoardForWhite(b)}; + } - bonus += distToCenter * 10; + Move bestMove = moves[0]; + int bestEval = b->turn ? std::numeric_limits<int>::lowest() + : std::numeric_limits<int>::max(); - // Bring own king closer - const int kingDistance = std::abs(myKing.file - enemyKing.file) + - std::abs(myKing.rank - enemyKing.rank); + for (Move move : moves) { + UndoMove undo = MakeMove(move, b); - bonus += (14 - kingDistance) * 5; + int eval = minimax(depth - 1, b, -INF, INF, 1); - return bonus; -} -static bool IsEndgame(Game *g) { - int queens = 0; - int rooks = 0; - - for (const Piece &piece : g->pieces) { - switch (piece.type) { - case QUEEN: - queens++; - break; - case ROOK: - rooks++; - break; - default: - break; + UnMakeMove(undo, b); + + if (b->turn) { + if (eval > bestEval) { + bestEval = eval; + bestMove = move; + } + } else { + if (eval < bestEval) { + bestEval = eval; + bestMove = move; + } } } - return queens == 0 || (queens == 2 && rooks <= 1); + return {bestMove, bestEval}; } -int EvaluateBoardForWhite(Game *g) { - int score = 0; +static void PrintInfo(const int depth, const int engineScore) { + if (std::abs(engineScore) >= MATE_THRESHOLD) { + int movesToMate = (MATE - std::abs(engineScore) + 1) / 2; + if (movesToMate < 1) { + movesToMate = 1; + } + std::cout << "info depth " << depth << " score mate " + << (engineScore > 0 ? movesToMate : -movesToMate) << "\n" + << std::flush; + } else { + std::cout << "info depth " << depth << " score cp " << engineScore << "\n" + << std::flush; + } +} - switch (GetNewGameState(g)) { - case WHITE_WON: - return MATE; - break; - case BLACK_WON: - return -MATE; - break; - case TURN: - break; - case DRAW: - return 0; +Move GetBestMove(Game *b, const int maxDepth) { + const int depthLimit = maxDepth > 0 ? maxDepth : DEFAULT_DEPTH_LIMIT; + + auto legalMoves = GetSortedLegalMoves(b, true, nullptr); + if (legalMoves.empty()) { + assert(false && "GetBestMove called with no legal moves"); + return {}; } - for (int i = 0; i < 64; i++) { - const Piece piece = g->pieces[i]; - if (piece.type == NONEPIECE) - continue; - int value = 0; + Move bestMove = legalMoves[0]; - switch (piece.type) { - case PAWN: - value = PAWN_VALUE; - value += PAWN_TABLE[PSTIndex(i, piece.color)]; - break; - case KNIGHT: - value = KNIGHT_VALUE; - value += KNIGHT_TABLE[PSTIndex(i, piece.color)]; - break; - case BISHOP: - value = BISHOP_VALUE; - value += BISHOP_TABLE[PSTIndex(i, piece.color)]; - break; - case ROOK: - value = ROOK_VALUE; - value += ROOK_TABLE[PSTIndex(i, piece.color)]; - break; - case QUEEN: - value = QUEEN_VALUE; - value += QUEEN_TABLE[PSTIndex(i, piece.color)]; - break; - case KING: - value = KING_TABLE_EARLY[PSTIndex(i, piece.color)]; - break; - default: - break; - } + for (int depth = 1; depth <= depthLimit; depth++) { + SearchResult result = SearchDepth(b, depth, &bestMove); + bestMove = result.bestMove; - if (piece.color) { - score += value; - } else { - score -= value; + PrintInfo(depth, b->turn ? result.score : -result.score); + + // A mate was found; deeper searches can only find a faster one. + if (std::abs(result.score) >= MATE_THRESHOLD) { + break; } } - 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; + return bestMove; } diff --git a/src/bot.hpp b/src/bot.hpp index a8375e1..821eb28 100644 --- a/src/bot.hpp +++ b/src/bot.hpp @@ -2,12 +2,7 @@ #define SRC_BOT_H_ #include "board/board.hpp" -#include <vector> -Move EngineGetBestMove(Game *b, int depth); -int EvaluateBoardForWhite(Game *b); +Move GetBestMove(Game *b, int depth); -int minimax(int depth, Game *b, float alpha, float beta); -int ScoreMove(const Game *board, const Move &move); -std::vector<Move> GetSortedLegalMoves(Game *g, bool generateQuietMoves = true); -#endif /* SRC_BOT_H_ */ +#endif /* SRC_EVALUATE_H_ */ diff --git a/src/evaluate.cpp b/src/evaluate.cpp new file mode 100644 index 0000000..c53e64e --- /dev/null +++ b/src/evaluate.cpp @@ -0,0 +1,189 @@ +#include "evaluate.hpp" +#include "moves.hpp" +#include <cstdlib> + +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; + +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, // + 5, 5, 10, 30, 30, 10, 5, 5, // + 0, 0, 0, 25, 25, 0, 0, 0, // + 5, -5, -10, 0, 0, -10, -5, 5, // + 5, 10, 10, -20, -20, 10, 10, 5, // + 0, 0, 0, 0, 0, 0, 0, 0 // +}; + +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, // + -30, 5, 15, 20, 20, 15, 5, -30, // + -30, 0, 15, 20, 20, 15, 0, -30, // + -30, 5, 10, 15, 15, 10, 5, -30, // + -40, -20, 0, 5, 5, 0, -20, -40, // + -50, -40, -30, -30, -30, -30, -40, -50, // +}; + +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, // + -10, 0, 10, 15, 15, 10, 0, -10, // + -10, 5, 5, 10, 10, 5, 5, -10, // + -10, 0, 5, 10, 10, 5, 0, -10, // + -10, 0, 0, 0, 0, 0, 0, -10, // + -20, -10, -10, -10, -10, -10, -10, -20, // +}; + +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, // + -5, 0, 0, 5, 5, 0, 0, -5, // + -5, 0, 0, 5, 5, 0, 0, -5, // + -5, 0, 0, 0, 0, 0, 0, -5, // + 5, 10, 10, 10, 10, 10, 10, 5, // + 0, 0, 5, 10, 10, 5, 0, 0, // +}; + +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, // + -5, 0, 5, 5, 5, 5, 0, -5, // + 0, 0, 5, 5, 5, 5, 0, -5, // + -10, 5, 5, 5, 5, 5, 0, -10, // + -10, 0, 5, 0, 0, 0, 0, -10, // + -20, -10, -10, -5, -5, -10, -10, -20, // +}; + +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, // + -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, // +}; + +/* + * i cound some up with better name + * this function just takes square and if color is black rotate it + */ +static int RotateBoardForBlack(const int square, const bool color) { + return color ? square : (56 ^ square); +} + +static int ForceKingToEdgeBonus(const Position enemyKing, + const Position myKing) { + int bonus = 0; + + // Push enemy king toward edge + const int distToCenter = + std::abs(enemyKing.file - 3) + std::abs(enemyKing.rank - 3); + + bonus += distToCenter * 10; + + // Bring own king closer + const int kingDistance = std::abs(myKing.file - enemyKing.file) + + std::abs(myKing.rank - enemyKing.rank); + + bonus += (14 - kingDistance) * 5; + + return bonus; +} + +static bool IsEndgame(Game *g) { + int queens = 0; + int rooks = 0; + + for (const Piece &piece : g->pieces) { + switch (piece.type) { + case QUEEN: + queens++; + break; + case ROOK: + rooks++; + break; + default: + break; + } + } + + return queens == 0 || (queens == 2 && rooks <= 1); +} + +int EvaluateBoardForWhite(Game *g) { + switch (GetNewGameState(g)) { + case WHITE_WON: + return MATE; + case BLACK_WON: + return -MATE; + case DRAW: + return 0; + case TURN: + break; + } + + int score = 0; + + for (int i = 0; i < 64; i++) { + const Piece piece = g->pieces[i]; + if (piece.type == NONEPIECE) + continue; + + int value = 0; + + switch (piece.type) { + case PAWN: + value = PAWN_VALUE; + value += PAWN_TABLE[RotateBoardForBlack(i, piece.color)]; + break; + case KNIGHT: + value = KNIGHT_VALUE; + value += KNIGHT_TABLE[RotateBoardForBlack(i, piece.color)]; + break; + case BISHOP: + value = BISHOP_VALUE; + value += BISHOP_TABLE[RotateBoardForBlack(i, piece.color)]; + break; + case ROOK: + value = ROOK_VALUE; + value += ROOK_TABLE[RotateBoardForBlack(i, piece.color)]; + break; + case QUEEN: + value = QUEEN_VALUE; + value += QUEEN_TABLE[RotateBoardForBlack(i, piece.color)]; + break; + case KING: + value = KING_TABLE_EARLY[RotateBoardForBlack(i, piece.color)]; + break; + default: + break; + } + + if (piece.color) { + score += value; + } else { + score -= value; + } + } + + Position whiteKing = FindKing(g, true); + Position blackKing = FindKing(g, false); + + if (IsEndgame(g)) { + score += ForceKingToEdgeBonus(blackKing, whiteKing); + score -= ForceKingToEdgeBonus(whiteKing, blackKing); + } + return score; +} diff --git a/src/evaluate.hpp b/src/evaluate.hpp new file mode 100644 index 0000000..d0baf14 --- /dev/null +++ b/src/evaluate.hpp @@ -0,0 +1,9 @@ +#ifndef SRC_EVALUATE_H_ +#define SRC_EVALUATE_H_ + +#include "board/board.hpp" + +int EvaluateBoardForWhite(Game *g); +bool InEndgame(Game *g); + +#endif /* SRC_EVALUATE_H_ */ diff --git a/src/uci.cpp b/src/uci.cpp index ad1e89e..818da90 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -21,34 +21,6 @@ std::ofstream uciLog("cache/uci_log.txt", std::ios::app); -bool LoadTable( - const std::string &filename, - std::unordered_map<uint64_t, TranspositionsEntry> *transpositions) { - std::ifstream file(filename); - - if (!file.is_open()) - return false; - - uint64_t hash; - int depth; - int eval; - - while (file >> hash >> depth >> eval) { - transpositions->operator[](hash) = {depth, eval}; - } - - return true; -} -void SaveTranspositionTable( - const std::unordered_map<uint64_t, TranspositionsEntry> &table) { - - std::ofstream file("cache/positions.txt", std::ios::trunc); - - for (const auto &[key, value] : table) { - file << key << ' ' << value.depth << ' ' << value.Eval << '\n'; - } -}; - void LogUci(const std::string &message) { if (!uciLog.is_open()) { return; @@ -117,11 +89,6 @@ void Uci() { if (cmd == "quit") { break; } - if (cmd == "save") { - SaveTranspositionTable(transpositions); - cout << "done\n"; - cout.flush(); - } if (cmd == "board") { for (int rank = 0; rank < 8; rank++) { std::printf("%d |", 8 - rank); @@ -135,11 +102,6 @@ void Uci() { std::printf(" | %d\n", 8 - rank); } } - if (cmd == "load") { - LoadTable("cache/positions.txt", &transpositions); - cout << "done\n"; - cout.flush(); - } if (cmd == "uci") { cout << "uciok\n"; cout.flush(); @@ -166,7 +128,7 @@ void Uci() { } } - Move best = EngineGetBestMove(&game, depth); + Move best = GetBestMove(&game, depth); cout << "bestmove "; cout << (char)(best.From.file + 'a') << 8 - best.From.rank |
