diff --git a/position/src/fen.rs b/position/src/fen.rs index dd9fa29..fab45e1 100644 --- a/position/src/fen.rs +++ b/position/src/fen.rs @@ -1,7 +1,8 @@ // Eryn Wells -use crate::{r#move::Castle, Position, PositionBuilder}; +use crate::{Position, PositionBuilder}; use chessfriend_core::{piece, Color, File, Piece, PlacedPiece, Rank, Square}; +use chessfriend_moves::Castle; use std::fmt::Write; macro_rules! fen { diff --git a/position/src/lib.rs b/position/src/lib.rs index d313231..1995be0 100644 --- a/position/src/lib.rs +++ b/position/src/lib.rs @@ -4,7 +4,6 @@ pub mod fen; mod check; mod display; -mod r#move; mod move_generator; mod position; mod sight; @@ -12,8 +11,8 @@ mod sight; #[macro_use] mod macros; +#[cfg(test)] #[macro_use] mod testing; -pub use position::{MoveBuilder as MakeMoveBuilder, Position, PositionBuilder}; -pub use r#move::{Castle, MakeMoveError, Move, MoveBuilder}; +pub use position::{MakeMoveError, MoveBuilder as MakeMoveBuilder, Position, PositionBuilder}; diff --git a/position/src/move_generator.rs b/position/src/move_generator.rs index c9d4b76..4d14ece 100644 --- a/position/src/move_generator.rs +++ b/position/src/move_generator.rs @@ -7,6 +7,8 @@ mod move_set; mod pawn; mod queen; mod rook; + +#[cfg(test)] mod tests; pub(crate) use move_set::MoveSet; @@ -17,9 +19,10 @@ use self::{ queen::ClassicalMoveGenerator as QueenMoveGenerator, rook::ClassicalMoveGenerator as RookMoveGenerator, }; -use crate::{Move, Position}; +use crate::Position; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; +use chessfriend_moves::Move; use std::collections::BTreeMap; trait MoveGenerator { @@ -66,7 +69,7 @@ macro_rules! move_generator_declaration { }; ($name:ident, getters) => { impl $name { - pub(super) fn iter(&self) -> impl Iterator + '_ { + pub(super) fn iter(&self) -> impl Iterator + '_ { self.move_sets.values().flat_map(|set| set.moves()) } diff --git a/position/src/move_generator/king.rs b/position/src/move_generator/king.rs index 1813339..45c928e 100644 --- a/position/src/move_generator/king.rs +++ b/position/src/move_generator/king.rs @@ -58,13 +58,14 @@ impl MoveGeneratorInternal for KingMoveGenerator { #[cfg(test)] mod tests { use super::*; - use crate::{assert_move_list, position, test_position, Move, MoveBuilder, PositionBuilder}; + use crate::{assert_move_list, position, test_position, testing::*, PositionBuilder}; use chessfriend_bitboard::bitboard; use chessfriend_core::{piece, Color, Square}; + use chessfriend_moves::{Builder as MoveBuilder, Castle, Move}; use std::collections::HashSet; #[test] - fn one_king() { + fn one_king() -> TestResult { let pos = position![White King on E4]; let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); @@ -74,24 +75,27 @@ mod tests { bitboard![E5, F5, F4, F3, E3, D3, D4, D5] ); + let builder = MoveBuilder::push(&piece!(White King on E4)); let expected_moves: HashSet = HashSet::from_iter([ - MoveBuilder::new(piece!(White King), Square::E4, Square::D5).build(), - MoveBuilder::new(piece!(White King), Square::E4, Square::E5).build(), - MoveBuilder::new(piece!(White King), Square::E4, Square::F5).build(), - MoveBuilder::new(piece!(White King), Square::E4, Square::F4).build(), - MoveBuilder::new(piece!(White King), Square::E4, Square::F3).build(), - MoveBuilder::new(piece!(White King), Square::E4, Square::E3).build(), - MoveBuilder::new(piece!(White King), Square::E4, Square::D3).build(), - MoveBuilder::new(piece!(White King), Square::E4, Square::D4).build(), + builder.clone().to(Square::D5).build()?, + builder.clone().to(Square::E5).build()?, + builder.clone().to(Square::F5).build()?, + builder.clone().to(Square::F4).build()?, + builder.clone().to(Square::F3).build()?, + builder.clone().to(Square::E3).build()?, + builder.clone().to(Square::D3).build()?, + builder.clone().to(Square::D4).build()?, ]); let generated_moves: HashSet = generator.iter().collect(); assert_move_list!(generated_moves, expected_moves, pos); + + Ok(()) } #[test] - fn one_king_corner() { + fn one_king_corner() -> TestResult { let pos = position![White King on A1]; let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); @@ -104,13 +108,14 @@ mod tests { "Generated:\n{generated_bitboard}\nExpected:\n{expected_bitboard}" ); + let builder = MoveBuilder::push(&piece!(White King on A1)); let expected_moves = [ - MoveBuilder::new(piece!(White King), Square::A1, Square::A2).build(), - MoveBuilder::new(piece!(White King), Square::A1, Square::B1).build(), - MoveBuilder::new(piece!(White King), Square::A1, Square::B2).build(), + builder.clone().to(Square::A2).build()?, + builder.clone().to(Square::B1).build()?, + builder.clone().to(Square::B2).build()?, ]; - let mut generated_moves: HashSet = generator.iter().collect(); + let mut generated_moves: HashSet<_> = generator.iter().collect(); for ex_move in expected_moves { assert!( @@ -125,6 +130,8 @@ mod tests { "Moves unexpectedly present: {:#?}", generated_moves ); + + Ok(()) } #[test] @@ -161,16 +168,8 @@ mod tests { let generated_moves: HashSet = generator.iter().collect(); let king = piece!(White King); - assert!(generated_moves.contains( - &MoveBuilder::new(king, Square::E1, Square::G1) - .castle(Castle::KingSide) - .build() - )); - assert!(generated_moves.contains( - &MoveBuilder::new(king, Square::E1, Square::C1) - .castle(Castle::QueenSide) - .build() - )); + assert!(generated_moves.contains(&MoveBuilder::castling(Castle::KingSide).build())); + assert!(generated_moves.contains(&MoveBuilder::castling(Castle::QueenSide).build())); } #[test] @@ -189,16 +188,8 @@ mod tests { let generated_moves: HashSet = generator.iter().collect(); let king = piece!(White King); - assert!(generated_moves.contains( - &MoveBuilder::new(king, Square::E1, Square::G1) - .castle(Castle::KingSide) - .build() - )); - assert!(!generated_moves.contains( - &MoveBuilder::new(king, Square::E1, Square::C1) - .castle(Castle::QueenSide) - .build() - )); + assert!(generated_moves.contains(&MoveBuilder::castling(Castle::KingSide).build())); + assert!(!generated_moves.contains(&MoveBuilder::castling(Castle::QueenSide).build())); } #[test] @@ -217,15 +208,7 @@ mod tests { let generated_moves: HashSet = generator.iter().collect(); let king = piece!(White King); - assert!(!generated_moves.contains( - &MoveBuilder::new(king, Square::E1, Square::G1) - .castle(Castle::KingSide) - .build() - )); - assert!(generated_moves.contains( - &MoveBuilder::new(king, Square::E1, Square::C1) - .castle(Castle::QueenSide) - .build() - )); + assert!(!generated_moves.contains(&MoveBuilder::castling(Castle::KingSide).build())); + assert!(generated_moves.contains(&MoveBuilder::castling(Castle::QueenSide).build())); } } diff --git a/position/src/move_generator/knight.rs b/position/src/move_generator/knight.rs index 6366808..2b385ec 100644 --- a/position/src/move_generator/knight.rs +++ b/position/src/move_generator/knight.rs @@ -34,54 +34,35 @@ impl MoveGeneratorInternal for KnightMoveGenerator { #[cfg(test)] mod tests { use super::*; - use crate::{position, Move, MoveBuilder}; + use crate::{assert_move_list, position, testing::*}; use chessfriend_core::{piece, Color, Square}; + use chessfriend_moves::Builder as MoveBuilder; use std::collections::HashSet; #[test] - fn one_knight() { + fn one_knight() -> TestResult { let pos = position![ White Knight on E4, ]; let generator = KnightMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generated_moves: HashSet<_> = generator.iter().collect(); - /* - let bb = generator.bitboard(); - assert_eq!( - bb, - BitBoard::new( - 0b00000000_00000000_00000000_00111000_00101000_00111000_00000000_00000000 - ) - ); - */ + let piece = piece!(White Knight on E4); + let expected_moves = HashSet::from_iter([ + MoveBuilder::push(&piece).to(Square::C3).build()?, + MoveBuilder::push(&piece).to(Square::D2).build()?, + MoveBuilder::push(&piece).to(Square::F2).build()?, + MoveBuilder::push(&piece).to(Square::G3).build()?, + MoveBuilder::push(&piece).to(Square::C5).build()?, + MoveBuilder::push(&piece).to(Square::D6).build()?, + MoveBuilder::push(&piece).to(Square::G5).build()?, + MoveBuilder::push(&piece).to(Square::F6).build()?, + ]); - let expected_moves = [ - MoveBuilder::new(piece!(White Knight), Square::E4, Square::C3).build(), - MoveBuilder::new(piece!(White Knight), Square::E4, Square::D2).build(), - MoveBuilder::new(piece!(White Knight), Square::E4, Square::F2).build(), - MoveBuilder::new(piece!(White Knight), Square::E4, Square::G3).build(), - MoveBuilder::new(piece!(White Knight), Square::E4, Square::C5).build(), - MoveBuilder::new(piece!(White Knight), Square::E4, Square::D6).build(), - MoveBuilder::new(piece!(White Knight), Square::E4, Square::G5).build(), - MoveBuilder::new(piece!(White Knight), Square::E4, Square::F6).build(), - ]; + assert_move_list!(generated_moves, expected_moves, pos); - let mut generated_moves: HashSet = generator.iter().collect(); - - for ex_move in expected_moves { - assert!( - generated_moves.remove(&ex_move), - "{:#?} was not generated", - &ex_move - ); - } - - assert!( - generated_moves.is_empty(), - "Moves unexpectedly present: {:#?}", - generated_moves - ); + Ok(()) } } diff --git a/position/src/move_generator/move_set.rs b/position/src/move_generator/move_set.rs index d03eaef..f1042c9 100644 --- a/position/src/move_generator/move_set.rs +++ b/position/src/move_generator/move_set.rs @@ -1,7 +1,7 @@ // Eryn Wells use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; +use chessfriend_core::{PlacedPiece, Square}; use chessfriend_moves::{Builder as MoveBuilder, Castle, Move}; /// A set of bitboards defining the moves for a single piece on the board. @@ -65,57 +65,34 @@ impl MoveSet { } pub(crate) fn moves(&self) -> impl Iterator + '_ { - let piece = self.piece.piece(); - let from_square = self.piece.square(); + let piece = &self.piece; self.bitboards .quiet .occupied_squares() - .map(move |to_square| MoveBuilder::new(*piece, from_square, to_square).build()) + .filter_map(|to_square| MoveBuilder::push(&self.piece).to(to_square).build().ok()) .chain( self.bitboards .captures .occupied_squares() - .map(move |to_square| { - MoveBuilder::new(*piece, from_square, to_square) - .capturing(PlacedPiece::new( - Piece::new(Color::White, Shape::Pawn), - to_square, - )) - .build() - }), + .filter_map(|to_square| MoveBuilder::push(piece).to(to_square).build().ok()), ) .chain( if (self.special & 0b1) != 0 { - Some(()) + let mv = MoveBuilder::castling(Castle::KingSide).build(); + Some(mv) } else { None } - .map(|()| { - MoveBuilder::new( - *piece, - from_square, - Castle::KingSide.target_squares(piece.color()).king, - ) - .castle(Castle::KingSide) - .build() - }), + .into_iter(), ) .chain( if (self.special & 0b10) != 0 { - Some(()) + Some(MoveBuilder::castling(Castle::QueenSide).build()) } else { None } - .map(|()| { - MoveBuilder::new( - *piece, - from_square, - Castle::QueenSide.target_squares(piece.color()).king, - ) - .castle(Castle::QueenSide) - .build() - }), + .into_iter(), ) } } diff --git a/position/src/move_generator/pawn.rs b/position/src/move_generator/pawn.rs index 1aa7a4d..67fefb3 100644 --- a/position/src/move_generator/pawn.rs +++ b/position/src/move_generator/pawn.rs @@ -1,9 +1,10 @@ // Eryn Wells use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; -use crate::{r#move::Move, MoveBuilder, Position}; +use crate::Position; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square}; +use chessfriend_moves::{Builder as MoveBuilder, Move}; use std::collections::BTreeMap; #[derive(Debug)] @@ -65,7 +66,7 @@ impl PawnMoveGenerator { push_mask: BitBoard, ) -> BTreeMap { let piece = Self::piece(color); - let mut moves_for_pieces = + let moves_for_pieces = BTreeMap::from_iter(position.bitboard_for_piece(piece).occupied_squares().map( |square| { let piece = PlacedPiece::new(piece, square); @@ -75,10 +76,6 @@ impl PawnMoveGenerator { }, )); - if position.has_en_passant_square() { - - } - moves_for_pieces } @@ -121,23 +118,21 @@ impl PawnMoveGenerator { fn en_passant_attack(position: &Position, piece: &PlacedPiece) -> Option { match position.en_passant() { Some(en_passant) => { - let en_passant_bitboard: BitBoard = en_passant.target_square().into(); + let target_square = en_passant.target_square(); + + let en_passant_bitboard: BitBoard = target_square.into(); let capture = BitBoard::pawn_attacks(piece.square(), piece.color()) & en_passant_bitboard; - if capture.is_empty() { return None; } match position.piece_on_square(en_passant.capture_square()) { - Some(captured_piece) => Some( - MoveBuilder::new( - *piece.piece(), - piece.square(), - en_passant.target_square(), - ) - .capturing_en_passant(captured_piece) - .build(), + Some(_) => Some( + MoveBuilder::push(piece) + .capturing_en_passant_on(target_square) + .build() + .ok()?, ), None => None, } @@ -150,46 +145,48 @@ impl PawnMoveGenerator { #[cfg(test)] mod tests { use super::*; - use crate::{assert_move_list, position::DiagramFormatter, test_position, Move, MoveBuilder}; - use chessfriend_core::{piece, Color, Piece, Square}; + use crate::{assert_move_list, position::DiagramFormatter, test_position, testing::*}; + use chessfriend_core::{piece, Color, Square}; + use chessfriend_moves::{Builder as MoveBuilder, Move}; use std::collections::HashSet; #[test] - fn one_2square_push() { + fn one_double_push() -> TestResult { let pos = test_position![White Pawn on E2]; let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let pawn = piece!(White Pawn on E2); let expected_moves = HashSet::from_iter([ - MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E3).build(), - MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build(), + MoveBuilder::push(&pawn).to(Square::E3).build()?, + MoveBuilder::double_push(pawn.square().file(), pawn.color()).build()?, ]); - let generated_moves: HashSet = generator.iter().collect(); + let generated_moves: HashSet<_> = generator.iter().collect(); assert_eq!(generated_moves, expected_moves); + + Ok(()) } #[test] - fn one_1square_push() { + fn one_single_push() -> TestResult { let pos = test_position![White Pawn on E3]; let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generated_moves: HashSet<_> = generator.iter().collect(); - let expected_moves = HashSet::from_iter([MoveBuilder::new( - Piece::pawn(Color::White), - Square::E3, - Square::E4, - ) - .build()]); - - let generated_moves: HashSet = generator.iter().collect(); + let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E3)) + .to(Square::E4) + .build()?]); assert_move_list!(generated_moves, expected_moves, pos); + + Ok(()) } #[test] - fn one_obstructed_2square_push() { + fn one_obstructed_2square_push() -> TestResult { let pos = test_position![ White Pawn on E2, White Knight on E4, @@ -199,16 +196,15 @@ mod tests { let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); - let expected_moves = HashSet::from_iter([MoveBuilder::new( - Piece::pawn(Color::White), - Square::E2, - Square::E3, - ) - .build()]); + let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E2)) + .to(Square::E3) + .build()?]); - let generated_moves: HashSet = generator.iter().collect(); + let generated_moves: HashSet<_> = generator.iter().collect(); assert_move_list!(generated_moves, expected_moves, pos); + + Ok(()) } #[test] @@ -220,14 +216,14 @@ mod tests { let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); - let generated_moves: HashSet = generator.iter().collect(); - let expected_moves: HashSet = HashSet::new(); + let generated_moves: HashSet<_> = generator.iter().collect(); + let expected_moves: HashSet<_> = HashSet::new(); assert_move_list!(generated_moves, expected_moves, pos); } #[test] - fn one_attack() { + fn one_attack() -> TestResult { let pos = test_position![ White Pawn on E4, White Bishop on E5, @@ -236,20 +232,19 @@ mod tests { let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); - let expected_moves = - HashSet::from_iter( - [MoveBuilder::new(piece!(White Pawn), Square::E4, Square::D5) - .capturing(piece!(Black Knight on D5)) - .build()], - ); + let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E4)) + .capturing_on(Square::D5) + .build()?]); - let generated_moves: HashSet = generator.iter().collect(); + let generated_moves: HashSet<_> = generator.iter().collect(); - assert_eq!(generated_moves, expected_moves); + assert_move_list!(generated_moves, expected_moves, pos); + + Ok(()) } #[test] - fn one_double_attack() { + fn one_double_attack() -> TestResult { let pos = test_position![ White Pawn on E4, White Bishop on E5, @@ -259,21 +254,44 @@ mod tests { let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let builder = MoveBuilder::push(&piece!(White Pawn on E4)); let expected_moves = HashSet::from_iter([ - MoveBuilder::new(piece!(White Pawn), Square::E4, Square::D5) - .capturing(piece!(Black Knight on D5)) - .build(), - MoveBuilder::new(piece!(White Pawn), Square::E4, Square::F5) - .capturing(piece!(Black Queen on F5)) - .build(), + builder.clone().capturing_on(Square::D5).build()?, + builder.clone().capturing_on(Square::F5).build()?, ]); - let generated_moves: HashSet = generator.iter().collect(); + let generated_moves: HashSet<_> = generator.iter().collect(); assert_eq!( generated_moves, expected_moves, "generated: {:#?}\nexpected: {:#?}", generated_moves, expected_moves ); + + Ok(()) + } + + #[test] + fn one_en_passant_attack() -> TestResult { + let pos = test_position!(Black, [ + White Pawn on D4, + Black Pawn on E4, + ], D3); + + let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generated_moves: HashSet = generator.iter().collect(); + + let builder = MoveBuilder::push(&piece!(Black Pawn on E4)); + let expected_moves = HashSet::from_iter([ + builder + .clone() + .capturing_en_passant_on(Square::D3) + .build()?, + builder.clone().to(Square::E3).build()?, + ]); + + assert_move_list!(generated_moves, expected_moves, pos); + + Ok(()) } } diff --git a/position/src/move_generator/tests/peterellisjones.rs b/position/src/move_generator/tests/peterellisjones.rs index 3828970..31a878a 100644 --- a/position/src/move_generator/tests/peterellisjones.rs +++ b/position/src/move_generator/tests/peterellisjones.rs @@ -6,15 +6,13 @@ //! [1]: https://peterellisjones.com //! [2]: https://peterellisjones.com/posts/generating-legal-chess-moves-efficiently/ -use crate::{ - assert_move_list, formatted_move_list, move_generator::Moves, r#move::AlgebraicMoveFormatter, - test_position, Move, MoveBuilder, -}; +use crate::{assert_move_list, formatted_move_list, test_position, testing::*}; use chessfriend_core::{piece, Square}; +use chessfriend_moves::Builder as MoveBuilder; use std::collections::HashSet; #[test] -fn pseudo_legal_move_generation() -> Result<(), String> { +fn pseudo_legal_move_generation() -> TestResult { let pos = test_position!(Black, [ Black King on E8, White King on E1, @@ -24,7 +22,7 @@ fn pseudo_legal_move_generation() -> Result<(), String> { let generated_moves = pos.moves(); let king_moves = generated_moves .moves_for_piece(&piece!(Black King on E8)) - .ok_or("No valid king moves")?; + .ok_or(TestError::NoLegalMoves)?; assert!(!king_moves.can_move_to_square(Square::F8)); assert!(!king_moves.can_move_to_square(Square::F7)); @@ -33,7 +31,7 @@ fn pseudo_legal_move_generation() -> Result<(), String> { } #[test] -fn gotcha_king_moves_away_from_a_checking_slider() -> Result<(), String> { +fn gotcha_king_moves_away_from_a_checking_slider() -> TestResult { let pos = test_position!(Black, [ Black King on E7, White King on E1, @@ -43,7 +41,7 @@ fn gotcha_king_moves_away_from_a_checking_slider() -> Result<(), String> { let generated_moves = pos.moves(); let king_moves = generated_moves .moves_for_piece(&piece!(Black King on E7)) - .ok_or("No valid king moves")?; + .ok_or(TestError::NoLegalMoves)?; assert!(!king_moves.can_move_to_square(Square::E8)); @@ -51,7 +49,7 @@ fn gotcha_king_moves_away_from_a_checking_slider() -> Result<(), String> { } #[test] -fn check_evasions_1() { +fn check_evasions_1() -> TestResult { let pos = test_position!(Black, [ Black King on E8, White King on E1, @@ -60,11 +58,12 @@ fn check_evasions_1() { let generated_moves = pos.moves(); + let builder = MoveBuilder::push(&piece!(Black King on E8)); let expected_moves = HashSet::from_iter([ - MoveBuilder::new(piece!(Black King), Square::E8, Square::D8).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::E7).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::F7).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::F8).build(), + builder.clone().to(Square::D8).build()?, + builder.clone().to(Square::E7).build()?, + builder.clone().to(Square::F7).build()?, + builder.clone().to(Square::F8).build()?, ]); assert_move_list!( @@ -72,10 +71,12 @@ fn check_evasions_1() { expected_moves, pos ); + + Ok(()) } #[test] -fn check_evasions_double_check() { +fn check_evasions_double_check() -> TestResult { let pos = test_position!(Black, [ Black King on E8, Black Bishop on F6, @@ -86,11 +87,12 @@ fn check_evasions_double_check() { let generated_moves = pos.moves(); + let builder = MoveBuilder::push(&piece!(Black King on E8)); let expected_moves = HashSet::from_iter([ - MoveBuilder::new(piece!(Black King), Square::E8, Square::D8).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::D7).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::F7).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::F8).build(), + builder.clone().to(Square::D8).build()?, + builder.clone().to(Square::D7).build()?, + builder.clone().to(Square::F7).build()?, + builder.clone().to(Square::F8).build()?, ]); assert_move_list!( @@ -98,10 +100,12 @@ fn check_evasions_double_check() { expected_moves, pos ); + + Ok(()) } #[test] -fn single_check_with_blocker() { +fn single_check_with_blocker() -> TestResult { let pos = test_position!(Black, [ Black King on E8, Black Knight on G6, @@ -111,15 +115,15 @@ fn single_check_with_blocker() { let generated_moves = pos.moves(); + let king_builder = MoveBuilder::push(&piece!(Black King on E8)); + let knight_builder = MoveBuilder::push(&piece!(Black Knight on G6)); let expected_moves = HashSet::from_iter([ - MoveBuilder::new(piece!(Black King), Square::E8, Square::D8).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::D7).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::F7).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::F8).build(), - MoveBuilder::new(piece!(Black Knight), Square::G6, Square::E7).build(), - MoveBuilder::new(piece!(Black Knight), Square::G6, Square::E5) - .capturing(piece!(White Rook on E5)) - .build(), + king_builder.clone().to(Square::D8).build()?, + king_builder.clone().to(Square::D7).build()?, + king_builder.clone().to(Square::F7).build()?, + king_builder.clone().to(Square::F8).build()?, + knight_builder.clone().to(Square::E7).build()?, + knight_builder.clone().capturing_on(Square::E5).build()?, ]); assert_move_list!( @@ -127,10 +131,12 @@ fn single_check_with_blocker() { expected_moves, pos ); + + Ok(()) } #[test] -fn en_passant_check_capture() { +fn en_passant_check_capture() -> TestResult { let pos = test_position!(Black, [ Black King on C5, Black Pawn on E4, @@ -143,17 +149,19 @@ fn en_passant_check_capture() { assert!( generated_moves.contains( - &MoveBuilder::new(piece!(Black Pawn), Square::E4, Square::D3) - .capturing_en_passant(piece!(White Pawn on D4)) - .build() + &MoveBuilder::push(&piece!(Black Pawn on E4)) + .capturing_en_passant_on(Square::D4) + .build()? ), "Valid moves: {:?}", formatted_move_list!(generated_moves, pos) ); + + Ok(()) } #[test] -fn en_passant_check_block() { +fn en_passant_check_block() -> TestResult { let pos = test_position!(Black, [ Black King on B5, Black Pawn on E4, @@ -167,13 +175,15 @@ fn en_passant_check_block() { assert!( generated_moves.contains( - &MoveBuilder::new(piece!(Black Pawn), Square::E4, Square::D3) - .capturing_en_passant(piece!(White Pawn on D4)) - .build() + &MoveBuilder::push(&piece!(Black Pawn on E4)) + .capturing_en_passant_on(Square::D4) + .build()? ), "Valid moves: {:?}", formatted_move_list!(generated_moves, pos) ); + + Ok(()) } #[test] @@ -204,7 +214,7 @@ fn pinned_pieces_rook_cannot_move_out_of_pin() -> Result<(), String> { } #[test] -fn en_passant_discovered_check() { +fn en_passant_discovered_check() -> TestResult { let pos = test_position!(Black, [ Black King on A4, Black Pawn on E4, @@ -214,13 +224,15 @@ fn en_passant_discovered_check() { let generated_moves = pos.moves().iter().collect::>(); - let unexpected_move = MoveBuilder::new(piece!(Black Pawn), Square::E4, Square::D3) - .capturing_en_passant(piece!(White Pawn on D4)) - .build(); + let unexpected_move = MoveBuilder::push(&piece!(Black Pawn on E4)) + .capturing_en_passant_on(Square::D4) + .build()?; assert!( !generated_moves.contains(&unexpected_move), "Valid moves: {:?}", formatted_move_list!(generated_moves, pos) ); + + Ok(()) } diff --git a/position/src/move_generator/tests/single_pieces.rs b/position/src/move_generator/tests/single_pieces.rs index 01a3e34..f8d06fb 100644 --- a/position/src/move_generator/tests/single_pieces.rs +++ b/position/src/move_generator/tests/single_pieces.rs @@ -1,36 +1,32 @@ // Eryn Wells -use crate::{position, r#move::AlgebraicMoveFormatter, Move, MoveBuilder}; +use crate::{assert_move_list, test_position, testing::*}; use chessfriend_core::{piece, Square}; +use chessfriend_moves::Builder as MoveBuilder; use std::collections::HashSet; #[test] -fn one_king() { - let pos = position![ +fn one_king() -> TestResult { + let pos = test_position![ White King on D3, Black King on H6, ]; + let builder = MoveBuilder::push(&piece!(White King on D3)); let expected_moves = HashSet::from_iter([ - MoveBuilder::new(piece!(White King), Square::D3, Square::D4).build(), - MoveBuilder::new(piece!(White King), Square::D3, Square::E4).build(), - MoveBuilder::new(piece!(White King), Square::D3, Square::E3).build(), - MoveBuilder::new(piece!(White King), Square::D3, Square::E2).build(), - MoveBuilder::new(piece!(White King), Square::D3, Square::D2).build(), - MoveBuilder::new(piece!(White King), Square::D3, Square::C2).build(), - MoveBuilder::new(piece!(White King), Square::D3, Square::C3).build(), - MoveBuilder::new(piece!(White King), Square::D3, Square::C4).build(), + builder.clone().to(Square::D4).build()?, + builder.clone().to(Square::E4).build()?, + builder.clone().to(Square::E3).build()?, + builder.clone().to(Square::E2).build()?, + builder.clone().to(Square::D2).build()?, + builder.clone().to(Square::C2).build()?, + builder.clone().to(Square::C3).build()?, + builder.clone().to(Square::C4).build()?, ]); - let generated_moves: HashSet = pos.moves().iter().collect(); + let generated_moves: HashSet<_> = pos.moves().iter().collect(); - assert_eq!( - generated_moves, - expected_moves, - "{:?}", - generated_moves - .symmetric_difference(&expected_moves) - .map(|m| format!("{}", AlgebraicMoveFormatter::new(&m, &pos))) - .collect::>() - ); + assert_move_list!(generated_moves, expected_moves, pos); + + Ok(()) } diff --git a/position/src/position/builders/mod.rs b/position/src/position/builders/mod.rs index 9324249..ea1f60d 100644 --- a/position/src/position/builders/mod.rs +++ b/position/src/position/builders/mod.rs @@ -3,5 +3,5 @@ mod move_builder; mod position_builder; -pub use move_builder::Builder as MoveBuilder; +pub use move_builder::{Builder as MoveBuilder, MakeMoveError}; pub use position_builder::Builder as PositionBuilder; diff --git a/position/src/position/builders/move_builder.rs b/position/src/position/builders/move_builder.rs index cb7f0f7..900c322 100644 --- a/position/src/position/builders/move_builder.rs +++ b/position/src/position/builders/move_builder.rs @@ -1,8 +1,19 @@ // Eryn Wells -use crate::{position::flags::Flags, r#move::Castle, MakeMoveError, Move, Position}; +use crate::{position::flags::Flags, Position}; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, Direction, Piece, PlacedPiece, Shape, Square}; +use chessfriend_moves::{Castle, Move}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MakeMoveError { + PlayerOutOfTurn, + NoPiece, + NoCapturedPiece, + NoLegalMoves, + IllegalCastle, + IllegalSquare(Square), +} /// A position builder that builds a new position by making a move. #[derive(Clone)] @@ -51,14 +62,14 @@ where M: MoveToMake, { pub fn make(self, mv: &Move) -> Result, MakeMoveError> { - let from_square = mv.from_square(); + let origin_square = mv.origin_square(); let piece = self .position - .piece_on_square(from_square) + .piece_on_square(origin_square) .ok_or(MakeMoveError::NoPiece)?; - let to_square = mv.to_square(); + let target_square = mv.target_square(); let moves = self .position @@ -72,8 +83,8 @@ where } } None => { - if !moves.can_move_to_square(to_square) { - return Err(MakeMoveError::IllegalSquare(to_square)); + if !moves.can_move_to_square(target_square) { + return Err(MakeMoveError::IllegalSquare(target_square)); } } } @@ -83,8 +94,8 @@ where let captured_piece = if mv.is_en_passant() { // En passant captures the pawn directly ahead (in the player's direction) of the en passant square. let capture_square = match player { - Color::White => to_square.neighbor(Direction::South), - Color::Black => to_square.neighbor(Direction::North), + Color::White => target_square.neighbor(Direction::South), + Color::Black => target_square.neighbor(Direction::North), } .ok_or(MakeMoveError::NoCapturedPiece)?; @@ -96,7 +107,7 @@ where } else if mv.is_capture() { Some( self.position - .piece_on_square(to_square) + .piece_on_square(target_square) .ok_or(MakeMoveError::NoCapturedPiece)?, ) } else { @@ -140,8 +151,8 @@ where } else { let en_passant_square: Option = if mv.is_double_push() { match piece.color() { - Color::White => to_square.neighbor(Direction::South), - Color::Black => to_square.neighbor(Direction::North), + Color::White => target_square.neighbor(Direction::South), + Color::Black => target_square.neighbor(Direction::North), } } else { None @@ -150,8 +161,8 @@ where Ok(Builder { position: self.position, move_to_make: ValidatedMove::RegularMove { - from_square, - to_square, + from_square: origin_square, + to_square: target_square, moving_piece: piece, captured_piece, promotion: mv.promotion(), @@ -219,18 +230,19 @@ impl<'p> Builder<'p, ValidatedMove> { } => { let mut pieces = self.position.piece_bitboards().clone(); - let target_squares = castle.target_squares(player); + let parameters = castle.parameters(player); - let king_from: BitBoard = king.square().into(); - let king_to: BitBoard = target_squares.king.into(); - *pieces.bitboard_for_piece_mut(king.piece()) ^= king_from | king_to; + let king_origin_square: BitBoard = king.square().into(); + let king_target_square: BitBoard = parameters.king_target_square().into(); + *pieces.bitboard_for_piece_mut(king.piece()) ^= + king_origin_square | king_target_square; let rook_from: BitBoard = rook.square().into(); - let rook_to: BitBoard = target_squares.rook.into(); + let rook_to: BitBoard = parameters.rook_target_square().into(); *pieces.bitboard_for_piece_mut(rook.piece()) ^= rook_from | rook_to; *pieces.bitboard_for_color_mut(player) &= - !(king_from | rook_from) | (king_to | rook_to); + !(king_origin_square | rook_from) | (king_target_square | rook_to); Position::new( player.other(), @@ -257,15 +269,24 @@ impl<'p> From<&'p Position> for Builder<'p, NoMove> { #[cfg(test)] mod tests { use super::*; - use crate::{position, r#move::Castle, MoveBuilder, PositionBuilder}; + use crate::testing::*; + use crate::{position, PositionBuilder}; use chessfriend_core::piece; + use chessfriend_moves::Builder as MoveBuilder; #[test] - fn move_white_pawn_one_square() -> Result<(), MakeMoveError> { + fn move_white_pawn_one_square() -> TestResult { let pos = position![White Pawn on E2]; - let mv = MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E3).build(); + let mv = MoveBuilder::new() + .from(Square::E2) + .to(Square::E3) + .build() + .map_err(TestError::BuildMove)?; - let new_position = Builder::::new(&pos).make(&mv)?.build(); + let new_position = Builder::::new(&pos) + .make(&mv) + .map_err(|err| TestError::MakeMove(err))? + .build(); println!("{}", &new_position); assert_eq!( @@ -277,9 +298,13 @@ mod tests { } #[test] - fn move_white_pawn_two_squares() -> Result<(), MakeMoveError> { + fn move_white_pawn_two_squares() -> TestResult { let pos = position![White Pawn on E2]; - let mv = MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build(); + let mv = MoveBuilder::new() + .from(Square::E2) + .to(Square::E4) + .build() + .map_err(TestError::BuildMove)?; let new_position = Builder::::new(&pos).make(&mv)?.build(); println!("{}", &new_position); @@ -308,9 +333,7 @@ mod tests { ]; println!("{}", &pos); - let mv = MoveBuilder::new(piece!(White King), Square::E1, Square::G1) - .castle(Castle::KingSide) - .build(); + let mv = MoveBuilder::castling(Castle::KingSide).build(); let new_position = Builder::::new(&pos).make(&mv)?.build(); println!("{}", &new_position); @@ -328,7 +351,7 @@ mod tests { } #[test] - fn en_passant_capture() -> Result<(), MakeMoveError> { + fn en_passant_capture() -> TestResult { let pos = PositionBuilder::new() .place_piece(piece!(White Pawn on B5)) .place_piece(piece!(Black Pawn on A7)) @@ -336,7 +359,8 @@ mod tests { .build(); println!("{pos}"); - let black_pawn_move = MoveBuilder::new(piece!(Black Pawn), Square::A7, Square::A5).build(); + let black_pawn_move = MoveBuilder::new().from(Square::A7).to(Square::A5).build()?; + assert!(black_pawn_move.is_double_push()); assert!(!black_pawn_move.is_en_passant()); @@ -352,9 +376,9 @@ mod tests { Some(piece!(White Pawn on B5)) ); - let white_pawn_capture = MoveBuilder::new(piece!(White Pawn), Square::B5, Square::A6) - .capturing_en_passant(piece!(Black Pawn on A5)) - .build(); + let white_pawn_capture = MoveBuilder::push(&piece!(White Pawn on B5)) + .capturing_en_passant_on(Square::A5) + .build()?; let en_passant_capture = Builder::::new(&en_passant_position) .make(&white_pawn_capture)? .build(); diff --git a/position/src/position/builders/position_builder.rs b/position/src/position/builders/position_builder.rs index 28652ed..a4f8d2f 100644 --- a/position/src/position/builders/position_builder.rs +++ b/position/src/position/builders/position_builder.rs @@ -120,13 +120,13 @@ impl Builder { for color in Color::ALL { for castle in Castle::ALL { - let starting_squares = castle.starting_squares(color); + let parameters = castle.parameters(color); let has_rook_on_starting_square = self .pieces - .get(&starting_squares.rook) + .get(¶meters.rook_origin_square()) .is_some_and(|piece| piece.shape() == Shape::Rook); let king_is_on_starting_square = - self.kings[color as usize] == Some(starting_squares.king); + self.kings[color as usize] == Some(parameters.king_origin_square()); if !king_is_on_starting_square || !has_rook_on_starting_square { flags.clear_player_has_right_to_castle_flag(color, castle); diff --git a/position/src/position/flags.rs b/position/src/position/flags.rs index 7c722a9..19d3165 100644 --- a/position/src/position/flags.rs +++ b/position/src/position/flags.rs @@ -45,8 +45,6 @@ impl Default for Flags { #[cfg(test)] mod tests { use super::*; - use crate::r#move::Castle; - use chessfriend_core::Color; #[test] fn castle_flags() { diff --git a/position/src/position/mod.rs b/position/src/position/mod.rs index 8ece091..644ccc1 100644 --- a/position/src/position/mod.rs +++ b/position/src/position/mod.rs @@ -9,7 +9,7 @@ mod pieces; mod position; pub use { - builders::{MoveBuilder, PositionBuilder}, + builders::{MakeMoveError, MoveBuilder, PositionBuilder}, diagram_formatter::DiagramFormatter, pieces::Pieces, position::Position, diff --git a/position/src/position/position.rs b/position/src/position/position.rs index bbdcb9e..d336703 100644 --- a/position/src/position/position.rs +++ b/position/src/position/position.rs @@ -372,9 +372,10 @@ impl fmt::Display for Position { #[cfg(test)] mod tests { - use crate::{position, test_position, Castle, Position, PositionBuilder}; + use super::*; + use crate::{position, test_position, Position, PositionBuilder}; use chessfriend_bitboard::bitboard; - use chessfriend_core::{piece, Color, Square}; + use chessfriend_core::piece; #[test] fn piece_on_square() { diff --git a/position/src/testing.rs b/position/src/testing.rs index ba4bd69..1693110 100644 --- a/position/src/testing.rs +++ b/position/src/testing.rs @@ -1,7 +1,7 @@ // Eryn Wells use crate::MakeMoveError; -use chessfriend_moves::{BuildMoveError, BuildMoveResult, Move}; +use chessfriend_moves::BuildMoveError; #[macro_export] macro_rules! assert_move_list {