Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Chess960 support #20

Merged
merged 5 commits into from
Dec 5, 2023
Merged
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
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