Nicolas Winkler 7 năm trước cách đây
mục cha
commit
f262641dba
9 tập tin đã thay đổi với 217 bổ sung76 xóa
  1. 1 0
      src/BitBoard.h
  2. 51 33
      src/ChessGame.cpp
  3. 12 7
      src/ChessGame.h
  4. 91 1
      src/Minimax.cpp
  5. 3 0
      src/Minimax.h
  6. 20 13
      src/MoveGeneration.cpp
  7. 18 5
      src/TranspositionTable.cpp
  8. 19 14
      src/TranspositionTable.h
  9. 2 3
      src/main.cpp

+ 1 - 0
src/BitBoard.h

@@ -227,6 +227,7 @@ struct chessy::Bitboard
     inline Index    getLeastSignificantBit (void) const     { return trailingZeroes(bits); }
     inline int      popcount    (void) const                { return chessy::popcount(bits); }
 
+    // TODO: don't use, they're inverted!!!
     static const uint64_t aFile = 0x8080808080808080ULL;
     static const uint64_t hFile = 0x0101010101010101ULL;
 };

+ 51 - 33
src/ChessGame.cpp

@@ -1,4 +1,7 @@
 #include "ChessGame.h"
+
+#include "Util.h"
+
 #include <sstream>
 #include <stdexcept>
 
@@ -14,6 +17,7 @@ MoveInfo::MoveInfo(Move move, const ChessGame& cg) :
 ChessGame::ChessGame(void)
 {
     board.resetBoard();
+    hash = zobrist::getHash(*this);
 }
 
 
@@ -23,19 +27,6 @@ ChessGame::ChessGame(const std::string& fenString)
 }
 
 
