use kish::{Board, Square, Team};
#[test]
fn captures_are_mandatory() {
let board = Board::from_squares(Team::White, &[Square::D4], &[Square::D5, Square::H8], &[]);
let actions = board.actions();
for action in &actions {
assert_ne!(
action.delta.pieces[Team::Black.to_usize()],
0,
"Must capture when capture is available"
);
}
}
#[test]
fn moves_allowed_when_no_capture() {
let board = Board::from_squares(
Team::White,
&[Square::D4],
&[Square::H8], &[],
);
let actions = board.actions();
for action in &actions {
assert_eq!(
action.delta.pieces[Team::Black.to_usize()],
0,
"Should be moves, not captures"
);
}
assert!(!actions.is_empty(), "Should have moves available");
}
#[test]
fn only_capturing_piece_can_move() {
let board = Board::from_squares(
Team::White,
&[Square::D4, Square::A1],
&[Square::D5], &[],
);
let actions = board.actions();
assert_eq!(actions.len(), 1, "Only one capture available");
assert_ne!(
actions[0].delta.pieces[Team::Black.to_usize()],
0,
"Must be a capture"
);
}
#[test]
fn white_pawn_captures_forward() {
let board = Board::from_squares(Team::White, &[Square::D4], &[Square::D5], &[]);
let actions = board.actions();
assert_eq!(actions.len(), 1);
assert_eq!(
actions[0].delta.pieces[Team::Black.to_usize()],
Square::D5.to_mask()
);
}
#[test]
fn white_pawn_captures_left() {
let board = Board::from_squares(Team::White, &[Square::D4], &[Square::C4], &[]);
let actions = board.actions();
assert_eq!(actions.len(), 1);
assert_eq!(
actions[0].delta.pieces[Team::Black.to_usize()],
Square::C4.to_mask()
);
}
#[test]
fn white_pawn_captures_right() {
let board = Board::from_squares(Team::White, &[Square::D4], &[Square::E4], &[]);
let actions = board.actions();
assert_eq!(actions.len(), 1);
assert_eq!(
actions[0].delta.pieces[Team::Black.to_usize()],
Square::E4.to_mask()
);
}
#[test]
fn white_pawn_cannot_capture_backward() {
let board = Board::from_squares(
Team::White,
&[Square::D4],
&[Square::D3, Square::D5], &[],
);
let actions = board.actions();
assert_eq!(actions.len(), 1);
assert_eq!(
actions[0].delta.pieces[Team::Black.to_usize()],
Square::D5.to_mask(),
"Should only capture forward (D5), not backward (D3)"
);
}
#[test]
fn black_pawn_cannot_capture_backward() {
let board = Board::from_squares(
Team::Black,
&[Square::D5, Square::D3], &[Square::D4],
&[],
);
let actions = board.actions();
assert_eq!(actions.len(), 1);
assert_eq!(
actions[0].delta.pieces[Team::White.to_usize()],
Square::D3.to_mask(),
"Should only capture forward (D3), not backward (D5)"
);
}
#[test]
fn pawn_cannot_capture_diagonally() {
let board = Board::from_squares(
Team::White,
&[Square::D4],
&[Square::C3, Square::C5, Square::E3, Square::E5],
&[],
);
let actions = board.actions();
for action in &actions {
assert_eq!(
action.delta.pieces[Team::Black.to_usize()],
0,
"Should not capture diagonal enemies"
);
}
}
#[test]
fn king_captures_from_distance() {
let board = Board::from_squares(
Team::White,
&[Square::A4],
&[Square::E4], &[Square::A4],
);
let actions = board.actions();
assert!(!actions.is_empty(), "King should have captures");
for action in &actions {
assert_eq!(
action.delta.pieces[Team::Black.to_usize()],
Square::E4.to_mask()
);
}
}
#[test]
fn king_can_land_anywhere_beyond_captured() {
let board = Board::from_squares(
Team::White,
&[Square::A4],
&[Square::D4], &[Square::A4],
);
let actions = board.actions();
assert_eq!(actions.len(), 4, "King should have 4 landing options");
let landing_squares: Vec<u64> = actions
.iter()
.map(|a| a.delta.pieces[Team::White.to_usize()] & !Square::A4.to_mask())
.collect();
assert!(landing_squares.contains(&Square::E4.to_mask()));
assert!(landing_squares.contains(&Square::F4.to_mask()));
assert!(landing_squares.contains(&Square::G4.to_mask()));
assert!(landing_squares.contains(&Square::H4.to_mask()));
}
#[test]
fn king_captures_in_all_directions() {
let board = Board::from_squares(
Team::White,
&[Square::D4],
&[Square::B4, Square::F4, Square::D2, Square::D6],
&[Square::D4],
);
let actions = board.actions();
let captured_pieces: u64 = actions
.iter()
.map(|a| a.delta.pieces[Team::Black.to_usize()])
.fold(0, |acc, x| acc | x);
assert!(captured_pieces != 0, "King should be able to capture");
}
#[test]
fn king_cannot_jump_two_pieces_at_once() {
let board = Board::from_squares(
Team::White,
&[Square::A4],
&[Square::C4, Square::D4], &[Square::A4],
);
let actions = board.actions();
for action in &actions {
assert_eq!(
action.delta.pieces[Team::Black.to_usize()],
0,
"No captures should be possible with two adjacent blocking pieces"
);
}
assert!(!actions.is_empty(), "Should have moves available");
}
#[test]
fn captured_piece_removed_immediately_allows_crossing() {
let board = Board::from_squares(
Team::White,
&[Square::A4],
&[Square::C4, Square::E4],
&[Square::A4],
);
let actions = board.actions();
let max_captures = actions
.iter()
.map(|a| a.delta.pieces[Team::Black.to_usize()].count_ones())
.max()
.unwrap_or(0);
assert_eq!(
max_captures, 2,
"Should capture 2 pieces using immediate removal"
);
}
#[test]
fn pawn_chain_capture_with_immediate_removal() {
let board = Board::from_squares(Team::White, &[Square::A2], &[Square::A3, Square::B4], &[]);
let actions = board.actions();
let max_captures = actions
.iter()
.map(|a| a.delta.pieces[Team::Black.to_usize()].count_ones())
.max()
.unwrap_or(0);
assert_eq!(max_captures, 2, "Pawn should capture 2 pieces in chain");
}
#[test]
fn multi_capture_must_continue() {
let board = Board::from_squares(Team::White, &[Square::A2], &[Square::A3, Square::B4], &[]);
let actions = board.actions();
for action in &actions {
let captures = action.delta.pieces[Team::Black.to_usize()].count_ones();
assert_eq!(captures, 2, "Must complete the full capture chain");
}
}
#[test]
fn three_piece_capture_chain() {
let board = Board::from_squares(
Team::White,
&[Square::A2],
&[Square::A3, Square::B4, Square::C5],
&[],
);
let actions = board.actions();
let max_captures = actions
.iter()
.map(|a| a.delta.pieces[Team::Black.to_usize()].count_ones())
.max()
.unwrap_or(0);
assert_eq!(max_captures, 3, "Should capture 3 pieces in chain");
}
#[test]
fn four_piece_capture_chain() {
let board = Board::from_squares(
Team::White,
&[Square::A2],
&[Square::A3, Square::B4, Square::C5, Square::D6],
&[],
);
let actions = board.actions();
let max_captures = actions
.iter()
.map(|a| a.delta.pieces[Team::Black.to_usize()].count_ones())
.max()
.unwrap_or(0);
assert_eq!(max_captures, 4, "Should capture 4 pieces in chain");
}
#[test]
fn king_chain_capture() {
let board = Board::from_squares(
Team::White,
&[Square::A4],
&[Square::C4, Square::E6],
&[Square::A4],
);
let actions = board.actions();
let max_captures = actions
.iter()
.map(|a| a.delta.pieces[Team::Black.to_usize()].count_ones())
.max()
.unwrap_or(0);
assert_eq!(max_captures, 2, "King should capture 2 pieces in chain");
}
#[test]
fn maximum_capture_rule_enforced() {
let board = Board::from_squares(
Team::White,
&[Square::D4],
&[Square::D5, Square::C4, Square::B5],
&[],
);
let actions = board.actions();
for action in &actions {
let captures = action.delta.pieces[Team::Black.to_usize()].count_ones();
assert!(captures >= 2, "Must choose maximum capture sequence");
}
}
#[test]
fn equal_length_captures_all_available() {
let board = Board::from_squares(
Team::White,
&[Square::D4],
&[Square::D5, Square::C4, Square::E4],
&[],
);
let actions = board.actions();
assert_eq!(
actions.len(),
3,
"Should have 3 equal-length capture options"
);
for action in &actions {
let captures = action.delta.pieces[Team::Black.to_usize()].count_ones();
assert_eq!(captures, 1, "All captures should be length 1");
}
}
#[test]
fn men_and_kings_count_equally() {
let board = Board::from_squares(
Team::White,
&[Square::D4],
&[Square::D5, Square::C4, Square::B5],
&[Square::D5], );
let actions = board.actions();
let max_captures = actions
.iter()
.map(|a| a.delta.pieces[Team::Black.to_usize()].count_ones())
.max()
.unwrap_or(0);
assert_eq!(
max_captures, 2,
"Should prefer 2-pawn capture over 1-king capture"
);
}
#[test]
fn king_cannot_reverse_up_down_during_capture() {
let board = Board::from_squares(
Team::White,
&[Square::D5],
&[Square::D3, Square::D7],
&[Square::D5],
);
let actions = board.actions();
for action in &actions {
let captures = action.delta.pieces[Team::Black.to_usize()].count_ones();
assert_eq!(
captures, 1,
"Should NOT chain captures requiring 180° turn (up then down)"
);
}
}
#[test]
fn king_cannot_reverse_left_right_during_capture() {
let board = Board::from_squares(
Team::White,
&[Square::D4],
&[Square::B4, Square::F4],
&[Square::D4],
);
let actions = board.actions();
for action in &actions {
let captures = action.delta.pieces[Team::Black.to_usize()].count_ones();
assert_eq!(
captures, 1,
"Should NOT chain captures requiring 180° turn (left then right)"
);
}
}
#[test]
fn king_can_turn_90_degrees_during_capture() {
let board = Board::from_squares(
Team::White,
&[Square::D4],
&[Square::D6, Square::F7],
&[Square::D4],
);
let actions = board.actions();
let max_captures = actions
.iter()
.map(|a| a.delta.pieces[Team::Black.to_usize()].count_ones())
.max()
.unwrap_or(0);
assert_eq!(max_captures, 2, "90° turn should be allowed");
}
#[test]
fn king_90_degree_l_shape_capture() {
let board = Board::from_squares(
Team::White,
&[Square::A1],
&[Square::A3, Square::C5, Square::E3],
&[Square::A1],
);
let actions = board.actions();
let max_captures = actions
.iter()
.map(|a| a.delta.pieces[Team::Black.to_usize()].count_ones())
.max()
.unwrap_or(0);
assert_eq!(max_captures, 3, "Should chain 3 captures with 90° turns");
}
#[test]
fn black_pawn_captures_forward_vertical() {
let board = Board::from_squares(Team::Black, &[Square::D4], &[Square::D5], &[]);
let actions = board.actions();
assert_eq!(actions.len(), 1, "Black pawn should have 1 capture");
assert_eq!(
actions[0].delta.pieces[Team::White.to_usize()],
Square::D4.to_mask(),
"Black pawn should capture D4"
);
}
#[test]
fn black_pawn_vertical_chain_capture() {
let board = Board::from_squares(Team::Black, &[Square::D5, Square::D3], &[Square::D6], &[]);
let actions = board.actions();
let max_captures = actions
.iter()
.map(|a| a.delta.pieces[Team::White.to_usize()].count_ones())
.max()
.unwrap_or(0);
assert_eq!(
max_captures, 2,
"Black pawn should chain 2 vertical captures"
);
}
#[test]
fn black_king_captures_all_directions() {
let board = Board::from_squares(
Team::Black,
&[Square::B4, Square::F4, Square::D2, Square::D6],
&[Square::D4],
&[Square::D4], );
let actions = board.actions();
assert!(
!actions.is_empty(),
"Black king should have capture options"
);
for action in &actions {
assert_ne!(
action.delta.pieces[Team::White.to_usize()],
0,
"All actions should be captures"
);
}
}