Skip to content

Commit

Permalink
Introduce Chess960 support (#20)
Browse files Browse the repository at this point in the history
This patch adds Fischer Random Chess support.
Both X-FEN and Shredder-FEN formats are supported.
In addition, some fixes to the testing system have been added. Also, some FRC positions have been added to the bench and test suite.

Non-regression STC:
LLR:  2.94/2.94<-6.00, 0.00> Elo diff: 1.33 [-1.73, 4.39] (95%)
Games: 9910 W: 1314 L: 1276 D: 7320 Draw ratio: 73.9%
Pntl: [52, 762, 3293, 792, 56]

Bench: 1899441
  • Loading branch information
ruicoelhopedro authored Dec 5, 2023
1 parent e276c44 commit 6bcce44
Show file tree
Hide file tree
Showing 13 changed files with 282 additions and 143 deletions.
12 changes: 7 additions & 5 deletions src/Bitboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,14 @@ namespace Bitboards
{
for (int i = 0; i < NUM_SQUARES; i++)
for (int j = 0; j < NUM_SQUARES; j++)
if (diagonals[i].test(j))
between_squares[i][j] = get_attacks<BISHOP>(static_cast<Square>(i), Bitboard::from_single_bit(j))&
get_attacks<BISHOP>(static_cast<Square>(j), Bitboard::from_single_bit(i));
if (i == j)
between_squares[i][j] = Bitboard();
else if (diagonals[i].test(j))
between_squares[i][j] = get_attacks<BISHOP>(static_cast<Square>(i), Bitboard::from_single_bit(j)) &
get_attacks<BISHOP>(static_cast<Square>(j), Bitboard::from_single_bit(i));
else if (ranks_files[i].test(j))
between_squares[i][j] = get_attacks<ROOK >(static_cast<Square>(i), Bitboard::from_single_bit(j))&
get_attacks<ROOK >(static_cast<Square>(j), Bitboard::from_single_bit(i));
between_squares[i][j] = get_attacks<ROOK >(static_cast<Square>(i), Bitboard::from_single_bit(j)) &
get_attacks<ROOK >(static_cast<Square>(j), Bitboard::from_single_bit(i));
}


Expand Down
21 changes: 0 additions & 21 deletions src/Move.cpp

This file was deleted.

28 changes: 0 additions & 28 deletions src/Move.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,33 +42,8 @@ class Move
constexpr Hash hash() const { return (m_move & 0b111111111111) | ((m_move & 0b11000000000000000) >> 3); }
constexpr Hash to_int() const { return m_move; }

std::string to_uci() const
{
if (from() == to())
return "0000";

if (is_promotion())
{
// Promotion
PieceType piece = promo_piece();
char promo_code = piece == KNIGHT ? 'n'
: piece == BISHOP ? 'b'
: piece == ROOK ? 'r'
: 'q';
return get_square(from()) + get_square(to()) + promo_code;
}
else
{
// Regular move
return get_square(from()) + get_square(to());
}
}

constexpr bool operator==(const Move& other) const { return m_move == other.m_move; }
constexpr bool operator!=(const Move& other) const { return m_move != other.m_move; }

// IO operators
friend std::ostream& operator<<(std::ostream& out, const Move& move);
};


Expand Down Expand Up @@ -171,9 +146,6 @@ class MoveList
const Move* end() const { return m_end; }
const Move* cbegin() const { return m_moves; }
const Move* cend() const { return m_end; }

// IO operators
friend std::ostream& operator<<(std::ostream& out, const MoveList& list);
};


Expand Down
172 changes: 136 additions & 36 deletions src/Position.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "Position.hpp"
#include "Types.hpp"
#include "NNUE.hpp"
#include "UCI.hpp"
#include "Zobrist.hpp"
#include "syzygy/syzygy.hpp"
#include <cassert>
Expand Down Expand Up @@ -46,6 +47,21 @@ CastleSide fen_castle_side(char c)
}


CastleFile fen_castle_file_chess960(char c)
{
char lower = tolower(c);
return lower == 'a' ? CastleFile::FILE_A
: lower == 'b' ? CastleFile::FILE_B
: lower == 'c' ? CastleFile::FILE_C
: lower == 'd' ? CastleFile::FILE_D
: lower == 'e' ? CastleFile::FILE_E
: lower == 'f' ? CastleFile::FILE_F
: lower == 'g' ? CastleFile::FILE_G
: lower == 'h' ? CastleFile::FILE_H
: CastleFile::NONE;
}


char fen_castle_side(CastleSide side, Turn turn)
{
char c = side == KINGSIDE ? 'k'
Expand All @@ -55,8 +71,16 @@ char fen_castle_side(CastleSide side, Turn turn)
}


char fen_castle_side_chess960(CastleFile side, Turn turn)
{
char c = "xabcdefgh"[static_cast<int>(side)];
return turn == WHITE ? toupper(c) : c;
}