-bool ChessGame::isValidMove(const Move& move) const
-{
-    return false;
-}
-
-
-std::vector<Move> ChessGame::getValidMoves(void) const
-{
-    std::vector<Move> ret;
-    return ret;
-}
-
-
 void ChessGame::move(Move move)
 {
     MoveInfo mi;
@@ -73,25 +64,26 @@ void ChessGame::loadFromFen(const std::string& fenString)
     else throw runtime_error("invalid turn "s + turn);
 
     castlingRights = 0;
-    if (castling != "-")
+    if (castling != "-") {
         for (auto character : castling) {
             switch (character) {
-                case 'k':
-                    setCanCastleKingSide(WHITE_SIDE, true);
-                    break;
-                case 'q':
-                    setCanCastleQueenSide(WHITE_SIDE, true);
-                    break;
-                case 'K':
-                    setCanCastleKingSide(BLACK_SIDE, true);
-                    break;
-                case 'Q':
-                    setCanCastleQueenSide(BLACK_SIDE, true);
-                    break;
-                default:
-                    throw runtime_error("invalid castling right: "s + character);
+            case 'k':
+                setCanCastleKingSide(WHITE_SIDE, true);
+                break;
+            case 'q':
+                setCanCastleQueenSide(WHITE_SIDE, true);
+                break;
+            case 'K':
+                setCanCastleKingSide(BLACK_SIDE, true);
+                break;
+            case 'Q':
+                setCanCastleQueenSide(BLACK_SIDE, true);
+                break;
+            default:
+                throw runtime_error("invalid castling right: "s + character);
             }
         }
+    }
 
     if (enPassant == "-"s) {
         this->enPassant = -1;
@@ -99,7 +91,7 @@ void ChessGame::loadFromFen(const std::string& fenString)
     else {
         if (enPassant.size() != 2 || (enPassant[1] != '3' && enPassant[1] != '6'))
             throw runtime_error("invalid en passant string: "s + enPassant);
-        this->enPassant = Index{ enPassant };
+        this->enPassant = Index{ enPassant }.getColumn();
     }
 
     try {
@@ -115,6 +107,8 @@ void ChessGame::loadFromFen(const std::string& fenString)
     catch(...) {
         throw runtime_error("invalid number of moves: "s + halfmoves);
     }
+
+    hash = zobrist::getHash(*this);
 }
 
 
@@ -139,7 +133,7 @@ std::string ChessGame::generateFen(void) const
         enPassant = "-"s;
     }
     else {
-        enPassant = this->enPassant.getName();
+        enPassant = Index{ this->turn == WHITE_SIDE ? 3 : 5, this->enPassant }.getName();
     }
 
     string halfmoves = to_string(reversibleHalfMoves);
@@ -152,68 +146,88 @@ std::string ChessGame::generateFen(void) const
 
 UndoInfo ChessGame::doMove(const MoveInfo& mi)
 {
-    UndoInfo ui;
+    assert(hash == zobrist::getHash(*this)); // valid zobrist hash
 
+    UndoInfo ui;
+    ui.hash = hash;
     // adjust castling rights
     ui.castlingRights = castlingRights;
+    hash ^= zobrist::castlingRights[castlingRights];
     if (mi.movedPiece == KING) {
         setCanCastleKingSide(turn, false);
         setCanCastleQueenSide(turn, false);
     }
+    hash ^= zobrist::castlingRights[castlingRights];
 
     // update possible en-passant moves
     ui.enPassantBefore = enPassant;
+    if (enPassant != -1)
+        hash ^= zobrist::enPassant[enPassant];
     if (mi.movedPiece == PAWN &&
-        std::abs(mi.move.destination.getRow() - mi.move.destination.getRow()) == 2) {
+        std::abs(mi.move.origin.getRow() - mi.move.destination.getRow()) == 2) {
         enPassant = mi.move.origin.getRow();
     }
     else {
         enPassant = -1;
     }
+    if (enPassant != -1)
+        hash ^= zobrist::enPassant[enPassant];
 
     ui.type = mi.movedPiece;
     Bitboard& b = board.getBitboards(turn)[ui.type];
     ui.before = b;
 
     Bitboard target = Bitboard::fromIndex(mi.move.destination);
+    hash ^= zobrist::get(mi.movedPiece, turn, mi.move.origin);
     b ^= Bitboard::fromIndex(mi.move.origin);
     if (mi.move.promotion == PAWN) {
         b |= target;
+        hash ^= zobrist::get(mi.movedPiece, turn, mi.move.destination);
         ui.promotion = PAWN;
     }
     else {
         ui.promotion = mi.move.promotion;
         ui.promotionBefore = board.getBitboards(turn)[ui.promotion];
         board.getBitboards(turn)[ui.promotion] |= target;
+        hash ^= zobrist::get(ui.promotion, turn, mi.move.destination);
     }
+    if (mi.move.isEnPassant)
+        int asas = 4;
 
     ui.captured = PieceType::EMPTY;
     if (mi.move.isEnPassant)
         target = Bitboard::fromIndex(Index{
-            turn == WHITE_SIDE ? 5 : 2, mi.move.destination.getColumn() });
+            turn == WHITE_SIDE ? 4 : 3, mi.move.destination.getColumn() });
     for (int i = 0; i < 6; i++) {
         Bitboard& e = board.getBitboards(otherSide(turn))[i];
         if (target & e) {
             ui.captured = static_cast<PieceType>(i);
             ui.beforeCaptured = e;
             e ^= target;
+            hash ^= zobrist::get(ui.captured, otherSide(turn),
+                target.getLeastSignificantBit());
             break;
         }
     }
 
     turn = otherSide(turn);
+    hash ^= zobrist::turn;
     moveCount++;
     ui.reversibleHalfMoves = reversibleHalfMoves;
     if(mi.movedPiece != PAWN &&
             board.getAtPosition(mi.move.destination) != EMPTY) {
         reversibleHalfMoves++;
     }
+
+    assert(hash == zobrist::getHash(*this)); // valid zobrist hash
     return ui;
 }
 
 
 void ChessGame::undoMove(const UndoInfo& ui)
 {
+    assert(hash == zobrist::getHash(*this)); // valid zobrist hash
+
     turn = otherSide(turn);
 
     Bitboard& b = board.getBitboards(turn)[ui.type];
@@ -222,9 +236,13 @@ void ChessGame::undoMove(const UndoInfo& ui)
         board.getBitboards(otherSide(turn))[ui.captured] = ui.beforeCaptured;
     if (ui.promotion != PieceType::PAWN)
         board.getBitboards(turn)[ui.promotion] = ui.promotionBefore;
+
     moveCount--;
     reversibleHalfMoves = ui.reversibleHalfMoves;
     castlingRights = ui.castlingRights;
     enPassant = ui.enPassantBefore;
+    hash = ui.hash;
+
+    assert(hash == zobrist::getHash(*this)); // valid zobrist hash
 }
 

