diff --git a/src/Bitboard.cpp b/src/Bitboard.cpp index 312ecc8..05924a0 100644 --- a/src/Bitboard.cpp +++ b/src/Bitboard.cpp @@ -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(static_cast(i), Bitboard::from_single_bit(j))& - get_attacks(static_cast(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(static_cast(i), Bitboard::from_single_bit(j)) & + get_attacks(static_cast(j), Bitboard::from_single_bit(i)); else if (ranks_files[i].test(j)) - between_squares[i][j] = get_attacks(static_cast(i), Bitboard::from_single_bit(j))& - get_attacks(static_cast(j), Bitboard::from_single_bit(i)); + between_squares[i][j] = get_attacks(static_cast(i), Bitboard::from_single_bit(j)) & + get_attacks(static_cast(j), Bitboard::from_single_bit(i)); } diff --git a/src/Move.cpp b/src/Move.cpp deleted file mode 100644 index 7b43cb6..0000000 --- a/src/Move.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "Move.hpp" -#include - - -std::ostream& operator<<(std::ostream& out, const Move& move) -{ - out << move.to_uci(); - return out; -} - - -std::ostream& operator<<(std::ostream& out, const MoveList& list) -{ - if (list.length() > 0) - { - out << list.m_moves[0]; - for (Move* i = (list.m_moves + 1); i < list.m_end; i++) - out << " " << *i; - } - return out; -} diff --git a/src/Move.hpp b/src/Move.hpp index 0ad2e7c..1d91848 100644 --- a/src/Move.hpp +++ b/src/Move.hpp @@ -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); }; @@ -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); }; diff --git a/src/Position.cpp b/src/Position.cpp index 2185466..a3ae0cb 100644 --- a/src/Position.cpp +++ b/src/Position.cpp @@ -1,6 +1,7 @@ #include "Position.hpp" #include "Types.hpp" #include "NNUE.hpp" +#include "UCI.hpp" #include "Zobrist.hpp" #include "syzygy/syzygy.hpp" #include @@ -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' @@ -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(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") {} @@ -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; @@ -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(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; @@ -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); } @@ -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 ? " " : "- "); @@ -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; @@ -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(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(KINGSIDE, m_turn); - if (move.from() == (m_turn == WHITE ? SQUARE_A1 : SQUARE_A8)) - result.set_castling(QUEENSIDE, m_turn); - } - // Per move type action if (move.is_capture()) { @@ -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(KINGSIDE, ~m_turn); - if (move.to() == (m_turn == WHITE ? SQUARE_A8 : SQUARE_A1)) - result.set_castling(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()) { @@ -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 @@ -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()); } @@ -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(); @@ -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? @@ -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() : get_pieces()); - 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)? @@ -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() diff --git a/src/Position.hpp b/src/Position.hpp index ea3c735..1e79eb0 100644 --- a/src/Position.hpp +++ b/src/Position.hpp @@ -20,7 +20,7 @@ class Board // Required fields Bitboard m_pieces[NUM_PIECE_TYPES][NUM_COLORS]; Turn m_turn; - bool m_castling_rights[NUM_CASTLE_SIDES][NUM_COLORS]; + CastleFile m_castling_rights[NUM_CASTLE_SIDES][NUM_COLORS]; Square m_enpassant_square; int m_half_move_clock; int m_full_move_clock; @@ -159,7 +159,7 @@ class Board template - void generate_moves_king(MoveList& list, Bitboard filter, Bitboard occupancy) const + void generate_moves_king(MoveList& list, Bitboard filter, Bitboard occupancy, MoveGenType type) const { Square king_square = get_pieces().bitscan_forward(); Bitboard attacks = Bitboards::get_attacks(king_square, occupancy) & filter; @@ -174,11 +174,11 @@ class Board } // Castling when not in check - if (!checkers()) + if (!checkers() && type != MoveGenType::CAPTURES) { - if (m_castling_rights[KINGSIDE][TURN] && filter.test(Bitboards::castle_target_square[TURN][KINGSIDE]) && can_castle(KINGSIDE, occupancy)) + if (castling_rights(KINGSIDE, TURN) && can_castle(KINGSIDE, occupancy)) list.push(king_square, Bitboards::castle_target_square[TURN][KINGSIDE], KING_CASTLE); - if (m_castling_rights[QUEENSIDE][TURN] && filter.test(Bitboards::castle_target_square[TURN][QUEENSIDE]) && can_castle(QUEENSIDE, occupancy)) + if (castling_rights(QUEENSIDE, TURN) && can_castle(QUEENSIDE, occupancy)) list.push(king_square, Bitboards::castle_target_square[TURN][QUEENSIDE], QUEEN_CASTLE); } @@ -217,7 +217,7 @@ class Board } // King moves: always legal - generate_moves_king(list, filter, occupancy); + generate_moves_king(list, filter, occupancy, type); // Check for pins if (pinned) @@ -250,6 +250,12 @@ class Board } + inline bool castling_rights(CastleSide side, Turn turn) const + { + return m_castling_rights[side][turn] != CastleFile::NONE; + } + + Hash generate_hash() const; @@ -271,13 +277,19 @@ class Board } - template - inline void set_castling(CastleSide side, Turn turn) + inline void set_castling(CastleSide side, Turn turn, CastleFile file) + { + m_hash ^= Zobrist::get_castle_side_turn(side, turn); + m_castling_rights[side][turn] = file; + } + + + inline void unset_castling(CastleSide side, Turn turn) { - if (m_castling_rights[side][turn] != CAN_CASTLE) + if (m_castling_rights[side][turn] != CastleFile::NONE) { m_hash ^= Zobrist::get_castle_side_turn(side, turn); - m_castling_rights[side][turn] = CAN_CASTLE; + m_castling_rights[side][turn] = CastleFile::NONE; } } @@ -397,15 +409,15 @@ class Board if (!checkers() && move.is_castle()) { // Starting square - if (move.from() != (TURN == WHITE ? SQUARE_E1 : SQUARE_E8)) + if (move.from() != m_king_sq[TURN]) return false; // Kingside - if (m_castling_rights[KINGSIDE][TURN] && move.move_type() == KING_CASTLE && + if (castling_rights(KINGSIDE, TURN) && move.move_type() == KING_CASTLE && move.to() == Bitboards::castle_target_square[TURN][KINGSIDE] && can_castle(KINGSIDE, occupancy)) return true; // Queenside - if (m_castling_rights[QUEENSIDE][TURN] && move.move_type() == QUEEN_CASTLE && + if (castling_rights(QUEENSIDE, TURN) && move.move_type() == QUEEN_CASTLE && move.to() == Bitboards::castle_target_square[TURN][QUEENSIDE] && can_castle(QUEENSIDE, occupancy)) return true; @@ -595,14 +607,25 @@ class Board template bool can_castle(CastleSide side, Bitboard occupancy) const { - // Check if target squares are empty - if (occupancy & Bitboards::castle_non_occupied_squares[TURN][side]) + // Remove king and rook from occupancy + Square king_sq = m_king_sq[TURN]; + Square rook_sq = get_rook_square(m_castling_rights[side][TURN], TURN); + occupancy.reset(king_sq); + occupancy.reset(rook_sq); + + // Build bitboards for the squares that the king and rook will travel + Square king_target = Bitboards::castle_target_square[TURN][side]; + Square rook_target = Bitboards::castle_target_square[TURN][side] + (side == KINGSIDE ? -1 : 1); + Bitboard king_travel = Bitboards::between(king_sq, king_target) | Bitboard::from_single_bit(king_target); + Bitboard rook_travel = Bitboards::between(rook_sq, rook_target) | Bitboard::from_single_bit(rook_target); + + // Check if traveling squares are empty + if (bool(occupancy & king_travel) || bool(occupancy & rook_travel)) return false; - // Check if middle squares are attacked - Bitboard attackable = Bitboards::castle_non_attacked_squares[TURN][side]; - while (attackable) - if (attackers<~TURN>(attackable.bitscan_forward_reset(), occupancy)) + // Check if the king's traveling squares are attacked + while (king_travel) + if (attackers<~TURN>(king_travel.bitscan_forward_reset(), occupancy)) return false; return true; @@ -614,8 +637,8 @@ class Board inline bool can_castle() const { - return m_castling_rights[0][0] || m_castling_rights[0][1] || - m_castling_rights[1][0] || m_castling_rights[1][1]; + return castling_rights(KINGSIDE, WHITE) || castling_rights(KINGSIDE, BLACK) || + castling_rights(QUEENSIDE, WHITE) || castling_rights(QUEENSIDE, BLACK); } @@ -653,6 +676,9 @@ class Board const NNUE::Accumulator& accumulator(Turn t) const; + + + std::string to_uci(Move m) const; }; diff --git a/src/Search.cpp b/src/Search.cpp index 3bcee9d..020c623 100644 --- a/src/Search.cpp +++ b/src/Search.cpp @@ -147,7 +147,7 @@ namespace Search : BoundType::LOWER_BOUND; // Blessed loss } - void MultiPVData::write_pv(int index, uint64_t nodes, uint64_t tb_hits, double elapsed) const + void MultiPVData::write_pv(const Board& board, int index, uint64_t nodes, uint64_t tb_hits, double elapsed) const { // Don't write if PV line is incomplete if (search_bound == BoundType::NO_BOUND) @@ -177,9 +177,9 @@ namespace Search // Pv line const Move* m = pv; - std::cout << " pv " << *(m++); + std::cout << " pv " << board.to_uci(*(m++)); while (*m != MOVE_NULL) - std::cout << " " << (*m++); + std::cout << " " << board.to_uci(*(m++)); std::cout << std::endl; } @@ -536,7 +536,7 @@ namespace Search if (RootSearch && data.thread().is_main() && data.thread().time().elapsed() > 3) std::cout << "info depth " << depth - << " currmove " << move.to_uci() + << " currmove " << position.board().to_uci(move) << " currmovenumber " << n_moves << std::endl; // Shallow depth pruning @@ -894,26 +894,27 @@ namespace Search for (auto move : move_list) if (!position.board().legal(move)) { - std::cout << "Bad illegal move " << move.to_uci() << " (" << move.to_int() << ") in " << position.board().to_fen() << std::endl; + std::cout << "Bad illegal move " + << position.board().to_uci(move) + << " (" << move.to_int() << ") in " + << position.board().to_fen() + << std::endl; final = false; } - // Illegality check: first count number of legal moves - int result = 0; + // Illegality check for (uint16_t number = 0; number < UINT16_MAX; number++) - if (position.board().legal(Move::from_int(number))) - result++; - // Something is wrong, find the bad legals - if (result != move_list.length()) { - std::cout << result << " vs " << move_list.length() << std::endl; - for (uint16_t number = 0; number < UINT16_MAX; number++) + Move move = Move::from_int(number); + if (position.board().legal(move) && !move_list.contains(move)) { - Move move = Move::from_int(number); - if (position.board().legal(move) && !move_list.contains(move)) - std::cout << "Bad legal move " << move.to_uci() << " (" << move.to_int() << ") in " << position.board().to_fen() << std::endl; + std::cout << "Bad legal move " + << position.board().to_uci(move) + << " (" << move.to_int() << ") in " + << position.board().to_fen() + << std::endl; + final = false; } - final = false; } return final; } diff --git a/src/Search.hpp b/src/Search.hpp index 7cd24fb..e7c63c2 100644 --- a/src/Search.hpp +++ b/src/Search.hpp @@ -117,7 +117,7 @@ namespace Search Score score() const; BoundType bound() const; - void write_pv(int index, uint64_t nodes, uint64_t tb_hits, double elapsed) const; + void write_pv(const Board& board, int index, uint64_t nodes, uint64_t tb_hits, double elapsed) const; }; @@ -217,13 +217,13 @@ namespace Search if (depth > 1) { position.make_move(move); - count = perft(position, depth - 1, hists); + count = perft(position, depth - 1, hists); position.unmake_move(); } n_nodes += count; if (OUTPUT) - std::cout << move.to_uci() << ": " << count << std::endl; + std::cout << position.board().to_uci(move) << ": " << count << std::endl; } } else @@ -241,13 +241,13 @@ namespace Search return 0; position.make_move(move); - count = perft(position, depth - 1, hists); + count = perft(position, depth - 1, hists); position.unmake_move(); n_nodes += count; if (OUTPUT) - std::cout << move.to_uci() << ": " << count << std::endl; + std::cout << position.board().to_uci(move) << ": " << count << std::endl; } } else @@ -255,7 +255,7 @@ namespace Search n_nodes = move_list.length(); if (OUTPUT) for (auto move : move_list) - std::cout << move.to_uci() << ": " << 1 << std::endl; + std::cout << position.board().to_uci(move) << ": " << 1 << std::endl; } } diff --git a/src/Tests.cpp b/src/Tests.cpp index 75d794d..228d298 100644 --- a/src/Tests.cpp +++ b/src/Tests.cpp @@ -61,6 +61,17 @@ namespace Tests tests.push_back(PerftTest("r2q1b1r/ppp1ppp1/n1k2n1p/3p4/P4P2/2NP3P/1PPKP1BP/R1BQ3R b - f3 0 8", 6, 542729390)); tests.push_back(PerftTest("rnbqkbnr/Pp1ppppp/8/8/2P5/4B3/PP2PPPP/RN1QKBNR b KQkq - 0 4", 6, 632428291)); tests.push_back(PerftTest("3r1r2/2q2p1k/p3b3/1p6/4Pp1P/P1Nn2P1/1PP5/R4R1K b - h3 0 4", 6, 2719206243)); + tests.push_back(PerftTest("bqnb1rkr/pp3ppp/3ppn2/2p5/5P2/P2P4/NPP1P1PP/BQ1BNRKR w HFhf - 2 9 (FRC)", 6, 227689589)); + tests.push_back(PerftTest("2nnrbkr/p1qppppp/8/1ppb4/6PP/3PP3/PPP2P2/BQNNRBKR w HEhe - 1 9 (FRC)", 6, 590751109)); + tests.push_back(PerftTest("b1q1rrkb/pppppppp/3nn3/8/P7/1PPP4/4PPPP/BQNNRKRB w GE - 1 9 (FRC)", 6, 177654692)); + tests.push_back(PerftTest("qbbnnrkr/2pp2pp/p7/1p2pp2/8/P3PP2/1PPP1KPP/QBBNNR1R w hf - 0 9 (FRC)", 6, 274103539)); + tests.push_back(PerftTest("1nbbnrkr/p1p1ppp1/3p4/1p3P1p/3Pq2P/8/PPP1P1P1/QNBBNRKR w HFhf - 0 9 (FRC)", 6, 1250970898)); + tests.push_back(PerftTest("1qnrkbbr/1pppppp1/p1n4p/8/P7/1P1N1P2/2PPP1PP/QN1RKBBR w HDhd - 0 9 (FRC)", 6, 783201510)); + tests.push_back(PerftTest("qn1rkrbb/pp1p1ppp/2p1p3/3n4/4P2P/2NP4/PPP2PP1/Q1NRKRBB w FDfd - 1 9 (FRC)", 6, 233468620)); + tests.push_back(PerftTest("bb1qnrkr/pp1p1pp1/1np1p3/4N2p/8/1P4P1/P1PPPP1P/BBNQ1RKR w HFhf - 0 9 (FRC)", 6, 776836316)); + tests.push_back(PerftTest("bnqbnr1r/p1p1ppkp/3p4/1p4p1/P7/3NP2P/1PPP1PP1/BNQB1RKR w HF - 0 9 (FRC)", 6, 809194268)); + tests.push_back(PerftTest("bnqnrbkr/1pp2pp1/p7/3pP2p/4P1P1/8/PPPP3P/BNQNRBKR w HEhe d6 0 9 (FRC)", 6, 1008880643)); + tests.push_back(PerftTest("b1qnrrkb/ppp1pp1p/n2p1Pp1/8/8/P7/1PPPP1PP/BNQNRKRB w GE - 0 9 (FRC)", 6, 193594729)); return tests; } @@ -103,17 +114,26 @@ namespace Tests bench.push_back("2r2rk1/1bqnbpp1/1p1ppn1p/pP6/N1P1P3/P2B1N1P/1B2QPP1/R2R2K1 b - - 0 1"); bench.push_back("r1bqk2r/pp2bppp/2p5/3pP3/P2Q1P2/2N1B3/1PP3PP/R4RK1 b kq - 0 1"); bench.push_back("r2qnrnk/p2b2b1/1p1p2pp/2pPpp2/1PP1P3/PRNBB3/3QNPPP/5RK1 w - - 0 1"); + bench.push_back("qrb1nnkr/ppp2ppp/4pb2/3p4/2P5/1P3N2/P2PPPPP/QRBB1NKR w HBhb - 1 4 (FRC)"); + bench.push_back("rbknnrbq/1pppp1pp/8/p7/P3p3/2N5/1PPP1PPP/RBK1NRBQ w FAfa - 0 4 (FRC)"); + bench.push_back("1bbnrkqr/ppp2ppp/1n6/3pp3/7P/1N1P4/PPP1PPP1/1BBNRKQR w HEhe - 0 4 (FRC)"); return bench; } int perft_tests() { - auto tests = test_suite(); + // Store initial state + bool Chess960 = UCI::Options::UCI_Chess960; + // Loop over each position int n_failed = 0; + auto tests = test_suite(); for (auto& test : tests) { + // Check if this is a FRC position + UCI::Options::UCI_Chess960 = (test.fen().find("(FRC)") != std::string::npos); + Position pos(test.fen()); auto hists = std::make_unique(); auto result = Search::perft(pos, test.depth(), *hists); @@ -128,6 +148,7 @@ namespace Tests } } + UCI::Options::UCI_Chess960 = Chess960; std::cout << "\nFailed/total tests: " << n_failed << "/" << tests.size() << std::endl; return n_failed; } @@ -145,12 +166,18 @@ namespace Tests Search::Timer time; uint64_t nodes = 0; + // Store initial state + bool Chess960 = UCI::Options::UCI_Chess960; + // Loop over each position int i = 0; Position& pos = pool->position(); std::vector fens = bench_suite(); for (auto fen : fens) { + // Check if this is a FRC position + UCI::Options::UCI_Chess960 = (fen.find("(FRC)") != std::string::npos); + // Update position pos = Position(fen); pos.set_init_ply(); @@ -173,7 +200,8 @@ namespace Tests std::cerr << "Elapsed time (s): " << std::setw(7) << elapsed << std::endl; std::cerr << "Nodes per second: " << uint64_t(nodes / elapsed) << std::endl; - // Restore initial thread pool and hash + // Restore initial options, thread pool and hash + UCI::Options::UCI_Chess960 = Chess960; pool->resize(UCI::Options::Threads); ttable.resize(UCI::Options::Hash); } diff --git a/src/Tests.hpp b/src/Tests.hpp index 5e220db..968f7b5 100644 --- a/src/Tests.hpp +++ b/src/Tests.hpp @@ -3,6 +3,7 @@ #include "Search.hpp" #include "Types.hpp" #include "Position.hpp" +#include "UCI.hpp" #include #include @@ -39,6 +40,9 @@ namespace Tests template int perft_techniques_tests() { + // Store initial state + bool Chess960 = UCI::Options::UCI_Chess960; + auto tests = test_suite(); // Allocate TT @@ -48,10 +52,14 @@ namespace Tests int n_failed = 0; for (auto& test : tests) { + // Check if this is a FRC position + UCI::Options::UCI_Chess960 = (test.fen().find("(FRC)") != std::string::npos); + Position pos(test.fen()); auto hists = std::make_unique(); - auto result_base = Search::perft(pos, test.depth() - 1, *hists); - auto result_test = Search::template perft(pos, test.depth() - 1, *hists); + Depth depth = test.depth() - 1 - 2 * (LEGALITY || VALIDITY); + auto result_base = Search::perft(pos, depth, *hists); + auto result_test = Search::template perft(pos, depth, *hists); if (result_base == result_test) { std::cout << "[ OK ] " << test.fen() << " (" << result_test << ")" << std::endl; @@ -67,6 +75,7 @@ namespace Tests if (TT) perft_table.resize(0); + UCI::Options::UCI_Chess960 = Chess960; std::cout << "\nFailed/total tests: " << n_failed << "/" << tests.size() << std::endl; return n_failed; } diff --git a/src/Thread.cpp b/src/Thread.cpp index 7defb3f..aeb4c43 100644 --- a/src/Thread.cpp +++ b/src/Thread.cpp @@ -88,7 +88,7 @@ void Thread::output_pvs() // Output information for (int iPV = 0; iPV < UCI::Options::MultiPV; iPV++) - m_multiPV[iPV].write_pv(iPV, nodes, tb_hits, elapsed); + m_multiPV[iPV].write_pv(m_position.board(), iPV, nodes, tb_hits, elapsed); } @@ -359,7 +359,7 @@ void Thread::search() { // Even in this case, output a score and bestmove std::cout << "info depth 0 score " << (m_position.in_check() ? "mate 0" : "cp 0") << std::endl; - std::cout << "bestmove " << MOVE_NULL << std::endl; + std::cout << "bestmove (0000)" << std::endl; // Stop the search m_pool.stop(); @@ -505,9 +505,9 @@ void Thread::search() Move pondermove = *(best_pv + 1); // Mandatory output to the GUI - std::cout << "bestmove " << bestmove; + std::cout << "bestmove " << m_position.board().to_uci(bestmove); if (pondermove != MOVE_NULL) - std::cout << " ponder " << pondermove; + std::cout << " ponder " << m_position.board().to_uci(pondermove); std::cout << std::endl; // Debug prints diff --git a/src/Types.hpp b/src/Types.hpp index 0451468..f346db7 100644 --- a/src/Types.hpp +++ b/src/Types.hpp @@ -115,6 +115,19 @@ enum CastleSide NO_SIDE = 2 }; +enum class CastleFile +{ + NONE, + FILE_A, + FILE_B, + FILE_C, + FILE_D, + FILE_E, + FILE_F, + FILE_G, + FILE_H +}; + enum ScoreType : Score { @@ -314,6 +327,12 @@ constexpr int rank(Square s, Turn turn) { return turn == WHITE ? rank(s) : 7 - r std::string get_square(Square square); +inline constexpr Square get_rook_square(CastleFile f, Turn turn) +{ + return (turn == WHITE ? SQUARE_A1 : SQUARE_A8) + static_cast(f) - 1; +} + + // Pseudo random number generator based on SplitMix64 class PseudoRandom { diff --git a/src/UCI.cpp b/src/UCI.cpp index 2be172d..5bb13bc 100644 --- a/src/UCI.cpp +++ b/src/UCI.cpp @@ -28,6 +28,7 @@ namespace UCI int Hash; int MultiPV; bool Ponder; + bool UCI_Chess960; int Threads; int MoveOverhead; std::string NNUE_File; @@ -113,6 +114,7 @@ namespace UCI [](int v) { pool->resize(v); })); OptionsMap.emplace("Move Overhead", Option(&Options::MoveOverhead, 0, 0, 5000)); OptionsMap.emplace("Ponder", Option(&Options::Ponder, false)); + OptionsMap.emplace("UCI_Chess960", Option(&Options::UCI_Chess960, false)); OptionsMap.emplace("NNUE_File", Option(&Options::NNUE_File, "", NNUE::load)); OptionsMap.emplace("SyzygyPath", Option(&Options::TB_Path, "", Syzygy::load)); OptionsMap.emplace("SyzygyProbeDepth", Option(&Options::TB_ProbeDepth, 1, 1, 199)); @@ -398,7 +400,7 @@ namespace UCI // Search for the move in the move list for (auto& move : list) - if (move.to_uci() == move_str) + if (position.board().to_uci(move) == move_str) return move; return MOVE_NULL; diff --git a/src/UCI.hpp b/src/UCI.hpp index e41e2fa..43639f8 100644 --- a/src/UCI.hpp +++ b/src/UCI.hpp @@ -85,6 +85,7 @@ namespace UCI extern int Hash; extern int MultiPV; extern bool Ponder; + extern bool UCI_Chess960; extern int Threads; extern int MoveOverhead; extern std::string NNUE_File;