Board::Board()
: Board("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
: Board(UCI::Options::UCI_Chess960 ? "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w HAha - 0 1"
: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
{}


Expand All @@ -83,6 +107,8 @@ Board::Board(std::string fen)

c++;
}
m_king_sq[WHITE] = m_pieces[KING][WHITE].bitscan_forward();
m_king_sq[BLACK] = m_pieces[KING][BLACK].bitscan_forward();

// Side to move
m_turn = WHITE;
Expand All @@ -92,8 +118,35 @@ Board::Board(std::string fen)
// Castling rights
std::memset(m_castling_rights, 0, sizeof(m_castling_rights));
while ((++c) < fen.cend() && !isspace(*c))
if (fen_castle_side(*c) != NO_SIDE)
set_castling<true>(fen_castle_side(*c), isupper(*c) ? WHITE : BLACK);
{
if (UCI::Options::UCI_Chess960)
{
char lower = tolower(*c);
if (lower == 'k' || lower == 'q')
{
// X-FEN notation: find available rook
Turn turn = isupper(*c) ? WHITE : BLACK;
Direction dir = lower == 'k' ? 1 : -1;
for (int f = file(m_king_sq[turn]); f >= 0 && f < 8; f += dir)
if (get_piece_type(m_board_pieces[make_square(turn == WHITE ? 0 : 7, f)]) == ROOK)
set_castling(lower == 'k' ? KINGSIDE : QUEENSIDE, turn, fen_castle_file_chess960('a' + f));
}
else if (lower >= 'a' && lower <= 'h')
{
// Shredder-FEN notation
Turn turn = isupper(*c) ? WHITE : BLACK;
CastleFile file = fen_castle_file_chess960(*c);
set_castling(get_rook_square(file, turn) > m_king_sq[turn] ? KINGSIDE : QUEENSIDE, turn, file);
}
}
else
{
CastleSide side = fen_castle_side(*c);
if (side != NO_SIDE)
set_castling(side, isupper(*c) ? WHITE : BLACK, side == KINGSIDE ? CastleFile::FILE_H : CastleFile::FILE_A);
}
}


// Ep square
m_enpassant_square = SQUARE_NULL;
Expand All @@ -120,8 +173,6 @@ Board::Board(std::string fen)

update_checkers();

m_king_sq[WHITE] = m_pieces[KING][WHITE].bitscan_forward();
m_king_sq[BLACK] = m_pieces[KING][BLACK].bitscan_forward();
regen_psqt(WHITE);
regen_psqt(BLACK);
}
Expand Down Expand Up @@ -160,10 +211,11 @@ std::string Board::to_fen() const
bool found = false;
for (auto turn : { WHITE, BLACK })
for (auto side : { KINGSIDE, QUEENSIDE })
if (m_castling_rights[side][turn])
if (castling_rights(side, turn))
{
found = true;
ss << fen_castle_side(side, turn);
ss << (UCI::Options::UCI_Chess960 ? fen_castle_side_chess960(m_castling_rights[side][turn], turn)
: fen_castle_side(side, turn));
}
ss << (found ? " " : "- ");

Expand Down Expand Up @@ -201,7 +253,7 @@ Hash Board::generate_hash() const
// Castling rights
for (CastleSide side : { KINGSIDE, QUEENSIDE })
for (Turn turn : { WHITE, BLACK })
if (m_castling_rights[side][turn])
if (castling_rights(side, turn))
hash ^= Zobrist::get_castle_side_turn(side, turn);

return hash;
Expand Down Expand Up @@ -262,22 +314,6 @@ Board Board::make_move(Move move) const
// Initial empty ep square
result.m_enpassant_square = SQUARE_NULL;

// Update castling rights after this move
if (piece == KING)
{
// Unset all castling rights after a king move
for (auto side : { KINGSIDE, QUEENSIDE })
result.set_castling<false>(side, m_turn);
}
else if (piece == ROOK)
{
// Unset castling rights for a certain side if a rook moves
if (move.from() == (m_turn == WHITE ? SQUARE_H1 : SQUARE_H8))
result.set_castling<false>(KINGSIDE, m_turn);
if (move.from() == (m_turn == WHITE ? SQUARE_A1 : SQUARE_A8))
result.set_castling<false>(QUEENSIDE, m_turn);
}

