#include "Minimax.h" #include "ChessGame.h" #include "Search.h" #include #include #include #include using namespace chessy; /*size_t chessy::perft(std::ostream& out, ChessGame& chessGame, int depth) { size_t result = 0; std::function searcher; std::function iterate; // apply a move and search deeper searcher = [&result, &iterate] (const MoveInfo& mi, ChessGame& cg, int depth, bool print) { if (depth > 1) { UndoInfo ui = cg.doMove(mi); size_t interRes = result; iterate(cg, depth - 1, false); if (print) std::cout << mi.move.asString() << ": " << (result - interRes) << std::endl; cg.undoMove(ui); } else { if (print) std::cout << mi.move.asString() << ": " << 1 << std::endl; result++; } }; // iterate through all positions in the current position iterate = [&searcher] (ChessGame& cg, int depth, bool print) { Search search (searcher, cg); if (cg.getTurn() == WHITE_SIDE) search.iterateAll(cg, depth, print); else search.iterateAll(cg, depth, print); }; using namespace std::chrono; auto begin = high_resolution_clock::now(); iterate(chessGame, depth, true); auto end = high_resolution_clock::now(); auto millis = duration_cast(end - begin); double seconds = millis.count() / 1000.0; size_t nodesPerSecond = (result * 1000 * 1000 * 1000) / duration_cast(end - begin).count(); out << "Searched " << result << " nodes in " << seconds << " seconds. (at " << nodesPerSecond << " nodes per second)" << std::endl; return result; }*/ size_t chessy::perft(std::ostream& out, ChessGame& cg, int depth) { std::function iterate = [&](bool print, int depth) -> size_t { if (depth <= 0) return 1; std::vector moves; moves.reserve(50); if (cg.getTurn() == WHITE_SIDE) generateAllMoves(cg, moves); else generateAllMoves(cg, moves); size_t sum = 0; for (Move move : moves) { MoveInfo mi{ move, cg }; UndoInfo ui = cg.doMove(mi); size_t v = iterate(false, depth - 1); if (print) out << mi.move.asString() << ": " << v << std::endl; sum += v; cg.undoMove(ui); } return sum; }; using namespace std::chrono; auto begin = high_resolution_clock::now(); size_t sum = iterate(true, depth); auto end = high_resolution_clock::now(); auto millis = duration_cast(end - begin); double seconds = millis.count() / 1000.0; size_t nodesPerSecond = (sum * 1000 * 1000 * 1000) / duration_cast(end - begin).count(); out << "Searched " << sum << " nodes in " << seconds << " seconds. (at " << nodesPerSecond << " nodes per second)" << std::endl; return sum; } #include using namespace std; std::pair chessy::miniMax(ChessGame& cg, int depth) { std::vector moves; moves.reserve(200); if (cg.getTurn() == WHITE_SIDE) { generateAllMoves(cg, moves); orderMoves(cg, moves); } else { generateAllMoves(cg, moves); orderMoves(cg, moves); } const Board& b = cg.getBoard(); auto isCheck = [&cg, &b] () { return cg.getTurn() == BLACK_SIDE ? b.isCheck() : b.isCheck(); }; Move bestMove{ 0, 0 }; MoveValue alpha = -1e+30; MoveValue beta = 1e+30; Move lastValidMove = bestMove; for (Move move : moves) { MoveInfo mi{ move, cg }; UndoInfo ui = cg.doMove(mi); MoveValue val; if (isCheck()) { cg.undoMove(ui); continue; } else { lastValidMove = move; val = -negamaxImplementation(cg, depth - 1, -beta, -alpha); } //cout << move.asString() << ": " << val << endl; cg.undoMove(ui); if(val > alpha) { alpha = val; bestMove = move; } } if (bestMove.origin == 0 && bestMove.destination == 0) { bestMove = lastValidMove; } return { bestMove, alpha }; //negamaxImplementation(chessGame, 5); } MoveValue chessy::negamaxImplementation(ChessGame& cg, int depth, chessy::MoveValue alpha, chessy::MoveValue beta) { if (depth <= 0) //return quiescence(cg, 2, -beta, -alpha); return evaluate(cg.getTurn(), cg); const Board& b = cg.getBoard(); auto isCheck = [&cg, &b] (Side turn) { return turn == BLACK_SIDE ? b.isCheck() : b.isCheck(); }; std::vector moves; moves.reserve(200); if (cg.getTurn() == WHITE_SIDE) { generateAllMoves(cg, moves); orderMoves(cg, moves); } else { generateAllMoves(cg, moves); orderMoves(cg, moves); } bool thereIsMove = false; for (Move move : moves) { MoveInfo mi{ move, cg }; UndoInfo ui = cg.doMove(mi); MoveValue val; if (isCheck(cg.getTurn())) val = -1e+30; else { val = -negamaxImplementation(cg, depth - 1, -beta, -alpha); thereIsMove = true; } cg.undoMove(ui); if(val >= beta) return beta; if(val > alpha) alpha = val; } if (!thereIsMove) return isCheck(otherSide(cg.getTurn())) ? -1e+30 : 0.0; return alpha; } MoveValue chessy::quiescence(ChessGame& cg, int maxDepth, MoveValue alpha, MoveValue beta) { if (maxDepth <= 0) return evaluate(cg.getTurn(), cg); const Board& b = cg.getBoard(); auto isCheck = [&cg, &b] (Side turn) { return turn == BLACK_SIDE ? b.isCheck() : b.isCheck(); }; std::vector moves; moves.reserve(50); if (cg.getTurn() == WHITE_SIDE) generateAllMoves(cg, moves); else generateAllMoves(cg, moves); bool thereIsMove = false; for (Move move : moves) { MoveInfo mi{ move, cg }; UndoInfo ui = cg.doMove(mi); MoveValue val; if (isCheck(cg.getTurn())) val = -1e+30; else { val = -quiescence(cg, maxDepth - 1, -beta, -alpha); thereIsMove = true; } cg.undoMove(ui); if(val >= beta) return beta; if(val > alpha) alpha = val; } if (!thereIsMove) return isCheck(otherSide(cg.getTurn())) ? -1e+30 : 0.0; return alpha; } template MoveValue chessy::evaluatePositives(const ChessGame& game) { MoveValue piecePoints = 0; const Board& bd = game.getBoard(); Bitboard p = bd.getPawns(); Bitboard n = bd.getKnights(); Bitboard b = bd.getBishops(); Bitboard r = bd.getRooks(); Bitboard q = bd.getQueens(); Bitboard k = bd.getKing(); piecePoints += 1 * p.popcount(); piecePoints += 3 * n.popcount(); piecePoints += 3 * b.popcount(); piecePoints += 5 * r.popcount(); piecePoints += 9 * q.popcount(); for (auto knight : PositionSet{ n }) piecePoints += KnightMoveGenerator{ knight, bd.get() }.getBitboard().popcount() * 0.05; for (auto bishop : PositionSet{ b }) piecePoints += PrimitiveBishopMoveGenerator{ bishop, bd.get(), bd.get() }.getBitboard().popcount() * 0.03; for (auto rook : PositionSet{ r }) piecePoints += PrimitiveRookMoveGenerator{ rook, bd.get(), bd.get() }.getBitboard().popcount() * 0.03; for (auto queen : PositionSet{ q }) piecePoints += PrimitiveQueenMoveGenerator{ queen, bd.get(), bd.get() }.getBitboard().popcount() * 0.02; return piecePoints; /* constexpr Side other = otherSide(side); p = bd.getPawns(); n = bd.getKnights(); b = bd.getBishops(); r = bd.getRooks(); q = bd.getQueens(); k = bd.getKing(); piecePoints -= 1 * p.popcount(); piecePoints -= 3 * n.popcount(); piecePoints -= 3 * b.popcount(); piecePoints -= 5 * r.popcount(); piecePoints -= 9 * q.popcount(); //for (auto knight : PositionSet{ n }) // piecePoints -= KnightMoveGenerator{ knight, bd.get() }.getBitboard().popcount() * 0.2; for (auto bishop : PositionSet{ b }) piecePoints -= PrimitiveBishopMoveGenerator{ bishop, bd.get(), bd.get() }.getBitboard().popcount() * 0.2; */ } MoveValue chessy::evaluate(Side side, const ChessGame& game) { if (side == WHITE_SIDE) return evaluatePositives(game) - evaluatePositives(game); else return evaluatePositives(game) - evaluatePositives(game); } /* template MiniMax::BestMove MiniMax::minimax(int); template MiniMax::BestMove MiniMax::minimax(int); Move MiniMax::calculateBest(int depth) { if (game.getTurn() == WHITE_SIDE) { BestMove bm = minimax(depth); return bm.move; } else return minimax(depth).move; } template float MiniMax::evaluate(void) const { int piecePoints = 0; Board& bd = game.getBoard(); Bitboard p = bd.getPawns(); Bitboard n = bd.getKnights(); Bitboard b = bd.getBishops(); Bitboard r = bd.getRooks(); Bitboard q = bd.getQueens(); Bitboard k = bd.getKing(); piecePoints += 1 * p.popcount(); piecePoints += 3 * n.popcount(); piecePoints += 3 * b.popcount(); piecePoints += 4 * r.popcount(); piecePoints += 6 * q.popcount(); if (k == Bitboard(0ULL)) piecePoints -= 100000; const Side other = otherSide(side); p = bd.getPawns(); n = bd.getKnights(); b = bd.getBishops(); r = bd.getRooks(); q = bd.getQueens(); k = bd.getKing(); piecePoints -= 1 * p.popcount(); piecePoints -= 3 * n.popcount(); piecePoints -= 3 * b.popcount(); piecePoints -= 4 * r.popcount(); piecePoints -= 6 * q.popcount(); if (k == Bitboard(0ULL)) piecePoints += 100000; return piecePoints; } template MiniMax::BestMove MiniMax::minimax(int depth) { if (depth == 0) { return { {0, 0}, -evaluate() }; } BestMove bestMove = { {0, 0}, -std::numeric_limits::infinity() }; Board& board = game.getBoard(); Bitboard friends; Bitboard enemies; if (side == WHITE_SIDE) { friends = board.getWhites(); enemies = board.getBlacks(); } else { friends = board.getBlacks(); enemies = board.getWhites(); } const Board temp = board; PawnPushGenerator mg{ game }; for (Move push : mg) { Bitboard& pawns = board.getPawns(); Bitboard pTemp = pawns; pawns.applyMove(push); BestMove m = minimax(depth - 1); m.move = push; bestMove.overwriteIfBetter(m); pawns = pTemp; } PawnDoublePushGenerator dp{ game }; for (Move push : dp) { Bitboard& pawns = board.getPawns(); Bitboard pTemp = pawns; pawns.applyMove(push); BestMove m = minimax(depth - 1); m.move = push; bestMove.overwriteIfBetter(m); pawns = pTemp; } PawnCaptureGenerator pl{ game }; for (Move capture : pl) { Bitboard& pawns = board.getPawns(); board.removeAt(capture.destination); pawns.applyMove(capture); BestMove m = minimax(depth - 1); m.move = capture; bestMove.overwriteIfBetter(m); board = temp; } PawnCaptureGenerator pr{ game }; for (Move capture : pr) { Bitboard& pawns = board.getPawns(); board.removeAt(capture.destination); pawns.applyMove(capture); BestMove m = minimax(depth - 1); m.move = capture; bestMove.overwriteIfBetter(m); board = temp; } PromotionGenerator pg{ game }; for (Move promotion : pg) { Bitboard& pawns = board.getPawns(); board.removeAt(promotion.destination); pawns.applyMove(promotion); BestMove m = minimax(depth - 1); m.move = promotion; bestMove.overwriteIfBetter(m); board = temp; } Bitboard& ns = board.getKnights(); PositionSet knights { ns }; for (auto knight : knights) { for (auto pos : KnightMoveGenerator{ knight, friends }) { Move move = { knight, pos }; board.removeAt(move.destination); ns.applyMove(move); BestMove m = minimax(depth - 1); m.move = move; bestMove.overwriteIfBetter(m); board = temp; } } Bitboard& bs = board.getBishops(); PositionSet bishops { bs }; for (auto bishop : bishops) { for (auto pos : PrimitiveBishopMoveGenerator{ bishop, enemies, friends }) { Move move = { bishop, pos }; board.removeAt(move.destination); bs.applyMove(move); BestMove m = minimax(depth - 1); m.move = move; bestMove.overwriteIfBetter(m); board = temp; } } Bitboard& rs = board.getRooks(); PositionSet rooks { rs }; for (auto rook : rooks) { for (auto pos : PrimitiveRookMoveGenerator{ rook, enemies, friends }) { Move move = { rook, pos }; board.removeAt(move.destination); rs.applyMove(move); BestMove m = minimax(depth - 1); m.move = move; bestMove.overwriteIfBetter(m); board = temp; } } Bitboard& qs = board.getQueens(); PositionSet queens { qs }; for (auto queen : queens) { for (auto pos : PrimitiveQueenMoveGenerator{ queen, enemies, friends }) { Move move = { queen, pos }; board.removeAt(move.destination); qs.applyMove(move); BestMove m = minimax(depth - 1); m.move = move; bestMove.overwriteIfBetter(m); board = temp; } } Bitboard& king = board.getKing(); Index kingIndex = king.getLeastSignificantBit(); for (auto pos : KingMoveGenerator{ king, friends }) { Move move = { kingIndex, pos }; board.removeAt(pos); king.applyMove(move); BestMove m = minimax(depth - 1); m.move = move; //if (depth >= 3) // std::cout << m.move.asString() << " " << bestMove.value << " -> " << m.value << std::endl; bestMove.overwriteIfBetter(m); board = temp; } if (side == WHITE_SIDE) { if (game.getCanCastleKingSide(side)) { if((board.getWhites().bits & 0x6) == 0) { Move kingMove = {3, 1}; Move rookMove = {0, 2}; king.applyMove(kingMove); rs.applyMove(rookMove); board = temp; } } } for (auto pos : CastlingGenerator{ game }) { Move move = { kingIndex, pos }; king.applyMove(move); BestMove m = minimax(depth - 1); m.move = move; //if (depth >= 3) // std::cout << m.move.asString() << " " << bestMove.value << " -> " << m.value << std::endl; bestMove.overwriteIfBetter(m); board = temp; } float v = evaluate(); bestMove.value *= 0.9f; bestMove.value += v * 0.2f; return -bestMove; } */