diff --git a/othello/src/board.rs b/othello/src/board.rs index eaca2b0..5c1fa0c 100644 --- a/othello/src/board.rs +++ b/othello/src/board.rs @@ -132,6 +132,42 @@ fn shift_in_direction(magnitude: i8, value: Board) -> Board { } impl BitBoard { + /// Convert from a JON (Jack Othello Notation) string into a usable board. + pub fn from_jon(fen: &str) -> Result { + let mut boards: [Board; 2] = [0, 0]; + let mut line: u8 = 7; + let mut rank_idx: u8 = 0; + let mut rank_jump: u8 = 0; + for c in fen.chars().into_iter() { + match c.to_ascii_lowercase() { + '/' => { + line -= 1; + rank_idx = 0; + rank_jump = 0; + } + 'b' => { + boards[0] += 1 << (line * 8 + rank_idx + rank_jump); + rank_idx += rank_jump + 1; + rank_jump = 0; + } + 'w' => { + boards[1] += 1 << (line * 8 + rank_idx + rank_jump); + rank_idx += rank_jump + 1; + rank_jump = 0; + } + '0'..'9' => { + rank_jump = c + .to_digit(10u32) + .ok_or("Failed to parse integer from character")? + as u8; + } + _ => { + return Err("Malformed FEN: Unexpected character encountered"); + } + } + } + Ok(Self { boards }) + } /// Compute board with valid moves marked pub fn available(&self, current_team: Team) -> Board { // bitwise OR gives spots with either white OR black discs @@ -360,4 +396,155 @@ mod tests { --------"; assert_eq!(format!("{}", bb), expected_output); } + + #[test] + fn jon_works() { + let bb = BitBoard::from_jon("///3wb/3bw///").expect("Starting board should be valid"); + println!("{}", bb); + assert_eq!(bb.boards, BitBoard::default().boards); + } + + #[test] + fn test_available_starting_position_black() { + // Starting position + let bb = BitBoard::from_jon("///3wb/3bw///").expect("Valid board"); + let available = bb.available(Team::Black); + + // Black can move to: d3, c4, f5, e6 + let expected = BitBoard::from_jon("//4b/5b/2b/3b//").expect("Valid board"); + assert_eq!(available, expected.boards[Team::Black as usize]); + } + + #[test] + fn test_available_starting_position_white() { + // Starting position + let bb = BitBoard::from_jon("///3wb/3bw///").expect("Valid board"); + let available = bb.available(Team::White); + + // White can move to: c5, d6, f4, e3 + let expected = BitBoard::from_jon("//3w/5w/2w/4w//").expect("Valid board"); + assert_eq!(available, expected.boards[Team::White as usize]); + } + + #[test] + fn test_available_empty_board() { + // Empty board - no pieces + let bb = BitBoard::from_jon("////////").expect("Valid board"); + let available_black = bb.available(Team::Black); + let available_white = bb.available(Team::White); + + // No moves available on empty board + assert_eq!(available_black, 0); + assert_eq!(available_white, 0); + } + + #[test] + fn test_available_single_piece_no_moves() { + // Single white piece, no black pieces + let bb = BitBoard::from_jon("///3w////").expect("Valid board"); + let available = bb.available(Team::Black); + + // Black has no valid moves (needs opponent pieces to capture) + assert_eq!(available, 0); + } + + #[test] + fn test_available_corner_move() { + // Board with pieces set up for a corner capture + // Black at b8, white at a8, black can play at a7 to capture + let bb = BitBoard::from_jon("wb//////").expect("Valid board"); + println!("{}", bb); + let available = bb.available(Team::Black); + + // Black can move to a7 to capture white at a8 + let expected = BitBoard::from_jon("/b//////").expect("Valid board"); + assert_eq!(available, expected.boards[Team::Black as usize]); + } + + #[test] + fn test_available_edge_captures() { + // Black pieces on edges with white pieces that can be captured + let bb = BitBoard::from_jon("b6w/8/8/8/8/8/8/8").expect("Valid board"); + let available = bb.available(Team::Black); + + // Black can capture by playing in between + let expected = BitBoard::from_jon("1bbbbbb/").expect("Valid board"); + assert_eq!(available, expected.boards[Team::Black as usize]); + } + + #[test] + fn test_available_multiple_direction_capture() { + // White surrounded by black - black can capture in multiple directions + let bb = BitBoard::from_jon("///2bbb/2bwb/2bbb///").expect("Valid board"); + let available = bb.available(Team::Black); + + // Black can play at d4 (where white is) - but this is invalid, let me reconsider + // Actually, moves must be on empty squares + // Let's set up where black can capture in multiple directions + let bb = BitBoard::from_jon("///2b1b/2bwb/2bbb///").expect("Valid board"); + let available = bb.available(Team::Black); + + // Black can move to d5 to capture white + let expected = BitBoard::from_jon("///3b////").expect("Valid board"); + assert_eq!(available, expected.boards[Team::Black as usize]); + } + + #[test] + fn test_available_no_valid_moves() { + // Position where current team has no valid moves + // Black pieces isolated with no white pieces to capture + let bb = BitBoard::from_jon("b///////w").expect("Valid board"); + let available_black = bb.available(Team::Black); + let available_white = bb.available(Team::White); + + // Neither player can capture anything + assert_eq!(available_black, 0); + assert_eq!(available_white, 0); + } + + #[test] + fn test_available_full_row_capture() { + // Black can capture an entire row of white pieces + let bb = BitBoard::from_jon("///////bwwwwwwb").expect("Valid board"); + let available = bb.available(Team::Black); + + // Black can play anywhere between the two black pieces on row 1 + let expected = BitBoard::from_jon("///////1bbbbbb").expect("Valid board"); + assert_eq!(available, expected.boards[Team::Black as usize]); + } + + #[test] + fn test_available_diagonal_capture() { + // Test diagonal captures + let bb = BitBoard::from_jon("b/1w/2w/3w////").expect("Valid board"); + let available = bb.available(Team::Black); + + // Black can capture diagonally + let expected = BitBoard::from_jon("////4b///").expect("Valid board"); + assert_eq!(available, expected.boards[Team::Black as usize]); + } + + #[test] + fn test_available_midgame_position() { + // More complex mid-game position + let bb = BitBoard::from_jon("//2bwb/2www/2bwb///").expect("Valid board"); + let available = bb.available(Team::Black); + + // Black should have several valid moves + // This would need to be calculated based on actual game rules + // Adding moves that would flip white pieces + let expected = BitBoard::from_jon("/3b/b1b1b/b3b/b1b1b/3b//").expect("Valid board"); + assert_eq!(available, expected.boards[Team::Black as usize]); + } + + #[test] + fn test_available_vertical_capture() { + // Test vertical captures + let bb = BitBoard::from_jon("b/w/w/w////").expect("Valid board"); + let available = bb.available(Team::Black); + + // Black can capture vertically downward + let expected = BitBoard::from_jon("////b///").expect("Valid board"); + assert_eq!(available, expected.boards[Team::Black as usize]); + } }