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}