From 4ac718e5fc45e9117e28190d8a1b9045b53b062c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Macio=C5=82ek?= Date: Fri, 11 Oct 2024 17:10:59 +0200 Subject: [PATCH] Move generation and basic tests --- src/board.rs | 357 +++++++++++++++++++++++++++++++++++-------- tests/board_tests.rs | 190 +++++++++++++++++++++-- 2 files changed, 471 insertions(+), 76 deletions(-) diff --git a/src/board.rs b/src/board.rs index 1abd1be..45985ba 100644 --- a/src/board.rs +++ b/src/board.rs @@ -59,8 +59,13 @@ impl Board { king: Bitboard(0b0000000000000000000000000000000000000000000000000000000000010000), }; - let white_occupancy = Bitboard::new().or(&white_pieces.pawns).or(&white_pieces.knights).or(&white_pieces.bishops) - .or(&white_pieces.rooks).or(&white_pieces.queens).or(&white_pieces.king); + let white_occupancy = Bitboard::new() + .or(&white_pieces.pawns) + .or(&white_pieces.knights) + .or(&white_pieces.bishops) + .or(&white_pieces.rooks) + .or(&white_pieces.queens) + .or(&white_pieces.king); let black_pieces = Pieces { pawns: Bitboard(0b0000000011111111000000000000000000000000000000000000000000000000), @@ -71,8 +76,13 @@ impl Board { king: Bitboard(0b0001000000000000000000000000000000000000000000000000000000000000), }; - let black_occupancy = Bitboard::new().or(&black_pieces.pawns).or(&black_pieces.knights).or(&black_pieces.bishops) - .or(&black_pieces.rooks).or(&black_pieces.queens).or(&black_pieces.king); + let black_occupancy = Bitboard::new() + .or(&black_pieces.pawns) + .or(&black_pieces.knights) + .or(&black_pieces.bishops) + .or(&black_pieces.rooks) + .or(&black_pieces.queens) + .or(&black_pieces.king); let turn = Color::White; @@ -247,21 +257,30 @@ impl Board { Color::White => ( self.white_pieces.king, self.black_occupancy, - &self.black_pieces + &self.black_pieces, ), Color::Black => ( self.black_pieces.king, self.white_occupancy, - &self.white_pieces - ) + &self.white_pieces, + ), }; - let attacks = self.generate_pawn_attacks(self.turn, opponent_pieces.pawns) - .or(&self.generate_knight_attacks(opponent_pieces.knights)) - .or(&self.generate_bishop_attacks(opponent_pieces.bishops, opponent_occupancy)) - .or(&self.generate_rook_attacks(opponent_pieces.rooks, opponent_occupancy)) - .or(&self.generate_queen_attacks(opponent_pieces.queens, opponent_occupancy)) - .or(&self.generate_king_attacks(opponent_pieces.king)); + let attacks = &Board::generate_pawn_attacks(self.turn, opponent_pieces.pawns) + .or(&Board::generate_knight_attacks(opponent_pieces.knights)) + .or(&Board::generate_bishop_attacks( + opponent_pieces.bishops, + opponent_occupancy, + )) + .or(&Board::generate_rook_attacks( + opponent_pieces.rooks, + opponent_occupancy, + )) + .or(&Board::generate_queen_attacks( + opponent_pieces.queens, + opponent_occupancy, + )) + .or(&Board::generate_king_attacks(opponent_pieces.king)); if attacks.is_set(king_position.first_set_bit().unwrap()) { return Some(self.turn); @@ -270,7 +289,7 @@ impl Board { None } - pub fn generate_pawn_attacks(&self, color: Color, pawns: Bitboard) -> Bitboard { + pub fn generate_pawn_attacks(color: Color, pawns: Bitboard) -> Bitboard { match color { Color::White => { let attacks_left = pawns.left_shift(7) & !Bitboard(0x8080808080808080); @@ -285,7 +304,7 @@ impl Board { } } - pub fn generate_knight_attacks(&self, knights: Bitboard) -> Bitboard { + pub fn generate_knight_attacks(knights: Bitboard) -> Bitboard { let mut attacks = Bitboard::new(); let knight_positions = knights; @@ -316,20 +335,20 @@ impl Board { attacks } - pub fn generate_bishop_attacks(&self, bishops: Bitboard, occupancy: Bitboard) -> Bitboard { + pub fn generate_bishop_attacks(bishops: Bitboard, occupancy: Bitboard) -> Bitboard { let mut attacks = Bitboard::new(); let bishop_positions = bishops; for i in 0..64 { if bishop_positions.is_set(i) { - attacks = attacks.or(&self.generate_diagonal_attacks(i, occupancy)); + attacks = attacks.or(&Board::generate_diagonal_attacks(i, occupancy)); } } attacks } - fn generate_diagonal_attacks(&self, index: usize, occupancy: Bitboard) -> Bitboard { + fn generate_diagonal_attacks(index: usize, occupancy: Bitboard) -> Bitboard { let mut attacks = Bitboard::new(); let directions = [9, 7, -9, -7]; @@ -362,20 +381,20 @@ impl Board { attacks } - pub fn generate_rook_attacks(&self, rooks: Bitboard, occupancy: Bitboard) -> Bitboard { + pub fn generate_rook_attacks(rooks: Bitboard, occupancy: Bitboard) -> Bitboard { let mut attacks = Bitboard::new(); let rook_positions = rooks; for i in 0..64 { if rook_positions.is_set(i) { - attacks = attacks.or(&self.generate_straight_attacks(i, occupancy)); + attacks = attacks.or(&Board::generate_straight_attacks(i, occupancy)); } } attacks } - fn generate_straight_attacks(&self, index: usize, occupancy: Bitboard) -> Bitboard { + fn generate_straight_attacks(index: usize, occupancy: Bitboard) -> Bitboard { let mut attacks = Bitboard::new(); let directions = [8, -8, 1, -1]; @@ -389,7 +408,9 @@ impl Board { break; } - if (direction == 1 && current_index % 8 == 0) || (direction == -1 && current_index % 8 == 7) { + if (direction == 1 && current_index % 8 == 0) + || (direction == -1 && current_index % 8 == 7) + { break; } @@ -405,29 +426,33 @@ impl Board { attacks } - pub fn generate_queen_attacks(&self, queens: Bitboard, occupancy: Bitboard) -> Bitboard { + pub fn generate_queen_attacks(queens: Bitboard, occupancy: Bitboard) -> Bitboard { let mut attacks = Bitboard::new(); let queen_positions = queens; for i in 0..64 { if queen_positions.is_set(i) { - attacks = attacks.or(&self.generate_diagonal_attacks(i, occupancy)); - attacks = attacks.or(&self.generate_straight_attacks(i, occupancy)); + attacks = attacks.or(&Board::generate_diagonal_attacks(i, occupancy)); + attacks = attacks.or(&Board::generate_straight_attacks(i, occupancy)); } } attacks } - pub fn generate_king_attacks(&self, king: Bitboard) -> Bitboard { + pub fn generate_king_attacks(king: Bitboard) -> Bitboard { let mut attacks = Bitboard::new(); let king_index = king.first_set_bit().unwrap(); let king_moves = [ - king_index.wrapping_add(8), king_index.wrapping_sub(8), - king_index.wrapping_add(1), king_index.wrapping_sub(1), - king_index.wrapping_add(9), king_index.wrapping_sub(9), - king_index.wrapping_add(7), king_index.wrapping_sub(7), + king_index.wrapping_add(8), + king_index.wrapping_sub(8), + king_index.wrapping_add(1), + king_index.wrapping_sub(1), + king_index.wrapping_add(9), + king_index.wrapping_sub(9), + king_index.wrapping_add(7), + king_index.wrapping_sub(7), ]; for &move_index in &king_moves { @@ -439,55 +464,267 @@ impl Board { attacks } - pub fn generate_all_moves(&self) -> Vec<(usize, usize)> { + pub fn generate_all_moves(board: &Board) -> Vec<(usize, usize)> { let mut moves = Vec::new(); - let (pieces, occupancy, opponent_occupancy) = match self.turn { - Color::White => (&self.white_pieces, self.white_occupancy, self.black_occupancy), - Color::Black => (&self.black_pieces, self.black_occupancy, self.white_occupancy), + let (pieces, occupancy, opponent_occupancy) = match board.turn { + Color::White => ( + &board.white_pieces, + board.white_occupancy, + board.black_occupancy, + ), + Color::Black => ( + &board.black_pieces, + board.black_occupancy, + board.white_occupancy, + ), }; - moves.extend(self.generate_pawn_moves(pieces.pawns, occupancy, opponent_occupancy)); + moves.extend(Board::generate_pawn_moves( + pieces.pawns, + occupancy, + opponent_occupancy, + board.turn, + board.en_passant_square, + )); + moves.extend(Board::generate_knight_moves( + pieces.knights, + occupancy, + opponent_occupancy, + )); + moves.extend(Board::generate_bishop_moves( + pieces.bishops, + occupancy, + opponent_occupancy, + )); + moves.extend(Board::generate_rook_moves( + pieces.rooks, + occupancy, + opponent_occupancy, + )); + moves.extend(Board::generate_queen_moves( + pieces.queens, + occupancy, + opponent_occupancy, + )); + moves.extend(Board::generate_king_moves( + pieces.king, + occupancy, + opponent_occupancy, + )); - moves.extend(self.generate_knight_moves(pieces.knights, occupancy, opponent_occupancy)); + moves + } + + pub fn generate_pawn_moves( + pawns: Bitboard, + occupancy: Bitboard, + opponent_occupancy: Bitboard, + turn: Color, + en_passant_square: Option, + ) -> Vec<(usize, usize)> { + let mut moves = Vec::new(); + let direction = match turn { + Color::White => 8, + Color::Black => -8, + }; - moves.extend(self.generate_bishop_moves(pieces.bishops, occupancy, opponent_occupancy)); + for i in 0..64 { + if pawns.is_set(i) { + let forward_one = (i as isize + direction) as usize; + if forward_one < 64 && !occupancy.is_set(forward_one) { + moves.push((i, forward_one)); + + if Board::is_pawn_starting_position(turn, i) { + let forward_two = (i as isize + 2 * direction) as usize; + if forward_two < 64 && !occupancy.is_set(forward_two) { + moves.push((i, forward_two)); + } + } + } - moves.extend(self.generate_rook_moves(pieces.rooks, occupancy, opponent_occupancy)); + let capture_left = (i as isize + direction - 1) as usize; + if capture_left < 64 && opponent_occupancy.is_set(capture_left) && i % 8 != 0 { + moves.push((i, capture_left)); + } - moves.extend(self.generate_queen_moves(pieces.queens, occupancy, opponent_occupancy)); + let capture_right = (i as isize + direction + 1) as usize; + if capture_right < 64 && opponent_occupancy.is_set(capture_right) && i % 8 != 7 { + moves.push((i, capture_right)); + } - moves.extend(self.generate_king_moves(pieces.king, occupancy, opponent_occupancy)); + if let Some(en_passant_square) = en_passant_square { + if en_passant_square == capture_left || en_passant_square == capture_right { + moves.push((i, en_passant_square)); + } + } + } + } moves } - fn generate_pawn_moves(&self, pawns: Bitboard, occupancy: Bitboard, opponent_occupancy: Bitboard) -> Vec<(usize, usize)> { - // Implement pawn move generation logic - vec![] - } + pub fn generate_knight_moves( + knights: Bitboard, + occupancy: Bitboard, + opponent_occupancy: Bitboard, + ) -> Vec<(usize, usize)> { + let mut moves = Vec::new(); + let knight_moves = [17, 15, 10, 6, -17, -15, -10, -6]; + + for i in 0..64 { + if knights.is_set(i) { + for &offset in &knight_moves { + let target = (i as isize + offset) as usize; + if target < 64 && (target as isize >= 0) && !occupancy.is_set(target) { + moves.push((i, target)); + } else if target < 64 + && (target as isize >= 0) + && opponent_occupancy.is_set(target) + { + moves.push((i, target)); + } + } + } + } - fn generate_knight_moves(&self, knights: Bitboard, occupancy: Bitboard, opponent_occupancy: Bitboard) -> Vec<(usize, usize)> { - // Implement knight move generation logic - vec![] + moves } - fn generate_bishop_moves(&self, bishops: Bitboard, occupancy: Bitboard, opponent_occupancy: Bitboard) -> Vec<(usize, usize)> { - // Implement bishop move generation logic - vec![] + pub fn generate_bishop_moves( + bishops: Bitboard, + occupancy: Bitboard, + opponent_occupancy: Bitboard, + ) -> Vec<(usize, usize)> { + let mut moves = Vec::new(); + let directions = [9, 7, -9, -7]; + + for i in 0..64 { + if bishops.is_set(i) { + for &direction in &directions { + let mut current_index = i as isize; + + loop { + current_index += direction; + + if current_index < 0 || current_index >= 64 { + break; + } + + if (direction == 9 || direction == -7) && current_index % 8 == 0 { + break; + } + if (direction == 7 || direction == -9) && current_index % 8 == 7 { + break; + } + + let current_index_usize = current_index as usize; + + if occupancy.is_set(current_index_usize) { + break; + } + + moves.push((i, current_index_usize)); + + if opponent_occupancy.is_set(current_index_usize) { + break; + } + } + } + } + } + + moves } - fn generate_rook_moves(&self, rooks: Bitboard, occupancy: Bitboard, opponent_occupancy: Bitboard) -> Vec<(usize, usize)> { - // Implement rook move generation logic - vec![] + pub fn generate_rook_moves( + rooks: Bitboard, + occupancy: Bitboard, + opponent_occupancy: Bitboard, + ) -> Vec<(usize, usize)> { + let mut moves = Vec::new(); + let directions = [8, -8, 1, -1]; + + for i in 0..64 { + if rooks.is_set(i) { + for &direction in &directions { + let mut current_index = i as isize; + + loop { + current_index += direction; + + if current_index < 0 || current_index >= 64 { + break; + } + + if (direction == 1 && current_index % 8 == 0) + || (direction == -1 && current_index % 8 == 7) + { + break; + } + + let current_index_usize = current_index as usize; + + if occupancy.is_set(current_index_usize) { + break; + } + + moves.push((i, current_index_usize)); + + if opponent_occupancy.is_set(current_index_usize) { + break; + } + } + } + } + } + + moves } - fn generate_queen_moves(&self, queens: Bitboard, occupancy: Bitboard, opponent_occupancy: Bitboard) -> Vec<(usize, usize)> { - // Implement queen move generation logic - vec![] + pub fn generate_queen_moves( + queens: Bitboard, + occupancy: Bitboard, + opponent_occupancy: Bitboard, + ) -> Vec<(usize, usize)> { + let mut moves = Vec::new(); + + for i in 0..64 { + if queens.is_set(i) { + moves.extend(Board::generate_bishop_moves( + Bitboard(1 << i), + occupancy, + opponent_occupancy, + )); + moves.extend(Board::generate_rook_moves( + Bitboard(1 << i), + occupancy, + opponent_occupancy, + )); + } + } + + moves } - fn generate_king_moves(&self, king: Bitboard, occupancy: Bitboard, opponent_occupancy: Bitboard) -> Vec<(usize, usize)> { - // Implement king move generation logic - vec![] + pub fn generate_king_moves( + king: Bitboard, + occupancy: Bitboard, + opponent_occupancy: Bitboard, + ) -> Vec<(usize, usize)> { + let mut moves = Vec::new(); + let king_index = king.first_set_bit().unwrap(); + + let king_moves = [8, -8, 1, -1, 9, -9, 7, -7]; + + for &offset in &king_moves { + let target = (king_index as isize + offset) as usize; + if target < 64 && (target as isize >= 0) && !occupancy.is_set(target) { + moves.push((king_index, target)); + } else if target < 64 && (target as isize >= 0) && opponent_occupancy.is_set(target) { + moves.push((king_index, target)); + } + } + + moves } -} \ No newline at end of file +} diff --git a/tests/board_tests.rs b/tests/board_tests.rs index ef9be4e..a8ee447 100644 --- a/tests/board_tests.rs +++ b/tests/board_tests.rs @@ -40,7 +40,8 @@ mod tests { #[test] fn test_is_square_enemy() { - let board = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap(); + let board = + Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap(); assert!(board.is_square_enemy(Color::White, 48)); // a7 should have a black pawn assert!(!board.is_square_enemy(Color::White, 8)); // a2 should have a white pawn } @@ -54,14 +55,18 @@ mod tests { #[test] fn test_generate_pawn_attacks() { let board = Board::new(); - let white_pawns = Bitboard(0b0000000000000000000000000000000000000000000000000000000000100000); + let white_pawns = + Bitboard(0b0000000000000000000000000000000000000000000000000000000000100000); let black_pawns = Bitboard(0b100000000000000000000000000000000000000000000000000000000000); - let white_attacks = board.generate_pawn_attacks(Color::White, white_pawns); - let black_attacks = board.generate_pawn_attacks(Color::Black, black_pawns); + let white_attacks = Board::generate_pawn_attacks(Color::White, white_pawns); + let black_attacks = Board::generate_pawn_attacks(Color::Black, black_pawns); assert_eq!(white_attacks, Bitboard(0b101000000000000)); - assert_eq!(black_attacks, Bitboard(0b10100000000000000000000000000000000000000000000000000)); + assert_eq!( + black_attacks, + Bitboard(0b10100000000000000000000000000000000000000000000000000) + ); } #[test] @@ -69,7 +74,7 @@ mod tests { let board = Board::new(); let knights = Bitboard(0b0000000000000000000000000000000000000000000000000000000000000010); - let attacks = board.generate_knight_attacks(knights); + let attacks = Board::generate_knight_attacks(knights); assert_eq!(attacks, Bitboard(0b1010000100000000000)); } @@ -78,9 +83,10 @@ mod tests { fn test_generate_bishop_attacks() { let board = Board::new(); let bishops = Bitboard(0b0000000000000000000000000000000000000000000000000000000000001000); - let occupancy = Bitboard(0b0000000000000000000000000000000000000000000000000000000000000000); + let occupancy = + Bitboard(0b0000000000000000000000000000000000000000000000000000000000000000); - let attacks = board.generate_bishop_attacks(bishops, occupancy); + let attacks = Board::generate_bishop_attacks(bishops, occupancy); let expected_attacks = Bitboard(0b1000000001000001001000100001010000000000); assert_eq!(attacks, expected_attacks); @@ -90,22 +96,30 @@ mod tests { fn test_generate_rook_attacks() { let board = Board::new(); let rooks = Bitboard(0b0000000000000000000000000000000000000000000000000000000000000001); - let occupancy = Bitboard(0b0000000000000000000000000000000000000000000000000000000000000000); + let occupancy = + Bitboard(0b0000000000000000000000000000000000000000000000000000000000000000); - let attacks = board.generate_rook_attacks(rooks, occupancy); + let attacks = Board::generate_rook_attacks(rooks, occupancy); - assert_eq!(attacks, Bitboard(0b100000001000000010000000100000001000000010000000111111110)); + assert_eq!( + attacks, + Bitboard(0b100000001000000010000000100000001000000010000000111111110) + ); } #[test] fn test_generate_queen_attacks() { let board = Board::new(); let queens = Bitboard(0b0000000000000000000000000000000000000000000000000000000000001000); - let occupancy = Bitboard(0b0000000000000000000000000000000000000000000000000000000000000000); + let occupancy = + Bitboard(0b0000000000000000000000000000000000000000000000000000000000000000); - let attacks = board.generate_queen_attacks(queens, occupancy); + let attacks = Board::generate_queen_attacks(queens, occupancy); - assert_eq!(attacks, Bitboard(0b100000001000000010001000100001001001001010100001110011110111)); + assert_eq!( + attacks, + Bitboard(0b100000001000000010001000100001001001001010100001110011110111) + ); } #[test] @@ -113,7 +127,7 @@ mod tests { let board = Board::new(); let king = Bitboard(0b0000000000000000000000000000000000000000000000000000000000001000); - let attacks = board.generate_king_attacks(king); + let attacks = Board::generate_king_attacks(king); assert_eq!(attacks, Bitboard(0b1110000010100)); } @@ -190,4 +204,148 @@ mod tests { assert_eq!(board.halfmove_clock, 0); assert_eq!(board.fullmove_number, 1); } -} \ No newline at end of file + + #[test] + fn test_generate_pawn_moves() { + let white_pawns = Bitboard(0b10000000000000); + let black_pawns = Bitboard(0b1000000000000000000000000000000000000000000000000000); + + let white_moves = + Board::generate_pawn_moves(white_pawns, white_pawns, Bitboard(0), Color::White, None); + let black_moves = + Board::generate_pawn_moves(black_pawns, black_pawns, Bitboard(0), Color::Black, None); + + assert_eq!(white_moves, vec![(13, 21), (13, 29)]); + assert_eq!(black_moves, vec![(51, 43), (51, 35)]); + } + + #[test] + fn test_generate_knight_moves() { + let knights = Bitboard(0b1000000000000000000000000000); + + let moves = Board::generate_knight_moves(knights, knights, Bitboard(0)); + + assert_eq!( + moves, + vec![ + (27, 44), + (27, 42), + (27, 37), + (27, 33), + (27, 10), + (27, 12), + (27, 17), + (27, 21) + ] + ); + } + + #[test] + fn test_generate_bishop_moves() { + let bishops = Bitboard(0b1000000000000000000000000000); + let occupancy = Bitboard(0); + + let moves = Board::generate_bishop_moves(bishops, occupancy, occupancy); + + let expected_moves = vec![ + (27, 36), + (27, 45), + (27, 54), + (27, 63), + (27, 34), + (27, 41), + (27, 48), + (27, 18), + (27, 9), + (27, 0), + (27, 20), + (27, 13), + (27, 6), + ]; + assert_eq!(moves, expected_moves); + } + + #[test] + fn test_generate_rook_moves() { + let rooks = Bitboard(0b1000000000000000000000000000); + let occupancy = Bitboard(0); + + let moves = Board::generate_rook_moves(rooks, rooks, occupancy); + + let expected_moves = vec![ + (27, 35), + (27, 43), + (27, 51), + (27, 59), + (27, 19), + (27, 11), + (27, 3), + (27, 28), + (27, 29), + (27, 30), + (27, 31), + (27, 26), + (27, 25), + (27, 24), + ]; + assert_eq!(moves, expected_moves); + } + + #[test] + fn test_generate_queen_moves() { + let queens = Bitboard(0b1000000000000000000000000000); + let occupancy = Bitboard(0); + + let moves = Board::generate_queen_moves(queens, queens, occupancy); + + let expected_moves = vec![ + (27, 36), + (27, 45), + (27, 54), + (27, 63), + (27, 34), + (27, 41), + (27, 48), + (27, 18), + (27, 9), + (27, 0), + (27, 20), + (27, 13), + (27, 6), + (27, 35), + (27, 43), + (27, 51), + (27, 59), + (27, 19), + (27, 11), + (27, 3), + (27, 28), + (27, 29), + (27, 30), + (27, 31), + (27, 26), + (27, 25), + (27, 24), + ]; + assert_eq!(moves, expected_moves); + } + + #[test] + fn test_generate_king_moves() { + let king = Bitboard(0b1000000000000000000000000000); + + let moves = Board::generate_king_moves(king, king, Bitboard(0)); + + let expected_moves = vec![ + (27, 35), + (27, 19), + (27, 28), + (27, 26), + (27, 36), + (27, 18), + (27, 34), + (27, 20), + ]; + assert_eq!(moves, expected_moves); + } +}