use eschac::board::*; use eschac::position::*; use eschac::san::*; use eschac::setup::*; static P1: &'static str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -"; static P2: &'static str = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -"; static P3: &'static str = "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -"; static P4: &'static str = "r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq -"; static P5: &'static str = "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ -"; static P6: &'static str = "r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - -"; fn recursive_check_aux(position: Position, depth: usize) { assert_eq!( position, position .as_setup() .to_string() .parse::() .unwrap() .validate() .unwrap(), ); if let Some(passed) = position.pass() { let mut position = position.clone(); position.remove_en_passant_target_square(); assert_eq!(position, passed.pass().unwrap()); } let computed_mirror = { let mut setup = Setup::new(); for square in Square::all() { setup.set( square.mirror(), position.get(square).map(|piece| Piece { role: piece.role, color: !piece.color, }), ); } setup.set_turn(!position.turn()); for color in Color::all() { for side in CastlingSide::all() { setup.set_castling_rights(!color, side, position.castling_rights(color, side)); } } setup.set_en_passant_target_square( position .en_passant_target_square() .map(|square| square.mirror()), ); setup.validate().unwrap() }; let expected_mirror = position.mirror(); assert_eq!(computed_mirror, expected_mirror); assert_eq!(expected_mirror.mirror(), position); match depth.checked_sub(1) { None => (), Some(depth) => { position.legal_moves().into_iter().for_each(|m| { let uci = m.to_uci(); assert_eq!(uci, uci.to_move(&position).unwrap().to_uci()); let san: San = m.to_san(); match san.to_move(&position) { Ok(m) => assert_eq!(san, m.to_san()), Err(err) => { panic!("{san} is {err} on {position:?}") } }; recursive_check_aux(m.make(), depth) }); } } } fn recursive_check(record: &str) { recursive_check_aux(record.parse::().unwrap().validate().unwrap(), 4); } #[test] fn recursive_check_1() { recursive_check(P1); } #[test] fn recursive_check_2() { recursive_check(P2); } #[test] fn recursive_check_3() { recursive_check(P3); } #[test] fn recursive_check_4() { recursive_check(P4); } #[test] fn recursive_check_5() { recursive_check(P5); } #[test] fn recursive_check_6() { recursive_check(P6); } #[test] fn setup() { assert_eq!(Position::new().as_setup().to_string(), P1); assert_eq!(Setup::new().to_string(), "8/8/8/8/8/8/8/8 w - -"); assert_eq!( "8/8/8/8/1Pp5/8/R1k5/K7 w - b3" .parse::() .unwrap() .to_string(), "8/8/8/8/1Pp5/8/R1k5/K7 w - b3", ); for (record, err) in [ ("", ParseSetupError::InvalidBoard), (" w - -", ParseSetupError::InvalidBoard), ("8/8/8/8/8/8/8 w - -", ParseSetupError::InvalidBoard), ("1/1/1/1/1/1/1/1 w - -", ParseSetupError::InvalidBoard), ( "44/44/44/44/44/44/44/44 w - -", ParseSetupError::InvalidBoard, ), ("8/8/8/8/8/8/8/8/8 w - -", ParseSetupError::InvalidBoard), ("p8/8/8/8/8/8/8/8 w - -", ParseSetupError::InvalidBoard), ("8/8/8/8/8/8/8/8 - - - ", ParseSetupError::InvalidTurn), ( "8/8/8/8/8/8/8/8 w QQQQ -", ParseSetupError::InvalidCastlingRights, ), ] { assert_eq!(record.parse::(), Err(err), "{record}"); } for (record, reason) in [ ( "8/8/8/8/8/8/8/8 w KQkq -", IllegalPositionReason::MissingKing, ), ( "3kk3/8/8/8/8/8/8/3KK3 w KQkq -", IllegalPositionReason::TooManyKings, ), ( "4k3/8/8/1Q6/8/8/8/4K3 w - -", IllegalPositionReason::HangingKing, ), ( "4k3/8/3N4/8/8/8/8/4K3 w - -", IllegalPositionReason::HangingKing, ), ( "4k3/8/8/8/8/8/8/4K2R w KQ -", IllegalPositionReason::InvalidCastlingRights, ), ( "4k3/8/8/8/8/8/8/P3K3 w KQ -", IllegalPositionReason::PawnOnBackRank, ), ( "p3k3/8/8/8/8/8/8/4K3 w KQ -", IllegalPositionReason::PawnOnBackRank, ), ( "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq e3", IllegalPositionReason::InvalidEnPassant, ), ( "rnbqkbnr/pppppppp/8/8/8/4P3/PPPP1PPP/RNBQKBNR b KQkq e3", IllegalPositionReason::InvalidEnPassant, ), ( "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e3", IllegalPositionReason::InvalidEnPassant, ), ( "8/8/8/5B2/4P3/3k4/8/4K3 b - e3", IllegalPositionReason::ImpossibleEnPassantPin, ), ( "8/8/8/3k4/4P3/5B2/8/4K3 b - e3", IllegalPositionReason::ImpossibleEnPassantPin, ), ( "rnbqkbnr/pppppppp/8/8/4B3/8/PPPPPPPP/RNBQKBNR b KQkq -", IllegalPositionReason::TooMuchMaterial, ), ( "rnbqkbnr/pppppppp/8/8/8/QBNP4/PPPPPPP1/RNBQKBNR b KQkq -", IllegalPositionReason::TooMuchMaterial, ), ] { assert!( record.parse::().map(|record| record.to_string()) == Ok(record.to_string()), "{record}", ); assert!( record .parse::() .unwrap() .validate() .is_err_and(|e| e.reasons().contains(reason)), "{record} should be invalid because of {reason:?}", ); } for record in [ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPB/RNBQKBNR b KQkq -", "rnbqkbnr/pppppppp/8/8/8/8/NNNNNNNN/RNBQKBNR b KQkq -", "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3", "3kr3/8/8/8/4P3/8/8/4K3 b - e3", "8/8/8/3k4/3P4/8/8/3RK3 b - d3", ] { assert!(record.parse::().is_ok(), "{record}"); } } #[test] fn mirror() { assert_eq!(Position::new().pass(), Some(Position::new().mirror())); let position = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b Kq e3" .parse::() .unwrap() .validate() .unwrap(); let mirror = "rnbqkbnr/pppp1ppp/8/4p3/8/8/PPPPPPPP/RNBQKBNR w Qk e6" .parse::() .unwrap() .validate() .unwrap(); assert_eq!(mirror, position.mirror()); } fn perft_aux(record: &str, tests: &[u128]) { let position = record.parse::().unwrap().validate().unwrap(); for (depth, value) in tests.iter().copied().enumerate() { assert_eq!( position.perft(depth), value, "\"{record}\" at depth {depth}", ); } } #[test] fn perft_1() { perft_aux(P1, &[1, 20, 400, 8_902, 197_281, 4_865_609, 119_060_324]); } #[test] fn perft_2() { perft_aux(P2, &[1, 48, 2039, 97_862, 4_085_603, 193_690_690]); } #[test] fn perft_3() { perft_aux( P3, &[1, 14, 191, 2_812, 43_238, 674_624, 11_030_083, 178_633_661], ); } #[test] fn perft_4() { perft_aux(P4, &[1, 6, 264, 9_467, 422_333, 15_833_292]); } #[test] fn perft_5() { perft_aux(P5, &[1, 44, 1_486, 62_379, 2_103_487, 89_941_194]); } #[test] fn perft_6() { perft_aux(P6, &[1, 46, 2_079, 89_890, 3_894_594, 164_075_551]); } #[test] fn san() { let position = "8/2KN1p2/5p2/3N1B1k/5PNp/7P/7P/8 w - -" .parse::() .unwrap() .validate() .unwrap(); let san1 = "N7xf6#".parse::().unwrap(); let m1 = san1.to_move(&position).unwrap(); let san2 = "N5xf6#".parse::().unwrap(); let m2 = san2.to_move(&position).unwrap(); let san3 = "Ngxf6+".parse::().unwrap(); let m3 = san3.to_move(&position).unwrap(); assert_eq!(m1.to_san(), san1); assert_eq!(m2.to_san(), san2); assert_eq!(m3.to_san(), san3); assert_eq!( "Nd7f6" .parse::() .unwrap() .to_move(&position) .unwrap() .to_san(), san1, ); assert_eq!( "Nf6".parse::().unwrap().to_move(&position).map(|_| ()), Err(InvalidSan::Ambiguous), ); }