chess_engine/
board.rs

1use super::*;
2use alloc::{
3    string::{String, ToString},
4    vec::Vec,
5};
6use core::cmp::Ordering;
7
8pub struct BoardBuilder {
9    board: Board,
10}
11
12impl From<Board> for BoardBuilder {
13    fn from(board: Board) -> Self {
14        Self { board }
15    }
16}
17
18impl Default for BoardBuilder {
19    fn default() -> Self {
20        let mut board = Board::empty();
21        board.white_castling_rights.disable_all();
22        board.black_castling_rights.disable_all();
23        Self { board }
24    }
25}
26
27impl BoardBuilder {
28    pub fn row(mut self, piece: Piece) -> Self {
29        let mut pos = piece.get_pos();
30        while pos.get_col() > 0 {
31            pos = pos.next_left()
32        }
33
34        for _ in 0..8 {
35            *self.board.get_square(pos) = Square::from(piece.move_to(pos));
36            pos = pos.next_right();
37        }
38
39        self
40    }
41
42    pub fn column(mut self, piece: Piece) -> Self {
43        let mut pos = piece.get_pos();
44        while pos.get_row() > 0 {
45            pos = pos.next_below()
46        }
47
48        for _ in 0..8 {
49            *self.board.get_square(pos) = Square::from(piece.move_to(pos));
50            pos = pos.next_above();
51        }
52
53        self
54    }
55
56    pub fn piece(mut self, piece: Piece) -> Self {
57        let pos = piece.get_pos();
58        *self.board.get_square(pos) = Square::from(piece);
59        self
60    }
61
62    pub fn enable_castling(mut self) -> Self {
63        self.board.black_castling_rights.enable_all();
64        self.board.white_castling_rights.enable_all();
65        self
66    }
67
68    pub fn disable_castling(mut self) -> Self {
69        self.board.black_castling_rights.disable_all();
70        self.board.white_castling_rights.disable_all();
71        self
72    }
73
74    pub fn enable_queenside_castle(mut self, color: Color) -> Self {
75        match color {
76            WHITE => self.board.white_castling_rights.enable_queenside(),
77            BLACK => self.board.black_castling_rights.enable_queenside(),
78        }
79        self
80    }
81
82    pub fn disable_queenside_castle(mut self, color: Color) -> Self {
83        match color {
84            WHITE => self.board.white_castling_rights.disable_queenside(),
85            BLACK => self.board.black_castling_rights.disable_queenside(),
86        }
87        self
88    }
89
90    pub fn enable_kingside_castle(mut self, color: Color) -> Self {
91        match color {
92            WHITE => self.board.white_castling_rights.enable_kingside(),
93            BLACK => self.board.black_castling_rights.enable_kingside(),
94        }
95        self
96    }
97
98    pub fn disable_kingside_castle(mut self, color: Color) -> Self {
99        match color {
100            WHITE => self.board.white_castling_rights.disable_kingside(),
101            BLACK => self.board.black_castling_rights.disable_kingside(),
102        }
103        self
104    }
105
106    pub fn build(self) -> Board {
107        self.board
108    }
109}
110
111#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
112pub struct CastlingRights {
113    kingside: bool,
114    queenside: bool,
115}
116
117impl Default for CastlingRights {
118    fn default() -> Self {
119        Self {
120            kingside: true,
121            queenside: true,
122        }
123    }
124}
125
126impl CastlingRights {
127    fn can_kingside_castle(&self) -> bool {
128        self.kingside
129    }
130
131    fn can_queenside_castle(&self) -> bool {
132        self.queenside
133    }
134
135    fn disable_kingside(&mut self) {
136        self.kingside = false
137    }
138
139    fn disable_queenside(&mut self) {
140        self.queenside = false
141    }
142
143    fn disable_all(&mut self) {
144        self.disable_kingside();
145        self.disable_queenside()
146    }
147
148    fn enable_kingside(&mut self) {
149        self.kingside = true
150    }
151
152    fn enable_queenside(&mut self) {
153        self.queenside = true
154    }
155
156    fn enable_all(&mut self) {
157        self.enable_kingside();
158        self.enable_queenside()
159    }
160}
161
162impl Default for Board {
163    fn default() -> Self {
164        BoardBuilder::default()
165            .piece(Piece::Rook(BLACK, A8))
166            .piece(Piece::Knight(BLACK, B8))
167            .piece(Piece::Bishop(BLACK, C8))
168            .piece(Piece::Queen(BLACK, D8))
169            .piece(Piece::King(BLACK, E8))
170            .piece(Piece::Bishop(BLACK, F8))
171            .piece(Piece::Knight(BLACK, G8))
172            .piece(Piece::Rook(BLACK, H8))
173            .row(Piece::Pawn(BLACK, A7))
174            .row(Piece::Pawn(WHITE, A2))
175            .piece(Piece::Rook(WHITE, A1))
176            .piece(Piece::Knight(WHITE, B1))
177            .piece(Piece::Bishop(WHITE, C1))
178            .piece(Piece::Queen(WHITE, D1))
179            .piece(Piece::King(WHITE, E1))
180            .piece(Piece::Bishop(WHITE, F1))
181            .piece(Piece::Knight(WHITE, G1))
182            .piece(Piece::Rook(WHITE, H1))
183            .enable_castling()
184            .build()
185    }
186}
187
188#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
189pub struct Board {
190    squares: [Square; 64],
191
192    en_passant: Option<Position>,
193
194    white_castling_rights: CastlingRights,
195    black_castling_rights: CastlingRights,
196
197    turn: Color,
198}
199
200impl Evaluate for Board {
201    #[inline]
202    fn value_for(&self, ally_color: Color) -> f64 {
203        self.squares
204            .iter()
205            .map(|square| match square.get_piece() {
206                Some(piece) => {
207                    if piece.get_color() == ally_color {
208                        piece.get_weighted_value()
209                    } else {
210                        -piece.get_weighted_value()
211                    }
212                }
213                None => 0.0,
214            })
215            .sum()
216    }
217
218    #[inline]
219    fn get_current_player_color(&self) -> Color {
220        self.turn
221    }
222
223    #[inline]
224    fn apply_eval_move(&self, m: Move) -> Self {
225        self.apply_move(m).change_turn()
226    }
227
228    #[inline]
229    fn get_legal_moves(&self) -> Vec<Move> {
230        let mut result = vec![];
231        let color = self.get_current_player_color();
232        for square in &self.squares {
233            if let Some(piece) = square.get_piece() {
234                if piece.get_color() == color {
235                    result.extend(piece.get_legal_moves(self))
236                }
237            }
238        }
239
240        result
241    }
242}
243
244impl core::fmt::Display for Board {
245    fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
246        let rating_bar = self.rating_bar(16);
247        let abc = if self.turn == WHITE {
248            "abcdefgh"
249        } else {
250            "hgfedcba"
251        };
252
253        write!(f, "   {}\n  ╔════════╗", abc)?;
254        let mut square_color = !self.turn;
255        let height = 8;
256        let width = 8;
257
258        for row in 0..height {
259            writeln!(f)?;
260
261            let print_row = match self.turn {
262                WHITE => height - row - 1,
263                BLACK => row,
264            };
265            write!(f, "{} ║", print_row + 1)?;
266
267            for col in 0..width {
268                let print_col = match self.turn {
269                    BLACK => width - col - 1,
270                    WHITE => col,
271                };
272
273                let pos = Position::new(print_row, print_col);
274
275                let s = if let Some(piece) = self.get_piece(pos) {
276                    piece.to_string()
277                } else {
278                    String::from(match square_color {
279                        WHITE => "░",
280                        BLACK => "▓",
281                    })
282                };
283                if Some(pos) == self.en_passant {
284                    write!(f, "\x1b[34m{}\x1b[m\x1b[0m", s)?;
285                } else if self.is_threatened(pos, self.turn) {
286                    write!(f, "\x1b[31m{}\x1b[m\x1b[0m", s)?;
287                } else if self.is_threatened(pos, !self.turn) {
288                    write!(f, "\x1b[32m{}\x1b[m\x1b[0m", s)?;
289                } else {
290                    write!(f, "{}", s)?;
291                }
292
293                square_color = !square_color;
294            }
295            write!(f, "║")?;
296
297            if row == 2 {
298                let white_adv = self.get_material_advantage(WHITE);
299                let black_adv = self.get_material_advantage(BLACK);
300
301                match white_adv.cmp(&black_adv) {
302                    Ordering::Equal => write!(f, " Both sides have equal material")?,
303                    Ordering::Greater => write!(f, " White +{} points", white_adv)?,
304                    Ordering::Less => write!(f, " Black +{} points", black_adv)?,
305                }
306            } else if row == 3 {
307                write!(f, " {} to move", self.turn)?;
308            } else if row == 4 {
309                write!(f, " [{}]", rating_bar)?;
310            }
311            square_color = !square_color;
312        }
313
314        write!(f, "\n  ╚════════╝\n   {}\n", abc)
315    }
316}
317
318impl Board {
319    /// Create the default board for the Horde variant
320    pub fn horde() -> Self {
321        BoardBuilder::from(Board::default())
322            .row(Piece::Pawn(WHITE, A1))
323            .row(Piece::Pawn(WHITE, A2))
324            .row(Piece::Pawn(WHITE, A3))
325            .row(Piece::Pawn(WHITE, A4))
326            .piece(Piece::Pawn(WHITE, F5))
327            .piece(Piece::Pawn(WHITE, G5))
328            .piece(Piece::Pawn(WHITE, B5))
329            .piece(Piece::Pawn(WHITE, C5))
330            .build()
331    }
332
333    pub fn empty() -> Self {
334        Self {
335            squares: [EMPTY_SQUARE; 64],
336            en_passant: None,
337
338            white_castling_rights: CastlingRights::default(),
339            black_castling_rights: CastlingRights::default(),
340
341            turn: WHITE,
342        }
343    }
344
345    pub fn rating_bar(&self, len: usize) -> String {
346        let (best_m, _, your_best_val) = self.get_best_next_move(2);
347        let (_, _, your_lowest_val) = self.get_worst_next_move(2);
348        let mut your_val = your_best_val + your_lowest_val;
349        let (_, _, their_best_val) = self.apply_move(best_m).change_turn().get_best_next_move(2);
350        let (_, _, their_lowest_val) = self.apply_move(best_m).change_turn().get_worst_next_move(2);
351        let mut their_val = their_best_val + their_lowest_val;
352
353        if your_val < 0.0 {
354            your_val = -your_val;
355            their_val += your_val * 2.0;
356        }
357
358        if their_val < 0.0 {
359            their_val = -their_val;
360            your_val += their_val * 2.0;
361        }
362
363        let your_percentage = your_val / (your_val + their_val);
364        let their_percentage = their_val / (your_val + their_val);
365
366        let (your_color, their_color) = match self.turn {
367            WHITE => ("▓", "░"),
368            BLACK => ("░", "▓"),
369        };
370
371        let white = match self.turn {
372            WHITE => your_color.repeat((your_percentage * len as f64) as usize),
373            BLACK => their_color.repeat((their_percentage * len as f64) as usize),
374        };
375
376        let black = match self.turn {
377            BLACK => your_color.repeat((your_percentage * len as f64) as usize),
378            WHITE => their_color.repeat((their_percentage * len as f64) as usize),
379        };
380
381        white + &black
382    }
383
384    /// Get the color of the current player
385    #[inline]
386    pub fn get_turn_color(&self) -> Color {
387        self.turn
388    }
389
390    /// Get the position of the En-Passant square
391    pub fn get_en_passant(&self) -> Option<Position> {
392        self.en_passant
393    }
394
395    /// Remove all of the pieces for a given player
396    pub fn remove_all(&self, color: Color) -> Self {
397        let mut result = *self;
398        for square in &mut result.squares {
399            if let Some(piece) = square.get_piece() {
400                if piece.get_color() == color {
401                    *square = EMPTY_SQUARE
402                }
403            }
404        }
405
406        result
407    }
408
409    /// Convert all of a given players pieces to queens
410    pub fn queen_all(&self, color: Color) -> Self {
411        let mut result = *self;
412        for square in &mut result.squares {
413            if let Some(piece) = square.get_piece() {
414                if !piece.is_king() && piece.get_color() == color {
415                    *square = Square::from(Piece::Queen(color, piece.get_pos()))
416                }
417            }
418        }
419
420        result
421    }
422
423    /// Make the game a certain player's turn
424    #[inline]
425    pub fn set_turn(&self, color: Color) -> Self {
426        let mut result = *self;
427        result.turn = color;
428        result
429    }
430
431    /// Get the value of the material advantage of a certain player
432    #[inline]
433    pub fn get_material_advantage(&self, color: Color) -> i32 {
434        self.squares
435            .iter()
436            .map(|square| match square.get_piece() {
437                Some(piece) => {
438                    if piece.get_color() == color {
439                        piece.get_material_value()
440                    } else {
441                        -piece.get_material_value()
442                    }
443                }
444                None => 0,
445            })
446            .sum()
447    }
448
449    #[inline]
450    fn get_square(&mut self, pos: Position) -> &mut Square {
451        &mut self.squares[((7 - pos.get_row()) * 8 + pos.get_col()) as usize]
452    }
453
454    #[inline]
455    fn add_piece(&mut self, piece: Piece) {
456        let pos = piece.get_pos();
457        *self.get_square(pos) = Square::from(piece);
458    }
459
460    /// Does a square have any piece?
461    #[inline]
462    pub fn get_piece(&self, pos: Position) -> Option<Piece> {
463        if pos.is_off_board() {
464            return None;
465        }
466        self.squares[((7 - pos.get_row()) * 8 + pos.get_col()) as usize].get_piece()
467    }
468
469    /// Does a square have an ally piece?
470    #[inline]
471    pub fn has_ally_piece(&self, pos: Position, ally_color: Color) -> bool {
472        if let Some(piece) = self.get_piece(pos) {
473            piece.get_color() == ally_color
474        } else {
475            false
476        }
477    }
478
479    /// If a square at a given position has an enemy piece from a given
480    /// ally color, return true. Otherwise, return false.
481    ///
482    /// For example, if a square has a black piece, and this method is called
483    /// upon it with an `ally_color` of `Color::White`, then it will return true.
484    /// If called with `Color::Black` upon the same square, however, it will return false.
485    #[inline]
486    pub fn has_enemy_piece(&self, pos: Position, ally_color: Color) -> bool {
487        if let Some(piece) = self.get_piece(pos) {
488            piece.get_color() == !ally_color
489        } else {
490            false
491        }
492    }
493
494    /// If a square at a given position has any piece, return true.
495    /// Otherwise, return false.
496    #[inline]
497    pub fn has_piece(&self, pos: Position) -> bool {
498        self.get_piece(pos) != None
499    }
500
501    /// If a square at a given position has no piece, return true.
502    /// Otherwise, return false.
503    #[inline]
504    pub fn has_no_piece(&self, pos: Position) -> bool {
505        self.get_piece(pos) == None
506    }
507
508    /// If there is a king on the board, return the position that it sits on.
509    pub fn get_king_pos(&self, color: Color) -> Option<Position> {
510        let mut king_pos = None;
511        for square in &self.squares {
512            if let Some(Piece::King(c, pos)) = square.get_piece() {
513                if c == color {
514                    king_pos = Some(pos);
515                }
516            }
517        }
518        king_pos
519    }
520
521    /// Is a square threatened by an enemy piece?
522    pub fn is_threatened(&self, pos: Position, ally_color: Color) -> bool {
523        for (i, square) in self.squares.iter().enumerate() {
524            let row = 7 - i / 8;
525            let col = i % 8;
526            let square_pos = Position::new(row as i32, col as i32);
527            if !square_pos.is_orthogonal_to(pos) && !square_pos.is_diagonal_to(pos) && !square_pos.is_knight_move(pos) {
528                continue;
529            }
530
531            if let Some(piece) = square.get_piece() {
532                if piece.get_color() == ally_color {
533                    continue;
534                }
535
536                if piece.is_legal_attack(pos, self) {
537                    return true;
538                }
539            }
540        }
541
542        false
543    }
544
545    /// Get whether or not the king of a given color is in check.
546    #[inline]
547    pub fn is_in_check(&self, color: Color) -> bool {
548        if let Some(king_pos) = self.get_king_pos(color) {
549            self.is_threatened(king_pos, color)
550        } else {
551            false
552        }
553    }
554
555    fn move_piece(&self, from: Position, to: Position) -> Self {
556        let mut result = *self;
557        result.en_passant = None;
558
559        if from.is_off_board() || to.is_off_board() {
560            return result;
561        }
562
563        let from_square = result.get_square(from);
564        if let Some(mut piece) = from_square.get_piece() {
565            *from_square = EMPTY_SQUARE;
566
567            if piece.is_pawn() && (to.get_row() == 0 || to.get_row() == 7) {
568                piece = Piece::Queen(piece.get_color(), piece.get_pos());
569            }
570
571            if piece.is_starting_pawn() && (from.get_row() - to.get_row()).abs() == 2 {
572                result.en_passant = Some(to.pawn_back(piece.get_color()))
573            }
574
575            result.add_piece(piece.move_to(to));
576
577            let castling_rights = match piece.get_color() {
578                WHITE => &mut result.white_castling_rights,
579                BLACK => &mut result.black_castling_rights,
580            };
581
582            if piece.is_king() {
583                castling_rights.disable_all();
584            } else if piece.is_queenside_rook() {
585                castling_rights.disable_queenside();
586            } else if piece.is_kingside_rook() {
587                castling_rights.disable_kingside();
588            }
589        }
590
591        result
592    }
593
594    /// Can a given player castle kingside?
595    pub fn can_kingside_castle(&self, color: Color) -> bool {
596        let right_of_king = Position::king_pos(color).next_right();
597        match color {
598            WHITE => {
599                self.has_no_piece(Position::new(0, 5))
600                    && self.has_no_piece(Position::new(0, 6))
601                    && self.get_piece(Position::new(0, 7)) == Some(Piece::Rook(color, Position::new(0, 7)))
602                    && self.white_castling_rights.can_kingside_castle()
603                    && !self.is_in_check(color)
604                    && !self.is_threatened(right_of_king, color)
605                    && !self.is_threatened(right_of_king.next_right(), color)
606            }
607            BLACK => {
608                self.has_no_piece(Position::new(7, 5))
609                    && self.has_no_piece(Position::new(7, 6))
610                    && self.get_piece(Position::new(7, 7)) == Some(Piece::Rook(color, Position::new(7, 7)))
611                    && self.black_castling_rights.can_kingside_castle()
612                    && !self.is_in_check(color)
613                    && !self.is_threatened(right_of_king, color)
614                    && !self.is_threatened(right_of_king.next_right(), color)
615            }
616        }
617    }
618
619    /// Can a given player castle queenside?
620    pub fn can_queenside_castle(&self, color: Color) -> bool {
621        match color {
622            WHITE => {
623                self.has_no_piece(Position::new(0, 1))
624                    && self.has_no_piece(Position::new(0, 2))
625                    && self.has_no_piece(Position::new(0, 3))
626                    && self.get_piece(Position::new(0, 0)) == Some(Piece::Rook(color, Position::new(0, 0)))
627                    && self.white_castling_rights.can_queenside_castle()
628                    && !self.is_in_check(color)
629                    && !self.is_threatened(Position::queen_pos(color), color)
630                }
631            BLACK => {
632                self.has_no_piece(Position::new(7, 1))
633                    && self.has_no_piece(Position::new(7, 2))
634                    && self.has_no_piece(Position::new(7, 3))
635                    && self.get_piece(Position::new(7, 0)) == Some(Piece::Rook(color, Position::new(7, 0)))
636                    && self.black_castling_rights.can_queenside_castle()
637                    && !self.is_in_check(color)
638                    && !self.is_threatened(Position::queen_pos(color), color)
639            }
640        }
641    }
642
643    pub(crate) fn is_legal_move(&self, m: Move, player_color: Color) -> bool {
644        match m {
645            Move::KingSideCastle => self.can_kingside_castle(player_color),
646            Move::QueenSideCastle => self.can_queenside_castle(player_color),
647            Move::Piece(from, to) => match self.get_piece(from) {
648                Some(Piece::Pawn(c, pos)) => {
649                    let piece = Piece::Pawn(c, pos);
650                    ((if let Some(en_passant) = self.en_passant {
651                        (en_passant == from.pawn_up(player_color).next_left()
652                            || en_passant == from.pawn_up(player_color).next_right()
653                            && en_passant == to)
654                            && c == player_color
655                    } else {
656                        false
657                    }) || piece.is_legal_move(to, self)
658                        && piece.get_color() == player_color)
659                        && !self.apply_move(m).is_in_check(player_color)
660                }
661                Some(piece) => {
662                    piece.is_legal_move(to, self)
663                        && piece.get_color() == player_color
664                        && !self.apply_move(m).is_in_check(player_color)
665                }
666                _ => false,
667            },
668            Move::Resign => true,
669        }
670    }
671    
672    /// Does the respective player have sufficient material?
673    pub fn has_sufficient_material(&self, color: Color) -> bool {
674        let mut pieces = vec![];
675        for square in &self.squares {
676            if let Some(piece) = square.get_piece() {
677                if piece.get_color() == color {
678                    pieces.push(piece);
679                }
680            }
681        }
682
683        pieces.sort();
684
685        if pieces.len() == 0 {
686            false
687        } else if pieces.len() == 1 && pieces[0].is_king() {
688            false
689        } else if pieces.len() == 2 && pieces[0].is_king() && pieces[1].is_knight() {
690            false
691        } else if pieces.len() == 2 && pieces[0].is_king() && pieces[1].is_bishop() {
692            false
693        } else if pieces.len() == 3
694            && pieces[0].is_king()
695            && pieces[1].is_knight()
696            && pieces[2].is_knight()
697        {
698            false
699        } else if pieces.len() == 3
700            && pieces[0].is_king()
701            && pieces[1].is_bishop()
702            && pieces[2].is_bishop()
703        {
704            false
705        } else {
706            true
707        }
708    }
709
710    /// Does the respective player have insufficient material?
711    #[inline]
712    pub fn has_insufficient_material(&self, color: Color) -> bool {
713        !self.has_sufficient_material(color)
714    }
715
716    /// Is the current player in stalemate?
717    pub fn is_stalemate(&self) -> bool {
718        (self.get_legal_moves().is_empty() && !self.is_in_check(self.get_current_player_color()))
719            || (self.has_insufficient_material(self.turn)
720                && self.has_insufficient_material(!self.turn))
721    }
722
723    /// Is the current player in checkmate?
724    pub fn is_checkmate(&self) -> bool {
725        self.is_in_check(self.get_current_player_color()) && self.get_legal_moves().is_empty()
726    }
727
728    /// Change the current turn to the next player.
729    #[inline]
730    pub fn change_turn(mut self) -> Self {
731        self.turn = !self.turn;
732        self
733    }
734
735    fn apply_move(&self, m: Move) -> Self {
736        match m {
737            Move::KingSideCastle => {
738                if let Some(king_pos) = self.get_king_pos(self.turn) {
739                    let rook_pos = match self.turn {
740                        WHITE => Position::new(0, 7),
741                        BLACK => Position::new(7, 7),
742                    };
743                    self.move_piece(king_pos, rook_pos.next_left())
744                        .move_piece(rook_pos, king_pos.next_right())
745                } else {
746                    *self
747                }
748            }
749            Move::QueenSideCastle => {
750                if let Some(king_pos) = self.get_king_pos(self.turn) {
751                    let rook_pos = match self.turn {
752                        WHITE => Position::new(0, 0),
753                        BLACK => Position::new(7, 0),
754                    };
755                    self.move_piece(king_pos, king_pos.next_left().next_left())
756                        .move_piece(rook_pos, king_pos.next_left())
757                } else {
758                    *self
759                }
760            }
761
762            Move::Piece(from, to) => {
763                let mut result = self.move_piece(from, to);
764
765                if let (Some(en_passant), Some(Piece::Pawn(player_color, _))) =
766                    (self.en_passant, self.get_piece(from))
767                {
768                    if (en_passant == from.pawn_up(player_color).next_left()
769                        || en_passant == from.pawn_up(player_color).next_right())
770                        && en_passant == to
771                    {
772                        result.squares[((7 - en_passant.pawn_back(player_color).get_row()) * 8
773                            + en_passant.get_col())
774                            as usize] = EMPTY_SQUARE;
775                    }
776                }
777
778                result
779            }
780            Move::Resign => self.remove_all(self.turn).queen_all(!self.turn),
781        }
782    }
783
784    /// Play a move and confirm it is legal.
785    pub fn play_move(&self, m: Move) -> GameResult {
786        let current_color = self.get_turn_color();
787
788        if m == Move::Resign {
789            GameResult::Victory(!current_color)
790        } else if self.is_legal_move(m, current_color) {
791            let next_turn = self.apply_move(m).change_turn();
792            if next_turn.is_checkmate() {
793                GameResult::Victory(current_color)
794            } else if next_turn.is_stalemate() {
795                GameResult::Stalemate
796            } else {
797                GameResult::Continuing(next_turn)
798            }
799        } else {
800            GameResult::IllegalMove(m)
801        }
802    }
803}