#include "bot.hpp" #include "board/board.hpp" #include "moves.hpp" #include "zobrist/zobrist.hpp" #include #include #include #include #include #include #include const int DEFAULT_DEPTH = 4; 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; const 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, 30, 30, 20, 10, 10, // 5, 5, 10, 25, 25, 10, 5, 5, // 0, 0, 0, 20, 20, 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 // }; 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, // }; 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, // }; 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, // }; 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, // }; 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, // }; 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)]; // Captures (MVV-LVA) 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]; } // Promotions if (move.promotion != NONEPIECE) { score += 8000; } return score; } std::vector GetSortedLegalMoves(Game *g) { auto moves = GetLegalMoves(g); if (moves.size() == 0) { return moves; } std::sort(moves.begin(), moves.end(), [&](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) { std::cout << "Expected a position with legal moves"; assert(false && "Unhanled 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; float eval = minimax(usedDepth - 1, 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; } float minimax(int depth, Game *b, float alpha, float beta) { uint64_t gameHash = GenerateZobristKey(b); if (b->Transpositions->contains(gameHash)) { TranspositionsEntry data = b->Transpositions->at(gameHash); if (data.depth >= depth) { return data.Eval; } } auto moves = GetSortedLegalMoves(b); if (depth == 0 || moves.size() == 0) { return EvaluateBoardForWhite(b, depth); } bool shouldStore = true; float bestEval = b->turn ? -INFINITY : INFINITY; if (b->turn) { for (Move move : moves) { UndoMove undo = MakeMove(move, b); float eval = minimax(depth - 1, b, alpha, beta); UnMakeMove(undo, b); bestEval = std::max(bestEval, eval); alpha = std::max(alpha, bestEval); if (alpha >= beta) { shouldStore = false; break; // *snips* } } } else { for (Move move : moves) { UndoMove undo = MakeMove(move, b); float eval = minimax(depth - 1, b, alpha, beta); UnMakeMove(undo, b); bestEval = std::min(bestEval, eval); beta = std::min(beta, bestEval); if (alpha >= beta) { shouldStore = false; break; // *snips* } } } if (shouldStore) { b->Transpositions->operator[](gameHash) = {depth, bestEval}; } return bestEval; } int PSTIndex(int square, bool white) { return white ? square : (56 ^ square); } float EvaluateBoardForWhite(Game *g, int depth) { float score = 0; GameState state = GetNewGameState(g); switch (state) { case WHITE_WON: return MATE + depth; break; case BLACK_WON: return -MATE - depth; break; case TURN: break; case STALEMATE: return 0; case DRAW: return 0; } for (int i = 0; i < 64; i++) { Piece piece = g->pieces[i]; if (piece.type == NONEPIECE) continue; int value = 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; } if (piece.color) score += value; else score -= value; } return score; }