From cb0fcc4702e4f006b6480db4822baf8ea25c254d Mon Sep 17 00:00:00 2001 From: Arthur Bols Date: Fri, 23 Dec 2022 23:57:29 +0100 Subject: [PATCH] update --- BasicEngine.cpp | 4 +- BitBoard.hpp | 9 ++++ Board.cpp | 15 +++---- Board.hpp | 2 +- BoardState.hpp | 13 ++++++ Move.cpp | 43 ++++++++++++++---- Move.hpp | 20 ++++++--- MoveGenerator.cpp | 100 +++++++++++++++++++++++++++++++++++------- MoveGenerator.hpp | 7 ++- Piece.hpp | 18 +++++++- Search.cpp | 17 +++++-- Search.hpp | 7 +-- Tests/EngineTests.cpp | 3 ++ Tests/FenTests.cpp | 2 +- 14 files changed, 208 insertions(+), 52 deletions(-) diff --git a/BasicEngine.cpp b/BasicEngine.cpp index 83e947a..9aa829a 100644 --- a/BasicEngine.cpp +++ b/BasicEngine.cpp @@ -1,3 +1,4 @@ +#include #include "BasicEngine.hpp" #include "Search.hpp" @@ -16,5 +17,6 @@ void BasicEngine::newGame() { PrincipalVariation BasicEngine::pv(const Board &board, const TimeInfo::Optional &timeInfo) { (void)board; (void)timeInfo; - return Search::start(board); + auto pv =Search::start(board); + return pv; } \ No newline at end of file diff --git a/BitBoard.hpp b/BitBoard.hpp index 21d0ce7..61553a7 100644 --- a/BitBoard.hpp +++ b/BitBoard.hpp @@ -39,6 +39,9 @@ public: explicit constexpr operator bool() const { return mBoard != 0; } + explicit constexpr operator unsigned int() const { + return mBoard; + } explicit constexpr operator unsigned long() const { return mBoard; } @@ -125,6 +128,7 @@ public: // WARN: Check for 0! unsigned lsb() const; unsigned pop(); + constexpr int count() const; private: U64 mBoard = 0; @@ -282,4 +286,9 @@ inline unsigned BitBoard::pop() { return i; } +constexpr int BitBoard::count() const { + return __builtin_popcountll(mBoard); +} + + #endif //CHESS_ENGINE_BITBOARD_HPP diff --git a/Board.cpp b/Board.cpp index aa9d575..3229411 100644 --- a/Board.cpp +++ b/Board.cpp @@ -40,19 +40,18 @@ int Board::evaluate() const { } int Board::evaluate(const PieceColor color) const { - // Pawns int score = 0; BitBoard bb; BitBoard colorMask = mPieceBBs[toIndex(color)]; - for (int i = 2; i < 8; i++) { - bb = mPieceBBs[i] & colorMask; - while (bb) { - bb.pop(); - score += mPieceValue[i]; - } + for (int i = 0; i < 6; i++) { + bb = mPieceBBs[i+2] & colorMask; + score += Piece::PieceValue[i] * bb.count(); } +// BoardState bs = BoardState(&mPieceBBs, mOccupiedBB, mTurn, mCR, mEPS); +// score += round(mMobilityWeight * MoveGenerator::Mobility(bs, color)); + return score; } @@ -67,8 +66,6 @@ bool Board::isCheckMate() const { auto attacked = MoveGenerator::generateAttackedSquares(bs, ~mOccupiedBB, !mTurn); return attacked & mPieceBBs[toIndex(PieceType::King)] & mPieceBBs[toIndex(mTurn)]; - - return moves.empty(); } void Board::setTurn(PieceColor turn) { diff --git a/Board.hpp b/Board.hpp index 23e44ec..95406f5 100644 --- a/Board.hpp +++ b/Board.hpp @@ -57,7 +57,7 @@ private: CastlingRights mCR = CastlingRights::None; std::optional mEPS; - constexpr static const int mPieceValue[8] = {0,0, 100, 350, 350, 525, 1000, 10000}; + constexpr static const double mMobilityWeight = 0.1; void handleCastlingRights(const Piece &piece, const BitBoard &bb); diff --git a/BoardState.hpp b/BoardState.hpp index 9421fd8..d88bf01 100644 --- a/BoardState.hpp +++ b/BoardState.hpp @@ -5,6 +5,8 @@ #include "BitBoard.hpp" #include "Board.hpp" +#define BB_NUM 8 + struct BoardState { BoardState(const BitBoard (*const pieceBBs)[8], const BitBoard occupiedBB, @@ -24,6 +26,17 @@ struct BoardState { const PieceColor turn; const CastlingRights cr; const std::optional eps; + + inline PieceType pieceType(const BitBoard &mask) const { + for (int i = 2; i < BB_NUM; i++) { + if ((*pieceBBs)[i] & mask) { + return static_cast(i); + } + } + + // Should not happen + return static_cast(0); + } }; #endif //CHESS_ENGINE_BOARDSTATE_HPP diff --git a/Move.cpp b/Move.cpp index 1037c71..1ede2b1 100644 --- a/Move.cpp +++ b/Move.cpp @@ -3,10 +3,21 @@ #include #include -Move::Move(const Square &from, const Square &to, const std::optional &promotion) - : mFrom(from), mTo(to), mPromotion(promotion) { + +Move::Move(const Square &from, const Square &to) : mFrom(from), mTo(to) { + } +Move::Move(const Square &from, const Square &to, const std::optional &promotion, unsigned score) + : mFrom(from), mTo(to), mPromotion(promotion) { + mScore -= score; +} + +Move::Move(const Square &from, const Square &to, unsigned int score) : mFrom(from), mTo(to) { + mScore -= score; +} + + Move::Optional Move::fromUci(const std::string &uci) { if (uci.length() < 4 || uci.length() > 5) return std::nullopt; @@ -48,17 +59,31 @@ std::ostream &operator<<(std::ostream &os, const Move &move) { } bool operator<(const Move &lhs, const Move &rhs) { - if (!(lhs.from() == rhs.from())) - return lhs.from() < rhs.from(); + if (lhs.mScore != rhs.mScore) { + return lhs.mScore < rhs.mScore; + } - if (!(lhs.to() == rhs.to())) - return lhs.to() < rhs.to(); + if (lhs.mFrom.index() != rhs.mFrom.index()) { + return lhs.mFrom.index() < rhs.mFrom.index(); + } + if (lhs.mTo.index() != rhs.mTo.index()) { + return lhs.mTo.index() < rhs.mTo.index(); + } return lhs.promotion() < rhs.promotion(); } bool operator==(const Move &lhs, const Move &rhs) { - return lhs.from() == rhs.from() - && lhs.to() == rhs.to() - && lhs.promotion() == rhs.promotion(); + return lhs.score() == rhs.score() + && lhs.from() == rhs.from() + && lhs.to() == rhs.to() + && lhs.promotion() == rhs.promotion(); +} + +unsigned int Move::score() const { + return mScore; +} + +void Move::setScore(unsigned int score) { + mScore = score; } diff --git a/Move.hpp b/Move.hpp index 73de343..c3c7636 100644 --- a/Move.hpp +++ b/Move.hpp @@ -7,14 +7,17 @@ #include #include #include +#include class Move { public: using Optional = std::optional; - Move(const Square &from, const Square &to, - const std::optional &promotion = std::nullopt); + explicit Move(const Square &from, const Square &to); + explicit Move(const Square &from, const Square &to, + const std::optional &promotion, unsigned score = 0); + explicit Move(const Square &from, const Square &to, unsigned score); static Optional fromUci(const std::string &uci); @@ -22,16 +25,23 @@ public: Square to() const; std::optional promotion() const; + + friend bool operator<(const Move &lhs, const Move &rhs); + friend bool operator==(const Move &lhs, const Move &rhs); + private: Square mFrom; Square mTo; - std::optional mPromotion; + std::optional mPromotion = std::nullopt; + unsigned mScore = std::numeric_limits::max(); +public: + unsigned int score() const; + + void setScore(unsigned int mScore); }; std::ostream &operator<<(std::ostream &os, const Move &move); // Needed for std::map, std::set -bool operator<(const Move &lhs, const Move &rhs); -bool operator==(const Move &lhs, const Move &rhs); #endif diff --git a/MoveGenerator.cpp b/MoveGenerator.cpp index 9626a33..64d3903 100644 --- a/MoveGenerator.cpp +++ b/MoveGenerator.cpp @@ -31,7 +31,7 @@ void MoveGenerator::generatePawnMoves(const BoardState &bs, const Square &from, if (isPromotion) { generateMovesWithPromotion(bs, from, movesBB, moves); } else { - generateMoves(bs, from, movesBB, moves); + generateMoves(bs, from, movesBB, PieceType::Pawn, moves); } } @@ -39,34 +39,42 @@ void MoveGenerator::generateBishopMoves(const BoardState &bs, const Square &from auto fromBB = BitBoard::fromIndex(from.index()); auto movesBB = BitBoard::bishopAttacks(fromBB, ~bs.occupiedBB) & ~(*bs.pieceBBs)[Board::toIndex(bs.turn)]; - generateMoves(bs, from, movesBB, moves); + generateMoves(bs, from, movesBB, PieceType::Bishop, moves); } -void MoveGenerator::generateMoves(const BoardState &bs, const Square &from, BitBoard movesBB, MoveVec &moves) { +void +MoveGenerator::generateMoves(const BoardState &bs, const Square &from, BitBoard movesBB, PieceType pt, MoveVec &moves) { auto fromBB = BitBoard::fromIndex(from.index()); while (movesBB) { auto to = Square(movesBB.pop()); - if (isCheck(bs, fromBB, to)) { + auto toBB = BitBoard::fromIndex(to.index()); + if (isCheck(bs, fromBB, toBB)) { continue; } - moves.emplace_back(from, to); + + moves.emplace_back(from, to, score(bs, toBB, pt)); } } -void MoveGenerator::generateMovesWithPromotion(const BoardState &bs, const Square &from, BitBoard movesBB, MoveVec &moves) { +void +MoveGenerator::generateMovesWithPromotion(const BoardState &bs, const Square &from, BitBoard movesBB, MoveVec &moves) { auto fromBB = BitBoard::fromIndex(from.index()); while (movesBB) { auto to = Square(movesBB.pop()); - if (isCheck(bs, fromBB, to)) { + auto toBB = BitBoard::fromIndex(to.index()); + if (isCheck(bs, fromBB, toBB)) { continue; } - for (const auto &kItem : Piece::PromotionTypes) { - moves.emplace_back(from, to, static_cast(kItem)); + for (const auto &kItem: Piece::PromotionTypes) { + + unsigned s = Piece::PromotionScore[Board::toIndex(kItem) - 2] + score(bs, toBB, PieceType::Pawn); + moves.emplace_back(from, to, static_cast(kItem), s); } } } + void MoveGenerator::generateKingMoves(const BoardState &bs, const Square &from, MoveGenerator::MoveVec &moves) { auto fromBB = BitBoard::fromIndex(from.index()); auto movesBB = BitBoard::kingMoves(fromBB) & ~(*bs.pieceBBs)[Board::toIndex(bs.turn)]; @@ -100,28 +108,28 @@ void MoveGenerator::generateKingMoves(const BoardState &bs, const Square &from, } } - generateMoves(bs, from, movesBB, moves); + generateMoves(bs, from, movesBB, PieceType::King, moves); } void MoveGenerator::generateRookMoves(const BoardState &bs, const Square &from, MoveVec &moves) { auto fromBB = BitBoard::fromIndex(from.index()); auto movesBB = BitBoard::rookAttacks(fromBB, ~bs.occupiedBB) & ~(*bs.pieceBBs)[Board::toIndex(bs.turn)]; - generateMoves(bs, from, movesBB, moves); + generateMoves(bs, from, movesBB, PieceType::Rook, moves); } void MoveGenerator::generateQueenMoves(const BoardState &bs, const Square &from, MoveVec &moves) { auto fromBB = BitBoard::fromIndex(from.index()); auto movesBB = BitBoard::queenAttacks(fromBB, ~bs.occupiedBB) & ~(*bs.pieceBBs)[Board::toIndex(bs.turn)]; - generateMoves(bs, from, movesBB, moves); + generateMoves(bs, from, movesBB, PieceType::Queen, moves); } void MoveGenerator::generateKnightMoves(const BoardState &bs, const Square &from, MoveVec &moves) { auto fromBB = BitBoard::fromIndex(from.index()); auto movesBB = BitBoard::knightMoves(fromBB) & ~(*bs.pieceBBs)[Board::toIndex(bs.turn)]; - generateMoves(bs, from, movesBB, moves); + generateMoves(bs, from, movesBB, PieceType::Knight, moves); } BitBoard MoveGenerator::generateAttackedSquares(const BoardState &bs, BitBoard target, PieceColor opColor) { @@ -152,16 +160,74 @@ BitBoard MoveGenerator::generateAttackedSquares(const BoardState &bs, BitBoard t return attacked; } -inline bool MoveGenerator::isCheck(const BoardState &bs, const BitBoard &fromBB, const Square &to) { + +inline bool MoveGenerator::isCheck(const BoardState &bs, const BitBoard &fromBB, const BitBoard &toBB) { auto kingBB = (*bs.pieceBBs)[Board::toIndex(bs.turn)] & (*bs.pieceBBs)[Board::toIndex(PieceType::King)]; - auto toBB = BitBoard::fromIndex(to.index()); auto changeBB = (fromBB ^ toBB); auto target = bs.occupiedBB ^ changeBB; - auto attacked = generateAttackedSquares(bs, ~target, !bs.turn); if (kingBB & fromBB) { - kingBB ^= changeBB; + + if (toBB & bs.occupiedBB) { + target ^= toBB; + } + if ((toBB & ~bs.occupiedBB)) { + kingBB ^= changeBB; + } } + + auto attacked = generateAttackedSquares(bs, ~target, !bs.turn); + return attacked & kingBB; } + +inline unsigned MoveGenerator::score(const BoardState &bs, const BitBoard &toBB, const PieceType moved) { + + unsigned score = 0; + auto opponentBB = bs.occupiedBB & ~(*bs.pieceBBs)[Board::toIndex(bs.turn)]; + + + // Check Capture + auto captureBB = toBB & opponentBB; + if (captureBB) { + auto captured = bs.pieceType(captureBB); + score = Piece::MVV_LVA[Board::toIndex(captured)][Board::toIndex(moved)]; + } + + return score; +} + +unsigned MoveGenerator::Mobility(const BoardState &bs, PieceColor color) { + unsigned count = 0; + auto colorMask = (*bs.pieceBBs)[Board::toIndex(color)]; + auto targets = (*bs.pieceBBs)[Board::toIndex(!color)]; + + // Pawns + auto pawnsBB = (*bs.pieceBBs)[Board::toIndex(PieceType::Pawn)] & colorMask; + if (color == PieceColor::White) { + auto movesBB = BitBoard::pawnNorthAttacks(pawnsBB, targets) & ~colorMask; + movesBB |= BitBoard::pawnNorthMoves(pawnsBB, ~bs.occupiedBB); + count += movesBB.count(); + } else { + auto movesBB = BitBoard::pawnSouthAttacks(pawnsBB, targets) & ~colorMask; + movesBB |= BitBoard::pawnSouthMoves(pawnsBB, ~bs.occupiedBB); + count += movesBB.count(); + } + // Bishops + count += (BitBoard::bishopAttacks((*bs.pieceBBs)[Board::toIndex(PieceType::Bishop)] & colorMask, ~bs.occupiedBB) & + ~colorMask).count(); + // Knights + count += (BitBoard::knightMoves((*bs.pieceBBs)[Board::toIndex(PieceType::Knight)] & colorMask) & + ~colorMask).count(); + // Rook + count += (BitBoard::rookAttacks((*bs.pieceBBs)[Board::toIndex(PieceType::Rook)] & colorMask, ~bs.occupiedBB) & + ~colorMask).count(); + // Queen + count += (BitBoard::queenAttacks((*bs.pieceBBs)[Board::toIndex(PieceType::Queen)] & colorMask, ~bs.occupiedBB) & + ~colorMask).count(); + // King + count += (BitBoard::kingMoves((*bs.pieceBBs)[Board::toIndex(PieceType::King)] & colorMask) & ~colorMask).count(); + + return count; +} \ No newline at end of file diff --git a/MoveGenerator.hpp b/MoveGenerator.hpp index 0101cd6..cba2dba 100644 --- a/MoveGenerator.hpp +++ b/MoveGenerator.hpp @@ -25,13 +25,16 @@ public: static void generateKnightMoves(const BoardState &bs, const Square &from, MoveVec &moves); static BitBoard generateAttackedSquares(const BoardState &bs, BitBoard target, PieceColor opColor); + static unsigned int Mobility(const BoardState &bs, PieceColor color); + private: static void generatePawnMoves(const BoardState &bs, const Square &from, BitBoard targets, MoveVec &moves); - static void generateMoves(const BoardState &bs, const Square &from, BitBoard movesBB, MoveVec &moves); + static void generateMoves(const BoardState &bs, const Square &from, BitBoard movesBB, PieceType pt, MoveVec &moves); static void generateMovesWithPromotion(const BoardState &bs, const Square &from, BitBoard movesBB, MoveVec &moves); - inline static bool isCheck(const BoardState &bs, const BitBoard &fromBB, const Square &to); + inline static bool isCheck(const BoardState &bs, const BitBoard &fromBB, const BitBoard &toBB); + inline static unsigned score(const BoardState &bs, const BitBoard &toBB, PieceType moved); }; #endif //CHESS_ENGINE_MOVEGENERATOR_HPP diff --git a/Piece.hpp b/Piece.hpp index d7e2070..aa86fe6 100644 --- a/Piece.hpp +++ b/Piece.hpp @@ -24,19 +24,34 @@ class Piece { public: - constexpr static const PieceType PromotionTypes[4] = {PieceType::Knight, PieceType::Bishop, PieceType::Rook, PieceType::Queen}; + constexpr static const PieceType PromotionTypes[4] = {PieceType::Knight, PieceType::Bishop, PieceType::Rook, + PieceType::Queen}; + constexpr static const unsigned PieceValue[6] = {100, 350, 350, 525, 1000, 10000}; + constexpr static const unsigned PromotionScore[6] = {0, 26, 36, 46, 56, 0}; + + constexpr static const unsigned MVV_LVA[6][6] = { + {15, 14, 13, 12, 11, 10}, // Pawn + {25, 24, 23, 22, 21, 20}, // Knight + {35, 34, 33, 32, 31, 30}, // Bishop + {45, 44, 43, 42, 41, 40}, // Rook + {55, 54, 53, 52, 51, 50}, // Queen + {65, 64, 63, 62, 61, 60}, // King + }; using Optional = std::optional; Piece(PieceColor color, PieceType type); static Optional fromSymbol(char symbol); + static char toSymbol(PieceType type); static std::optional pieceTypeFromSymbol(char symbol); PieceColor color() const; + PieceType type() const; + char toSymbol() const; private: @@ -45,6 +60,7 @@ private: }; bool operator==(const Piece &lhs, const Piece &rhs); + std::ostream &operator<<(std::ostream &os, const Piece &piece); // Invert a color (White becomes Black and vice versa) diff --git a/Search.cpp b/Search.cpp index fac0b58..fdc67a4 100644 --- a/Search.cpp +++ b/Search.cpp @@ -1,8 +1,11 @@ +#include #include "Search.hpp" #include "Move.hpp" #include "Board.hpp" -int Search::search(Node *node, const Board &board, unsigned depth) { +#include + +int Search::search(Node *node, const Board &board, unsigned depth, int alpha, int beta) { if (depth == 0) { return evaluate(board); @@ -13,6 +16,7 @@ int Search::search(Node *node, const Board &board, unsigned depth) { if (moves.empty()) { return evaluate(board); } + std::stable_sort(moves.begin(), moves.end()); int maxValue = kNegInfinity; for (auto &kMove: moves) { @@ -20,13 +24,18 @@ int Search::search(Node *node, const Board &board, unsigned depth) { nodeBoard.makeMove(kMove); Node child(kMove); - child.value = search(&child, nodeBoard, depth - 1); + child.value = search(&child, nodeBoard, depth - 1, -beta, -alpha); auto value = -child.value; if (value > maxValue) { maxValue = value; node->next = std::make_unique(child); } + + alpha = std::max(alpha, value); + if (alpha >= beta) { + break; + } } node->value = maxValue; @@ -39,9 +48,11 @@ int Search::evaluate(const Board &board) { return board.evaluate(); } + PrincipalVariation Search::start(const Board &board) { auto rootMoves = Board::MoveVec(); board.pseudoLegalMoves(rootMoves); + std::stable_sort(rootMoves.begin(), rootMoves.end()); if (rootMoves.size() <= 1) { auto b = board; @@ -56,7 +67,7 @@ PrincipalVariation Search::start(const Board &board) { Board nodeBoard = board; nodeBoard.makeMove(kMove); - auto value = -search(&node, nodeBoard, 4); + auto value = -search(&node, nodeBoard, 5, kNegInfinity, kPosInfinity); if (value > best->value) { best->child = std::make_unique(node); best->board = nodeBoard; diff --git a/Search.hpp b/Search.hpp index eba1c9f..cbb7ec2 100644 --- a/Search.hpp +++ b/Search.hpp @@ -9,15 +9,16 @@ namespace Search { +const int kPosInfinity = std::numeric_limits::max(); const int kNegInfinity = std::numeric_limits::min(); struct Node { - explicit Node(const Move move) : move(move) { + explicit Node(const Move &move) : move(move) { } Node(Node &node) : move(node.move) { next = std::move(node.next); - value = node.value + 10; + value = node.value; } std::unique_ptr next; Move move; @@ -30,7 +31,7 @@ struct RootNode { int value = kNegInfinity; }; -int search(Node *node, const Board &board, unsigned depth); +int search(Node *node, const Board &board, unsigned depth, int alpha, int beta); PrincipalVariation start(const Board &board); int evaluate(const Board &board); } diff --git a/Tests/EngineTests.cpp b/Tests/EngineTests.cpp index aeafb96..f04a4b6 100644 --- a/Tests/EngineTests.cpp +++ b/Tests/EngineTests.cpp @@ -35,6 +35,7 @@ static void testMove(const char *fen, const std::vector &expectedMo for (const auto &moveName : expectedMoves) { auto pv = engine->pv(board.value()); Move move = *pv.begin(); + move.setScore(0); REQUIRE(move == Move::fromUci(moveName)); board->makeMove(move); } @@ -67,6 +68,8 @@ TEST_CASE("Puzzles mateIn1_simple", "[Engine][Puzzle]") { auto [fen, moves] = GENERATE(table>({ {"2bQ1k1r/1p3p2/p4b1p/4pNp1/2q5/8/PPP2PPP/1K1RR3 b - - 3 23",{"f6d8", "d1d8"}}, // https://lichess.org/gQa01loO/black#46 {"5rk1/ppp5/2n4p/3b2p1/6R1/P1B1P2P/1Pq5/R2Q2K1 b - - 3 29",{"c2f2"}}, // https://lichess.org/haKJxadR#57 + {"r1b1k2r/ppp2pp1/2p5/2b1q1Bp/3PP1n1/2P5/PP2BPPP/RN1Q1RK1 b kq - 0 10",{"e5h2"}}, + {"r3rk2/5ppQ/b1pp1q1p/p5n1/P1P5/2N5/1PB2PP1/R3R1K1 w - - 6 24", {"h7h8"}}, })); testMove(fen, moves); } diff --git a/Tests/FenTests.cpp b/Tests/FenTests.cpp index f44d5c4..fbf9ccb 100644 --- a/Tests/FenTests.cpp +++ b/Tests/FenTests.cpp @@ -104,7 +104,7 @@ TEST_CASE("En passant square is correctly parsed", "[Fen][EnPassant]") { { "rnbqkbnr/ppppp1pp/8/8/2PPPp2/8/PP3PPP/RNBQKBNR b KQkq e3 0 3", Square::E3 - } + }, })); CAPTURE(fen, ep);