diff --git a/src/board.rs b/src/board.rs index e8555b3..7d9cba0 100644 --- a/src/board.rs +++ b/src/board.rs @@ -5,6 +5,11 @@ use crate::attack::{ get_rook_attacks, }; use crate::game::State; +use crate::movegen::{ + bishop_pseudo_moves, king_pseudo_moves, knight_pseudo_moves, pawn_pseudo_moves, + queen_pseudo_moves, rook_pseudo_moves, +}; +use crate::r#move::{Move, MoveType, Promote}; #[derive(Debug, PartialEq, Eq)] pub struct Board { @@ -12,10 +17,6 @@ pub struct Board { pub black_pieces: [Piece; 6], pub state: State, } -use crate::movegen::{ - bishop_pseudo_moves, king_pseudo_moves, knight_pseudo_moves, pawn_pseudo_moves, - queen_pseudo_moves, rook_pseudo_moves, Move, -}; impl Board { pub const fn new() -> Self { @@ -100,6 +101,21 @@ impl Board { || enemy[Kind::King.idx()].bitboard & get_king_attacks(sq) != 0 } + pub fn is_move_legit(&self, square: usize, opponent_color: Color) -> bool { + !self.is_attacked(square, opponent_color) + } + + pub fn pseudo_moves_all(&self, color: Color) -> Vec { + let mut moves = vec![]; + moves.extend(self.pseudo_moves(color, Kind::Pawn)); + moves.extend(self.pseudo_moves(color, Kind::Knight)); + moves.extend(self.pseudo_moves(color, Kind::Bishop)); + moves.extend(self.pseudo_moves(color, Kind::Rook)); + moves.extend(self.pseudo_moves(color, Kind::Queen)); + moves.extend(self.pseudo_moves(color, Kind::King)); + moves + } + pub fn pseudo_moves(&self, color: Color, kind: Kind) -> Vec { let all_occupancies = self.get_all_occupancies(); let (pieces, enemy_occupancies, own_occupancies) = match color { @@ -123,7 +139,7 @@ impl Board { self.state.get_en_passant_target_square(), color, ), - Kind::Knight => knight_pseudo_moves(pieces, own_occupancies), + Kind::Knight => knight_pseudo_moves(pieces, all_occupancies, own_occupancies), Kind::Bishop => bishop_pseudo_moves(pieces, all_occupancies, own_occupancies), Kind::Rook => rook_pseudo_moves(pieces, all_occupancies, own_occupancies), Kind::Queen => queen_pseudo_moves(pieces, all_occupancies, own_occupancies), diff --git a/src/main.rs b/src/main.rs index b7c3fdf..b9edf87 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ pub mod board; pub mod fen; pub mod game; pub mod magic; +pub mod r#move; pub mod movegen; use game::Game; diff --git a/src/move.rs b/src/move.rs new file mode 100644 index 0000000..970711c --- /dev/null +++ b/src/move.rs @@ -0,0 +1,46 @@ +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub enum Promote { + Knight, + Rook, + Bishop, + Queen, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub enum MoveType { + Quiet, + Capture, + DoublePush, + Promotion(Promote), + PromotionCapture(Promote), + EnPassant, + Castle, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub struct Move { + pub source: u32, + pub target: u32, + pub move_type: MoveType, +} + +impl Move { + pub const fn new(source: u32, target: u32) -> Self { + Self { + source, + target, + move_type: MoveType::Quiet, + } + } + + pub const fn new_with_type(source: u32, target: u32, move_type: MoveType) -> Self { + Self { + source, + target, + move_type, + } + } +} + +#[cfg(test)] +mod tests {} diff --git a/src/movegen.rs b/src/movegen.rs index cd4d63e..2bf1d32 100644 --- a/src/movegen.rs +++ b/src/movegen.rs @@ -4,6 +4,7 @@ use crate::attack::{ }; use crate::board::{Board, Color}; use crate::game::Castle; +use crate::r#move::{Move, MoveType, Promote}; use u64 as Bitboard; const NOT_A_FILE: Bitboard = 0xfefefefefefefefe; @@ -11,18 +12,6 @@ const NOT_H_FILE: Bitboard = 0x7f7f7f7f7f7f7f7f; const RANK_4: Bitboard = 0x00000000FF000000; const RANK_5: Bitboard = 0x000000FF00000000; -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Move { - pub source: u32, - pub target: u32, -} - -impl Move { - pub const fn new(source: u32, target: u32) -> Self { - Self { source, target } - } -} - pub fn pawn_pseudo_moves( pawns: Bitboard, all_occupancies: Bitboard, @@ -30,7 +19,7 @@ pub fn pawn_pseudo_moves( en_passant_square: Option, color: Color, ) -> Vec { - let mut moves: Vec = vec![]; + let mut moves = vec![]; match color { Color::White => { moves.extend(white_pawn_quiet_moves(pawns, all_occupancies)); @@ -41,26 +30,36 @@ pub fn pawn_pseudo_moves( )); } Color::Black => { + moves.extend(black_pawn_quiet_moves(pawns, all_occupancies)); moves.extend(black_pawn_capture_moves( pawns, enemy_occupancies, en_passant_square, )); - moves.extend(black_pawn_quiet_moves(pawns, all_occupancies)); } } moves } fn white_pawn_quiet_moves(pawns: Bitboard, occupancies: Bitboard) -> Vec { - let mut moves: Vec = vec![]; + let mut moves = vec![]; let empty = !occupancies; let mut single_push_targets = (pawns << 8) & empty; while single_push_targets != 0 { let to = single_push_targets.trailing_zeros(); let from = to - 8; - moves.push(Move::new(from, to)); + + if from as u64 & 0xff000000000000 != 0 { + moves.extend([ + Move::new_with_type(from, to, MoveType::Promotion(Promote::Knight)), + Move::new_with_type(from, to, MoveType::Promotion(Promote::Bishop)), + Move::new_with_type(from, to, MoveType::Promotion(Promote::Rook)), + Move::new_with_type(from, to, MoveType::Promotion(Promote::Queen)), + ]); + } else { + moves.push(Move::new(from, to)); + } single_push_targets &= single_push_targets - 1; } let single_push_targets = (pawns << 8) & empty; @@ -68,29 +67,40 @@ fn white_pawn_quiet_moves(pawns: Bitboard, occupancies: Bitboard) -> Vec { while double_push_targets != 0 { let to = double_push_targets.trailing_zeros(); let from = to - 16; - moves.push(Move::new(from, to)); + moves.push(Move::new_with_type(from, to, MoveType::DoublePush)); double_push_targets &= double_push_targets - 1; } moves } fn black_pawn_quiet_moves(pawns: Bitboard, occupancies: Bitboard) -> Vec { - let mut moves: Vec = vec![]; + let mut moves = vec![]; let empty = !occupancies; let mut single_push_targets = (pawns >> 8) & empty; while single_push_targets != 0 { let to = single_push_targets.trailing_zeros(); let from = to + 8; - moves.push(Move::new(from, to)); + + if from as u64 & 0xff000000000000 != 0 { + moves.extend([ + Move::new_with_type(from, to, MoveType::Promotion(Promote::Knight)), + Move::new_with_type(from, to, MoveType::Promotion(Promote::Bishop)), + Move::new_with_type(from, to, MoveType::Promotion(Promote::Rook)), + Move::new_with_type(from, to, MoveType::Promotion(Promote::Queen)), + ]); + } else { + moves.push(Move::new(from, to)); + } single_push_targets &= single_push_targets - 1; } + let single_push_targets = (pawns >> 8) & empty; let mut double_push_targets = (single_push_targets >> 8) & empty & RANK_5; while double_push_targets != 0 { let to = double_push_targets.trailing_zeros(); let from = to + 16; - moves.push(Move::new(from, to)); + moves.push(Move::new_with_type(from, to, MoveType::DoublePush)); double_push_targets &= double_push_targets - 1; } moves @@ -101,27 +111,51 @@ fn white_pawn_capture_moves( enemy_occupancies: Bitboard, en_passant_square: Option, ) -> Vec { - let mut moves: Vec = vec![]; + let mut moves = vec![]; let mut w_pawns_capture_east = pawns & ((enemy_occupancies >> 9) & NOT_H_FILE); let mut w_pawns_capture_west = pawns & ((enemy_occupancies >> 7) & NOT_A_FILE); while w_pawns_capture_east != 0 { let from = w_pawns_capture_east.trailing_zeros(); let to = from + 9; - moves.push(Move::new(from, to)); + + if from as u64 & 0xff000000000000 != 0 { + moves.extend([ + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Knight)), + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Bishop)), + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Rook)), + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Queen)), + ]); + } else { + moves.push(Move::new_with_type(from, to, MoveType::Capture)); + } w_pawns_capture_east &= w_pawns_capture_east - 1; } while w_pawns_capture_west != 0 { let from = w_pawns_capture_west.trailing_zeros(); let to = from + 7; - moves.push(Move::new(from, to)); + + if from as u64 & 0xff000000000000 != 0 { + moves.extend([ + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Knight)), + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Bishop)), + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Rook)), + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Queen)), + ]); + } else { + moves.push(Move::new_with_type(from, to, MoveType::Capture)); + } w_pawns_capture_west &= w_pawns_capture_west - 1; } if let Some(en_passant_square) = en_passant_square { let attacked_from = get_pawn_attacks(en_passant_square as usize, Color::Black); let result = attacked_from & pawns; - moves.push(Move::new(result.trailing_zeros(), en_passant_square as u32)); + moves.push(Move::new_with_type( + result.trailing_zeros(), + en_passant_square as u32, + MoveType::EnPassant, + )); }; moves } @@ -131,44 +165,74 @@ fn black_pawn_capture_moves( enemy_occupancies: Bitboard, en_passant_square: Option, ) -> Vec { - let mut moves: Vec = vec![]; + let mut moves = vec![]; let mut b_pawns_capture_east = pawns & ((enemy_occupancies << 7) & NOT_H_FILE); let mut b_pawns_capture_west = pawns & ((enemy_occupancies << 9) & NOT_A_FILE); while b_pawns_capture_east != 0 { let from = b_pawns_capture_east.trailing_zeros(); let to = from - 7; - moves.push(Move::new(from, to)); + if from as u64 & 0xff00 != 0 { + moves.extend([ + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Knight)), + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Bishop)), + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Rook)), + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Queen)), + ]); + } else { + moves.push(Move::new_with_type(from, to, MoveType::Capture)); + } b_pawns_capture_east &= b_pawns_capture_east - 1; } while b_pawns_capture_west != 0 { let from = b_pawns_capture_west.trailing_zeros(); let to = from - 9; - moves.push(Move::new(from, to)); + if from as u64 & 0xff00 != 0 { + moves.extend([ + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Knight)), + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Bishop)), + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Rook)), + Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Queen)), + ]); + } else { + moves.push(Move::new_with_type(from, to, MoveType::Capture)); + } b_pawns_capture_west &= b_pawns_capture_west - 1; } if let Some(en_passant_square) = en_passant_square { let attacked_from = get_pawn_attacks(en_passant_square as usize, Color::White); let result = attacked_from & pawns; - moves.push(Move::new(result.trailing_zeros(), en_passant_square as u32)); + moves.push(Move::new_with_type( + result.trailing_zeros(), + en_passant_square as u32, + MoveType::EnPassant, + )); }; moves } -pub fn knight_pseudo_moves(mut knights: Bitboard, occupancies: Bitboard) -> Vec { - let mut moves: Vec = vec![]; - let empty = !occupancies; +pub fn knight_pseudo_moves( + mut knights: Bitboard, + all_occupancies: Bitboard, + own_occupancies: Bitboard, +) -> Vec { + let mut moves = vec![]; + let enemy_occupancies = all_occupancies ^ own_occupancies; while knights != 0 { let knight_square = knights.trailing_zeros() as usize; let from = knight_square as u32; - let mut attacks = get_knight_attacks(knight_square) & empty; + let mut attacks = get_knight_attacks(knight_square) & !own_occupancies; while attacks != 0 { - moves.push(Move::new(from, attacks.trailing_zeros())); - + let attack_sq = attacks.trailing_zeros(); + if (1_u64 << attack_sq) & enemy_occupancies != 0 { + moves.push(Move::new_with_type(from, attack_sq, MoveType::Capture)); + } else { + moves.push(Move::new(from, attack_sq)); + } attacks &= attacks - 1; } knights &= knights - 1; @@ -181,16 +245,21 @@ pub fn bishop_pseudo_moves( all_occupancies: Bitboard, own_occupancies: Bitboard, ) -> Vec { - let mut moves: Vec = vec![]; - let empty = !own_occupancies; + let mut moves = vec![]; + let enemy_occupancies = all_occupancies ^ own_occupancies; while bishops != 0 { let bishop_square = bishops.trailing_zeros() as usize; let from = bishop_square as u32; - let mut attacks = get_bishop_attacks(all_occupancies, bishop_square) & empty; + let mut attacks = get_bishop_attacks(all_occupancies, bishop_square) & !own_occupancies; while attacks != 0 { - moves.push(Move::new(from, attacks.trailing_zeros())); + let attack_sq = attacks.trailing_zeros(); + if (1_u64 << attack_sq) & enemy_occupancies != 0 { + moves.push(Move::new_with_type(from, attack_sq, MoveType::Capture)); + } else { + moves.push(Move::new(from, attack_sq)); + } attacks &= attacks - 1; } bishops &= bishops - 1; @@ -203,16 +272,21 @@ pub fn rook_pseudo_moves( all_occupancies: Bitboard, own_occupancies: Bitboard, ) -> Vec { - let mut moves: Vec = vec![]; - let empty = !own_occupancies; + let mut moves = vec![]; + let enemy_occupancies = all_occupancies ^ own_occupancies; while rooks != 0 { let rook_square = rooks.trailing_zeros() as usize; let from = rook_square as u32; - let mut attacks = get_rook_attacks(all_occupancies, rook_square) & empty; + let mut attacks = get_rook_attacks(all_occupancies, rook_square) & !own_occupancies; while attacks != 0 { - moves.push(Move::new(from, attacks.trailing_zeros())); + let attack_sq = attacks.trailing_zeros(); + if (1_u64 << attack_sq) & enemy_occupancies != 0 { + moves.push(Move::new_with_type(from, attack_sq, MoveType::Capture)); + } else { + moves.push(Move::new(from, attack_sq)); + } attacks &= attacks - 1; } rooks &= rooks - 1; @@ -225,16 +299,21 @@ pub fn queen_pseudo_moves( all_occupancies: Bitboard, own_occupancies: Bitboard, ) -> Vec { - let mut moves: Vec = vec![]; - let empty = !own_occupancies; + let mut moves = vec![]; + let enemy_occupancies = all_occupancies ^ own_occupancies; while queens != 0 { let queen_square = queens.trailing_zeros() as usize; let from = queen_square as u32; - let mut attacks = get_queen_attacks(all_occupancies, queen_square) & empty; + let mut attacks = get_queen_attacks(all_occupancies, queen_square) & !own_occupancies; while attacks != 0 { - moves.push(Move::new(from, attacks.trailing_zeros())); + let attack_sq = attacks.trailing_zeros(); + if (1_u64 << attack_sq) & enemy_occupancies != 0 { + moves.push(Move::new_with_type(from, attack_sq, MoveType::Capture)); + } else { + moves.push(Move::new(from, attack_sq)); + } attacks &= attacks - 1; } queens &= queens - 1; @@ -249,14 +328,19 @@ pub fn king_pseudo_moves( board: &Board, color: Color, ) -> Vec { - let mut moves: Vec = vec![]; - let empty: u64 = !own_occupancies; + let mut moves = vec![]; let king_square = king.trailing_zeros() as usize; let from = king_square as u32; - let mut attacks = get_king_attacks(king_square) & empty; + let mut attacks = get_king_attacks(king_square) & !own_occupancies; + let enemy_occupancies = all_occupancies ^ own_occupancies; while attacks != 0 { - moves.push(Move::new(from, attacks.trailing_zeros())); + let attack_sq = attacks.trailing_zeros(); + if (1_u64 << attack_sq) & enemy_occupancies != 0 { + moves.push(Move::new_with_type(from, attack_sq, MoveType::Capture)); + } else { + moves.push(Move::new(from, attack_sq)); + } attacks &= attacks - 1; } @@ -273,7 +357,7 @@ fn king_castling_moves(board: &Board, color: Color, all_occupancies: Bitboard) - let mut add_move_if_empty_path = |path_mask, king_to| { if all_occupancies & path_mask != 0 { - moves.push(Move::new(king_from, king_to)) + moves.push(Move::new_with_type(king_from, king_to, MoveType::Castle)) } }; @@ -331,19 +415,19 @@ mod tests { let new_game = from_fen(FEN_PAWN_MOVES)?; let expected = vec![ Move::new(9, 17), - Move::new(9, 25), + Move::new_with_type(9, 25, MoveType::DoublePush), Move::new(10, 18), - Move::new(10, 26), + Move::new_with_type(10, 26, MoveType::DoublePush), Move::new(14, 22), - Move::new(14, 30), + Move::new_with_type(14, 30, MoveType::DoublePush), Move::new(15, 23), - Move::new(15, 31), + Move::new_with_type(15, 31, MoveType::DoublePush), Move::new(29, 37), Move::new(32, 40), - Move::new(32, 41), - Move::new(36, 43), + Move::new_with_type(32, 41, MoveType::Capture), + Move::new_with_type(36, 43, MoveType::Capture), Move::new(36, 44), - Move::new(36, 45), + Move::new_with_type(36, 45, MoveType::Capture), ]; let mut actual = new_game.board.pseudo_moves(Color::White, Kind::Pawn); @@ -351,12 +435,12 @@ mod tests { assert_eq!(expected, actual); let expected = vec![ - Move::new(41, 32), + Move::new_with_type(41, 32, MoveType::Capture), Move::new(41, 33), Move::new(43, 35), - Move::new(43, 36), + Move::new_with_type(43, 36, MoveType::Capture), Move::new(48, 40), - Move::new(55, 39), + Move::new_with_type(55, 39, MoveType::DoublePush), Move::new(55, 47), ]; let mut actual = new_game.board.pseudo_moves(Color::Black, Kind::Pawn); @@ -380,7 +464,7 @@ mod tests { Move::new(21, 11), Move::new(21, 31), Move::new(21, 36), - Move::new(21, 38), + Move::new_with_type(21, 38, MoveType::Capture), ]; let mut actual = new_game.board.pseudo_moves(Color::White, Kind::Knight); actual.sort(); @@ -403,7 +487,7 @@ mod tests { Move::new(26, 35), Move::new(26, 40), Move::new(26, 44), - Move::new(26, 53), + Move::new_with_type(26, 53, MoveType::Capture), ]; let mut actual = new_game.board.pseudo_moves(Color::White, Kind::Bishop); actual.sort(); @@ -424,14 +508,14 @@ mod tests { Move::new(11, 35), Move::new(11, 43), Move::new(11, 51), - Move::new(11, 59), + Move::new_with_type(11, 59, MoveType::Capture), Move::new(12, 13), Move::new(12, 14), Move::new(12, 15), Move::new(12, 20), Move::new(12, 28), Move::new(12, 36), - Move::new(12, 44), + Move::new_with_type(12, 44, MoveType::Capture), ]; let mut actual = new_game.board.pseudo_moves(Color::White, Kind::Rook); actual.sort(); @@ -454,7 +538,7 @@ mod tests { Move::new(17, 25), Move::new(17, 33), Move::new(17, 41), - Move::new(17, 49), + Move::new_with_type(17, 49, MoveType::Capture), ]; let mut actual = new_game.board.pseudo_moves(Color::White, Kind::Queen); actual.sort(); @@ -487,7 +571,7 @@ mod tests { assert_eq!(expected, actual); let new_game_2 = from_fen(FEN_KING_MOVES[1])?; - let expected = vec![Move::new(4, 2), Move::new(4, 3)]; + let expected = vec![Move::new_with_type(4, 2, MoveType::Castle), Move::new(4, 3)]; let mut actual = new_game_2.board.pseudo_moves(Color::White, Kind::King); actual.sort(); assert_eq!(expected, actual); @@ -498,7 +582,7 @@ mod tests { Move::new(60, 53), Move::new(60, 59), Move::new(60, 61), - Move::new(60, 62), + Move::new_with_type(60, 62, MoveType::Castle), ]; let mut actual = new_game_3.board.pseudo_moves(Color::Black, Kind::King); actual.sort();