use super::{Bitboard, Rank, Square}; /// A direction on the board. Either along the rook, bishop, or knight directions #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Direction { North, West, South, East, NorthWest, SouthWest, SouthEast, NorthEast, NorthNorthWest, NorthWestWest, SouthWestWest, SouthSouthWest, SouthSouthEast, SouthEastEast, NorthEastEast, NorthNorthEast, } impl Direction { /// Directions that a rook could use. pub const ROOK_DIRECTIONS: [Self; 4] = [Self::North, Self::West, Self::South, Self::East]; /// Directions that a bishop could use. pub const BISHOP_DIRECTIONS: [Self; 4] = [ Self::NorthWest, Self::SouthWest, Self::SouthEast, Self::NorthEast, ]; /// Directions that a knight could use. pub const KNIGHT_DIRECTIONS: [Self; 8] = [ Self::NorthNorthWest, Self::NorthWestWest, Self::SouthWestWest, Self::SouthSouthWest, Self::SouthSouthEast, Self::SouthEastEast, Self::NorthEastEast, Self::NorthNorthEast, ]; /// Iterate over all directions a rook can take. pub fn iter_rook() -> impl Iterator { Self::ROOK_DIRECTIONS.iter().cloned() } /// Iterate over all directions a bishop can take. pub fn iter_bishop() -> impl Iterator { Self::BISHOP_DIRECTIONS.iter().cloned() } /// Iterate over all directions a queen or king can take. pub fn iter_royalty() -> impl Iterator { Self::iter_rook().chain(Self::iter_bishop()) } /// Iterate over all directions a knight can take. pub fn iter_knight() -> impl Iterator { Self::KNIGHT_DIRECTIONS.iter().cloned() } /// Move a [Square] along the given [Direction], unless it would wrap at the end of the board pub fn move_square(self, square: Square) -> Option { let res = self.move_board(square.into_bitboard()); res.into_iter().next() } /// Move every piece on a board along the given direction. Do not wrap around the board. #[inline(always)] pub fn move_board(self, board: Bitboard) -> Bitboard { // No need to filter for A/H ranks thanks to wrapping match self { Self::North => (board - Rank::Eighth.into_bitboard()) << 1, Self::West => board >> 8, Self::South => (board - Rank::First.into_bitboard()) >> 1, Self::East => board << 8, Self::NorthWest => (board - Rank::Eighth.into_bitboard()) >> 7, Self::SouthWest => (board - Rank::First.into_bitboard()) >> 9, Self::SouthEast => (board - Rank::First.into_bitboard()) << 7, Self::NorthEast => (board - Rank::Eighth.into_bitboard()) << 9, Self::NorthNorthWest => { (board - Rank::Eighth.into_bitboard() - Rank::Seventh.into_bitboard()) >> 6 } Self::NorthWestWest => (board - Rank::Eighth.into_bitboard()) >> 15, Self::SouthWestWest => (board - Rank::First.into_bitboard()) >> 17, Self::SouthSouthWest => { (board - Rank::First.into_bitboard() - Rank::Second.into_bitboard()) >> 10 } Self::SouthSouthEast => { (board - Rank::First.into_bitboard() - Rank::Second.into_bitboard()) << 6 } Self::SouthEastEast => (board - Rank::First.into_bitboard()) << 15, Self::NorthEastEast => (board - Rank::Eighth.into_bitboard()) << 17, Self::NorthNorthEast => { (board - Rank::Eighth.into_bitboard() - Rank::Seventh.into_bitboard()) << 10 } } } /// Slide a board along the given [Direction], i.e: return all successive applications of /// [Direction::move_square] until no new squares can be reached. /// It does not make sense to use this method with knight-only directions, and it will panic in /// debug-mode if it happens. #[inline(always)] pub fn slide_square(self, square: Square) -> Bitboard { self.slide_board(square.into_bitboard()) } /// Slide a board along the given [Direction], i.e: return all successive applications of /// [Direction::move_board] until no new squares can be reached. /// It does not make sense to use this method with knight-only directions, and it will panic in /// debug-mode if it happens. #[inline(always)] pub fn slide_board(self, board: Bitboard) -> Bitboard { self.slide_board_with_blockers(board, Bitboard::EMPTY) } /// Slide a board along the given [Direction], i.e: return all successive applications of /// [Direction::move_board] until no new squares can be reached. /// Take into account the `blockers` [Bitboard]: a combination of all pieces on the board which /// cannot be slid over. The slide is over once a square that is part of `blockers` is reached. /// It does not make sense to use this method with knight-only directions, and it will panic in /// debug-mode if it happens. #[inline(always)] pub fn slide_board_with_blockers(self, mut board: Bitboard, blockers: Bitboard) -> Bitboard { debug_assert!(!Self::KNIGHT_DIRECTIONS.contains(&self)); let mut res = Default::default(); while !board.is_empty() { board = self.move_board(board); res |= board; if !(board & blockers).is_empty() { break; } } res } } #[cfg(test)] mod test { use super::*; use crate::board::{File, Rank}; #[test] fn north() { assert_eq!( Direction::North.move_board(Square::A1.into_bitboard()), Square::A2.into_bitboard() ); assert_eq!( Direction::North.move_board(Square::A2.into_bitboard()), Square::A3.into_bitboard() ); assert_eq!( Direction::North.move_board(Square::A7.into_bitboard()), Square::A8.into_bitboard() ); assert_eq!( Direction::North.move_board(Square::A8.into_bitboard()), Bitboard::EMPTY ); } #[test] fn west() { assert_eq!( Direction::West.move_board(Square::A1.into_bitboard()), Bitboard::EMPTY ); assert_eq!( Direction::West.move_board(Square::B1.into_bitboard()), Square::A1.into_bitboard() ); assert_eq!( Direction::West.move_board(Square::G1.into_bitboard()), Square::F1.into_bitboard() ); assert_eq!( Direction::West.move_board(Square::H1.into_bitboard()), Square::G1.into_bitboard() ); } #[test] fn south() { assert_eq!( Direction::South.move_board(Square::A1.into_bitboard()), Bitboard::EMPTY ); assert_eq!( Direction::South.move_board(Square::A2.into_bitboard()), Square::A1.into_bitboard() ); assert_eq!( Direction::South.move_board(Square::A7.into_bitboard()), Square::A6.into_bitboard() ); assert_eq!( Direction::South.move_board(Square::A8.into_bitboard()), Square::A7.into_bitboard() ); } #[test] fn east() { assert_eq!( Direction::East.move_board(Square::A1.into_bitboard()), Square::B1.into_bitboard() ); assert_eq!( Direction::East.move_board(Square::B1.into_bitboard()), Square::C1.into_bitboard() ); assert_eq!( Direction::East.move_board(Square::G1.into_bitboard()), Square::H1.into_bitboard() ); assert_eq!( Direction::East.move_board(Square::H1.into_bitboard()), Bitboard::EMPTY ); } #[test] fn north_west() { assert_eq!( Direction::NorthWest.move_board(Square::A1.into_bitboard()), Bitboard::EMPTY ); assert_eq!( Direction::NorthWest.move_board(Square::B1.into_bitboard()), Square::A2.into_bitboard() ); assert_eq!( Direction::NorthWest.move_board(Square::H1.into_bitboard()), Square::G2.into_bitboard() ); assert_eq!( Direction::NorthWest.move_board(Square::A8.into_bitboard()), Bitboard::EMPTY ); assert_eq!( Direction::NorthWest.move_board(Square::B8.into_bitboard()), Bitboard::EMPTY ); assert_eq!( Direction::NorthWest.move_board(Square::H8.into_bitboard()), Bitboard::EMPTY ); } #[test] fn south_west() { assert_eq!( Direction::SouthWest.move_board(Square::A1.into_bitboard()), Bitboard::EMPTY ); assert_eq!( Direction::SouthWest.move_board(Square::B1.into_bitboard()), Bitboard::EMPTY ); assert_eq!( Direction::SouthWest.move_board(Square::H1.into_bitboard()), Bitboard::EMPTY ); assert_eq!( Direction::SouthWest.move_board(Square::A8.into_bitboard()), Bitboard::EMPTY ); assert_eq!( Direction::SouthWest.move_board(Square::B8.into_bitboard()), Square::A7.into_bitboard() ); assert_eq!( Direction::SouthWest.move_board(Square::H8.into_bitboard()), Square::G7.into_bitboard() ); } #[test] fn south_east() { assert_eq!( Direction::SouthEast.move_board(Square::A1.into_bitboard()), Bitboard::EMPTY ); assert_eq!( Direction::SouthEast.move_board(Square::B1.into_bitboard()), Bitboard::EMPTY ); assert_eq!( Direction::SouthEast.move_board(Square::H1.into_bitboard()), Bitboard::EMPTY ); assert_eq!( Direction::SouthEast.move_board(Square::A8.into_bitboard()), Square::B7.into_bitboard() ); assert_eq!( Direction::SouthEast.move_board(Square::B8.into_bitboard()), Square::C7.into_bitboard() ); assert_eq!( Direction::SouthEast.move_board(Square::H8.into_bitboard()), Bitboard::EMPTY ); } #[test] fn north_east() { assert_eq!( Direction::NorthEast.move_board(Square::A1.into_bitboard()), Square::B2.into_bitboard() ); assert_eq!( Direction::NorthEast.move_board(Square::B1.into_bitboard()), Square::C2.into_bitboard() ); assert_eq!( Direction::NorthEast.move_board(Square::H1.into_bitboard()), Bitboard::EMPTY ); assert_eq!( Direction::NorthEast.move_board(Square::A8.into_bitboard()), Bitboard::EMPTY ); assert_eq!( Direction::NorthEast.move_board(Square::B8.into_bitboard()), Bitboard::EMPTY ); assert_eq!( Direction::NorthEast.move_board(Square::H8.into_bitboard()), Bitboard::EMPTY ); } #[test] fn north_north_west() { assert_eq!( Direction::NorthNorthWest.move_board(Square::A1.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthNorthWest.move_board(Square::B2.into_bitboard()), Square::A4.into_bitboard() ); assert_eq!( Direction::NorthNorthWest.move_board(Square::H1.into_bitboard()), Square::G3.into_bitboard() ); assert_eq!( Direction::NorthNorthWest.move_board(Square::G2.into_bitboard()), Square::F4.into_bitboard() ); assert_eq!( Direction::NorthNorthWest.move_board(Square::A8.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthNorthWest.move_board(Square::B7.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthNorthWest.move_board(Square::H8.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthNorthWest.move_board(Square::G7.into_bitboard()), Bitboard::EMPTY, ); } #[test] fn north_west_west() { assert_eq!( Direction::NorthWestWest.move_board(Square::A1.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthWestWest.move_board(Square::B2.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthWestWest.move_board(Square::H1.into_bitboard()), Square::F2.into_bitboard() ); assert_eq!( Direction::NorthWestWest.move_board(Square::G2.into_bitboard()), Square::E3.into_bitboard() ); assert_eq!( Direction::NorthWestWest.move_board(Square::A8.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthWestWest.move_board(Square::B7.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthWestWest.move_board(Square::H8.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthWestWest.move_board(Square::G7.into_bitboard()), Square::E8.into_bitboard() ); } #[test] fn south_west_west() { assert_eq!( Direction::SouthWestWest.move_board(Square::A1.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthWestWest.move_board(Square::B2.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthWestWest.move_board(Square::H1.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthWestWest.move_board(Square::G2.into_bitboard()), Square::E1.into_bitboard() ); assert_eq!( Direction::SouthWestWest.move_board(Square::A8.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthWestWest.move_board(Square::B7.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthWestWest.move_board(Square::H8.into_bitboard()), Square::F7.into_bitboard() ); assert_eq!( Direction::SouthWestWest.move_board(Square::G7.into_bitboard()), Square::E6.into_bitboard() ); } #[test] fn south_south_west() { assert_eq!( Direction::SouthSouthWest.move_board(Square::A1.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthSouthWest.move_board(Square::B2.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthSouthWest.move_board(Square::H1.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthSouthWest.move_board(Square::G2.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthSouthWest.move_board(Square::A8.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthSouthWest.move_board(Square::B7.into_bitboard()), Square::A5.into_bitboard() ); assert_eq!( Direction::SouthSouthWest.move_board(Square::H8.into_bitboard()), Square::G6.into_bitboard() ); assert_eq!( Direction::SouthSouthWest.move_board(Square::G7.into_bitboard()), Square::F5.into_bitboard() ); } #[test] fn south_south_east() { assert_eq!( Direction::SouthSouthEast.move_board(Square::A1.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthSouthEast.move_board(Square::B2.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthSouthEast.move_board(Square::H1.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthSouthEast.move_board(Square::G2.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthSouthEast.move_board(Square::A8.into_bitboard()), Square::B6.into_bitboard() ); assert_eq!( Direction::SouthSouthEast.move_board(Square::B7.into_bitboard()), Square::C5.into_bitboard() ); assert_eq!( Direction::SouthSouthEast.move_board(Square::H8.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthSouthEast.move_board(Square::G7.into_bitboard()), Square::H5.into_bitboard() ); } #[test] fn south_east_east() { assert_eq!( Direction::SouthEastEast.move_board(Square::A1.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthEastEast.move_board(Square::B2.into_bitboard()), Square::D1.into_bitboard() ); assert_eq!( Direction::SouthEastEast.move_board(Square::H1.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthEastEast.move_board(Square::G2.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthEastEast.move_board(Square::A8.into_bitboard()), Square::C7.into_bitboard() ); assert_eq!( Direction::SouthEastEast.move_board(Square::B7.into_bitboard()), Square::D6.into_bitboard() ); assert_eq!( Direction::SouthEastEast.move_board(Square::H8.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::SouthEastEast.move_board(Square::G7.into_bitboard()), Bitboard::EMPTY, ); } #[test] fn north_east_east() { assert_eq!( Direction::NorthEastEast.move_board(Square::A1.into_bitboard()), Square::C2.into_bitboard() ); assert_eq!( Direction::NorthEastEast.move_board(Square::B2.into_bitboard()), Square::D3.into_bitboard() ); assert_eq!( Direction::NorthEastEast.move_board(Square::H1.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthEastEast.move_board(Square::G2.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthEastEast.move_board(Square::A8.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthEastEast.move_board(Square::B7.into_bitboard()), Square::D8.into_bitboard() ); assert_eq!( Direction::NorthEastEast.move_board(Square::H8.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthEastEast.move_board(Square::G7.into_bitboard()), Bitboard::EMPTY, ); } #[test] fn north_north_east() { assert_eq!( Direction::NorthNorthEast.move_board(Square::A1.into_bitboard()), Square::B3.into_bitboard() ); assert_eq!( Direction::NorthNorthEast.move_board(Square::B2.into_bitboard()), Square::C4.into_bitboard() ); assert_eq!( Direction::NorthNorthEast.move_board(Square::H1.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthNorthEast.move_board(Square::G2.into_bitboard()), Square::H4.into_bitboard() ); assert_eq!( Direction::NorthNorthEast.move_board(Square::A8.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthNorthEast.move_board(Square::B7.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthNorthEast.move_board(Square::H8.into_bitboard()), Bitboard::EMPTY, ); assert_eq!( Direction::NorthNorthEast.move_board(Square::G7.into_bitboard()), Bitboard::EMPTY, ); } #[test] fn slide() { assert_eq!( Direction::North.slide_square(Square::A1), File::A.into_bitboard() - Square::A1 ); assert_eq!( Direction::West.slide_square(Square::H1), Rank::First.into_bitboard() - Square::H1 ); assert_eq!( Direction::South.slide_square(Square::A8), File::A.into_bitboard() - Square::A8 ); assert_eq!( Direction::East.slide_square(Square::A1), Rank::First.into_bitboard() - Square::A1 ); assert_eq!( Direction::NorthWest.slide_square(Square::H1), Bitboard::ANTI_DIAGONAL - Square::H1 ); assert_eq!( Direction::SouthWest.slide_square(Square::H8), Bitboard::DIAGONAL - Square::H8 ); assert_eq!( Direction::SouthEast.slide_square(Square::A8), Bitboard::ANTI_DIAGONAL - Square::A8 ); assert_eq!( Direction::NorthEast.slide_square(Square::A1), Bitboard::DIAGONAL - Square::A1 ); } #[test] fn blocked_slides() { assert_eq!( Direction::North .slide_board_with_blockers(Square::A1.into_bitboard(), Square::A2.into_bitboard()), Square::A2.into_bitboard() ); assert_eq!( Direction::North .slide_board_with_blockers(Square::A1.into_bitboard(), Square::A3.into_bitboard()), Square::A2 | Square::A3 ); assert_eq!( Direction::North .slide_board_with_blockers(Square::A1.into_bitboard(), Square::A4.into_bitboard()), Square::A2 | Square::A3 | Square::A4 ); // Ensure that the starting square being in `blockers` is not an issue assert_eq!( Direction::North .slide_board_with_blockers(Square::A1.into_bitboard(), Square::A1.into_bitboard()), File::A.into_bitboard() - Square::A1 ); } }