checke_rs/
position.rs

1use lazy_static::lazy_static;
2use regex::{Captures, Regex};
3use thiserror::Error;
4
5use crate::bitboard::{
6    BOTTOM_SQUARES,
7    LEFT_SQUARES,
8    MonoBitBoard,
9    RIGHT_SQUARES,
10    TOP_SQUARES,
11};
12use crate::board::{Board, Player};
13
14#[derive(PartialEq, Debug)]
15pub enum Position {
16    Bottom,
17    Left,
18    BottomLeft,
19    BottomRight,
20    Top,
21    Right,
22    TopRight,
23    TopLeft,
24    Interior,
25}
26
27impl From<MonoBitBoard> for Position {
28    fn from(bitboard: MonoBitBoard) -> Self {
29        let is_left = LEFT_SQUARES.contains(bitboard);
30        let is_right = RIGHT_SQUARES.contains(bitboard);
31        let is_top = TOP_SQUARES.contains(bitboard);
32        let is_bottom = BOTTOM_SQUARES.contains(bitboard);
33
34        match (is_left, is_right, is_top, is_bottom) {
35            (false, false, false, true) => Position::Bottom,
36            (true, false, false, false) => Position::Left,
37            (true, false, false, true) => Position::BottomLeft,
38            (false, true, false, true) => Position::BottomRight,
39            (false, false, true, false) => Position::Top,
40            (false, true, false, false) => Position::Right,
41            (false, true, true, false) => Position::TopRight,
42            (true, false, true, false) => Position::TopLeft,
43            (false, false, false, false) => Position::Interior,
44            _ => panic!("This should be impossible to reach.")
45        }
46    }
47}
48
49/// Error denoting an issue parsing checkers notation.
50#[derive(Debug, Error)]
51pub enum NotationError {
52    #[error("Provided value did not conform to a valid checkers notation format.")]
53    InvalidFormat,
54
55    #[error("Provided value operates outside the realm of a classical checkers board.")]
56    OutOfRange,
57
58    #[error("Provided value represented a piece standing still. The destination square was equal to the source.")]
59    Idle,
60}
61
62#[derive(Debug, Error)]
63#[error("Square could not be converted")]
64pub struct SquareConversionError;
65
66/// Represents every valid square on a classical checkers board.
67#[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive, PartialEq)]
68pub enum Square {
69    One = 1,
70    Two = 2,
71    Three = 3,
72    Four = 4,
73    Five = 5,
74    Six = 6,
75    Seven = 7,
76    Eight = 8,
77    Nine = 9,
78    Ten = 10,
79    Eleven = 11,
80    Twelve = 12,
81    Thirteen = 13,
82    Fourteen = 14,
83    Fifteen = 15,
84    Sixteen = 16,
85    Seventeen = 17,
86    Eighteen = 18,
87    Nineteen = 19,
88    Twenty = 20,
89    TwentyOne = 21,
90    TwentyTwo = 22,
91    TwentyThree = 23,
92    TwentyFour = 24,
93    TwentyFive = 25,
94    TwentySix = 26,
95    TwentySeven = 27,
96    TwentyEight = 28,
97    TwentyNine = 29,
98    Thirty = 30,
99    ThirtyOne = 31,
100    ThirtyTwo = 32,
101}
102
103impl Square {
104    /// Provides an iterator over all square values.
105    ///
106    /// ```
107    /// use checke_rs::position::Square;
108    ///
109    /// let square_count = Square::iter().count();
110    ///
111    /// assert_eq!(square_count, 32)
112    /// ```
113    pub fn iter() -> impl Iterator<Item=Self> {
114        [
115            Square::One, Square::Two, Square::Three, Square::Four,
116            Square::Five, Square::Six, Square::Seven, Square::Eight,
117            Square::Nine, Square::Ten, Square::Eleven, Square::Twelve,
118            Square::Thirteen, Square::Fourteen, Square::Fifteen, Square::Sixteen,
119            Square::Seventeen, Square::Eighteen, Square::Nineteen, Square::Twenty,
120            Square::TwentyOne, Square::TwentyTwo, Square::TwentyThree, Square::TwentyFour,
121            Square::TwentyFive, Square::TwentySix, Square::TwentySeven, Square::TwentyEight,
122            Square::TwentyNine, Square::Thirty, Square::ThirtyOne, Square::ThirtyTwo
123        ].iter().copied()
124    }
125
126    /// Creates a [BitCell] representing this square instance.
127    pub fn to_bitboard(&self) -> MonoBitBoard {
128        let value = match self {
129            Square::One => 0x4000000000000000,
130            Square::Two => 0x1000000000000000,
131            Square::Three => 0x400000000000000,
132            Square::Four => 0x100000000000000,
133            Square::Five => 0x80000000000000,
134            Square::Six => 0x20000000000000,
135            Square::Seven => 0x8000000000000,
136            Square::Eight => 0x2000000000000,
137            Square::Nine => 0x400000000000,
138            Square::Ten => 0x100000000000,
139            Square::Eleven => 0x40000000000,
140            Square::Twelve => 0x10000000000,
141            Square::Thirteen => 0x8000000000,
142            Square::Fourteen => 0x2000000000,
143            Square::Fifteen => 0x800000000,
144            Square::Sixteen => 0x200000000,
145            Square::Seventeen => 0x40000000,
146            Square::Eighteen => 0x10000000,
147            Square::Nineteen => 0x4000000,
148            Square::Twenty => 0x1000000,
149            Square::TwentyOne => 0x800000,
150            Square::TwentyTwo => 0x200000,
151            Square::TwentyThree => 0x80000,
152            Square::TwentyFour => 0x20000,
153            Square::TwentyFive => 0x4000,
154            Square::TwentySix => 0x1000,
155            Square::TwentySeven => 0x400,
156            Square::TwentyEight => 0x100,
157            Square::TwentyNine => 0x80,
158            Square::Thirty => 0x20,
159            Square::ThirtyOne => 0x8,
160            Square::ThirtyTwo => 0x2
161        };
162        MonoBitBoard::new(value).unwrap()
163    }
164
165    /// Returns the square instance represented as a u8.
166    ///
167    /// ```rust
168    /// use checke_rs::position::Square;
169    ///
170    /// let number = Square::One.to_number();
171    ///
172    /// assert_eq!(number, 1)
173    /// ```
174    pub fn to_number(&self) -> u8 {
175        num::ToPrimitive::to_u8(self).unwrap()
176    }
177}
178
179impl TryFrom<u8> for Square {
180    type Error = NotationError;
181
182    fn try_from(value: u8) -> Result<Self, Self::Error> {
183        num::FromPrimitive::from_u8(value).ok_or(NotationError::OutOfRange)
184    }
185}
186
187impl TryFrom<&str> for Square {
188    type Error = NotationError;
189
190    fn try_from(text: &str) -> Result<Self, Self::Error> {
191        let value = text.parse::<u8>().map_err(|_| NotationError::InvalidFormat)?;
192        Square::try_from(value)
193    }
194}
195
196impl TryFrom<MonoBitBoard> for Square {
197    type Error = SquareConversionError;
198
199    /// Converts the given [MonoBitBoard] to a [Square] instance.
200    ///
201    /// ```
202    /// use checke_rs::bitboard::MonoBitBoard;
203    /// use checke_rs::position::Square;
204    ///
205    /// let expected_square = Square::ThirtyTwo;
206    /// let bitboard = expected_square.to_bitboard();
207    /// let converted_square = Square::try_from(bitboard).unwrap();
208    ///
209    /// assert_eq!(converted_square, expected_square)
210    /// ```
211    ///
212    /// Raises an error when the given [MonoBitBoard] could not be converted
213    /// to a classical checkers square.
214    ///
215    /// ```
216    /// use checke_rs::bitboard::MonoBitBoard;
217    /// use checke_rs::position::Square;
218    ///
219    /// let bitboard = MonoBitBoard::new(0b1).unwrap();
220    /// let result = Square::try_from(bitboard);
221    ///
222    /// assert!(result.is_err())
223    /// ```
224    fn try_from(bitboard: MonoBitBoard) -> Result<Self, Self::Error> {
225        Square::iter()
226            .find(|square| square.to_bitboard() == bitboard)
227            .ok_or(SquareConversionError)
228    }
229}
230
231/// Represents a move that can occur on a board. A move is represented by the source square
232/// and the destination square.
233#[derive(Copy, Clone, Debug)]
234pub struct Move(pub Square, pub Square);
235
236impl Move {
237    /// Creates a new [Move] instance from two given squares.
238    /// Move instances have no context of a board or any checkers rules. Moves are simply
239    /// a source and destination square. Move validation is expected to be done via other
240    /// mechanisms.
241    ///
242    /// Basic usage:
243    /// ```
244    /// use checke_rs::position::{Move, Square};
245    ///
246    /// let Move(source, dest) = Move::new(Square::Eight, Square::Nine).unwrap();
247    ///
248    /// assert_eq!(source, Square::Eight);
249    /// assert_eq!(dest, Square::Nine);
250    /// ```
251    pub fn new(source: Square, destination: Square) -> Result<Self, NotationError> {
252        match source != destination {
253            true => Ok(Move(source, destination)),
254            false => Err(NotationError::Idle)
255        }
256    }
257
258    /// Create a new [Move] instance using the given checkers notation text.
259    ///
260    /// ```rust
261    /// use checke_rs::position::{Move, Square};
262    ///
263    /// let Move(source, dest) = Move::from_notation("18x25").unwrap();
264    ///
265    /// assert_eq!(source, Square::Eighteen);
266    /// assert_eq!(dest, Square::TwentyFive);
267    /// ```
268    pub fn from_notation(text: &str) -> Result<Self, NotationError> {
269        lazy_static! {
270            static ref CN_PATTERN: Regex = Regex::new(r"^([1-9]+[0-9]*)([-xX])([1-9]+[0-9]*)$").unwrap();
271        }
272
273        match CN_PATTERN.captures(text) {
274            Some(captures) => Move::parse_captures(captures),
275            None => Err(NotationError::InvalidFormat)
276        }
277    }
278
279    fn parse_captures(captures: Captures) -> Result<Move, NotationError> {
280        let source_text = captures.get(1).unwrap().as_str();
281        let dest_text = captures.get(3).unwrap().as_str();
282
283        let source = Square::try_from(source_text)?;
284        let dest = Square::try_from(dest_text)?;
285
286        Move::new(source, dest)
287    }
288}
289
290impl TryFrom<&str> for Move {
291    type Error = NotationError;
292
293    /// Converts a string slice representing checkers notation into a [Move] instance.
294    ///
295    /// ```rust
296    /// use checke_rs::position::{Move, Square};
297    ///
298    /// let Move(source, dest) = Move::try_from("18x25").unwrap();
299    ///
300    /// assert_eq!(source, Square::Eighteen);
301    /// assert_eq!(dest, Square::TwentyFive);
302    /// ```
303    fn try_from(text: &str) -> Result<Self, Self::Error> {
304        Move::from_notation(text)
305    }
306}
307
308/// Iterator capable of generating all possible moves for a given board and player of that board.
309pub struct MoveIter<'a> {
310    board: &'a Board,
311    player: Player,
312    piece_index: u8,
313}
314
315impl<'a> MoveIter<'a> {
316    /// Creates a new [MoveIter] instance from board reference and player type.
317    /// ```
318    /// use checke_rs::board::{Board, Player};
319    /// use checke_rs::position::MoveIter;
320    ///
321    /// let board = Board::default();
322    /// let moves = MoveIter::new(&board, Player::Black);
323    /// ```
324    pub fn new(board: &'a Board, player: Player) -> Self {
325        MoveIter { board, player, piece_index: 0 }
326    }
327
328    fn get_current_piece(&self) -> Option<MonoBitBoard> {
329        let player_bitboard = self.board.pieces_by_player(self.player);
330        let player_pieces = player_bitboard.pieces();
331        return player_pieces.get(self.piece_index as usize).cloned();
332    }
333}
334
335impl<'a> Iterator for MoveIter<'a> {
336    type Item = Move;
337
338    fn next(&mut self) -> Option<Self::Item> {
339        let current_piece = self.get_current_piece()?;
340        let position = Position::from(current_piece);
341
342        match position {
343            Position::Bottom => {}
344            Position::Left => {}
345            Position::BottomLeft => {}
346            Position::BottomRight => {}
347            Position::Top => {}
348            Position::Right => {}
349            Position::TopRight => {}
350            Position::TopLeft => {}
351            Position::Interior => {}
352        }
353
354        self.piece_index += 1;
355        Some(Move::new(Square::Two, Square::Nine).unwrap())
356    }
357}