Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 87 additions & 76 deletions src/chess/board.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,12 @@
#include "move.h"
#include "move_gen.h"

// clang-format off
constexpr std::array<U8, 64> 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 <bit>

constexpr std::array<Square, 4> kRookEndSquares = {
Square(kD8), Square(kF8), Square(kD1), Square(kF1)};
constexpr std::array<Square, 4> kKingEndSquares = {
Square(kC8), Square(kG8), Square(kC1), Square(kG1)};

Board::Board() : history_({}) {}

Expand Down Expand Up @@ -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;
}

Expand All @@ -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<int>(from) - static_cast<int>(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.CastleSquare(state_.turn, CastleRights::kKingside) == to;
return state_.castle_rights.CastleSquare(state_.turn, CastleRights::kQueenside) == to;
}

if (move_type == MoveType::kEnPassant) {
Expand Down Expand Up @@ -138,22 +119,39 @@ 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<int>(from) - static_cast<int>(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;

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()) return false;

between_bb = move_gen::RayIncluding(king_sq, king_to);
between_bb.ClearBit(king_sq);

while (between_bb)
if (state_.threats.IsSet(between_bb.PopLsb())) return false;

return true;
}

// Make sure the destination square isn't attacked
Expand Down Expand Up @@ -208,13 +206,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;
Expand Down Expand Up @@ -252,20 +253,40 @@ 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) {
new_piece = PieceType(static_cast<int>(move.GetPromotionType()) + 1);
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.CastleSquare(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.CastleSquare(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);
Expand Down Expand Up @@ -467,28 +488,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<int>(from) - static_cast<int>(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_.RemovePiece(rook_sq, us);
state_.PlacePiece(king_to, PieceType::kKing, us);
state_.PlacePiece(rook_to, PieceType::kRook, us);
}

void Board::CalculateThreats() {
Expand Down
71 changes: 45 additions & 26 deletions src/chess/board.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::array<U8, 2>, 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<Square, 4>& GetUnderlying() {
return rights_;
}

[[nodiscard]] U8 AsU8() const {
return rights_;
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 |= 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 CastleSquare(Color turn, CastleSide side) const {
return rights_[CastleIndex(turn, side)];
}

private:
U8 rights_;
std::array<Square, 4> rights_;
};

struct BoardState {
Expand Down Expand Up @@ -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;

Expand Down
24 changes: 20 additions & 4 deletions src/chess/fen.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "fen.h"

#include "../engine/uci/uci.h"

namespace fen {

// clang-format off
Expand Down Expand Up @@ -54,15 +56,29 @@ 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, kH1);
else if (ch == 'Q')
state.castle_rights.SetCanQueensideCastle(Color::kWhite, true);
state.castle_rights.SetCanQueensideCastle(Color::kWhite, kA1);
else if (ch == 'k')
state.castle_rights.SetCanKingsideCastle(Color::kBlack, true);
state.castle_rights.SetCanKingsideCastle(Color::kBlack, kH8);
else if (ch == 'q')
state.castle_rights.SetCanQueensideCastle(Color::kBlack, true);
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<File>(std::tolower(ch) - 'a');
const Square king_sq = state.King(color).GetLsb();

state.castle_rights.SetCastlingRights(color, file > king_sq.File() ? CastleRights::kKingside : CastleRights::kQueenside, Square::FromRankFile(color == kWhite ? kRank1 : kRank8, file));
}
}

state.zobrist_key ^= zobrist::castle_rights[state.castle_rights.AsU8()];
Expand Down
Loading