Skip to main content

chess_lab/variants/
chess960.rs

1use rand::Rng;
2
3use crate::{
4    core::{Color, GameStatus, Move, Piece, Position, Variant, VariantBuilder},
5    errors::{FenError, MoveError, PGNError},
6    logic::Game,
7    parsing::{
8        fen::get_minified_fen,
9        pgn::{parse_multiple_pgn, parse_pgn},
10    },
11    utils::os::{read_file, write_file},
12};
13
14/// Chess960 is a variant of chess that uses the same rules as [standard chess](crate::variants::StandardChess), but the starting position of the pieces is randomized.
15///
16/// # Attributes
17/// * `game` - The [Game] struct that contains the current state of the game
18///
19#[derive(Debug, Clone)]
20pub struct Chess960 {
21    /// The [Game] struct that contains the current state of the game
22    game: Game,
23}
24
25impl Default for Chess960 {
26    /// Generates a random starting position for the pieces
27    ///
28    /// # Returns
29    /// A [Chess960] struct with a random starting position
30    ///
31    /// # Example
32    /// ```
33    /// # use chess_lab::variants::Chess960;
34    /// let variant = Chess960::default();
35    /// ```
36    ///
37    fn default() -> Chess960 {
38        let mut first_row = String::new();
39        let mut remaining_pieces = vec!['r', 'n', 'b', 'q', 'b', 'n', 'r'];
40        let mut last_piece = ' ';
41
42        let mut rng = rand::thread_rng();
43        while last_piece != 'r' {
44            let index = rng.gen_range(0..remaining_pieces.len());
45            let piece = remaining_pieces.remove(index);
46            first_row.push(piece);
47            last_piece = piece;
48        }
49
50        remaining_pieces = remaining_pieces.into_iter().filter(|c| *c != 'r').collect();
51
52        remaining_pieces.push('k');
53
54        while last_piece != 'k' {
55            let index = rng.gen_range(0..remaining_pieces.len());
56            let piece = remaining_pieces.remove(index);
57            first_row.push(piece);
58            last_piece = piece;
59        }
60
61        remaining_pieces.push('r');
62
63        while !remaining_pieces.is_empty() {
64            let index = rng.gen_range(0..remaining_pieces.len());
65            let piece = remaining_pieces.remove(index);
66            first_row.push(piece);
67        }
68
69        let mut game = Game::from_fen(&format!(
70            "{}/pppppppp/8/8/8/8/PPPPPPPP/{} w KQkq - 0 1",
71            first_row,
72            first_row.to_uppercase()
73        ))
74        .unwrap();
75
76        game.history.variant = Some("Chess960".to_string());
77
78        Chess960 { game }
79    }
80}
81
82impl VariantBuilder for Chess960 {
83    /// Returns the name of the [Variant]
84    ///
85    /// # Returns
86    /// A string with the name of the [Variant]
87    ///
88    /// # Example
89    /// ```
90    /// # use chess_lab::core::VariantBuilder;
91    /// # use chess_lab::variants::Chess960;
92    /// let name = Chess960::name();
93    /// assert_eq!(name, "Chess960");
94    /// ```
95    ///
96    fn name() -> &'static str {
97        "Chess960"
98    }
99
100    /// Returns a new instance of the variant from a [Game] struct
101    ///
102    /// # Arguments
103    /// * `game` - The [Game] struct that contains the current state of the game
104    ///
105    /// # Returns
106    /// A Chess960 struct with the game state
107    ///
108    /// # Example
109    /// ```
110    /// # use chess_lab::core::VariantBuilder;
111    /// # use chess_lab::variants::Chess960;
112    /// use chess_lab::logic::Game;
113    ///
114    /// let game = Game::default();
115    /// let variant = Chess960::new(game);
116    /// ```
117    ///
118    fn new(game: Game) -> Chess960 {
119        Chess960 { game }
120    }
121
122    /// Returns a new instance of the variant from a FEN string
123    ///
124    /// # Arguments
125    /// * `fen` - The FEN string that represents the game state
126    ///
127    /// # Returns
128    /// A `Result<Chess960, FenError>` object
129    /// * `Ok(Chess960)` - A [Chess960] struct with the game state
130    /// * `Err(FenError)` - An error that indicates that the FEN string is invalid
131    ///
132    /// # Example
133    /// ```
134    /// # use chess_lab::core::VariantBuilder;
135    /// # use chess_lab::variants::Chess960;
136    /// let fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
137    /// let variant = Chess960::from_fen(fen).unwrap();
138    /// ```
139    ///
140    fn from_fen(fen: &str) -> Result<Chess960, FenError> {
141        Ok(Chess960 {
142            game: Game::from_fen(fen)?,
143        })
144    }
145
146    /// Returns a new instance of the variant from a PGN string
147    ///
148    /// # Arguments
149    /// * `pgn` - The PGN string that represents the game state
150    ///
151    /// # Returns
152    /// A `Result<Chess960, PgnError>` object
153    /// * `Ok(Chess960)` - A [Chess960] struct with the game state
154    /// * `Err(PgnError)` - An error that indicates that the PGN string is invalid
155    ///
156    /// # Example
157    /// ```
158    /// # use chess_lab::core::VariantBuilder;
159    /// # use chess_lab::variants::Chess960;
160    /// let pgn = "[Variant \"Chess960\"]\n1. e4 e5 2. Nf3 Nc6";
161    /// let variant = Chess960::from_pgn(pgn).unwrap();
162    /// ```
163    ///
164    fn from_pgn(pgn: &str) -> Result<Chess960, PGNError> {
165        parse_pgn(pgn)
166    }
167
168    /// Loads a new instance of the variant from a PGN file
169    ///
170    /// # Arguments
171    /// * `path` - The path to the PGN file
172    ///
173    /// # Returns
174    /// A `Result<Chess960, PgnError>` object
175    /// * `Ok(Chess960)` - A Chess960 struct with the game state
176    /// * `Err(PgnError)` - An error that indicates that the PGN file is invalid
177    ///
178    /// # Example
179    /// ```
180    /// # use chess_lab::core::VariantBuilder;
181    /// # use chess_lab::variants::Chess960;
182    /// let path = "data/chess960/ex1.pgn";
183    /// let variant = Chess960::load(path).unwrap();
184    /// ```
185    ///
186    fn load(path: &str) -> Result<Chess960, PGNError> {
187        let pgn = read_file(path)?;
188        Chess960::from_pgn(&pgn)
189    }
190
191    /// Loads all the instances of the variant from a PGN file
192    ///
193    /// # Arguments
194    /// * `path` - The path to the PGN file
195    ///
196    /// # Returns
197    /// A `Result<Vec<Chess960>, PgnError>` object
198    /// * `Ok(Vec<Chess960>)` - A vector with all the Chess960 structs with the game state
199    /// * `Err(PgnError)` - An error that indicates that the PGN file is invalid
200    ///
201    /// # Example
202    /// ```
203    /// # use chess_lab::core::VariantBuilder;
204    /// # use chess_lab::variants::Chess960;
205    /// let path = "data/chess960/ex2.pgn";
206    /// let variants = Chess960::load_all(path).unwrap();
207    /// ```
208    ///
209    fn load_all(path: &str) -> Result<Vec<Chess960>, PGNError> {
210        let pgn = read_file(path)?;
211        parse_multiple_pgn(&pgn)
212    }
213}
214
215impl Variant for Chess960 {
216    /// Moves a piece on the board
217    ///
218    /// # Arguments
219    /// * `move_str` - The move string that represents the move to be made
220    ///
221    /// # Returns
222    /// A `Result<GameStatus, MoveError>` object
223    /// * `Ok(GameStatus)` - The status of the game after the move
224    /// * `Err(MoveError)` - An error that indicates that the move is invalid
225    ///
226    /// # Example
227    /// ```
228    /// # use chess_lab::core::Variant;
229    /// # use chess_lab::variants::Chess960;
230    /// let mut variant = Chess960::default();
231    /// variant.move_piece("e4").unwrap();
232    /// ```
233    ///
234    fn move_piece(&mut self, move_str: &str) -> Result<GameStatus, MoveError> {
235        self.game.move_piece(move_str)
236    }
237
238    /// Undoes the last [Move] made
239    ///
240    /// # Example
241    /// ```
242    /// # use chess_lab::core::Variant;
243    /// # use chess_lab::variants::Chess960;
244    /// let mut variant = Chess960::default();
245    /// variant.move_piece("e4").unwrap();
246    /// variant.undo();
247    /// ```
248    ///
249    fn undo(&mut self) {
250        self.game.undo()
251    }
252
253    /// Redoes the last [Move] that was undone
254    ///
255    /// # Example
256    /// ```
257    /// # use chess_lab::core::Variant;
258    /// # use chess_lab::variants::Chess960;
259    /// let mut variant = Chess960::default();
260    /// variant.move_piece("e4").unwrap();
261    /// variant.undo();
262    /// variant.redo();
263    /// ```
264    ///
265    fn redo(&mut self) {
266        self.game.redo()
267    }
268
269    /// Returns the PGN string of the game
270    ///
271    /// # Returns
272    /// A string with the PGN of the game
273    ///
274    /// # Example
275    /// ```
276    /// # use chess_lab::core::Variant;
277    /// # use chess_lab::variants::Chess960;
278    /// let variant = Chess960::default();
279    /// let pgn = variant.pgn();
280    /// ```
281    ///
282    fn pgn(&self) -> String {
283        self.game.pgn()
284    }
285
286    /// Returns the FEN string of the [Game]
287    ///
288    /// # Returns
289    /// A string with the FEN of the [Game]
290    ///
291    /// # Example
292    /// ```
293    /// # use chess_lab::core::Variant;
294    /// # use chess_lab::variants::Chess960;
295    ///
296    /// let variant = Chess960::default();
297    /// let fen = variant.fen();
298    /// ```
299    ///
300    fn fen(&self) -> String {
301        self.game.fen()
302    }
303
304    /// Returns the piece at a given position
305    ///
306    /// # Arguments
307    /// * `pos` - The position to get the piece from
308    ///
309    /// # Returns
310    /// The piece at the given position, if there is one
311    ///
312    /// # Example
313    /// ```
314    /// # use chess_lab::core::Variant;
315    /// # use chess_lab::variants::Chess960;
316    /// use chess_lab::core::Position;
317    ///
318    /// let variant = Chess960::default();
319    /// let piece = variant.get_piece_at(Position::from_string("e2").unwrap());
320    /// assert!(piece.is_some());
321    /// assert_eq!(piece.unwrap().to_string(), "P");
322    /// ```
323    ///
324    fn get_piece_at(&self, pos: Position) -> Option<Piece> {
325        self.game.get_piece_at(pos)
326    }
327
328    /// Returns the legal moves of a piece at a given position
329    ///
330    /// # Arguments
331    /// * `pos` - The position to get the legal moves from
332    ///
333    /// # Returns
334    /// A vector with the legal moves of the piece at the given position
335    ///
336    /// # Example
337    /// ```
338    /// # use chess_lab::core::Variant;
339    /// # use chess_lab::variants::Chess960;
340    /// use chess_lab::core::Position;
341    ///
342    /// let variant = Chess960::default();
343    /// let legal_moves = variant.get_legal_moves(Position::from_string("e2").unwrap());
344    /// assert!(legal_moves.iter().any(|m| m.to_string() == "e4"));
345    /// ```
346    ///
347    fn get_legal_moves(&self, pos: Position) -> Vec<Move> {
348        self.game.get_legal_moves(pos)
349    }
350
351    /// Saves the PGN string of the [Game] to a file
352    ///
353    /// # Arguments
354    /// * `path` - The path to the file
355    /// * `overwrite` - A boolean that indicates if the file should be overwritten
356    ///
357    /// # Returns
358    /// A `Result<(), std::io::Error>` object
359    /// * `Ok(())` - The PGN was saved successfully
360    /// * `Err(std::io::Error)` - An error that indicates that the PGN could not be saved
361    ///
362    /// # Example
363    /// ```
364    /// # use chess_lab::core::Variant;
365    /// # use chess_lab::variants::Chess960;
366    ///
367    /// let variant = Chess960::default();
368    /// variant.save("data/chess960/ex.pgn", true).unwrap();
369    /// # std::fs::remove_file("data/chess960/ex.pgn").unwrap();
370    /// ```
371    ///
372    fn save(&self, path: &str, overwrite: bool) -> Result<(), std::io::Error> {
373        write_file(path, self.pgn().as_str(), !overwrite)?;
374        Ok(())
375    }
376
377    /// Resigns the [Game] for a [Color]
378    ///
379    /// # Arguments
380    /// * `color` - The color that resigns
381    ///
382    /// # Example
383    /// ```
384    /// # use chess_lab::core::Variant;
385    /// # use chess_lab::variants::Chess960;
386    /// use chess_lab::core::Color;
387    ///
388    /// let mut variant = Chess960::default();
389    /// variant.resign(Color::White);
390    /// ```
391    ///
392    fn resign(&mut self, color: Color) {
393        self.game.resign(color)
394    }
395
396    /// Sets the [Game] as a draw
397    ///
398    /// # Example
399    /// ```
400    /// # use chess_lab::core::Variant;
401    /// # use chess_lab::variants::Chess960;
402    /// let mut variant = Chess960::default();
403    /// variant.draw();
404    /// ```
405    ///
406    fn draw(&mut self) {
407        self.game.draw_by_agreement()
408    }
409
410    /// Sets the game lost in time for a [Color]
411    ///
412    /// # Arguments
413    /// * `color` - The [Color] that lost in time
414    ///
415    /// # Example
416    /// ```
417    /// # use chess_lab::core::Variant;
418    /// # use chess_lab::variants::Chess960;
419    /// use chess_lab::core::Color;
420    ///
421    /// let mut variant = Chess960::default();
422    /// variant.lost_on_time(Color::White);
423    /// ```
424    ///
425    fn lost_on_time(&mut self, color: Color) {
426        self.game.lost_on_time(color)
427    }
428
429    /// Returns the minified FEN string of the [Game]
430    ///
431    /// # Returns
432    /// A string with the minified FEN of the [Game]
433    ///
434    /// # Example
435    /// ```
436    /// # use chess_lab::core::Variant;
437    /// # use chess_lab::variants::Chess960;
438    /// let variant = Chess960::default();
439    /// let minified_fen = variant.get_minified_fen();
440    /// ```
441    ///
442    fn get_minified_fen(&self) -> String {
443        get_minified_fen(&self.fen())
444    }
445
446    /// Returns the last [Move] of the [Game]
447    ///
448    /// # Returns
449    /// The last [Move] of the [Game], if there is one
450    ///
451    /// # Examples
452    /// ```
453    /// # use chess_lab::core::Variant;
454    /// # use chess_lab::variants::StandardChess;
455    /// let mut game = StandardChess::default();
456    /// game.move_piece("e4").unwrap();
457    /// let last_move = game.get_last_move();
458    /// assert_eq!(last_move.unwrap().to_string(), "e4");
459    /// ```
460    ///
461    fn get_last_move(&self) -> Option<crate::core::Move> {
462        self.game.get_last_move()
463    }
464
465    /// Returns whether it is white's turn to move
466    ///
467    /// # Returns
468    /// Whether it is white's turn to move
469    ///
470    /// # Examples
471    /// ```
472    /// # use chess_lab::core::Variant;
473    /// # use chess_lab::variants::Chess960;
474    /// let game = Chess960::default();
475    /// let color = game.is_white_turn();
476    /// ```
477    ///
478    fn is_white_turn(&self) -> bool {
479        self.game.is_white_turn
480    }
481
482    /// Returns the halfmove clock of the [Game]
483    ///
484    /// # Returns
485    /// The halfmove clock of the [Game]
486    ///
487    /// # Examples
488    /// ```
489    /// # use chess_lab::core::Variant;
490    /// # use chess_lab::variants::Chess960;
491    /// let game = Chess960::default();
492    /// let halfmove_clock = game.get_halfmove_clock();
493    /// ```
494    ///
495    fn get_halfmove_clock(&self) -> u32 {
496        self.game.halfmove_clock
497    }
498
499    /// Returns the fullmove number of the [Game]
500    ///
501    /// # Returns
502    /// The fullmove number of the [Game]
503    ///
504    /// # Examples
505    /// ```
506    /// # use chess_lab::core::Variant;
507    /// # use chess_lab::variants::Chess960;
508    /// let game = Chess960::default();
509    /// let fullmove_number = game.get_fullmove_number();
510    /// ```
511    ///
512    fn get_fullmove_number(&self) -> u32 {
513        self.game.fullmove_number
514    }
515
516    /// Returns the current castling rights of the [Game]
517    ///
518    /// # Returns
519    /// The current castling rights of the [Game]
520    ///
521    /// # Examples
522    /// ```
523    /// # use chess_lab::core::Variant;
524    /// # use chess_lab::variants::Chess960;
525    /// let game = Chess960::default();
526    /// let castling_rights = game.get_castling_rights();
527    /// ```
528    ///
529    fn get_castling_rights(&self) -> String {
530        let mut castling_rights = String::new();
531
532        if self.game.castling_rights == 0 {
533            castling_rights.push('-');
534        } else {
535            if self.game.castling_rights & 0b1000 != 0 {
536                castling_rights.push('K');
537            }
538            if self.game.castling_rights & 0b0100 != 0 {
539                castling_rights.push('Q');
540            }
541            if self.game.castling_rights & 0b0010 != 0 {
542                castling_rights.push('k');
543            }
544            if self.game.castling_rights & 0b0001 != 0 {
545                castling_rights.push('q');
546            }
547        }
548        castling_rights
549    }
550
551    /// Returns the en passant square of the [Game]
552    ///
553    /// # Returns
554    /// The en passant square of the [Game]
555    ///
556    /// # Examples
557    /// ```
558    /// # use chess_lab::core::Variant;
559    /// # use chess_lab::variants::Chess960;
560    /// let game = Chess960::default();
561    /// let en_passant = game.get_en_passant();
562    /// ```
563    ///
564    fn get_en_passant(&self) -> Option<Position> {
565        self.game.en_passant
566    }
567
568    /// Returns the starting FEN of the [Game]
569    ///
570    /// # Returns
571    /// A copy of the starting FEN of the [Game]
572    ///
573    /// # Examples
574    /// ```
575    /// # use chess_lab::core::Variant;
576    /// # use chess_lab::variants::Chess960;
577    /// let game = Chess960::default();
578    /// let starting_fen = game.get_starting_fen();
579    /// ```
580    ///
581    fn get_starting_fen(&self) -> String {
582        self.game.starting_fen.clone()
583    }
584
585    /// Returns the status of the [Game]
586    ///
587    /// # Returns
588    /// The status of the [Game]
589    ///
590    /// # Examples
591    /// ```
592    /// # use chess_lab::core::Variant;
593    /// # use chess_lab::variants::Chess960;
594    ///
595    /// let game = Chess960::default();
596    /// let status = game.get_status();
597    /// ```
598    ///
599    fn get_status(&self) -> GameStatus {
600        self.game.status
601    }
602}
603
604#[cfg(test)]
605mod tests {
606    use crate::core::WinReason;
607
608    use super::*;
609
610    #[test]
611    fn test_standard_chess_name() {
612        assert_eq!(Chess960::name(), "Chess960");
613    }
614
615    #[test]
616    fn test_default() {
617        let variant = Chess960::default();
618
619        let minified_fen = variant.get_minified_fen();
620
621        let parts: Vec<&str> = minified_fen.split('/').collect();
622
623        assert!(parts[0].chars().all(|c| c.is_lowercase()));
624        assert!(parts[7].chars().all(|c| c.is_uppercase()));
625        assert_eq!(parts[0], parts[7].to_lowercase());
626
627        for piece in ['r', 'n', 'b', 'q', 'k'] {
628            let count = parts[0].chars().filter(|&c| c == piece).count();
629            match piece {
630                'r' => assert_eq!(count, 2),
631                'n' => assert_eq!(count, 2),
632                'b' => assert_eq!(count, 2),
633                'q' => assert_eq!(count, 1),
634                'k' => assert_eq!(count, 1),
635                _ => (),
636            }
637        }
638
639        let mut castle_chars = vec!['r', 'k', 'r'];
640        for char in parts[0].chars() {
641            if !castle_chars.is_empty() && char == castle_chars[0] {
642                castle_chars.remove(0);
643            }
644        }
645        assert!(castle_chars.is_empty());
646    }
647
648    #[test]
649    fn test_new() {
650        let game = Game::default();
651        let variant = Chess960::new(game.clone());
652        assert_eq!(variant.fen(), game.fen());
653    }
654
655    #[test]
656    fn test_from_fen() {
657        let fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
658        let variant = Chess960::from_fen(fen).unwrap();
659        assert_eq!(variant.fen(), fen);
660    }
661
662    #[test]
663    fn test_from_pgn() {
664        let pgn = "[Variant \"Chess960\"]\n
665            [FEN \"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\"]\n
666            1. e4 e5 2. Nf3 Nc6 3. Bb5 a6";
667        let variant = Chess960::from_pgn(pgn).unwrap();
668        assert!(variant.pgn().contains("1. e4 e5 2. Nf3 Nc6 3. Bb5 a6"));
669    }
670
671    #[test]
672    fn test_load() {
673        let path = "data/chess960/ex1.pgn";
674        let variant = Chess960::load(path).unwrap();
675        assert!(variant.pgn().contains("1. e4 c6 2. d4 d5 3. exd5 cxd5"));
676    }
677
678    #[test]
679    fn test_load_all() {
680        let path = "data/chess960/ex2.pgn";
681        let variants = Chess960::load_all(path).unwrap();
682        assert_eq!(variants.len(), 3);
683    }
684
685    #[test]
686    fn test_move_piece() {
687        let mut variant =
688            Chess960::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
689        let status = variant.move_piece("e4").unwrap();
690        assert_eq!(status, GameStatus::InProgress);
691        assert_eq!(
692            variant.fen(),
693            "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
694        );
695    }
696
697    #[test]
698    fn test_get_piece_at() {
699        let variant =
700            Chess960::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
701        let piece = variant.get_piece_at(Position::from_string("e2").unwrap());
702        assert!(piece.is_some());
703        assert_eq!(piece.unwrap().to_string(), "P");
704    }
705
706    #[test]
707    fn test_get_legal_moves() {
708        let variant =
709            Chess960::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
710        let legal_moves = variant.get_legal_moves(Position::from_string("e2").unwrap());
711        assert!(legal_moves.iter().any(|m| m.to_string() == "e4"));
712    }
713
714    #[test]
715    fn test_undo_redo() {
716        let mut variant =
717            Chess960::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
718        variant.move_piece("e4").unwrap();
719        variant.undo();
720        assert_eq!(
721            variant.fen(),
722            "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
723        );
724        variant.redo();
725        assert_eq!(
726            variant.fen(),
727            "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
728        );
729    }
730
731    #[test]
732    fn test_save() {
733        let mut variant = Chess960::default();
734        let path = "data/chess960/test_save.pgn";
735
736        variant.move_piece("e4").unwrap();
737        variant.save(path, true).unwrap();
738
739        let loaded_variant = Chess960::load(path).unwrap();
740        assert_eq!(variant.fen(), loaded_variant.fen());
741
742        // Clean up
743        std::fs::remove_file(path).unwrap();
744    }
745
746    #[test]
747    fn test_resign() {
748        let mut variant = Chess960::default();
749
750        variant.resign(Color::White);
751        assert_eq!(
752            variant.get_status(),
753            GameStatus::BlackWins(WinReason::Resignation)
754        );
755    }
756
757    #[test]
758    fn test_draw() {
759        let mut variant = Chess960::default();
760
761        variant.draw();
762        assert_eq!(
763            variant.get_status(),
764            GameStatus::Draw(crate::core::DrawReason::Agreement)
765        );
766    }
767
768    #[test]
769    fn test_lost_on_time() {
770        let mut variant = Chess960::default();
771
772        variant.lost_on_time(Color::Black);
773        assert_eq!(variant.get_status(), GameStatus::WhiteWins(WinReason::Time));
774    }
775
776    #[test]
777    fn test_minified_fen() {
778        let variant =
779            Chess960::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
780        let minified_fen = variant.get_minified_fen();
781        assert_eq!(minified_fen, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR");
782    }
783
784    #[test]
785    fn test_get_last_move() {
786        let mut variant = Chess960::default();
787        assert_eq!(variant.get_last_move(), None);
788
789        variant.move_piece("e4").unwrap();
790        let last_move = variant.get_last_move();
791        assert!(last_move.is_some());
792        assert_eq!(last_move.unwrap().to_string(), "e4");
793    }
794
795    #[test]
796    fn test_is_white_turn() {
797        let mut variant = Chess960::default();
798        assert!(variant.is_white_turn());
799
800        variant.move_piece("e4").unwrap();
801        assert!(!variant.is_white_turn());
802    }
803
804    #[test]
805    fn test_get_castling_rights() {
806        let mut variant = Chess960::default();
807        let castling_rights = variant.get_castling_rights();
808        assert_eq!(castling_rights, "KQkq");
809
810        variant =
811            Chess960::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1").unwrap();
812        let castling_rights = variant.get_castling_rights();
813        assert_eq!(castling_rights, "-");
814    }
815
816    #[test]
817    fn test_get_en_passant() {
818        let mut variant = Chess960::default();
819        assert_eq!(variant.get_en_passant(), None);
820
821        variant.move_piece("e4").unwrap();
822        variant.move_piece("d5").unwrap();
823        variant.move_piece("e5").unwrap();
824        variant.move_piece("f5").unwrap();
825        assert_eq!(
826            variant.get_en_passant().unwrap(),
827            Position::new(5, 5).unwrap()
828        );
829    }
830
831    #[test]
832    fn test_get_halfmove_clock() {
833        let variant = Chess960::default();
834        assert_eq!(variant.get_halfmove_clock(), 0);
835    }
836
837    #[test]
838    fn test_get_fullmove_number() {
839        let variant = Chess960::default();
840        assert_eq!(variant.get_fullmove_number(), 1);
841    }
842
843    #[test]
844    fn test_get_starting_fen() {
845        let variant = Chess960::default();
846        let starting_fen = variant.get_starting_fen();
847
848        assert!(starting_fen.contains("w KQkq - 0 1"));
849    }
850
851    #[test]
852    fn test_get_status() {
853        let variant = Chess960::default();
854        assert_eq!(variant.get_status(), GameStatus::InProgress);
855    }
856}