// Per move type action
if (move.is_capture())
{
Expand All @@ -288,10 +324,9 @@ Board Board::make_move(Move move) const
result.pop_piece(get_piece_at(target), ~m_turn, target);

// Castling: check if any rook has been captured
if (move.to() == (m_turn == WHITE ? SQUARE_H8 : SQUARE_H1))
result.set_castling<false>(KINGSIDE, ~m_turn);
if (move.to() == (m_turn == WHITE ? SQUARE_A8 : SQUARE_A1))
result.set_castling<false>(QUEENSIDE, ~m_turn);
for (auto side : { KINGSIDE, QUEENSIDE })
if (castling_rights(side, ~m_turn) && move.to() == get_rook_square(m_castling_rights[side][~m_turn], ~m_turn))
result.unset_castling(side, ~m_turn);
}
else if (move.is_double_pawn_push())
{
Expand All @@ -301,10 +336,23 @@ Board Board::make_move(Move move) const
}
else if (move.is_castle())
{
// Move the rook to the new square
Square iS = move.to() + (move.to() > move.from() ? +1 : -2);
Square iE = move.to() + (move.to() > move.from() ? -1 : +1);
result.move_piece(ROOK, m_turn, iS, iE);
// Get the start and ending squares for the rook
CastleSide side = file(move.to()) >= 4 ? KINGSIDE : QUEENSIDE;
Square iS = get_rook_square(m_castling_rights[side][m_turn], m_turn);
Square iE = move.to() + (side == KINGSIDE ? -1 : +1);

// Make the move in stages to ensure correct updates in Chess960
if (UCI::Options::UCI_Chess960)
{
result.pop_piece(ROOK, m_turn, iS);
result.move_piece(piece, m_turn, move.from(), move.to());
result.set_piece(ROOK, m_turn, iE);
}
else
{
result.move_piece(piece, m_turn, move.from(), move.to());
result.move_piece(ROOK, m_turn, iS, iE);
}
}

// Set piece on target square
Expand All @@ -313,7 +361,7 @@ Board Board::make_move(Move move) const
result.pop_piece(piece, m_turn, move.from());
result.set_piece(move.promo_piece(), m_turn, move.to());
}
else
else if (!move.is_castle())
{
result.move_piece(piece, m_turn, move.from(), move.to());
}
Expand All @@ -325,6 +373,22 @@ Board Board::make_move(Move move) const
result.regen_psqt(m_turn);
}

// Update castling rights after this move
if (piece == KING)
{
// Unset all castling rights after a king move
for (auto side : { KINGSIDE, QUEENSIDE })
result.unset_castling(side, m_turn);
}
else if (piece == ROOK)
{
// Unset castling rights for a certain side if a rook moves
if (move.from() == get_rook_square(m_castling_rights[KINGSIDE][m_turn], m_turn))
result.unset_castling(KINGSIDE, m_turn);
if (move.from() == get_rook_square(m_castling_rights[QUEENSIDE][m_turn], m_turn))
result.unset_castling(QUEENSIDE, m_turn);
}

// Swap turns
result.m_turn = ~m_turn;
result.m_hash ^= Zobrist::get_black_move();
Expand Down Expand Up @@ -559,7 +623,7 @@ uint8_t Board::phase() const
bool Board::legal(Move move) const
{
// Same source and destination squares?
if (move.from() == move.to())
if (move.from() == move.to() && !(UCI::Options::UCI_Chess960 && move.is_castle()))
return false;

// Ep without the square defined?
Expand All @@ -570,9 +634,13 @@ bool Board::legal(Move move) const
if (move.move_type() == INVALID_1 || move.move_type() == INVALID_2)
return false;

// Source square is not ours or destination ours?
// Source square is not ours?
Bitboard our_pieces = (m_turn == WHITE ? get_pieces<WHITE>() : get_pieces<BLACK>());
if (!our_pieces.test(move.from()) || our_pieces.test(move.to()))
if (!our_pieces.test(move.from()))
return false;

// Destination square ours?
if (our_pieces.test(move.to()) && !(UCI::Options::UCI_Chess960 && move.is_castle()))
return false;

// Capture and destination square not occupied by the opponent (including ep)?
Expand Down Expand Up @@ -646,6 +714,38 @@ const NNUE::Accumulator& Board::accumulator(Turn t) const



std::string Board::to_uci(Move m) const
{
// Castling for Chess960 (this needs to come before null moves)
if (UCI::Options::UCI_Chess960 && m.is_castle())
{
CastleSide side = file(m.to()) >= 4 ? KINGSIDE : QUEENSIDE;
Turn turn = rank(m.from()) < 4 ? WHITE : BLACK;
return get_square(m.from()) + get_square(get_rook_square(m_castling_rights[side][turn], turn));
}

// Null moves
if (m.from() == m.to())
return "0000";

// Promotions
if (m.is_promotion())
{
// Promotion
PieceType piece = m.promo_piece();
char promo_code = piece == KNIGHT ? 'n'
: piece == BISHOP ? 'b'
: piece == ROOK ? 'r'
: 'q';
return get_square(m.from()) + get_square(m.to()) + promo_code;
}

// Regular move
return get_square(m.from()) + get_square(m.to());
}





Position::Position()
Expand Down
Loading

0 comments on commit 6bcce44

Please sign in to comment.