+ 12 - 7
src/ChessGame.h

@@ -6,6 +6,8 @@
 
 #include "Board.h"
 
+#include "TranspositionTable.h"
+
 namespace chessy
 {
     struct MoveInfo;
@@ -15,6 +17,7 @@ namespace chessy
 }
 
 
+
 /*!
  * This structure holds additional information about a move.
  */
@@ -42,6 +45,7 @@ struct chessy::UndoInfo
     Bitboard before;
     Bitboard beforeCaptured;
     Bitboard promotionBefore;
+    HashValue hash;
     PieceType type;
     PieceType captured;
     PieceType promotion;
@@ -55,21 +59,25 @@ class chessy::ChessGame
 {
     Board board;
 
+    short reversibleHalfMoves =     0;
+
     //! stores the castling rights according to the following scheme:
     //! <b> XXXXQKqk </b>,
     //! where X is an unused bit, Q and K store weather black can castle
     //! to queen side/king side and q and k store the same for white.
-    uint8_t castlingRights;
-
-    short reversibleHalfMoves =     0;
+    uint8_t castlingRights =        0xF;
 
+    //! file of en-passant-capturable pawn
     // -1 for no en passant possible
-    Index enPassant =              -1;
+    int8_t enPassant =              -1;
 
     Side turn =                     WHITE_SIDE;
 
     int moveCount =                 1;
 
+    //! holds the zobrist hash value for the current position
+    HashValue hash;
+
 public:
 
     ChessGame(void);
@@ -77,9 +85,6 @@ public:
     ChessGame(const ChessGame&) = default;
     ~ChessGame(void) = default;
 
-    bool isValidMove(const Move& move) const;
-    std::vector<Move> getValidMoves(void) const;
-
     inline const Board& getBoard(void) const    { return board; }
     inline       Board& getBoard(void)          { return board; }
 

+ 91 - 1
src/Minimax.cpp

@@ -11,7 +11,7 @@
 using namespace chessy;
 
 
-size_t chessy::perft(std::ostream& out, ChessGame& chessGame, int depth)
+/*size_t chessy::perft(std::ostream& out, ChessGame& chessGame, int depth)
 {
     size_t result = 0;
 
@@ -32,6 +32,9 @@ size_t chessy::perft(std::ostream& out, ChessGame& chessGame, int depth)
             cg.undoMove(ui);
         }
         else {
+            if (print)
+                std::cout << mi.move.asString() << ": " <<
+                    1 << std::endl;
             result++;
         }
     };
@@ -61,6 +64,49 @@ size_t chessy::perft(std::ostream& out, ChessGame& chessGame, int depth)
         " seconds. (at " << nodesPerSecond << " nodes per second)" << std::endl;
 
     return result;
+}*/
+size_t chessy::perft(std::ostream& out, ChessGame& cg, int depth)
+{
+    std::function<size_t(bool, int)> iterate = [&](bool print, int depth) -> size_t {
+        if (depth <= 0)
+            return 1;
+        std::vector<Move> moves;
+        moves.reserve(50);
+        if (cg.getTurn() == WHITE_SIDE)
+            generateAllMoves<WHITE_SIDE, false>(cg, moves);
+        else
+            generateAllMoves<BLACK_SIDE, false>(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<milliseconds>(end - begin);
+    double seconds = millis.count() / 1000.0;
+    size_t nodesPerSecond = (sum * 1000 * 1000 * 1000) /
+        duration_cast<nanoseconds>(end - begin).count();
+
+    out << "Searched " << sum << " nodes in " << seconds <<
+        " seconds. (at " << nodesPerSecond << " nodes per second)" << std::endl;
+
+
+    return sum;
 }
 
 #include <iostream>
@@ -119,6 +165,7 @@ 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();
@@ -128,6 +175,7 @@ MoveValue chessy::negamaxImplementation(ChessGame& cg, int depth,
     };
 
     std::vector<Move> moves;
+    moves.reserve(200);
     if (cg.getTurn() == WHITE_SIDE) {
         generateAllMoves<WHITE_SIDE, false>(cg, moves);
         orderMoves<WHITE_SIDE>(cg, moves);
@@ -160,6 +208,48 @@ MoveValue chessy::negamaxImplementation(ChessGame& cg, int depth,
 }
 
 
+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<WHITE_SIDE>() :
+            b.isCheck<BLACK_SIDE>();
+    };
+
+    std::vector<Move> moves;
+    moves.reserve(50);
+    if (cg.getTurn() == WHITE_SIDE)
+        generateAllMoves<WHITE_SIDE, true>(cg, moves);
+    else
+        generateAllMoves<BLACK_SIDE, true>(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<Side side>
 MoveValue chessy::evaluatePositives(const ChessGame& game)
 {

+ 3 - 0
src/Minimax.h

@@ -27,6 +27,9 @@ namespace chessy
     MoveValue negamaxImplementation(ChessGame& cg, int depth,
             MoveValue alpha, MoveValue beta);
 
+    MoveValue quiescence(ChessGame& cg, int maxDepth,
+        MoveValue alpha, MoveValue beta);
+
     template<Side side>
     MoveValue evaluatePositives(const ChessGame& game);
 

+ 20 - 13
src/MoveGeneration.cpp

@@ -386,10 +386,14 @@ void chessy::generateCastling(const ChessGame& cg, std::vector<Move>& moves)
 template<Side side>
 void chessy::generateEnPassant(const ChessGame& cg, std::vector<Move>& moves)
 {
-    if (cg.getEnPassantIndex() != -1) {
-        Bitboard mask = side == WHITE_SIDE ? 0x000000FF00000000 : 0x00000000FF000000;
+    int epFile = cg.getEnPassantIndex();
+    if (epFile != -1) {
+        Bitboard rowMask = side == WHITE_SIDE ? 0x000000FF00000000 : 0x00000000FF000000;
+        Bitboard fileMask = Bitboard::hFile << epFile;
+        if (epFile > 0) fileMask |= Bitboard::hFile << (epFile - 1);
+        if (epFile < 7) fileMask |= Bitboard::hFile << (epFile + 1);
         Bitboard pawns = cg.getBoard().getPawns<side>();
-        for (auto pawn : PositionSet{ pawns & mask })
+        for (auto pawn : PositionSet{ pawns & rowMask & fileMask })
             moves.emplace_back(pawn, Index{ side == WHITE_SIDE ? 5 : 2,
                 cg.getEnPassantIndex() }, false, true);
     }
@@ -398,17 +402,20 @@ void chessy::generateEnPassant(const ChessGame& cg, std::vector<Move>& moves)
 template<Side side, bool capturesOnly>
 void chessy::generateAllMoves(const ChessGame& cg, std::vector<Move>& moves)
 {
-    generatePawnPushes<side>(cg, moves);
-    generatePawnDoublePushes<side>(cg, moves);
+    if (!capturesOnly) {
+        generatePawnPushes<side>(cg, moves);
+        generatePawnDoublePushes<side>(cg, moves);
+        generateEnPassant<side>(cg, moves);
+    }
     generatePawnCaptures<side>(cg, moves);
-    generatePawnPromotions<side, false>(cg, moves);
-    generateKnightMoves<side, false>(cg, moves);
-    generateQueenMoves<side, false>(cg, moves);
-    generateBishopMoves<side, false>(cg, moves);
-    generateRookMoves<side, false>(cg, moves);
-    generateKingMoves<side, false>(cg, moves);
-    generateCastling<side>(cg, moves);
-    generateEnPassant<side>(cg, moves);
+    generatePawnPromotions<side, capturesOnly>(cg, moves);
+    generateKnightMoves<side, capturesOnly>(cg, moves);
+    generateQueenMoves<side, capturesOnly>(cg, moves);
+    generateBishopMoves<side, capturesOnly>(cg, moves);
+    generateRookMoves<side, capturesOnly>(cg, moves);
+    generateKingMoves<side, capturesOnly>(cg, moves);
+    if (!capturesOnly)
+        generateCastling<side>(cg, moves);
 }
 
 

+ 18 - 5
src/TranspositionTable.cpp

@@ -1,29 +1,42 @@
 #include "TranspositionTable.h"
 #include "MoveGeneration.h"
+#include "ChessGame.h"
 #include <random>
 
 using namespace chessy;
 
+std::array<HashValue, 64 * 12>  zobrist::pieces;
+std::array<HashValue, 16>       zobrist::castlingRights;
+std::array<HashValue, 8>        zobrist::enPassant;
+HashValue                       zobrist::turn;
 
-HashValue ZobristValues::getHash(const ChessGame& cg) const
+
+HashValue zobrist::getHash(const ChessGame& cg)
 {
-    HashValue value;
+    HashValue value = 0;
     const Board& b = cg.getBoard();
     // iterate over all piece types
     for (int i = 0; i < 12; i++) {
-        Bitboard t = b.getBitboards(i < 6 ? WHITE_SIDE : BLACK_SIDE)[i];
+        Side side = i < 6 ? WHITE_SIDE : BLACK_SIDE;
+        Bitboard t = b.getBitboards(side)[i % 6];
         for (auto pos : PositionSet{ t }) {
-            value ^= getNumber(PieceType(i), pos);
+            value ^= get(PieceType(i % 6), side, pos);
         }
     }
     value ^= castlingRights[cg.getCastlingRights()];
     if (cg.getEnPassantIndex() != -1)
         value ^= enPassant[cg.getEnPassantIndex()];
+
+    if (cg.getTurn() != WHITE_SIDE)
+        value ^= turn;
+
+    return value;
 }
 
 
-void ZobristValues::create(uint64_t seed)
+void zobrist::initialize(void)
 {
+    uint64_t seed = 0xCFC9725D8992B787;
     std::mt19937_64 engine(seed);
     std::uniform_int_distribution<HashValue> distribution;
     auto rnd = [&]() { return distribution(engine); };

+ 19 - 14
src/TranspositionTable.h

@@ -3,32 +3,37 @@
 #include <array>
 #include <cinttypes>
 
-#include "ChessGame.h"
+#include "Bitboard.h"
 
 namespace chessy
 {
     using HashValue = uint64_t;
-    struct ZobristValues;
     class TranspositionTable;
+
+    // forward declaration
+    class ChessGame;
 }
 
 
-struct chessy::ZobristValues
+namespace chessy
 {
-    std::array<HashValue, 64 * 12> pieces;
-    std::array<HashValue, 16> castlingRights;
-    std::array<HashValue, 8> enPassant;
-    HashValue turn;
+    namespace zobrist
+    {
+        extern std::array<HashValue, 64 * 12> pieces;
+        extern std::array<HashValue, 16> castlingRights;
+        extern std::array<HashValue, 8> enPassant;
+        extern HashValue turn;
 
-    HashValue getHash(const ChessGame&) const;
+        HashValue getHash(const ChessGame&);
 
-    inline HashValue getNumber(PieceType p, Index i) const
-    {
-        return pieces[static_cast<int>(p) * 64 + i];
-    }
+        inline HashValue get(PieceType p, Side side, Index i)
+        {
+            return pieces[(static_cast<int>(p) + side * 6) * 64 + i];
+        }
 
-    void create(uint64_t seed);
-};
+        void initialize(void);
+    }
+}
 
 
 class chessy::TranspositionTable

+ 2 - 3
src/main.cpp

@@ -2,6 +2,7 @@
 #include "EngineInfo.h"
 #include "UciParser.h"
 
+#include "TranspositionTable.h"
 #include "Search.h"
 
 using namespace std;
@@ -30,9 +31,7 @@ auto main(int argc, char** argv) -> int
 
     return 0;*/
 
-    argc = popcount(argc);
-
-
+    chessy::zobrist::initialize();
 
     if (argc > 1 && (string(argv[1]) == "-h" || string(argv[1]) == "--help")) {
         cout << info::helpText << endl;