diff --git a/src/board.rs b/src/board.rs index 27dc7d1..31893c1 100644 --- a/src/board.rs +++ b/src/board.rs @@ -13,6 +13,10 @@ 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 { @@ -96,6 +100,37 @@ impl Board { || enemy[Kind::Queen.idx()].bitboard & get_queen_attacks(all_occupancies, sq) != 0 || enemy[Kind::King.idx()].bitboard & get_king_attacks(sq) != 0 } + + 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 { + Color::White => ( + self.white_pieces[kind.idx()].bitboard, + self.get_black_occupancies(), + self.get_white_occupancies(), + ), + Color::Black => ( + self.black_pieces[kind.idx()].bitboard, + self.get_white_occupancies(), + self.get_black_occupancies(), + ), + }; + + match kind { + Kind::Pawn => pawn_pseudo_moves( + pieces, + all_occupancies, + enemy_occupancies, + self.state, + color, + ), + Kind::Knight => knight_pseudo_moves(pieces, 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), + Kind::King => king_pseudo_moves(pieces, own_occupancies), + } + } } impl Default for Board { @@ -167,12 +202,12 @@ pub enum Kind { } impl Kind { - fn idx(&self) -> usize { + pub const fn idx(&self) -> usize { *self as usize } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum Color { White, Black, @@ -184,9 +219,7 @@ mod tests { use super::*; - const FEN_EXAMPLE: [&str; 1] = [ - "8/6P1/4n2b/1p6/1Kp5/6n1/4b3/1k6 w - - 0 1", - ]; + const FEN_EXAMPLE: [&str; 1] = ["8/6P1/4n2b/1p6/1Kp5/6n1/4b3/1k6 w - - 0 1"]; #[test] fn test_get_all_occupancies() -> Result<(), String> { diff --git a/src/main.rs b/src/main.rs index b1805af..b7c3fdf 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 movegen; use game::Game; diff --git a/src/movegen.rs b/src/movegen.rs new file mode 100644 index 0000000..4760655 --- /dev/null +++ b/src/movegen.rs @@ -0,0 +1,422 @@ +use crate::attack::{ + get_bishop_attacks, get_king_attacks, get_knight_attacks, get_pawn_attacks, get_queen_attacks, + get_rook_attacks, +}; +use crate::board::Color; +use crate::game::State; +use u64 as Bitboard; + +const NOT_A_FILE: Bitboard = 0xfefefefefefefefe; +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, + enemy_occupancies: Bitboard, + state: State, + color: Color, +) -> Vec { + let mut moves: Vec = vec![]; + match color { + Color::White => { + moves.extend(white_pawn_quiet_moves(pawns, all_occupancies)); + moves.extend(white_pawn_capture_moves(pawns, enemy_occupancies, state)); + } + Color::Black => { + moves.extend(black_pawn_capture_moves(pawns, enemy_occupancies, state)); + 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 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)); + 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_4; + while double_push_targets != 0 { + let to = double_push_targets.trailing_zeros(); + let from = to - 16; + moves.push(Move::new(from, to)); + double_push_targets &= double_push_targets - 1; + } + moves +} + +fn black_pawn_quiet_moves(pawns: Bitboard, occupancies: Bitboard) -> Vec { + let mut moves: Vec = 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)); + 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)); + double_push_targets &= double_push_targets - 1; + } + moves +} + +fn white_pawn_capture_moves( + pawns: Bitboard, + enemy_occupancies: Bitboard, + state: State, +) -> Vec { + let mut moves: Vec = 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)); + 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)); + w_pawns_capture_west &= w_pawns_capture_west - 1; + } + + let epts = state.get_en_passant_target_square(); + if epts != 0 { + let attacked_from = get_pawn_attacks(epts as usize, Color::Black); + let result = attacked_from & pawns; + moves.push(Move::new(result.trailing_zeros(), epts as u32)); + } + moves +} + +fn black_pawn_capture_moves( + pawns: Bitboard, + enemy_occupancies: Bitboard, + state: State, +) -> Vec { + let mut moves: Vec = 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)); + 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)); + b_pawns_capture_west &= b_pawns_capture_west - 1; + } + + let epts = state.get_en_passant_target_square(); + if epts != 0 { + let attacked_from = get_pawn_attacks(epts as usize, Color::White); + let result = attacked_from & pawns; + moves.push(Move::new(result.trailing_zeros(), epts as u32)); + } + moves +} + +pub fn knight_pseudo_moves(mut knights: Bitboard, occupancies: Bitboard) -> Vec { + let mut moves: Vec = vec![]; + let empty = !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; + + while attacks != 0 { + moves.push(Move::new(from, attacks.trailing_zeros())); + + attacks &= attacks - 1; + } + knights &= knights - 1; + } + moves +} + +pub fn bishop_pseudo_moves( + mut bishops: Bitboard, + all_occupancies: Bitboard, + own_occupancies: Bitboard, +) -> Vec { + let mut moves: Vec = vec![]; + let empty = !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; + + while attacks != 0 { + moves.push(Move::new(from, attacks.trailing_zeros())); + attacks &= attacks - 1; + } + bishops &= bishops - 1; + } + moves + // moves arkoudaki se agapo polu! to gataki sou +} + +pub fn rook_pseudo_moves( + mut rooks: Bitboard, + all_occupancies: Bitboard, + own_occupancies: Bitboard, +) -> Vec { + let mut moves: Vec = vec![]; + let empty = !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; + + while attacks != 0 { + moves.push(Move::new(from, attacks.trailing_zeros())); + attacks &= attacks - 1; + } + rooks &= rooks - 1; + } + moves +} + +pub fn queen_pseudo_moves( + mut queens: Bitboard, + all_occupancies: Bitboard, + own_occupancies: Bitboard, +) -> Vec { + let mut moves: Vec = vec![]; + let empty = !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; + + while attacks != 0 { + moves.push(Move::new(from, attacks.trailing_zeros())); + attacks &= attacks - 1; + } + queens &= queens - 1; + } + moves +} + +pub fn king_pseudo_moves(king: Bitboard, occupancies: Bitboard) -> Vec { + let mut moves: Vec = vec![]; + let empty: u64 = !occupancies; + let king_square = king.trailing_zeros() as usize; + let from = king_square as u32; + let mut attacks = get_king_attacks(king_square) & empty; + + while attacks != 0 { + moves.push(Move::new(from, attacks.trailing_zeros())); + attacks &= attacks - 1; + } + moves +} + +#[cfg(test)] +mod tests { + use crate::{attack::init_attacks, fen::from_fen}; + + use super::*; + use crate::board::Kind; + + const FEN_PAWN_MOVES: &str = "r1bqk2r/p4pbp/1pnp1np1/P3P1B1/3NNP2/8/1PP3PP/R2QKB1R w - - 0 1"; + + #[test] + fn test_pawn_pseudo_moves() -> Result<(), String> { + init_attacks(); + let new_game = from_fen(FEN_PAWN_MOVES)?; + let expected = vec![ + Move::new(9, 17), + Move::new(9, 25), + Move::new(10, 18), + Move::new(10, 26), + Move::new(14, 22), + Move::new(14, 30), + Move::new(15, 23), + Move::new(15, 31), + Move::new(29, 37), + Move::new(32, 40), + Move::new(32, 41), + Move::new(36, 43), + Move::new(36, 44), + Move::new(36, 45), + ]; + + let mut actual = new_game.board.pseudo_moves(Color::White, Kind::Pawn); + actual.sort(); + assert_eq!(expected, actual); + + let expected = vec![ + Move::new(41, 32), + Move::new(41, 33), + Move::new(43, 35), + Move::new(43, 36), + Move::new(48, 40), + Move::new(55, 39), + Move::new(55, 47), + ]; + let mut actual = new_game.board.pseudo_moves(Color::Black, Kind::Pawn); + actual.sort(); + assert_eq!(expected, actual); + + Ok(()) + } + + const FEN_KNIGHT_MOVES: &str = "r1bqkbnr/ppp2p1p/2np4/6p1/3PPp2/5N2/PPP3PP/RNBQKB1R w - - 0 1"; + + #[test] + fn test_knight_pseudo_moves() -> Result<(), String> { + init_attacks(); + let new_game = from_fen(FEN_KNIGHT_MOVES)?; + let expected = vec![ + Move::new(1, 11), + Move::new(1, 16), + Move::new(1, 18), + Move::new(21, 6), + Move::new(21, 11), + Move::new(21, 31), + Move::new(21, 36), + Move::new(21, 38), + ]; + let mut actual = new_game.board.pseudo_moves(Color::White, Kind::Knight); + actual.sort(); + assert_eq!(expected, actual); + + Ok(()) + } + + const FEN_BISHOP_MOVES: &str = "rn1qk1nr/pbppbppp/1p6/8/2B1Pp2/3P1N2/PPB3PP/RN1QK2R w - - 0 1"; + + #[test] + fn test_bishop_pseudo_moves() -> Result<(), String> { + init_attacks(); + let new_game = from_fen(FEN_BISHOP_MOVES)?; + let expected = vec![ + Move::new(10, 17), + Move::new(10, 24), + Move::new(26, 17), + Move::new(26, 33), + Move::new(26, 35), + Move::new(26, 40), + Move::new(26, 44), + Move::new(26, 53), + ]; + let mut actual = new_game.board.pseudo_moves(Color::White, Kind::Bishop); + actual.sort(); + assert_eq!(expected, actual); + + Ok(()) + } + + const FEN_ROOK_MOVES: &str = "rnbqkb1r/pp3p1p/4pn2/6p1/2P5/2N5/P1BRR3/3QK1N1 w kq - 0 1"; + + #[test] + fn test_rook_pseudo_moves() -> Result<(), String> { + init_attacks(); + let new_game = from_fen(FEN_ROOK_MOVES)?; + let expected = vec![ + Move::new(11, 19), + Move::new(11, 27), + Move::new(11, 35), + Move::new(11, 43), + Move::new(11, 51), + Move::new(11, 59), + 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), + ]; + let mut actual = new_game.board.pseudo_moves(Color::White, Kind::Rook); + actual.sort(); + assert_eq!(expected, actual); + + Ok(()) + } + + const FEN_QUEEN_MOVES: &str = "rnbqkb1r/ppp1pp1p/5np1/3p4/2PP4/1QN5/PP2PPPP/R1B1KBNR w - - 0 1"; + + #[test] + fn test_queen_pseudo_moves() -> Result<(), String> { + init_attacks(); + let new_game = from_fen(FEN_QUEEN_MOVES)?; + let expected = vec![ + Move::new(17, 3), + Move::new(17, 10), + Move::new(17, 16), + Move::new(17, 24), + Move::new(17, 25), + Move::new(17, 33), + Move::new(17, 41), + Move::new(17, 49), + ]; + let mut actual = new_game.board.pseudo_moves(Color::White, Kind::Queen); + actual.sort(); + assert_eq!(expected, actual); + + Ok(()) + } + + const FEN_KING_MOVES: &str = "R7/6k1/8/8/P6P/6K1/8/4r3 w - - 0 1"; + + #[test] + fn test_king_pseudo_moves() -> Result<(), String> { + init_attacks(); + let new_game = from_fen(FEN_KING_MOVES)?; + let expected = vec![ + Move::new(22, 13), + Move::new(22, 14), + Move::new(22, 15), + Move::new(22, 21), + Move::new(22, 23), + Move::new(22, 29), + Move::new(22, 30), + ]; + let mut actual = new_game.board.pseudo_moves(Color::White, Kind::King); + actual.sort(); + assert_eq!(expected, actual); + + Ok(()) + } +}