diff --git a/include/bitbishop/move.hpp b/include/bitbishop/move.hpp index d3901e9..1832b6a 100644 --- a/include/bitbishop/move.hpp +++ b/include/bitbishop/move.hpp @@ -20,4 +20,67 @@ struct Move { bool is_capture; ///< True if the move captures an opponent's piece. bool is_en_passant; ///< True if the move is an en passant capture. bool is_castling; ///< True if the move is a castling move (kingside or queenside). + + /** + * @brief Creates a normal (non-special) move. + * @param from The starting square of the move. + * @param to The target square of the move. + * @param is_capture True if the move captures an opponent's piece; false otherwise. + * @return A Move instance representing the normal move. + */ + static Move make(Square from, Square to, bool is_capture = false) { + return {.from = from, + .to = to, + .promotion = std::nullopt, + .is_capture = is_capture, + .is_en_passant = false, + .is_castling = false}; + } + + /** + * @brief Creates a pawn promotion move. + * @param from The starting square of the pawn. + * @param to The target square where the promotion occurs. + * @param piece The piece type to promote the pawn into. + * @param is_capture True if the move captures an opponent's piece; false otherwise. + * @return A Move instance representing the promotion move. + */ + static Move make_promotion(Square from, Square to, Piece piece, bool is_capture = false) { + return {.from = from, + .to = to, + .promotion = piece, + .is_capture = is_capture, + .is_en_passant = false, + .is_castling = false}; + } + + /** + * @brief Creates an en passant capture move. + * @param from The starting square of the capturing pawn. + * @param to The target square where the capturing pawn lands. + * @return A Move instance representing the en passant capture. + */ + static Move make_en_passant(Square from, Square to) { + return {.from = from, + .to = to, + .promotion = std::nullopt, + .is_capture = true, + .is_en_passant = true, + .is_castling = false}; + } + + /** + * @brief Creates a castling move. + * @param from The starting square of the king. + * @param to The target square of the king during castling. + * @return A Move instance representing the castling move. + */ + static Move make_castling(Square from, Square to) { + return {.from = from, + .to = to, + .promotion = std::nullopt, + .is_capture = false, + .is_en_passant = false, + .is_castling = true}; + } }; diff --git a/include/bitbishop/movegen/bishop_moves.hpp b/include/bitbishop/movegen/bishop_moves.hpp index 716a302..c24f3c5 100644 --- a/include/bitbishop/movegen/bishop_moves.hpp +++ b/include/bitbishop/movegen/bishop_moves.hpp @@ -64,7 +64,7 @@ void generate_bishop_legal_moves(std::vector& moves, const Board& board, C for (Square to : candidates) { const bool is_capture = enemy.test(to); - moves.emplace_back(from, to, std::nullopt, is_capture, false, false); + moves.emplace_back(Move::make(from, to, is_capture)); } } } diff --git a/include/bitbishop/movegen/castling_moves.hpp b/include/bitbishop/movegen/castling_moves.hpp index dfb6e3a..58afc48 100644 --- a/include/bitbishop/movegen/castling_moves.hpp +++ b/include/bitbishop/movegen/castling_moves.hpp @@ -42,7 +42,7 @@ void generate_castling_moves(std::vector& moves, const Board& board, Color Square g_square = (us == Color::WHITE) ? G1 : G8; if (!enemy_attacks.test(f_square) && !enemy_attacks.test(g_square)) { - moves.emplace_back(king_from, g_square, std::nullopt, false, false, true); + moves.emplace_back(Move::make_castling(king_from, g_square)); } } @@ -51,7 +51,7 @@ void generate_castling_moves(std::vector& moves, const Board& board, Color Square c_square = (us == Color::WHITE) ? C1 : C8; if (!enemy_attacks.test(d_square) && !enemy_attacks.test(c_square)) { - moves.emplace_back(king_from, c_square, std::nullopt, false, false, true); + moves.emplace_back(Move::make_castling(king_from, c_square)); } } } diff --git a/include/bitbishop/movegen/king_moves.hpp b/include/bitbishop/movegen/king_moves.hpp index b6c3034..4098a1e 100644 --- a/include/bitbishop/movegen/king_moves.hpp +++ b/include/bitbishop/movegen/king_moves.hpp @@ -19,6 +19,6 @@ void generate_legal_king_moves(std::vector& moves, const Board& board, Col for (Square to : candidates) { const bool is_capture = enemy.test(to); - moves.emplace_back(king_sq, to, std::nullopt, is_capture, false, false); + moves.emplace_back(Move::make(king_sq, to, is_capture)); } } diff --git a/include/bitbishop/movegen/knight_moves.hpp b/include/bitbishop/movegen/knight_moves.hpp index 32f7531..50f1297 100644 --- a/include/bitbishop/movegen/knight_moves.hpp +++ b/include/bitbishop/movegen/knight_moves.hpp @@ -26,7 +26,7 @@ void generate_knight_legal_moves(std::vector& moves, const Board& board, C for (Square to : candidates) { const bool is_capture = enemy.test(to); - moves.emplace_back(from, to, std::nullopt, is_capture, false, false); + moves.emplace_back(Move::make(from, to, is_capture)); } } } diff --git a/include/bitbishop/movegen/pawn_moves.hpp b/include/bitbishop/movegen/pawn_moves.hpp index fa5f251..aee07ae 100644 --- a/include/bitbishop/movegen/pawn_moves.hpp +++ b/include/bitbishop/movegen/pawn_moves.hpp @@ -98,7 +98,7 @@ void add_pawn_promotions(std::vector& moves, Square from, Square to, Color const auto& promotion_pieces = (side == Color::WHITE) ? WHITE_PROMOTIONS : BLACK_PROMOTIONS; for (auto piece : promotion_pieces) { - moves.emplace_back(from, to, piece, capture, false, false); + moves.emplace_back(Move::make_promotion(from, to, piece, capture)); } } @@ -129,7 +129,7 @@ inline void generate_single_push(std::vector& moves, Square from, Color us if (is_promotion_rank(to, us)) { add_pawn_promotions(moves, from, to, us, false); } else { - moves.emplace_back(from, to, std::nullopt, false, false, false); + moves.emplace_back(Move::make(from, to)); } } } @@ -164,7 +164,7 @@ inline void generate_double_push(std::vector& moves, Square from, Color us Bitboard single_bb = single_push[from.flat_index()] & occupied; if (single_bb.empty() && bb) { Square to = bb.pop_lsb().value(); - moves.emplace_back(from, to, std::nullopt, false, false, false); + moves.emplace_back(Move::make(from, to)); } } @@ -190,11 +190,12 @@ inline void generate_captures(std::vector& moves, Square from, Color us, c bb &= check_mask; bb &= pin_mask; + constexpr bool is_capture = true; for (Square to : bb) { if (is_promotion_rank(to, us)) { - add_pawn_promotions(moves, from, to, us, true); + add_pawn_promotions(moves, from, to, us, is_capture); } else { - moves.emplace_back(from, to, std::nullopt, true, false, false); + moves.emplace_back(Move::make(from, to, is_capture)); } } } @@ -241,7 +242,7 @@ inline void generate_en_passant(std::vector& moves, Square from, Color us, Bitboard attackers = generate_attacks(tmp, them); if (!attackers.test(king_sq)) { - moves.emplace_back(from, epsq, std::nullopt, true, true, false); + moves.emplace_back(Move::make_en_passant(from, epsq)); } } diff --git a/include/bitbishop/movegen/queen_moves.hpp b/include/bitbishop/movegen/queen_moves.hpp index 156692a..790683e 100644 --- a/include/bitbishop/movegen/queen_moves.hpp +++ b/include/bitbishop/movegen/queen_moves.hpp @@ -65,7 +65,7 @@ void generate_queen_legal_moves(std::vector& moves, const Board& board, Co for (Square to : candidates) { const bool is_capture = enemy.test(to); - moves.emplace_back(from, to, std::nullopt, is_capture, false, false); + moves.emplace_back(Move::make(from, to, is_capture)); } } } diff --git a/include/bitbishop/movegen/rook_moves.hpp b/include/bitbishop/movegen/rook_moves.hpp index d5a45c5..533f01b 100644 --- a/include/bitbishop/movegen/rook_moves.hpp +++ b/include/bitbishop/movegen/rook_moves.hpp @@ -65,7 +65,7 @@ void generate_rook_legal_moves(std::vector& moves, const Board& board, Col for (Square to : candidates) { const bool is_capture = enemy.test(to); - moves.emplace_back(from, to, std::nullopt, is_capture, false, false); + moves.emplace_back(Move::make(from, to, is_capture)); } } } diff --git a/tests/bitbishop/movegen/test_bishop_moves.cpp b/tests/bitbishop/movegen/test_bishop_moves.cpp index cb6ee81..5309e7f 100644 --- a/tests/bitbishop/movegen/test_bishop_moves.cpp +++ b/tests/bitbishop/movegen/test_bishop_moves.cpp @@ -410,7 +410,7 @@ TEST(GenerateBishopLegalMovesTest, MovesVectorAccumulates) { std::vector moves; // Add a dummy move first - moves.emplace_back(A1, A2, std::nullopt, false, false, false); + moves.emplace_back(Move::make(A1, A2)); Bitboard check_mask = Bitboard::Ones(); PinResult pins; diff --git a/tests/bitbishop/movegen/test_castling_moves.cpp b/tests/bitbishop/movegen/test_castling_moves.cpp index 1804843..59f12c8 100644 --- a/tests/bitbishop/movegen/test_castling_moves.cpp +++ b/tests/bitbishop/movegen/test_castling_moves.cpp @@ -398,7 +398,7 @@ TEST(GenerateCastlingMovesTest, MovesVectorAccumulates) { Board board("r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"); std::vector moves; - moves.emplace_back(A1, A2, std::nullopt, false, false, false); + moves.emplace_back(Move::make(A1, A2)); Bitboard checkers = Bitboard::Zeros(); Bitboard enemy_attacks = Bitboard::Zeros(); diff --git a/tests/bitbishop/movegen/test_king_moves.cpp b/tests/bitbishop/movegen/test_king_moves.cpp index 160a16b..8855d64 100644 --- a/tests/bitbishop/movegen/test_king_moves.cpp +++ b/tests/bitbishop/movegen/test_king_moves.cpp @@ -54,9 +54,9 @@ TEST(GenerateLegalKingMovesTest, CornerKingLimitedMoves) { EXPECT_EQ(moves.size(), 3); // Corner king can only move to 3 squares - EXPECT_TRUE(contains_move(moves, {A1, A2, std::nullopt, false, false, false})); - EXPECT_TRUE(contains_move(moves, {A1, B1, std::nullopt, false, false, false})); - EXPECT_TRUE(contains_move(moves, {A1, B2, std::nullopt, false, false, false})); + EXPECT_TRUE(contains_move(moves, Move::make(A1, A2))); + EXPECT_TRUE(contains_move(moves, Move::make(A1, B1))); + EXPECT_TRUE(contains_move(moves, Move::make(A1, B2))); } /** @@ -352,7 +352,7 @@ TEST(GenerateLegalKingMovesTest, MovesVectorAccumulates) { std::vector moves; // Add a dummy move first - moves.emplace_back(A1, A2, std::nullopt, false, false, false); + moves.emplace_back(Move::make(A1, A2)); Bitboard enemy_attacks = Bitboard::Zeros(); @@ -360,7 +360,7 @@ TEST(GenerateLegalKingMovesTest, MovesVectorAccumulates) { // Should have 1 dummy + 8 king moves = 9 total EXPECT_EQ(moves.size(), 9); - EXPECT_TRUE(contains_move(moves, {A1, A2, std::nullopt, false, false, false})); + EXPECT_TRUE(contains_move(moves, Move::make(A1, A2))); } /** diff --git a/tests/bitbishop/movegen/test_knight_moves.cpp b/tests/bitbishop/movegen/test_knight_moves.cpp index eeaee36..88658b0 100644 --- a/tests/bitbishop/movegen/test_knight_moves.cpp +++ b/tests/bitbishop/movegen/test_knight_moves.cpp @@ -378,7 +378,7 @@ TEST(GenerateKnightLegalMovesTest, MovesVectorAccumulates) { std::vector moves; // Add a dummy move first - moves.emplace_back(A1, A2, std::nullopt, false, false, false); + moves.emplace_back(Move::make(A1, A2)); Bitboard check_mask = Bitboard::Ones(); PinResult pins; diff --git a/tests/bitbishop/movegen/test_legal_moves.cpp b/tests/bitbishop/movegen/test_legal_moves.cpp index 9dd8854..4d5b5cd 100644 --- a/tests/bitbishop/movegen/test_legal_moves.cpp +++ b/tests/bitbishop/movegen/test_legal_moves.cpp @@ -271,13 +271,13 @@ TEST(GenerateLegalMovesTest, MovesVectorNotCleared) { board.set_piece(E8, BLACK_KING); std::vector moves; - moves.emplace_back(A1, A2, std::nullopt, false, false, false); + moves.emplace_back(Move::make(A1, A2)); size_t initial_size = moves.size(); generate_legal_moves(moves, board, Color::WHITE); EXPECT_GT(moves.size(), initial_size); - EXPECT_TRUE(contains_move(moves, {A1, A2, std::nullopt, false, false, false})); + EXPECT_TRUE(contains_move(moves, Move::make(A1, A2))); } /** diff --git a/tests/bitbishop/movegen/test_pawn_moves/test_generate_pawn_legal_moves.cpp b/tests/bitbishop/movegen/test_pawn_moves/test_generate_pawn_legal_moves.cpp index a79cf94..42e656a 100644 --- a/tests/bitbishop/movegen/test_pawn_moves/test_generate_pawn_legal_moves.cpp +++ b/tests/bitbishop/movegen/test_pawn_moves/test_generate_pawn_legal_moves.cpp @@ -471,7 +471,7 @@ TEST(GeneratePawnLegalMovesTest, MovesVectorAccumulates) { board.set_piece(E2, WHITE_PAWN); std::vector moves; - moves.emplace_back(A1, A2, std::nullopt, false, false, false); + moves.emplace_back(Move::make(A1, A2)); Bitboard check_mask = Bitboard::Ones(); PinResult pins; @@ -479,7 +479,7 @@ TEST(GeneratePawnLegalMovesTest, MovesVectorAccumulates) { generate_pawn_legal_moves(moves, board, Color::WHITE, E1, check_mask, pins); EXPECT_EQ(moves.size(), 3); - EXPECT_TRUE(contains_move(moves, {A1, A2, std::nullopt, false, false, false})); + EXPECT_TRUE(contains_move(moves, Move::make(A1, A2))); } /** diff --git a/tests/bitbishop/movegen/test_queen_moves.cpp b/tests/bitbishop/movegen/test_queen_moves.cpp index 9bfd1bb..c081345 100644 --- a/tests/bitbishop/movegen/test_queen_moves.cpp +++ b/tests/bitbishop/movegen/test_queen_moves.cpp @@ -390,7 +390,7 @@ TEST(GenerateQueenLegalMovesTest, MovesVectorAccumulates) { std::vector moves; // Add a dummy move first - moves.emplace_back(A1, A2, std::nullopt, false, false, false); + moves.emplace_back(Move::make(A1, A2)); Bitboard check_mask = Bitboard::Ones(); PinResult pins; @@ -399,7 +399,7 @@ TEST(GenerateQueenLegalMovesTest, MovesVectorAccumulates) { // Should have 1 dummy + 27 queen moves = 28 total EXPECT_EQ(moves.size(), 28); - EXPECT_TRUE(contains_move(moves, {A1, A2, std::nullopt, false, false, false})); + EXPECT_TRUE(contains_move(moves, Move::make(A1, A2))); } /** diff --git a/tests/bitbishop/movegen/test_rook_moves.cpp b/tests/bitbishop/movegen/test_rook_moves.cpp index be97013..75122ae 100644 --- a/tests/bitbishop/movegen/test_rook_moves.cpp +++ b/tests/bitbishop/movegen/test_rook_moves.cpp @@ -404,7 +404,7 @@ TEST(GenerateRookLegalMovesTest, MovesVectorAccumulates) { std::vector moves; // Add a dummy move first - moves.emplace_back(A1, A2, std::nullopt, false, false, false); + moves.emplace_back(Move::make(A1, A2)); Bitboard check_mask = Bitboard::Ones(); PinResult pins; @@ -413,7 +413,7 @@ TEST(GenerateRookLegalMovesTest, MovesVectorAccumulates) { // Should have 1 dummy + 14 rook moves = 15 total EXPECT_EQ(moves.size(), 15); - EXPECT_TRUE(contains_move(moves, {A1, A2, std::nullopt, false, false, false})); + EXPECT_TRUE(contains_move(moves, Move::make(A1, A2))); } /** diff --git a/tests/bitbishop/test_move.cpp b/tests/bitbishop/test_move.cpp new file mode 100644 index 0000000..7f40d53 --- /dev/null +++ b/tests/bitbishop/test_move.cpp @@ -0,0 +1,92 @@ +#include + +#include + +using namespace Squares; +using namespace Pieces; + +TEST(MoveTest, NormalMove_NoCapture) { + Square from = E2; + Square to = E4; + + Move m = Move::make(from, to); + + EXPECT_EQ(m.from, from); + EXPECT_EQ(m.to, to); + EXPECT_FALSE(m.promotion.has_value()); + EXPECT_FALSE(m.is_capture); + EXPECT_FALSE(m.is_en_passant); + EXPECT_FALSE(m.is_castling); +} + +TEST(MoveTest, NormalMove_WithCapture) { + Square from = D4; + Square to = E5; + + Move m = Move::make(from, to, true); + + EXPECT_EQ(m.from, from); + EXPECT_EQ(m.to, to); + EXPECT_FALSE(m.promotion.has_value()); + EXPECT_TRUE(m.is_capture); + EXPECT_FALSE(m.is_en_passant); + EXPECT_FALSE(m.is_castling); +} + +TEST(MoveTest, PromotionMove_NoCapture) { + Square from = E7; + Square to = E8; + + Move m = Move::make_promotion(from, to, WHITE_QUEEN); + + EXPECT_EQ(m.from, from); + EXPECT_EQ(m.to, to); + ASSERT_TRUE(m.promotion.has_value()); + EXPECT_EQ(m.promotion.value(), WHITE_QUEEN); + EXPECT_FALSE(m.is_capture); + EXPECT_FALSE(m.is_en_passant); + EXPECT_FALSE(m.is_castling); +} + +TEST(MoveTest, PromotionMove_WithCapture) { + Square from = D7; + Square to = E8; + + Move m = Move::make_promotion(from, to, BLACK_KNIGHT, true); + + EXPECT_EQ(m.from, from); + EXPECT_EQ(m.to, to); + ASSERT_TRUE(m.promotion.has_value()); + EXPECT_EQ(m.promotion.value(), BLACK_KNIGHT); + EXPECT_TRUE(m.is_capture); + EXPECT_FALSE(m.is_en_passant); + EXPECT_FALSE(m.is_castling); +} + +TEST(MoveTest, EnPassantMove) { + Square from = E5; + Square to = D6; + + Move m = Move::make_en_passant(from, to); + + EXPECT_EQ(m.from, from); + EXPECT_EQ(m.to, to); + EXPECT_FALSE(m.promotion.has_value()); + EXPECT_TRUE(m.is_capture); + EXPECT_TRUE(m.is_en_passant); + EXPECT_FALSE(m.is_castling); +} + +TEST(MoveTest, CastlingMove) { + Square from = E1; + Square to = G1; // kingside castling + + Move m = Move::make_castling(from, to); + + EXPECT_EQ(m.from, from); + EXPECT_EQ(m.to, to); + EXPECT_FALSE(m.promotion.has_value()); + EXPECT_FALSE(m.is_capture); + EXPECT_FALSE(m.is_en_passant); + EXPECT_TRUE(m.is_castling); +}