From b6ce279cc352717806cec2d212777839c9805de5 Mon Sep 17 00:00:00 2001 From: Quinniboi10 <69563433+Quinniboi10@users.noreply.github.com> Date: Sat, 12 Jul 2025 10:39:19 -0500 Subject: [PATCH 1/5] FRC support Bench: 1718488 --- src/chess/board.cc | 166 +++++++++++++++------------- src/chess/board.h | 71 +++++++----- src/chess/fen.cc | 25 ++++- src/chess/move.cc | 29 +++-- src/chess/move_gen.cc | 81 +++++++++----- src/chess/move_gen.h | 6 +- src/engine/evaluation/evaluation.cc | 3 +- src/engine/uci/uci.cc | 1 + src/utils/types.h | 7 ++ 9 files changed, 241 insertions(+), 148 deletions(-) diff --git a/src/chess/board.cc b/src/chess/board.cc index b07644f0..9e32daf3 100644 --- a/src/chess/board.cc +++ b/src/chess/board.cc @@ -6,18 +6,12 @@ #include "move.h" #include "move_gen.h" -// clang-format off -constexpr std::array kCastlingRights = { - 7, 15, 15, 15, 3, 15, 15, 11, - 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, - 13, 15, 15, 15, 12, 15, 15, 14 -}; -// clang-format on +#include + +constexpr std::array kRookEndSquares = { + Square(kD8), Square(kF8), Square(kD1), Square(kF1)}; +constexpr std::array kKingEndSquares = { + Square(kC8), Square(kG8), Square(kC1), Square(kG1)}; Board::Board() : history_({}) {} @@ -49,11 +43,14 @@ void Board::SetFromFen(std::string_view fen_str) { } bool Board::IsMovePseudoLegal(Move move) const { + if (move.IsNull()) return false; + const auto from = move.GetFrom(), to = move.GetTo(); const Color us = state_.turn; const BitBoard &our_pieces = state_.Occupied(us); - if (!our_pieces.IsSet(from) || our_pieces.IsSet(to)) { + if (!our_pieces.IsSet(from) || + (our_pieces.IsSet(to) && move.GetType() != MoveType::kCastle)) { return false; } @@ -67,29 +64,13 @@ bool Board::IsMovePseudoLegal(Move move) const { const BitBoard occupied = our_pieces | their_pieces; if (move_type == MoveType::kCastle) { - if (piece_type != PieceType::kKing) { - return false; - } + if (piece_type != PieceType::kKing) return false; - constexpr int kKingsideCastleDist = -2; - constexpr int kQueensideCastleDist = 2; - constexpr BitBoard kWhiteKingsideOccupancy = 0x60; - constexpr BitBoard kWhiteQueensideOccupancy = 0xe; - constexpr BitBoard kBlackKingsideOccupancy = 0x6000000000000000; - constexpr BitBoard kBlackQueensideOccupancy = 0xe00000000000000; - - // Note: the only way move_dist is ever 2 or -2 is from - // move_gen::CastlingMoves allowing it - const int move_dist = static_cast(from) - static_cast(to); - if (move_dist == kKingsideCastleDist) { - return !state_.checkers && state_.castle_rights.CanKingsideCastle(us) && - !(occupied & (us == Color::kWhite ? kWhiteKingsideOccupancy - : kBlackKingsideOccupancy)); - } else if (move_dist == kQueensideCastleDist) { - return !state_.checkers && state_.castle_rights.CanQueensideCastle(us) && - !(occupied & (us == Color::kWhite ? kWhiteQueensideOccupancy - : kBlackQueensideOccupancy)); - } + if (state_.GetPieceType(to) != PieceType::kRook) return false; + + if (to > from) + return state_.castle_rights.CastleSq(state_.turn, CastleRights::kKingside) == to; + return state_.castle_rights.CastleSq(state_.turn, CastleRights::kQueenside) == to; } if (move_type == MoveType::kEnPassant) { @@ -138,22 +119,42 @@ bool Board::IsMoveLegal(Move move) const { const auto piece_type = state_.GetPieceType(from); if (piece_type == PieceType::kKing) { - constexpr int kKingsideCastleDist = -2; - constexpr int kQueensideCastleDist = 2; - - // Note: the only way move_dist is ever 2 or -2 is from - // move_gen::CastlingMoves allowing it - const int move_dist = static_cast(from) - static_cast(to); - if (move_dist == kKingsideCastleDist) { - return !move_gen::GetAttackersTo( - state_, is_white ? Squares::kG1 : Squares::kG8, them) && - !move_gen::GetAttackersTo( - state_, is_white ? Squares::kF1 : Squares::kF8, them); - } else if (move_dist == kQueensideCastleDist) { - return !move_gen::GetAttackersTo( - state_, is_white ? Squares::kC1 : Squares::kC8, them) && - !move_gen::GetAttackersTo( - state_, is_white ? Squares::kD1 : Squares::kD8, them); + if (move.GetType() == MoveType::kCastle) { + if (state_.InCheck()) return false; + + const bool is_kingside = to > from; + + assert(is_kingside ? state_.castle_rights.CanKingsideCastle(us) + : state_.castle_rights.CanQueensideCastle(us)); + + if (state_.pinned[state_.turn].IsSet(to)) return false; + + const Square king_sq = move.GetFrom(); + const Square rook_sq = move.GetTo(); + + const Square king_to = kKingEndSquares[CastleRights::CastleIndex( + us, + king_sq > rook_sq ? CastleRights::kQueenside + : CastleRights::kKingside)]; + const Square rook_to = kRookEndSquares[CastleRights::CastleIndex( + us, + king_sq > rook_sq ? CastleRights::kQueenside + : CastleRights::kKingside)]; + + BitBoard between_bb = move_gen::RayIncluding(king_sq, king_to) | + move_gen::RayIncluding(rook_sq, rook_to); + between_bb.ClearBit(king_sq); + between_bb.ClearBit(rook_sq); + + if ((between_bb & state_.Occupied()).PopCount() > 0) return false; + + between_bb = move_gen::RayIncluding(king_sq, king_to); + between_bb.ClearBit(king_sq); + + while (between_bb.PopCount() > 0) + if (state_.threats.IsSet(between_bb.PopLsb())) return false; + + return true; } // Make sure the destination square isn't attacked @@ -208,13 +209,16 @@ void Board::MakeMove(Move move) { const auto from = move.GetFrom(), to = move.GetTo(); const auto piece = state_.GetPieceType(from), - captured = state_.GetPieceType(to); + captured = move.GetType() == MoveType::kCastle ? PieceType::kNone : state_.GetPieceType(to); const auto move_type = move.GetType(); // Initialize accumulator change nnue::AccumulatorChange accum_change{}; accum_change.sub_0 = {from, piece, us}; - accum_change.add_0 = {to, piece, us}; + if (move_type != MoveType::kCastle) + accum_change.add_0 = {to, piece, us}; + else + accum_change.add_0 = {kKingEndSquares[CastleRights::CastleIndex(us, to > from ? CastleRights::kKingside : CastleRights::kQueenside)], piece, us}; int new_fifty_move_clock = piece == PieceType::kPawn ? 0 : state_.fifty_moves_clock + 1; @@ -252,8 +256,8 @@ void Board::MakeMove(Move move) { if (move_type == MoveType::kCastle) { HandleCastling(move); accum_change.type = nnue::AccumulatorChange::kCastle; - const Square rook_from = to > from ? Square(to + 1) : Square(to - 2); - const Square rook_to = to > from ? Square(to - 1) : Square(to + 1); + const Square rook_from = to; + const Square rook_to = kRookEndSquares[CastleRights::CastleIndex(us, from > to ? CastleRights::kQueenside : CastleRights::kKingside)]; accum_change.add_1 = {rook_to, PieceType::kRook, us}; accum_change.sub_1 = {rook_from, PieceType::kRook, us}; } else if (move_type == MoveType::kPromotion) { @@ -261,11 +265,31 @@ void Board::MakeMove(Move move) { accum_change.add_0.piece = new_piece; } - state_.PlacePiece(to, new_piece, state_.turn); + if (move_type != MoveType::kCastle) + state_.PlacePiece(to, new_piece, state_.turn); // Update the castling rights depending on the piece that moved state_.zobrist_key ^= zobrist::castle_rights[state_.castle_rights.AsU8()]; - state_.castle_rights &= kCastlingRights[from] & kCastlingRights[to]; + + if (piece == kRook) { + const auto side = from > state_.King(us).GetLsb() ? CastleRights::kKingside : CastleRights::kQueenside; + + const Square sq = state_.castle_rights.CastleSq(us, side); + + if (from == sq) + state_.castle_rights.SetCastlingRights(us, side, kNoSquare); + + } else if (piece == kKing) + state_.castle_rights.UnsetCastlingRights(us); + if (state_.GetPieceType(to)) { + const auto side = to > state_.King(them).GetLsb() ? CastleRights::kKingside : CastleRights::kQueenside; + + const Square sq = state_.castle_rights.CastleSq(them, side); + + if (to == sq) + state_.castle_rights.SetCastlingRights(them, side, kNoSquare); + } + state_.zobrist_key ^= zobrist::castle_rights[state_.castle_rights.AsU8()]; state_.turn = FlipColor(state_.turn); @@ -467,28 +491,18 @@ bool Board::IsInsufficientMaterial() const { void Board::HandleCastling(Move move) { const Color us = state_.turn; - const bool is_white = us == Color::kWhite; - const auto from = move.GetFrom(), to = move.GetTo(); - const auto move_rook_for_castling = [this, &us](Square rook_from, - Square rook_to) { - state_.RemovePiece(rook_from, state_.turn); - state_.PlacePiece(rook_to, PieceType::kRook, state_.turn); - }; + const Square king_sq = move.GetFrom(); + const Square rook_sq = move.GetTo(); - constexpr int kKingsideCastleDist = -2; - constexpr int kQueensideCastleDist = 2; + const Square king_to = kKingEndSquares[CastleRights::CastleIndex( + us, king_sq > rook_sq ? CastleRights::kQueenside : CastleRights::kKingside)]; + const Square rook_to = kRookEndSquares[CastleRights::CastleIndex( + us, king_sq > rook_sq ? CastleRights::kQueenside : CastleRights::kKingside)]; - // Note: the only way move_dist is ever 2 or -2 is from - // move_gen::CastlingMoves allowing it - const int move_dist = static_cast(from) - static_cast(to); - if (move_dist == kKingsideCastleDist) { - move_rook_for_castling(is_white ? Squares::kH1 : Squares::kH8, - is_white ? Squares::kF1 : Squares::kF8); - } else if (move_dist == kQueensideCastleDist) { - move_rook_for_castling(is_white ? Squares::kA1 : Squares::kA8, - is_white ? Squares::kD1 : Squares::kD8); - } + state_.PlacePiece(king_to, PieceType::kKing, us); + state_.RemovePiece(rook_sq, us); + state_.PlacePiece(rook_to, PieceType::kRook, us); } void Board::CalculateThreats() { diff --git a/src/chess/board.h b/src/chess/board.h index cc1f00d6..aaaafb17 100644 --- a/src/chess/board.h +++ b/src/chess/board.h @@ -22,66 +22,85 @@ class Accumulator; class CastleRights { public: - static constexpr int kKingsideIndex = 0; - static constexpr int kQueensideIndex = 1; + + enum CastleSide { + kKingside, + kQueenside + }; + + // Castling rights are stored as qkQK + static constexpr U8 CastleIndex(Color turn, CastleSide side) { + return turn == kWhite ? (side == kKingside ? 3 : 2) + : (side == kKingside ? 1 : 0); + }; static constexpr std::array, 2> kMasks = { {{CastleRightMasks::kBlackKingside, CastleRightMasks::kBlackQueenside}, {CastleRightMasks::kWhiteKingside, CastleRightMasks::kWhiteQueenside}}}; - CastleRights() : rights_(0) {} + CastleRights() { + rights_.fill(kNoSquare); + } bool operator==(const CastleRights &other) const { return rights_ == other.rights_; } [[nodiscard]] constexpr bool CanKingsideCastle(Color turn) const { - return rights_ & kMasks[turn][kKingsideIndex]; + return rights_[CastleIndex(turn, kKingside)] != Squares::kNoSquare; } [[nodiscard]] constexpr bool CanQueensideCastle(Color turn) const { - return rights_ & kMasks[turn][kQueensideIndex]; + return rights_[CastleIndex(turn, kQueenside)] != Squares::kNoSquare; } [[nodiscard]] constexpr bool CanCastle(Color turn) const { return CanKingsideCastle(turn) || CanQueensideCastle(turn); } - constexpr void SetCanCastle(Color turn, bool queenside) { - if (queenside) { - SetCanQueensideCastle(turn, true); - } else { - SetCanQueensideCastle(turn, false); - } + constexpr void SetCastlingRights(Color turn, CastleSide side, Square sq) { + rights_[CastleIndex(turn, side)] = sq; } - constexpr void SetCanKingsideCastle(Color turn, bool value) { - const U8 mask = kMasks[turn][kKingsideIndex]; - value ? rights_ |= mask : rights_ &= ~mask; + constexpr void SetCanKingsideCastle(Color turn, Square sq) { + rights_[CastleIndex(turn, kKingside)] = sq; } - constexpr void SetCanQueensideCastle(Color turn, bool value) { - const U8 mask = kMasks[turn][kQueensideIndex]; - value ? rights_ |= mask : rights_ &= ~mask; + constexpr void SetCanQueensideCastle(Color turn, Square sq) { + rights_[CastleIndex(turn, kQueenside)] = sq; } - constexpr void SetBothRights(Color turn, bool value) { - const U8 mask = - kMasks[turn][kKingsideIndex] | kMasks[turn][kQueensideIndex]; - value ? rights_ |= mask : rights_ &= ~mask; + constexpr void UnsetCastlingRights(Color turn) { + SetCanKingsideCastle(turn, kNoSquare); + SetCanQueensideCastle(turn, kNoSquare); } - U8 operator&=(U8 mask) { - rights_ &= mask; + [[nodiscard]] std::array& GetUnderlying() { return rights_; } [[nodiscard]] U8 AsU8() const { - return rights_; + constexpr U8 blackQ = 0b1; + constexpr U8 blackK = 0b10; + constexpr U8 whiteQ = 0b100; + constexpr U8 whiteK = 0b1000; + + U8 flags = 0; + + if (rights_[CastleIndex(kWhite, kKingside)] != kNoSquare) flags |= whiteK; + if (rights_[CastleIndex(kWhite, kQueenside)] != kNoSquare) flags |= whiteQ; + if (rights_[CastleIndex(kBlack, kKingside)] != kNoSquare) flags |= blackK; + if (rights_[CastleIndex(kBlack, kQueenside)] != kNoSquare) flags |= blackQ; + + return flags; + } + + [[nodiscard]] Square CastleSq(Color turn, CastleSide side) const { + return rights_[CastleIndex(turn, side)]; } private: - U8 rights_; + std::array rights_; }; struct BoardState { @@ -322,7 +341,7 @@ class Board { [[nodiscard]] U64 PredictKeyAfter(Move move) const; - [[nodiscard]] bool HasUpcomingRepetition(U16 ply) const ; + [[nodiscard]] bool HasUpcomingRepetition(U16 ply) const; [[nodiscard]] bool IsRepetition(U16 ply) const; diff --git a/src/chess/fen.cc b/src/chess/fen.cc index 9f5fa1f9..92d916d5 100644 --- a/src/chess/fen.cc +++ b/src/chess/fen.cc @@ -1,5 +1,7 @@ #include "fen.h" +#include "../engine/uci/uci.h" + namespace fen { // clang-format off @@ -54,15 +56,30 @@ BoardState StringToBoard(std::string_view fen_str) { std::string castle_rights; stream >> castle_rights; + state.castle_rights.GetUnderlying().fill(kNoSquare); for (const char &ch : castle_rights) { + // Standard FENs, XFEN later if (ch == 'K') - state.castle_rights.SetCanKingsideCastle(Color::kWhite, true); + state.castle_rights.SetCanKingsideCastle(Color::kWhite, Square(kH1)); else if (ch == 'Q') - state.castle_rights.SetCanQueensideCastle(Color::kWhite, true); + state.castle_rights.SetCanQueensideCastle(Color::kWhite, Square(kA1)); else if (ch == 'k') - state.castle_rights.SetCanKingsideCastle(Color::kBlack, true); + state.castle_rights.SetCanKingsideCastle(Color::kBlack, Square(kH8)); else if (ch == 'q') - state.castle_rights.SetCanQueensideCastle(Color::kBlack, true); + state.castle_rights.SetCanQueensideCastle(Color::kBlack, Square(kA8)); + + // FRC FEN + if (std::tolower(ch) >= 'a' && + std::tolower(ch) <= 'h') { + uci::listener.GetOption("UCI_Chess960").SetValue("true"); + const File file = static_cast(std::tolower(ch) - 'a'); + const Square kingSq = state.King(std::islower(ch) ? Color::kBlack : Color::kWhite).GetLsb(); + + if (std::isupper(ch)) + state.castle_rights.SetCastlingRights(Color::kWhite, file > kingSq.File() ? CastleRights::kKingside : CastleRights::kQueenside, Square::FromRankFile(kRank1, file)); + else + state.castle_rights.SetCastlingRights(Color::kBlack, file > kingSq.File() ? CastleRights::kKingside : CastleRights::kQueenside, Square::FromRankFile(kRank8, file)); + } } state.zobrist_key ^= zobrist::castle_rights[state.castle_rights.AsU8()]; diff --git a/src/chess/move.cc b/src/chess/move.cc index 1cf55a5a..1d1dc2fa 100644 --- a/src/chess/move.cc +++ b/src/chess/move.cc @@ -1,5 +1,6 @@ #include "move.h" +#include "../engine/uci/uci.h" #include "board.h" Move Move::NullMove() { @@ -23,18 +24,19 @@ Move Move::FromStr(std::string_view str, const BoardState &state) { return Move::NullMove(); const auto from = Square::FromRankFile(from_rank, from_file); - const auto to = Square::FromRankFile(to_rank, to_file); + auto to = Square::FromRankFile(to_rank, to_file); auto flag = MoveType::kNormal; if (str.length() < kMaxMoveLen) { const auto piece = state.GetPieceType(from); - if (piece == PieceType::kKing && std::abs(from_file - to_file) == 2) { + if (piece == PieceType::kKing && (std::abs(from_file - to_file) == 2 || (state.GetPieceType(to) == kRook && state.GetPieceColor(to) == state.turn))) { + if (!uci::listener.GetOption("UCI_Chess960").GetValue()) + to = Square(state.turn == kWhite ? (to > from ? kH1 : kA1) + : (to > from ? kH8 : kA8)); flag = MoveType::kCastle; - } else if (piece == PieceType::kPawn) { - if (state.en_passant && to == state.en_passant) { + } else if (piece == PieceType::kPawn && state.en_passant && to == state.en_passant) { flag = MoveType::kEnPassant; - } } return Move(from, to, flag); @@ -66,7 +68,8 @@ Move Move::FromStr(std::string_view str, const BoardState &state) { } bool Move::IsCapture(const BoardState &state) const { - return state.GetPieceType(GetTo()) != PieceType::kNone || IsEnPassant(state); + return state.GetPieceType(GetTo()) != PieceType::kNone && GetType() != MoveType::kCastle || + IsEnPassant(state); } bool Move::IsNoisy(const BoardState &state) const { @@ -87,12 +90,14 @@ bool Move::IsUnderPromotion() const { std::string Move::ToString() const { if (data_ == 0) return "null"; - const auto from_rank = GetFrom().Rank(), from_file = GetFrom().File(); - const auto to_rank = GetTo().Rank(), to_file = GetTo().File(); + std::string res = GetFrom().ToString(); + if (GetType() == MoveType::kCastle && + !uci::listener.GetOption("UCI_Chess960").GetValue()) { + const bool isKingside = GetFrom() < GetTo(); + return res + (GetFrom() + (isKingside ? 2 : -2)).ToString(); + } - std::string res = std::string(1, 'a' + from_file) + - std::to_string(from_rank + 1) + - std::string(1, 'a' + to_file) + std::to_string(to_rank + 1); + res += GetTo().ToString(); if (GetType() == MoveType::kPromotion) { switch (GetPromotionType()) { @@ -114,4 +119,4 @@ std::string Move::ToString() const { } return res; -} +} \ No newline at end of file diff --git a/src/chess/move_gen.cc b/src/chess/move_gen.cc index ac9fa548..405c3a2a 100644 --- a/src/chess/move_gen.cc +++ b/src/chess/move_gen.cc @@ -88,6 +88,40 @@ constexpr std::array, 64> GenerateRayBetweenMasks() { return masks; } +constexpr std::array, 64> GenerateRayIncludingMasks() { + std::array, 64> masks{}; + for (int square = 0; square < kSquareCount; square++) { + const BitBoard src_mask = BitBoard::FromSquare(square); + const BitBoard src_bishop_rays = + magics::attacks::GenerateBishopMoves(Square(square), 0ULL); + const BitBoard src_rook_rays = + magics::attacks::GenerateRookMoves(Square(square), 0ULL); + + for (int other_square = 0; other_square < kSquareCount; other_square++) { + if (square == other_square) { + continue; + } + + const BitBoard dest_mask = BitBoard::FromSquare(other_square); + if (src_bishop_rays & dest_mask) { + // Calculate the rays between square and other_square + masks[square][other_square] = + magics::attacks::GenerateBishopMoves(Square(square), dest_mask) & + magics::attacks::GenerateBishopMoves(Square(other_square), + src_mask); + } else if (src_rook_rays & dest_mask) { + // Calculate the rays between square and other_square + masks[square][other_square] = + magics::attacks::GenerateRookMoves(Square(square), dest_mask) & + magics::attacks::GenerateRookMoves(Square(other_square), src_mask); + } + masks[square][other_square] = masks[square][other_square] | + (1ULL << square) | (1ULL << other_square); + } + } + return masks; +} + constexpr std::array, 64> GenerateRayIntersectingMasks() { std::array, 64> masks{}; @@ -127,6 +161,7 @@ constexpr auto kKingMasks = GenerateKingMasks(); constexpr auto kPawnAttackMasks = GeneratePawnAttackMasks(); // Must be const because magic attacks are initialized at runtime const auto kRayBetweenMasks = GenerateRayBetweenMasks(); +const auto kRayIncludingMasks = GenerateRayIncludingMasks(); const auto kRayIntersectingMasks = GenerateRayIntersectingMasks(); BitBoard PawnPushMoves(Square square, const BoardState &state) { @@ -188,13 +223,7 @@ BitBoard QueenMoves(Square square, const BitBoard &occupied) { } BitBoard KingMoves(Square square, const BoardState &state) { - BitBoard moves = KingAttacks(square); - - const auto color = state.GetPieceColor(square); - if (state.castle_rights.CanCastle(state.turn) && !state.checkers) - moves |= CastlingMoves(color, state); - - return moves; + return KingAttacks(square); } BitBoard KingAttacks(Square square) { @@ -204,26 +233,21 @@ BitBoard KingAttacks(Square square) { BitBoard CastlingMoves(Color side, const BoardState &state) { BitBoard moves, occupied = state.Occupied(); - constexpr BitBoard kWhiteKingsideOccupancy = 0x60; - constexpr BitBoard kWhiteQueensideOccupancy = 0xe; - constexpr BitBoard kBlackKingsideOccupancy = 0x6000000000000000; - constexpr BitBoard kBlackQueensideOccupancy = 0xe00000000000000; - if (side == Color::kWhite) { if (state.castle_rights.CanKingsideCastle(Color::kWhite)) { - if (!(occupied & kWhiteKingsideOccupancy)) moves.SetBit(Squares::kG1); + moves.SetBit(state.castle_rights.CastleSq(Color::kWhite, CastleRights::kKingside)); } if (state.castle_rights.CanQueensideCastle(Color::kWhite)) { - if (!(occupied & kWhiteQueensideOccupancy)) moves.SetBit(Squares::kC1); + moves.SetBit(state.castle_rights.CastleSq(Color::kWhite, CastleRights::kQueenside)); } } else { if (state.castle_rights.CanKingsideCastle(Color::kBlack)) { - if (!(occupied & kBlackKingsideOccupancy)) moves.SetBit(Squares::kG8); + moves.SetBit(state.castle_rights.CastleSq(Color::kBlack, CastleRights::kKingside)); } if (state.castle_rights.CanQueensideCastle(Color::kBlack)) { - if (!(occupied & kBlackQueensideOccupancy)) moves.SetBit(Squares::kC8); + moves.SetBit(state.castle_rights.CastleSq(Color::kBlack, CastleRights::kQueenside)); } } @@ -279,6 +303,10 @@ BitBoard RayBetween(Square first, Square second) { return kRayBetweenMasks[first][second]; } +BitBoard RayIncluding(Square first, Square second) { + return kRayIncludingMasks[first][second]; +} + BitBoard RayIntersecting(Square first, Square second) { return kRayIntersectingMasks[first][second]; } @@ -457,13 +485,11 @@ MoveList GenerateMoves(const Board &board) { if constexpr (move_type & MoveGenType::kQuiet) targets |= ~occupied; if constexpr (move_type & MoveGenType::kNoisy) targets |= their_pieces; + const Square king_square = state.King(state.turn).GetLsb(); if (state.checkers.MoreThanOne()) { // Only king moves are legal if there's multiple pieces checking the king - const Square king_square = state.King(state.turn).GetLsb(); - for (Square to : KingMoves(king_square, state) & targets) { - const bool is_castle = std::abs(to.File() - king_square.File()) == 2; - move_list.Push(Move( - king_square, to, is_castle ? MoveType::kCastle : MoveType::kNormal)); + for (Square to : KingAttacks(king_square) & targets) { + move_list.Push(Move(king_square, to, MoveType::kNormal)); } return move_list; } @@ -495,11 +521,14 @@ MoveList GenerateMoves(const Board &board) { } } - const Square king_square = state.King(state.turn).GetLsb(); - for (Square to : KingMoves(king_square, state) & targets) { - const bool is_castle = std::abs(to.File() - king_square.File()) == 2; - move_list.Push(Move( - king_square, to, is_castle ? MoveType::kCastle : MoveType::kNormal)); + for (Square to : KingAttacks(king_square) & targets) { + move_list.Push(Move(king_square, to, MoveType::kNormal)); + } + + if (!state.checkers && move_type & MoveGenType::kQuiet) { + for (Square to : CastlingMoves(state.turn, state)) { + move_list.Push(Move(king_square, to, MoveType::kCastle)); + } } return move_list; diff --git a/src/chess/move_gen.h b/src/chess/move_gen.h index 2394fe29..31111c04 100644 --- a/src/chess/move_gen.h +++ b/src/chess/move_gen.h @@ -31,8 +31,6 @@ BitBoard KingAttacks(Square square); BitBoard CastlingMoves(Color which, const BoardState &state); -BitBoard GetAttackedSquares(const BoardState &state, Color attacker); - BitBoard GetAttackersTo(const BoardState &state, Square square, Color attacker); BitBoard GetAttackersTo(const BoardState &state, @@ -49,6 +47,10 @@ BitBoard GetSlidingAttackersTo(const BoardState &state, // squares BitBoard RayBetween(Square first, Square second); +// Returns a bitboard with the set bits being sliding attacks between the two +// squares, including the source squares +BitBoard RayIncluding(Square first, Square second); + // Returns a bitboard with the set bits being the ray that the two squares lie // on BitBoard RayIntersecting(Square first, Square second); diff --git a/src/engine/evaluation/evaluation.cc b/src/engine/evaluation/evaluation.cc index 10994b93..1cd6c730 100644 --- a/src/engine/evaluation/evaluation.cc +++ b/src/engine/evaluation/evaluation.cc @@ -29,8 +29,7 @@ bool StaticExchange(Move move, int threshold, const BoardState &state) { const PieceType &from_piece = state.GetPieceType(from); // Ignore en passant captures and castling - if (move.IsEnPassant(state) || - (from_piece == kKing && std::abs(static_cast(from) - to) == 2)) { + if (move.IsEnPassant(state) || move.GetType() == MoveType::kCastle) { return threshold <= 0; } diff --git a/src/engine/uci/uci.cc b/src/engine/uci/uci.cc index 4dcdbd0f..6417a896 100644 --- a/src/engine/uci/uci.cc +++ b/src/engine/uci/uci.cc @@ -26,6 +26,7 @@ void Initialize(search::Searcher &searcher) { listener.AddOption("MultiPV", 1, 1, 6); listener.AddOption("MoveOverhead", 10, 0, 10000); listener.AddOption("Minimal", false); + listener.AddOption("UCI_Chess960", false); listener.AddOption("SyzygyPath", std::string(""), [](const Option &option) { syzygy::SetPath(option.GetValue()); }); diff --git a/src/utils/types.h b/src/utils/types.h index f18404b8..c2de5abc 100644 --- a/src/utils/types.h +++ b/src/utils/types.h @@ -7,6 +7,7 @@ #include #include +#include "../../third-party/fmt/include/fmt/format.h" #include "list.h" using I8 = std::int8_t; @@ -185,6 +186,12 @@ class Square { return square_ * scalar; } + [[nodiscard]] std::string ToString() const { + return fmt::format("{}{}", + static_cast('a' + (square_ % 8)), + static_cast('1' + (square_ / 8))); + } + private: U8 square_; }; From 6de7d6a9e0764d885c92cf24603da63aeccc3622 Mon Sep 17 00:00:00 2001 From: Quinniboi10 <69563433+Quinniboi10@users.noreply.github.com> Date: Sat, 12 Jul 2025 17:36:03 -0500 Subject: [PATCH 2/5] Cleanup Bench: 1718488 --- src/chess/board.cc | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/chess/board.cc b/src/chess/board.cc index 9e32daf3..b9934332 100644 --- a/src/chess/board.cc +++ b/src/chess/board.cc @@ -124,9 +124,6 @@ bool Board::IsMoveLegal(Move move) const { const bool is_kingside = to > from; - assert(is_kingside ? state_.castle_rights.CanKingsideCastle(us) - : state_.castle_rights.CanQueensideCastle(us)); - if (state_.pinned[state_.turn].IsSet(to)) return false; const Square king_sq = move.GetFrom(); @@ -146,12 +143,12 @@ bool Board::IsMoveLegal(Move move) const { between_bb.ClearBit(king_sq); between_bb.ClearBit(rook_sq); - if ((between_bb & state_.Occupied()).PopCount() > 0) return false; + if (between_bb & state_.Occupied()) return false; between_bb = move_gen::RayIncluding(king_sq, king_to); between_bb.ClearBit(king_sq); - while (between_bb.PopCount() > 0) + while (between_bb) if (state_.threats.IsSet(between_bb.PopLsb())) return false; return true; From 4045e24d52337ad6819cd7dbe64ff0af047693ca Mon Sep 17 00:00:00 2001 From: Quinniboi10 <69563433+Quinniboi10@users.noreply.github.com> Date: Sat, 19 Jul 2025 13:40:08 -0500 Subject: [PATCH 3/5] Bugfix when king and rook switch places Bench: 1718488 --- src/chess/board.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chess/board.cc b/src/chess/board.cc index b9934332..14d54025 100644 --- a/src/chess/board.cc +++ b/src/chess/board.cc @@ -497,8 +497,8 @@ void Board::HandleCastling(Move move) { const Square rook_to = kRookEndSquares[CastleRights::CastleIndex( us, king_sq > rook_sq ? CastleRights::kQueenside : CastleRights::kKingside)]; - state_.PlacePiece(king_to, PieceType::kKing, us); state_.RemovePiece(rook_sq, us); + state_.PlacePiece(king_to, PieceType::kKing, us); state_.PlacePiece(rook_to, PieceType::kRook, us); } From 585350d882f6f929b739b4b03f255650a5a29b03 Mon Sep 17 00:00:00 2001 From: Quinniboi10 <69563433+Quinniboi10@users.noreply.github.com> Date: Sat, 19 Jul 2025 13:58:59 -0500 Subject: [PATCH 4/5] Code style changes Bench: 1718488 --- src/chess/board.cc | 8 ++++---- src/chess/board.h | 18 +++++++++--------- src/chess/fen.cc | 17 ++++++++--------- src/chess/move.cc | 4 ++-- src/chess/move_gen.cc | 8 ++++---- 5 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/chess/board.cc b/src/chess/board.cc index 14d54025..daa2e8c0 100644 --- a/src/chess/board.cc +++ b/src/chess/board.cc @@ -69,8 +69,8 @@ bool Board::IsMovePseudoLegal(Move move) const { if (state_.GetPieceType(to) != PieceType::kRook) return false; if (to > from) - return state_.castle_rights.CastleSq(state_.turn, CastleRights::kKingside) == to; - return state_.castle_rights.CastleSq(state_.turn, CastleRights::kQueenside) == to; + return state_.castle_rights.CastleSquare(state_.turn, CastleRights::kKingside) == to; + return state_.castle_rights.CastleSquare(state_.turn, CastleRights::kQueenside) == to; } if (move_type == MoveType::kEnPassant) { @@ -271,7 +271,7 @@ void Board::MakeMove(Move move) { if (piece == kRook) { const auto side = from > state_.King(us).GetLsb() ? CastleRights::kKingside : CastleRights::kQueenside; - const Square sq = state_.castle_rights.CastleSq(us, side); + const Square sq = state_.castle_rights.CastleSquare(us, side); if (from == sq) state_.castle_rights.SetCastlingRights(us, side, kNoSquare); @@ -281,7 +281,7 @@ void Board::MakeMove(Move move) { if (state_.GetPieceType(to)) { const auto side = to > state_.King(them).GetLsb() ? CastleRights::kKingside : CastleRights::kQueenside; - const Square sq = state_.castle_rights.CastleSq(them, side); + const Square sq = state_.castle_rights.CastleSquare(them, side); if (to == sq) state_.castle_rights.SetCastlingRights(them, side, kNoSquare); diff --git a/src/chess/board.h b/src/chess/board.h index aaaafb17..9a6b303c 100644 --- a/src/chess/board.h +++ b/src/chess/board.h @@ -80,22 +80,22 @@ class CastleRights { } [[nodiscard]] U8 AsU8() const { - constexpr U8 blackQ = 0b1; - constexpr U8 blackK = 0b10; - constexpr U8 whiteQ = 0b100; - constexpr U8 whiteK = 0b1000; + constexpr U8 kBlackQueen = 0b1; + constexpr U8 kBlackKing = 0b10; + constexpr U8 kWhiteQueen = 0b100; + constexpr U8 kWhiteKing = 0b1000; U8 flags = 0; - if (rights_[CastleIndex(kWhite, kKingside)] != kNoSquare) flags |= whiteK; - if (rights_[CastleIndex(kWhite, kQueenside)] != kNoSquare) flags |= whiteQ; - if (rights_[CastleIndex(kBlack, kKingside)] != kNoSquare) flags |= blackK; - if (rights_[CastleIndex(kBlack, kQueenside)] != kNoSquare) flags |= blackQ; + if (rights_[CastleIndex(kWhite, kKingside)] != kNoSquare) flags |= kWhiteKing; + if (rights_[CastleIndex(kWhite, kQueenside)] != kNoSquare) flags |= kWhiteQueen; + if (rights_[CastleIndex(kBlack, kKingside)] != kNoSquare) flags |= kBlackKing; + if (rights_[CastleIndex(kBlack, kQueenside)] != kNoSquare) flags |= kBlackQueen; return flags; } - [[nodiscard]] Square CastleSq(Color turn, CastleSide side) const { + [[nodiscard]] Square CastleSquare(Color turn, CastleSide side) const { return rights_[CastleIndex(turn, side)]; } diff --git a/src/chess/fen.cc b/src/chess/fen.cc index 92d916d5..d7da953d 100644 --- a/src/chess/fen.cc +++ b/src/chess/fen.cc @@ -60,25 +60,24 @@ BoardState StringToBoard(std::string_view fen_str) { for (const char &ch : castle_rights) { // Standard FENs, XFEN later if (ch == 'K') - state.castle_rights.SetCanKingsideCastle(Color::kWhite, Square(kH1)); + state.castle_rights.SetCanKingsideCastle(Color::kWhite, kH1); else if (ch == 'Q') - state.castle_rights.SetCanQueensideCastle(Color::kWhite, Square(kA1)); + state.castle_rights.SetCanQueensideCastle(Color::kWhite, kA1); else if (ch == 'k') - state.castle_rights.SetCanKingsideCastle(Color::kBlack, Square(kH8)); + state.castle_rights.SetCanKingsideCastle(Color::kBlack, kH8); else if (ch == 'q') - state.castle_rights.SetCanQueensideCastle(Color::kBlack, Square(kA8)); + state.castle_rights.SetCanQueensideCastle(Color::kBlack, kA8); // FRC FEN if (std::tolower(ch) >= 'a' && std::tolower(ch) <= 'h') { uci::listener.GetOption("UCI_Chess960").SetValue("true"); + + const Color color = std::isupper(ch) ? kWhite : kBlack; const File file = static_cast(std::tolower(ch) - 'a'); - const Square kingSq = state.King(std::islower(ch) ? Color::kBlack : Color::kWhite).GetLsb(); + const Square king_sq = state.King(color).GetLsb(); - if (std::isupper(ch)) - state.castle_rights.SetCastlingRights(Color::kWhite, file > kingSq.File() ? CastleRights::kKingside : CastleRights::kQueenside, Square::FromRankFile(kRank1, file)); - else - state.castle_rights.SetCastlingRights(Color::kBlack, file > kingSq.File() ? CastleRights::kKingside : CastleRights::kQueenside, Square::FromRankFile(kRank8, file)); + state.castle_rights.SetCastlingRights(color, file > king_sq.File() ? CastleRights::kKingside : CastleRights::kQueenside, Square::FromRankFile(color == kWhite ? kRank1 : kRank8, file)); } } diff --git a/src/chess/move.cc b/src/chess/move.cc index 1d1dc2fa..686adbd5 100644 --- a/src/chess/move.cc +++ b/src/chess/move.cc @@ -93,8 +93,8 @@ std::string Move::ToString() const { std::string res = GetFrom().ToString(); if (GetType() == MoveType::kCastle && !uci::listener.GetOption("UCI_Chess960").GetValue()) { - const bool isKingside = GetFrom() < GetTo(); - return res + (GetFrom() + (isKingside ? 2 : -2)).ToString(); + const bool is_kingside = GetFrom() < GetTo(); + return res + (GetFrom() + (is_kingside ? 2 : -2)).ToString(); } res += GetTo().ToString(); diff --git a/src/chess/move_gen.cc b/src/chess/move_gen.cc index 405c3a2a..e01ac6a6 100644 --- a/src/chess/move_gen.cc +++ b/src/chess/move_gen.cc @@ -235,19 +235,19 @@ BitBoard CastlingMoves(Color side, const BoardState &state) { if (side == Color::kWhite) { if (state.castle_rights.CanKingsideCastle(Color::kWhite)) { - moves.SetBit(state.castle_rights.CastleSq(Color::kWhite, CastleRights::kKingside)); + moves.SetBit(state.castle_rights.CastleSquare(Color::kWhite, CastleRights::kKingside)); } if (state.castle_rights.CanQueensideCastle(Color::kWhite)) { - moves.SetBit(state.castle_rights.CastleSq(Color::kWhite, CastleRights::kQueenside)); + moves.SetBit(state.castle_rights.CastleSquare(Color::kWhite, CastleRights::kQueenside)); } } else { if (state.castle_rights.CanKingsideCastle(Color::kBlack)) { - moves.SetBit(state.castle_rights.CastleSq(Color::kBlack, CastleRights::kKingside)); + moves.SetBit(state.castle_rights.CastleSquare(Color::kBlack, CastleRights::kKingside)); } if (state.castle_rights.CanQueensideCastle(Color::kBlack)) { - moves.SetBit(state.castle_rights.CastleSq(Color::kBlack, CastleRights::kQueenside)); + moves.SetBit(state.castle_rights.CastleSquare(Color::kBlack, CastleRights::kQueenside)); } } From d5b8cf9bb1d2390c4054302c53b67be059cf50da Mon Sep 17 00:00:00 2001 From: Quinn <69563433+Quinniboi10@users.noreply.github.com> Date: Fri, 1 Aug 2025 08:07:51 -1000 Subject: [PATCH 5/5] Bugfix number I lost count Bench: 1718488 --- src/data_gen/format/binpack.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/data_gen/format/binpack.h b/src/data_gen/format/binpack.h index fedeff02..a65ed3b7 100644 --- a/src/data_gen/format/binpack.h +++ b/src/data_gen/format/binpack.h @@ -98,12 +98,7 @@ class BinPackFormatter : public OutputFormatter { void PushMove(Move move, Color turn, Score score) override { Square from = move.GetFrom(), to = move.GetTo(); - // No FRC support :( - const auto move_type = move.GetType(); - if (move_type == MoveType::kCastle) { - const int displacement = to.File() - from.File() > 0 ? 1 : -2; - to += displacement; - } + // FRC support :D U16 move_data = 0; move_data |= from;