Skip to content
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
14 changes: 14 additions & 0 deletions src/common.hpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#pragma once

#include "util/types.hpp"
#include <atomic>

namespace Clockwork {

inline std::atomic<bool> g_frc = false;

constexpr i32 MAX_PLY = 256;
constexpr Value VALUE_INF = 32501;
constexpr Value VALUE_MATED = 32500;
Expand All @@ -25,6 +28,17 @@ constexpr char color_char(Color color) {
unreachable();
}

constexpr i32 color_backrank(Color color) {
using enum Color;
switch (color) {
case White:
return 0;
case Black:
return 7;
}
unreachable();
}

constexpr Color invert(Color color) {
return static_cast<Color>(static_cast<i32>(color) ^ 1);
}
Expand Down
10 changes: 7 additions & 3 deletions src/move.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@ std::optional<Move> Move::parse(std::string_view str, const Position& ctx) {
}
}
if (ptype == PieceType::King) {
// TODO: FRC
Square rook_aside = ctx.rook_info(ctx.active_color()).aside;
Square rook_hside = ctx.rook_info(ctx.active_color()).hside;
if (*to == rook_aside || *to == rook_hside) {
return Move(*from, *to, MoveFlags::Castle);
}
if (from->file() == 4 && to->file() == 2) {
return Move(*from, ctx.rook_info(ctx.active_color()).aside, MoveFlags::Castle);
return Move(*from, rook_aside, MoveFlags::Castle);
}
if (from->file() == 4 && to->file() == 6) {
return Move(*from, ctx.rook_info(ctx.active_color()).hside, MoveFlags::Castle);
return Move(*from, rook_hside, MoveFlags::Castle);
}
}
return Move(*from, *to, capture ? MoveFlags::CaptureBit : MoveFlags::Normal);
Expand Down
3 changes: 1 addition & 2 deletions src/move.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ struct Move {
friend std::ostream& operator<<(std::ostream& os, Move mv) {
os << mv.from();

if (mv.flags() == MoveFlags::Castle) {
// TODO: FRC
if (!g_frc && mv.flags() == MoveFlags::Castle) {
if (mv.to().file() < mv.from().file()) {
os << 'c';
} else {
Expand Down
99 changes: 67 additions & 32 deletions src/movegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,26 +154,16 @@ bool MoveGen::is_legal_no_checkers(Move m, Bitboard valid_dests, bool can_ep) co

return false;
} else if (src.ptype() == PieceType::King) {
Bitboard danger = m_position.attack_table(invert(active_color)).get_attacked_bitboard();
Bitboard danger = m_position.attack_table(invert(active_color)).get_attacked_bitboard();
RookInfo rook_info = m_position.rook_info(active_color);

if (m.flags() == MoveFlags::Castle) {
// TODO: FRC
Bitboard empty = m_position.board().get_empty_bitboard();
RookInfo rook_info = m_position.rook_info(active_color);
Bitboard empty = m_position.board().get_empty_bitboard();
if (rook_info.aside == m.to()) {
Bitboard clear =
empty | Bitboard::from_square(king_sq) | Bitboard::from_square(rook_info.aside);
u8 rank_empty = clear.front_rank(active_color);
u8 rank_safe = (~danger).front_rank(active_color);
return (rank_empty & 0x1F) == 0x1F && (rank_safe & 0x1C) == 0x1C;
return is_aside_castling_legal(empty, danger);
}
if (rook_info.hside == m.to()) {
Bitboard clear =
empty | Bitboard::from_square(king_sq) | Bitboard::from_square(rook_info.hside);
u8 rank_empty = clear.front_rank(active_color);
u8 rank_safe = (~danger).front_rank(active_color);

return (rank_empty & 0xF0) == 0xF0 && (rank_safe & 0x70) == 0x70;
return is_hside_castling_legal(empty, danger);
}
return false;
}
Expand Down Expand Up @@ -299,27 +289,14 @@ void MoveGen::generate_moves_to(MoveList& noisy,
write(noisy, at, pawn_active & enemy & ~promo_zone, pawn_mask, MoveFlags::CaptureBit);

// Castling
// TODO: FRC
if constexpr (king_moves) {
Square king_sq = m_position.king_sq(active_color);
RookInfo rook_info = m_position.rook_info(active_color);
if (rook_info.aside.is_valid()) {
Bitboard clear =
empty | Bitboard::from_square(king_sq) | Bitboard::from_square(rook_info.aside);
u8 rank_empty = clear.front_rank(active_color);
u8 rank_safe = (~danger).front_rank(active_color);
if ((rank_empty & 0x1F) == 0x1F && (rank_safe & 0x1C) == 0x1C) {
quiet.push_back(Move{king_sq, rook_info.aside, MoveFlags::Castle});
}
if (is_aside_castling_legal(empty, danger)) {
quiet.push_back(Move{king_sq, rook_info.aside, MoveFlags::Castle});
}
if (rook_info.hside.is_valid()) {
Bitboard clear =
empty | Bitboard::from_square(king_sq) | Bitboard::from_square(rook_info.hside);
u8 rank_empty = clear.front_rank(active_color);
u8 rank_safe = (~danger).front_rank(active_color);
if ((rank_empty & 0xF0) == 0xF0 && (rank_safe & 0x70) == 0x70) {
quiet.push_back(Move{king_sq, rook_info.hside, MoveFlags::Castle});
}
if (is_hside_castling_legal(empty, danger)) {
quiet.push_back(Move{king_sq, rook_info.hside, MoveFlags::Castle});
}
}

Expand Down Expand Up @@ -384,6 +361,64 @@ void MoveGen::generate_moves_two_checkers(MoveList& noisy, MoveList& quiet, u16
generate_king_moves_to(noisy, quiet, non_checker_ray);
}

bool MoveGen::is_aside_castling_legal(Bitboard empty, Bitboard danger) const {
Color active_color = m_position.active_color();
Square king_sq = m_position.king_sq(active_color);
Square aside = m_position.rook_info(active_color).aside;

if (!aside.is_valid()) {
return false;
}

Bitboard clear = empty | Bitboard::from_square(king_sq) | Bitboard::from_square(aside);
u8 rank_empty = clear.front_rank(active_color);
u8 rank_safe = (~danger).front_rank(active_color);

if (g_frc) {
if (m_pinned.is_set(aside)) {
return false;
}
u8 king_ray = rays::inclusive(king_sq, Square::from_file_and_rank(2, king_sq.rank()))
.front_rank(active_color);
u8 rook_ray = rays::inclusive(aside, Square::from_file_and_rank(3, aside.rank()))
.front_rank(active_color);
u8 should_be_empty = king_ray | rook_ray;
return (rank_empty & should_be_empty) == should_be_empty
&& (rank_safe & king_ray) == king_ray;
} else {
return (rank_empty & 0x1F) == 0x1F && (rank_safe & 0x1C) == 0x1C;
}
}

bool MoveGen::is_hside_castling_legal(Bitboard empty, Bitboard danger) const {
Color active_color = m_position.active_color();
Square king_sq = m_position.king_sq(active_color);
Square hside = m_position.rook_info(active_color).hside;

if (!hside.is_valid()) {
return false;
}

Bitboard clear = empty | Bitboard::from_square(king_sq) | Bitboard::from_square(hside);
u8 rank_empty = clear.front_rank(active_color);
u8 rank_safe = (~danger).front_rank(active_color);

if (g_frc) {
if (m_pinned.is_set(hside)) {
return false;
}
u8 king_ray = rays::inclusive(king_sq, Square::from_file_and_rank(6, king_sq.rank()))
.front_rank(active_color);
u8 rook_ray = rays::inclusive(hside, Square::from_file_and_rank(5, hside.rank()))
.front_rank(active_color);
u8 should_be_empty = king_ray | rook_ray;
return (rank_empty & should_be_empty) == should_be_empty
&& (rank_safe & king_ray) == king_ray;
} else {
return (rank_empty & 0xF0) == 0xF0 && (rank_safe & 0x70) == 0x70;
}
}

void MoveGen::write(MoveList& moves, Square dest, u16 piecemask, MoveFlags mf) {
for (; piecemask != 0; piecemask = clear_lowest_bit(piecemask)) {
PieceId id{static_cast<u8>(std::countr_zero(piecemask))};
Expand Down
3 changes: 3 additions & 0 deletions src/movegen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class MoveGen {
[[nodiscard]] bool is_legal_one_checker(Move m, u16 checkers) const;
[[nodiscard]] bool is_legal_two_checkers(Move m, u16 checkers) const;

[[nodiscard]] bool is_aside_castling_legal(Bitboard empty, Bitboard danger) const;
[[nodiscard]] bool is_hside_castling_legal(Bitboard empty, Bitboard danger) const;

template<bool king_moves>
void generate_moves_to(MoveList& noisy, MoveList& quiet, Bitboard valid_dests, bool can_ep);
void generate_king_moves_to(MoveList& noisy, MoveList& quiet, Bitboard valid_dests);
Expand Down
77 changes: 53 additions & 24 deletions src/position.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -621,27 +621,57 @@ std::optional<Position> Position::parse(std::string_view board,
}

// Parse castling rights
// TODO: FRC, Error detection
if (castle != "-") {
auto verify_rook = [&](Color color, i32 file) -> Square {
Square rook_sq = Square::from_file_and_rank(file, color_backrank(color));
Place rook_place = result.m_board[rook_sq];
if (rook_place.color() == color && rook_place.ptype() == PieceType::Rook) {
return rook_sq;
}
return Square::invalid();
};
auto scan_for_rook = [&](Color color, i32 file, i32 direction) -> Square {
while (file >= 0 && file <= 7) {
Square sq = Square::from_file_and_rank(file, color_backrank(color));
Place place = result.m_board[sq];
if (place.color() == color) {
if (place.ptype() == PieceType::Rook) {
return sq;
}
if (place.ptype() == PieceType::King) {
return Square::invalid();
}
}
file += direction;
}
return Square::invalid();
};
for (char ch : castle) {
switch (ch) {
case 'K':
case 'H':
result.m_rook_info[0].hside = *Square::parse("h1");
break;
case 'Q':
case 'A':
result.m_rook_info[0].aside = *Square::parse("a1");
break;
case 'k':
case 'h':
result.m_rook_info[1].hside = *Square::parse("h8");
break;
case 'q':
case 'a':
result.m_rook_info[1].aside = *Square::parse("a8");
break;
default:
if (ch == 'K') {
result.m_rook_info[0].hside = scan_for_rook(Color::White, 7, -1);
} else if (ch == 'Q') {
result.m_rook_info[0].aside = scan_for_rook(Color::White, 0, +1);
} else if (ch == 'k') {
result.m_rook_info[1].hside = scan_for_rook(Color::Black, 7, -1);
} else if (ch == 'q') {
result.m_rook_info[1].aside = scan_for_rook(Color::Black, 0, +1);
} else if (ch >= 'A' && ch <= 'H') {
i32 rook_file = ch - 'A';
i32 king_file = result.king_sq(Color::White).file();
if (rook_file < king_file) {
result.m_rook_info[0].aside = verify_rook(Color::White, rook_file);
} else {
result.m_rook_info[0].hside = verify_rook(Color::White, rook_file);
}
} else if (ch >= 'a' && ch <= 'h') {
i32 rook_file = ch - 'a';
i32 king_file = result.king_sq(Color::Black).file();
if (rook_file < king_file) {
result.m_rook_info[1].aside = verify_rook(Color::Black, rook_file);
} else {
result.m_rook_info[1].hside = verify_rook(Color::Black, rook_file);
}
} else {
return std::nullopt;
}
}
Expand Down Expand Up @@ -739,23 +769,22 @@ std::ostream& operator<<(std::ostream& os, const Position& position) {

os << ' ' << color_char(position.m_active_color) << ' ';

// TODO: FRC
RookInfo white_rook_info = position.rook_info(Color::White);
RookInfo black_rook_info = position.rook_info(Color::Black);
if (white_rook_info.is_clear() && black_rook_info.is_clear()) {
os << '-';
}
if (white_rook_info.hside.is_valid()) {
os << 'K';
os << static_cast<char>(g_frc ? white_rook_info.hside.file() + 'A' : 'K');
}
if (white_rook_info.aside.is_valid()) {
os << 'Q';
os << static_cast<char>(g_frc ? white_rook_info.aside.file() + 'A' : 'Q');
}
if (black_rook_info.hside.is_valid()) {
os << 'k';
os << static_cast<char>(g_frc ? black_rook_info.hside.file() + 'a' : 'k');
}
if (black_rook_info.aside.is_valid()) {
os << 'q';
os << static_cast<char>(g_frc ? black_rook_info.aside.file() + 'a' : 'q');
}

if (position.m_enpassant.is_valid()) {
Expand Down
11 changes: 10 additions & 1 deletion src/uci.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ void UCIHandler::execute_command(const std::string& line) {
if (command == "uci") {
std::cout << "id name Clockwork\n";
std::cout << "id author The Clockwork community\n";
std::cout << "option name UCI_Chess960 type check default false\n";
std::cout << "option name Threads type spin default 1 min 1 max " << MAX_THREADS << "\n";
std::cout << "option name Hash type spin default 16 min 1 max " << MAX_HASH << "\n";
tuned::uci_print_tunable_options();
Expand Down Expand Up @@ -184,7 +185,15 @@ void UCIHandler::handle_setoption(std::istringstream& is) {

is >> value_str;

if (name == "Hash") {
if (name == "UCI_Chess960") {
if (value_str == "true") {
g_frc = true;
} else if (value_str == "false") {
g_frc = false;
} else {
std::cout << "Invalid value " << value_str << std::endl;
}
} else if (name == "Hash") {
if (auto value = parse_number<usize>(value_str)) {
usize hash_size = std::clamp<usize>(*value, 1, MAX_HASH);
m_tt.resize(hash_size);
Expand Down
10 changes: 10 additions & 0 deletions tests/test_perft.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,18 @@ int main() {
"r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10",
{{1, 46, 2079, 89890, 3894594, 164075551}},
},
{
"1bbrnkqr/pp1p1ppp/2p1p3/1n6/5P2/3Q4/PPPPP1PP/NBBRNK1R w HDhd - 2 9",
{{1, 36, 891, 31075, 781792, 26998966}},
},
{
"2r1kr2/8/8/8/8/8/8/1R2K1R1 w GBfc - 0 1",
{{1, 22, 501, 11459, 264663, 6236222, 149271720}},
},
}};

g_frc = true;

for (auto [fen, results] : cases) {
Position position = *Position::parse(fen);
std::cout << fen << ":" << std::endl;
Expand Down
31 changes: 31 additions & 0 deletions tests/test_position.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,36 @@ void roundtrip_classical_fens() {
"r4rk1/1Bp1qppp/2np1n2/1pb1p1B1/4P1b1/P1NP1N2/1PP1QPPP/R4RK1 b - b6 1 11",
}};

g_frc = false;

for (std::string_view fen : cases) {
Position position = *Position::parse(fen);

std::ostringstream os;
os << position;

std::cout << fen << std::endl;
std::cout << position << std::endl;

REQUIRE(fen == os.str());
}
}

void roundtrip_dfrc_fens() {
std::vector<std::string_view> cases{{
"2r1kr2/8/8/8/8/8/8/1R2K1R1 w GBfc - 0 1",
"rkr5/8/8/8/8/8/8/5RKR w HFca - 0 1",
"2r3kr/8/8/8/8/8/8/2KRR3 w h - 3 2",
"5rkr/8/8/8/8/8/8/RKR5 w CAhf - 0 1",
"3rkr2/8/8/8/8/8/8/R3K2R w HAfd - 0 1",
"4k3/8/8/8/8/8/8/4KR2 w F - 0 1",
"4kr2/8/8/8/8/8/8/4K3 w f - 0 1",
"4k3/8/8/8/8/8/8/2R1K3 w C - 0 1",
"2r1k3/8/8/8/8/8/8/4K3 w c - 0 1",
}};

g_frc = true;

for (std::string_view fen : cases) {
Position position = *Position::parse(fen);

Expand All @@ -32,5 +62,6 @@ void roundtrip_classical_fens() {

int main() {
roundtrip_classical_fens();
roundtrip_dfrc_fens();
return 0;
}
Loading