chess_lab/common/constants/
game.rs

1use std::fmt::{Display, Error, Formatter};
2
3use crate::logic::Piece;
4
5use super::Position;
6
7/// Represents the color of a chess piece
8///
9/// # Variants
10/// * `White`: The white color
11/// * `Black`: The black color
12///
13#[derive(Debug, PartialEq, Eq, Clone, Copy)]
14pub enum Color {
15    White,
16    Black,
17}
18
19impl Color {
20    /// Gets the opposite color
21    ///
22    /// # Returns
23    /// The color opposite to the current one
24    ///
25    /// # Example
26    /// ```
27    /// use chess_lab::constants::Color;
28    ///
29    /// assert_eq!(Color::White.opposite(), Color::Black);
30    /// assert_eq!(Color::Black.opposite(), Color::White);
31    /// ```
32    ///
33    pub fn opposite(&self) -> Color {
34        match self {
35            Color::White => Color::Black,
36            Color::Black => Color::White,
37        }
38    }
39}
40
41/// Represents the type of a chess piece
42///
43/// # Variants
44/// * `Pawn`: A pawn
45/// * `Knight`: A knight
46/// * `Bishop`: A bishop
47/// * `Rook`: A rook
48/// * `Queen`: A queen
49/// * `King`: A king
50///
51#[derive(Debug, PartialEq, Eq, Clone, Copy)]
52pub enum PieceType {
53    Pawn,
54    Knight,
55    Bishop,
56    Rook,
57    Queen,
58    King,
59}
60
61impl PieceType {
62    /// Gets the piece type from a character
63    ///
64    /// # Arguments
65    /// * `c`: The character to convert (only valid uppercase characters)
66    ///
67    /// # Returns
68    /// The piece type if the character is valid, otherwise `None`
69    ///
70    /// # Example
71    /// ```
72    /// use chess_lab::constants::PieceType;
73    ///
74    /// assert_eq!(PieceType::from_char('P'), Some(PieceType::Pawn));
75    /// assert_eq!(PieceType::from_char('N'), Some(PieceType::Knight));
76    /// assert_eq!(PieceType::from_char('B'), Some(PieceType::Bishop));
77    /// assert_eq!(PieceType::from_char('R'), Some(PieceType::Rook));
78    /// assert_eq!(PieceType::from_char('Q'), Some(PieceType::Queen));
79    /// assert_eq!(PieceType::from_char('K'), Some(PieceType::King));
80    ///
81    /// assert_eq!(PieceType::from_char('x'), None);
82    /// assert_eq!(PieceType::from_char('y'), None);
83    /// assert_eq!(PieceType::from_char('p'), None);
84    /// ```
85    ///
86    pub fn from_char(c: char) -> Option<PieceType> {
87        match c {
88            'P' => Some(PieceType::Pawn),
89            'N' => Some(PieceType::Knight),
90            'B' => Some(PieceType::Bishop),
91            'R' => Some(PieceType::Rook),
92            'Q' => Some(PieceType::Queen),
93            'K' => Some(PieceType::King),
94            _ => None,
95        }
96    }
97
98    /// Gets the character representation of the piece type
99    ///
100    /// # Returns
101    /// The character representation of the piece type
102    ///
103    /// # Example
104    /// ```
105    /// use chess_lab::constants::PieceType;
106    ///
107    /// assert_eq!(PieceType::Pawn.to_char(), 'P');
108    /// assert_eq!(PieceType::Knight.to_char(), 'N');
109    /// assert_eq!(PieceType::Bishop.to_char(), 'B');
110    /// assert_eq!(PieceType::Rook.to_char(), 'R');
111    /// assert_eq!(PieceType::Queen.to_char(), 'Q');
112    /// assert_eq!(PieceType::King.to_char(), 'K');
113    /// ```
114    ///
115    pub fn to_char(&self) -> char {
116        match self {
117            PieceType::Pawn => 'P',
118            PieceType::Knight => 'N',
119            PieceType::Bishop => 'B',
120            PieceType::Rook => 'R',
121            PieceType::Queen => 'Q',
122            PieceType::King => 'K',
123        }
124    }
125}
126
127/// Represents the status of a chess game
128///
129/// # Variants
130/// * `InProgress`: The game is in progress
131/// * `Draw`: The game is a draw
132///     - `reason`: The reason for the draw
133/// * `WhiteWins`: White wins the game
134///     - `reason`: The reason for the win
135/// * `BlackWins`: Black wins the game
136///     - `reason`: The reason for the win
137///
138#[derive(Debug, PartialEq, Eq, Clone, Copy)]
139pub enum GameStatus {
140    InProgress,
141    Draw(DrawReason),
142    WhiteWins(WinReason),
143    BlackWins(WinReason),
144}
145
146/// Represents the reason for a draw
147///
148/// # Variants
149/// * `Stalemate`: The game is a stalemate
150/// * `InsufficientMaterial`: The game is a draw due to insufficient material
151/// * `ThreefoldRepetition`: The game is a draw due to threefold repetition
152/// * `FiftyMoveRule`: The game is a draw due to the fifty move rule
153/// * `Agreement`: The game is a draw due to agreement
154///
155#[derive(Debug, PartialEq, Eq, Clone, Copy)]
156pub enum DrawReason {
157    Stalemate,
158    InsufficientMaterial,
159    ThreefoldRepetition,
160    FiftyMoveRule,
161    Agreement,
162}
163
164/// Represents the reason for a win
165///
166/// # Variants
167/// * `Checkmate`: The game is a win due to checkmate
168/// * `Resignation`: The game is a win due to resignation
169/// * `Time`: The game is a win due to time
170///
171#[derive(Debug, PartialEq, Eq, Clone, Copy)]
172pub enum WinReason {
173    Checkmate,
174    Resignation,
175    Time,
176}
177
178/// Represents the type of a move
179///
180/// # Variants
181/// * `Normal`: A normal move
182///     - `capture`: Whether the move is a capture
183///     - `promotion`: The piece type to promote to
184/// * `Castle`: A castle move
185///     - `side`: The side of the board to castle on
186/// * `EnPassant`: An en passant move
187///    - The move is an en passant
188///
189#[derive(Debug, Clone, PartialEq)]
190pub enum MoveType {
191    Normal {
192        capture: bool,
193        promotion: Option<PieceType>,
194    },
195    Castle {
196        side: CastleType,
197    },
198    EnPassant,
199}
200
201/// Represents the side of the board to castle on
202///
203/// # Variants
204/// * `KingSide`: The king side
205/// * `QueenSide`: The queen side
206///
207#[derive(Debug, Clone, PartialEq)]
208pub enum CastleType {
209    KingSide,
210    QueenSide,
211}
212
213/// Represents a move in a chess game
214///
215/// # Example
216/// ```
217/// use chess_lab::constants::{Color, PieceType, Position, Move, MoveType};
218/// use chess_lab::logic::Piece;
219///
220/// let piece = Piece {
221///     color: Color::White,
222///     piece_type: PieceType::Pawn,
223/// };
224/// let from = Position::new(4, 1);
225/// let to = Position::new(4, 3);
226/// let move_type = MoveType::Normal {
227///     capture: false,
228///     promotion: None,
229/// };
230/// let captured_piece = None;
231/// let rook_from = None;
232/// let ambiguity = (false, false);
233/// let mv = Move::new(
234///     piece,
235///     from,
236///     to,
237///     move_type,
238///     captured_piece,
239///     rook_from,
240///     ambiguity,
241///     false,
242///     false
243/// );
244///
245/// assert_eq!(mv.to_string(), "e4");
246/// ```
247///
248#[derive(Debug, Clone, PartialEq)]
249pub struct Move {
250    pub piece: Piece,
251    pub from: Position,
252    pub to: Position,
253    pub move_type: MoveType,
254    pub captured_piece: Option<PieceType>,
255    pub rook_from: Option<Position>,
256    pub ambiguity: (bool, bool),
257    pub check: bool,
258    pub checkmate: bool,
259}
260
261impl Move {
262    /// Creates a new move
263    ///
264    /// # Arguments
265    /// * `piece`: The piece that is moving
266    /// * `from`: The position the piece is moving from
267    /// * `to`: The position the piece is moving to
268    /// * `move_type`: The type of the move
269    /// * `captured_piece`: The piece that is captured, if any
270    /// * `rook_from`: The position of the rook, if the move is a castle
271    /// * `ambiguity`: A tuple of booleans representing the ambiguity of the move
272    /// * `check`: Whether the move puts the opponent in check
273    /// * `checkmate`: Whether the move puts the opponent in checkmate
274    ///
275    /// # Panics
276    /// Panics if the move is a capture, but no captured piece is provided
277    /// Panics if the move is not a capture, but a captured piece is provided
278    /// Panics if the move is a promotion, but the piece is not a pawn
279    /// Panics if the move is a castle, but the piece is not a king
280    /// Panics if the move is a castle, but the rook position is not provided
281    /// Panics if the move is an en passant, but the piece is not a pawn
282    ///
283    /// # Example
284    /// ```
285    /// use chess_lab::constants::{Color, PieceType, Position, Move, MoveType};
286    /// use chess_lab::logic::Piece;
287    ///
288    /// let piece = Piece {
289    ///     color: Color::White,
290    ///     piece_type: PieceType::Pawn,
291    /// };
292    /// let from = Position::new(4, 1);
293    /// let to = Position::new(4, 3);
294    /// let move_type = MoveType::Normal {
295    ///     capture: false,
296    ///     promotion: None,
297    /// };
298    /// let captured_piece = None;
299    /// let rook_from = None;
300    /// let ambiguity = (false, false);
301    /// let mv = Move::new(
302    ///     piece,
303    ///     from,
304    ///     to,
305    ///     move_type,
306    ///     captured_piece,
307    ///     rook_from,
308    ///     ambiguity,
309    ///     false,
310    ///     false
311    /// );
312    /// ```
313    ///
314    pub fn new(
315        piece: Piece,
316        from: Position,
317        to: Position,
318        move_type: MoveType,
319        captured_piece: Option<PieceType>,
320        rook_from: Option<Position>,
321        ambiguity: (bool, bool),
322        check: bool,
323        checkmate: bool,
324    ) -> Move {
325        match &move_type {
326            MoveType::Normal { capture, promotion } => {
327                if *capture {
328                    assert!(
329                        captured_piece.is_some(),
330                        "The move is a capture, but no captured piece is provided"
331                    );
332                } else {
333                    assert!(
334                        captured_piece.is_none(),
335                        "The move is not a capture, but a captured piece is provided"
336                    );
337                }
338                if promotion.is_some() {
339                    assert!(
340                        piece.piece_type == PieceType::Pawn,
341                        "The move is a promotion, but the piece is not a pawn"
342                    );
343                }
344            }
345            MoveType::Castle { side: _ } => {
346                assert!(
347                    piece.piece_type == PieceType::King,
348                    "The move is a castle, but the piece is not a king"
349                );
350                assert!(
351                    rook_from.is_some(),
352                    "The move is a castle, but no rook position is provided"
353                );
354            }
355            MoveType::EnPassant => {
356                assert!(
357                    piece.piece_type == PieceType::Pawn,
358                    "The move is an en passant, but the piece is not a pawn"
359                );
360            }
361        }
362        Move {
363            piece,
364            from,
365            to,
366            move_type,
367            captured_piece,
368            rook_from,
369            ambiguity,
370            check,
371            checkmate,
372        }
373    }
374}
375
376impl Display for Move {
377    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
378        let mut result = String::new();
379        if self.piece.piece_type != PieceType::Pawn {
380            result.push(self.piece.piece_type.to_char());
381        }
382        match &self.move_type {
383            MoveType::Castle { side } => {
384                result = match side {
385                    CastleType::KingSide => "O-O".to_string(),
386                    CastleType::QueenSide => "O-O-O".to_string(),
387                };
388            }
389            MoveType::Normal { capture, promotion } => {
390                let from_string = self.from.to_string();
391                if self.ambiguity.0 || (PieceType::Pawn == self.piece.piece_type && *capture) {
392                    result.push(from_string.chars().nth(0).unwrap());
393                }
394                if self.ambiguity.1 {
395                    result.push(from_string.chars().nth(1).unwrap());
396                }
397                if *capture {
398                    result.push('x');
399                }
400                result.push_str(&self.to.to_string());
401                if let Some(promotion) = promotion {
402                    result.push('=');
403                    result.push(promotion.to_char());
404                }
405            }
406            MoveType::EnPassant => {
407                result.push_str(&self.from.to_string());
408                result.push('x');
409                result.push_str(&self.to.to_string());
410            }
411        }
412        if self.checkmate {
413            result.push('#');
414        } else if self.check {
415            result.push('+');
416        }
417
418        write!(f, "{}", result)
419    }
420}