rusty_chess_core/
game.rs

1use rayon::prelude::*;
2use std::collections::HashMap;
3use std::fmt::{self, Formatter};
4use std::sync::Mutex;
5
6#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
7#[repr(u8)]
8pub enum Color {
9    White,
10    Black,
11}
12pub const COLOR_COUNT: usize = 2;
13
14impl Color {
15    #[must_use]
16    pub const fn invert(&self) -> Color {
17        match self {
18            Color::White => Color::Black,
19            Color::Black => Color::White,
20        }
21    }
22}
23
24#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
25#[repr(u8)]
26pub enum PieceType {
27    Pawn,
28    Bishop,
29    Knight,
30    King,
31    Rook,
32    Queen,
33}
34
35impl PieceType {
36    #[must_use] pub const fn value(&self) -> u8 {
37        match self {
38            PieceType::Pawn => 1,
39            PieceType::Knight | PieceType::Bishop => 3,
40            PieceType::Rook => 5,
41            PieceType::Queen => 8,
42            PieceType::King => 0,
43        }
44    }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48pub struct Position(pub char, pub char);
49
50impl Position {
51    /// Adds a tuple of i8 to the position and returns a new position.
52    /// No boundary check is done!
53    fn add(self, to_add: (i8, i8)) -> Position {
54        fn checked_add(a: char, b: i8) -> char {
55            let b_abs = b.unsigned_abs();
56            let a_u8 = a as u8;
57            if b < 0 {
58                a_u8.saturating_sub(b_abs) as char
59            } else {
60                a_u8.saturating_add(b_abs) as char
61            }
62        }
63        let new_x = checked_add(self.0, to_add.0);
64        let new_y = checked_add(self.1, to_add.1);
65
66        Position(new_x, new_y)
67    }
68
69    #[inline]
70    #[must_use] pub fn as_index(self: Position) -> usize {
71        let x = self.0 as u8 - b'a';
72        let y = self.1 as u8 - b'1';
73        (y as usize) * BOARD_SIZE + (x as usize)
74    }
75
76    #[inline]
77    fn try_as_index(self: Position) -> Option<usize> {
78        if self.0 < 'a' || self.0 > 'h' || self.1 < '1' || self.1 > '8' {
79            return None;
80        }
81        Some(self.as_index())
82    }
83}
84impl From<(char, char)> for Position {
85    fn from(val: (char, char)) -> Self {
86        Position(val.0, val.1)
87    }
88}
89
90impl From<(&char, &char)> for Position {
91    fn from(val: (&char, &char)) -> Self {
92        Position(*val.0, *val.1)
93    }
94}
95impl TryFrom<usize> for Position {
96    type Error = ();
97
98    fn try_from(val: usize) -> Result<Self, Self::Error> {
99        if val >= TOTAL_SQUARES {
100            return Err(());
101        }
102        let x = (val % BOARD_SIZE) as u8 + b'a';
103        let y = (val / BOARD_SIZE) as u8 + b'1';
104        Ok(Position(x as char, y as char))
105    }
106}
107
108pub const BOARD_SIZE: usize = 8;
109pub const TOTAL_SQUARES: usize = BOARD_SIZE * BOARD_SIZE;
110type Board = [Option<Piece>; TOTAL_SQUARES];
111
112const fn all_possibles_sqares() -> [(char, char); TOTAL_SQUARES] {
113    let mut squares = [('a', 'a'); 64];
114    let mut i: usize = 0;
115    while i < BOARD_SIZE {
116        let mut j: usize = 0;
117        while j < BOARD_SIZE {
118            squares[i * BOARD_SIZE + j] = ((b'a' + i as u8) as char, (b'1' + j as u8) as char);
119            j += 1;
120        }
121        i += 1;
122    }
123    squares
124}
125const ALL_POSSIBLE_SQUARES: [(char, char); TOTAL_SQUARES] = all_possibles_sqares();
126
127const fn horizontal_directions() -> [([i8; BOARD_SIZE], [i8; BOARD_SIZE]); 4] {
128    let mut forward = [0; BOARD_SIZE];
129    let mut backward = [0; BOARD_SIZE];
130    let mut i = 0;
131    while i < BOARD_SIZE {
132        forward[i] = i as i8 + 1;
133        backward[i] = -(i as i8 + 1);
134        i += 1;
135    }
136    [
137        (forward, [0i8; BOARD_SIZE]),  // horizontal right
138        (backward, [0i8; BOARD_SIZE]), // horizontal left
139        ([0i8; BOARD_SIZE], forward),  // vertical up
140        ([0i8; BOARD_SIZE], backward), // vertical down
141    ]
142}
143const HORIZONTAL_DIRECTIONS: [([i8; BOARD_SIZE], [i8; BOARD_SIZE]); 4] = horizontal_directions();
144const fn diagonal_directions() -> [([i8; BOARD_SIZE], [i8; BOARD_SIZE]); 4] {
145    let mut forward = [0; BOARD_SIZE];
146    let mut backward = [0; BOARD_SIZE];
147    let mut i = 0;
148    while i < BOARD_SIZE {
149        forward[i] = i as i8 + 1;
150        backward[i] = -(i as i8 + 1);
151        i += 1;
152    }
153    [
154        (forward, forward),   // right diagonal up
155        (forward, backward),  // right diagonal down
156        (backward, forward),  // left diagonal up
157        (backward, backward), // left diagonal down
158    ]
159}
160const DIAGONAL_DIRECTIONS: [([i8; BOARD_SIZE], [i8; BOARD_SIZE]); 4] = diagonal_directions();
161
162const fn queen_directions() -> [([i8; BOARD_SIZE], [i8; BOARD_SIZE]); 8] {
163    let mut directions = [([0i8; BOARD_SIZE], [0i8; BOARD_SIZE]);
164        HORIZONTAL_DIRECTIONS.len() + DIAGONAL_DIRECTIONS.len()];
165    let mut i = 0;
166    while i < HORIZONTAL_DIRECTIONS.len() {
167        directions[i] = HORIZONTAL_DIRECTIONS[i];
168        i += 1;
169    }
170    while i < directions.len() {
171        directions[i] = DIAGONAL_DIRECTIONS[i - HORIZONTAL_DIRECTIONS.len()];
172        i += 1;
173    }
174    directions
175}
176pub const QUEEN_DIRECTIONS: [([i8; BOARD_SIZE], [i8; BOARD_SIZE]);
177    HORIZONTAL_DIRECTIONS.len() + DIAGONAL_DIRECTIONS.len()] = queen_directions();
178
179#[derive(Debug, Clone, PartialEq, Eq, Hash)]
180enum Obstacle {
181    OutOfBoundary,
182    Piece(Color),
183}
184
185#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
186pub struct Piece {
187    pub piece_type: PieceType,
188    pub color: Color,
189}
190
191impl fmt::Display for Piece {
192    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
193        match self.piece_type {
194            // unicode representation (https://en.wikipedia.org/wiki/Chess_symbols_in_Unicode):
195            PieceType::Pawn => {
196                if self.color == Color::White {
197                    write!(f, "\u{265F}")
198                } else {
199                    write!(f, "\u{2659}")
200                }
201            }
202            PieceType::Bishop => {
203                if self.color == Color::White {
204                    write!(f, "\u{265D}")
205                } else {
206                    write!(f, "\u{2657}")
207                }
208            }
209            PieceType::Knight => {
210                if self.color == Color::White {
211                    write!(f, "\u{265E}")
212                } else {
213                    write!(f, "\u{2658}")
214                }
215            }
216            PieceType::King => {
217                if self.color == Color::White {
218                    write!(f, "\u{265A}")
219                } else {
220                    write!(f, "\u{2654}")
221                }
222            }
223            PieceType::Rook => {
224                if self.color == Color::White {
225                    write!(f, "\u{265C}")
226                } else {
227                    write!(f, "\u{2656}")
228                }
229            }
230            PieceType::Queen => {
231                if self.color == Color::White {
232                    write!(f, "\u{265B}")
233                } else {
234                    write!(f, "\u{2655}")
235                }
236            }
237        }
238    }
239}
240
241impl Piece {
242    #[must_use]
243    pub const fn new(piece_type: PieceType, color: Color) -> Piece {
244        Piece { piece_type, color }
245    }
246}
247
248#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
249#[repr(u8)]
250enum MoveType {
251    Normal,
252    Jump,
253    Enpassant,
254    LongCastle,
255    ShortCastle,
256}
257
258#[derive(Debug, Clone, PartialEq, Eq, Hash)]
259pub struct Move {
260    pub piece: Piece,
261    pub from: Position,
262    pub to: Position,
263    pub captured_piece: Option<Piece>,
264    move_type: MoveType,
265    traversed_squares: Vec<Position>,
266}
267
268impl fmt::Display for Move {
269    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
270        write!(
271            f,
272            "{:?}: {:?} -> {:?}",
273            self.piece.piece_type, self.from, self.to
274        )
275    }
276}
277
278#[derive(Debug, Clone)]
279pub enum UserInput {
280    Move(Position, Position),
281    Promotion(Piece, Position),
282    Draw,
283    Resign,
284}
285
286#[derive(Debug, Clone)]
287pub enum UserOutput {
288    CheckMate,
289    StaleMate,
290    InvalidMove,
291    Promotion(Position),
292    Draw,
293}
294
295#[derive(Debug, Clone)]
296pub struct Game {
297    pub turn: Color,
298    pub board: Board,
299    pub captured: [Vec<Piece>; COLOR_COUNT],
300    history: Vec<Move>,
301    number_of_repeated_board_states: HashMap<(Color, Board, Vec<Move>), u8>,
302    number_of_moves_without_captures_or_pawn_moves: u8,
303    able_to_long_castle: [bool; COLOR_COUNT],
304    able_to_short_castle: [bool; COLOR_COUNT],
305    protected_squares: [Vec<Position>; COLOR_COUNT],
306    pieces_attacking_king: [Vec<(Piece, Vec<Position>)>; COLOR_COUNT],
307}
308
309impl fmt::Display for Game {
310    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
311        let mut res = String::new();
312        res.push_str("  -");
313        for _ in 1..=16 {
314            res.push_str("--");
315        }
316        res.push('\n');
317
318        for y in ('1'..='8').rev() {
319            res.push_str(format!("{y} | ").as_str());
320            for x in 'a'..='h' {
321                match &self.board[Position(x, y).as_index()] {
322                    None => res.push_str("  | "),
323                    Some(piece) => res.push_str(format!("{piece} | ").as_str()),
324                }
325            }
326            res.push_str("\n".to_string().as_str());
327            res.push_str("  -".to_string().as_str());
328            for _ in 1..=16 {
329                res.push_str("--");
330            }
331            res.push('\n');
332        }
333        res.push_str("    ");
334        for x in 'a'..='h' {
335            res.push_str(format!("{x}   ").as_str());
336        }
337        res.push('\n');
338        write!(f, "{res}")
339    }
340}
341
342impl Default for Game {
343    fn default() -> Self {
344        Self::new()
345    }
346}
347
348// NOTE: all the public functions are used by the UI
349impl Game {
350    #[must_use]
351    pub fn new() -> Game {
352        let mut board: Board = [None; TOTAL_SQUARES];
353        for x in 'a'..='h' {
354            for y in '1'..='8' {
355                let piece = match (x, y) {
356                    (_, '2') => Some(Piece::new(PieceType::Pawn, Color::White)),
357                    (_, '7') => Some(Piece::new(PieceType::Pawn, Color::Black)),
358                    ('a' | 'h', '1') => Some(Piece::new(PieceType::Rook, Color::White)),
359                    ('a' | 'h', '8') => Some(Piece::new(PieceType::Rook, Color::Black)),
360                    ('b' | 'g', '1') => Some(Piece::new(PieceType::Knight, Color::White)),
361                    ('b' | 'g', '8') => Some(Piece::new(PieceType::Knight, Color::Black)),
362                    ('c' | 'f', '1') => Some(Piece::new(PieceType::Bishop, Color::White)),
363                    ('c' | 'f', '8') => Some(Piece::new(PieceType::Bishop, Color::Black)),
364                    ('d', '1') => Some(Piece::new(PieceType::Queen, Color::White)),
365                    ('d', '8') => Some(Piece::new(PieceType::Queen, Color::Black)),
366                    ('e', '1') => Some(Piece::new(PieceType::King, Color::White)),
367                    ('e', '8') => Some(Piece::new(PieceType::King, Color::Black)),
368                    _ => None,
369                };
370                board[Position(x, y).as_index()] = piece;
371            }
372        }
373        let captured = [Vec::new(), Vec::new()];
374        let history = Vec::new();
375        let able_to_castle = [true, true];
376        let mut protected_squares_white: Vec<Position> = Vec::new();
377        let mut protected_squares_black: Vec<Position> = Vec::new();
378        for x in 'a'..='h' {
379            protected_squares_white.push(Position(x, '3'));
380            protected_squares_white.push(Position(x, '2'));
381            protected_squares_black.push(Position(x, '6'));
382            protected_squares_black.push(Position(x, '7'));
383            if x != 'a' || x != 'h' {
384                protected_squares_white.push(Position(x, '1'));
385                protected_squares_black.push(Position(x, '8'));
386            }
387        }
388
389        let protected_squares = [protected_squares_white, protected_squares_black];
390
391        let pieces_attacking_king = [Vec::new(), Vec::new()];
392
393        let mut game = Game {
394            turn: Color::White,
395            board,
396            captured,
397            history,
398            protected_squares,
399            pieces_attacking_king,
400            number_of_moves_without_captures_or_pawn_moves: 0,
401            number_of_repeated_board_states: HashMap::new(),
402            able_to_long_castle: able_to_castle,
403            able_to_short_castle: able_to_castle,
404        };
405
406        let board = game.board;
407        let all_possible_moves = game.get_all_possible_moves();
408        let key = (game.turn, board, all_possible_moves);
409
410        game.number_of_repeated_board_states.insert(key, 1);
411
412        game
413    }
414
415    #[allow(clippy::too_many_lines)]
416    pub fn process_input(&mut self, user_input: &UserInput) -> Option<UserOutput> {
417        match user_input {
418            UserInput::Move(from, to) => {
419                match self.get_move_if_valid(*from, *to) {
420                    Some(mv) => {
421                        if mv.piece.piece_type == PieceType::Pawn
422                            && mv.move_type == MoveType::Normal
423                            && (mv.to.1 == '8' || mv.to.1 == '1')
424                        {
425                            // update position
426                            self.board[from.as_index()] = None;
427                            self.board[to.as_index()] = Some(mv.piece);
428                            self.history.push(mv.clone());
429                            return Some(UserOutput::Promotion(mv.to));
430                        }
431
432                        self.turn = self.turn.invert();
433                        // update position
434                        self.board[from.as_index()] = None;
435                        self.board[to.as_index()] = Some(mv.piece);
436
437                        if mv.move_type == MoveType::Enpassant {
438                            let direction = if mv.piece.color == Color::White {
439                                1
440                            } else {
441                                -1
442                            };
443                            self.board[mv.to.add((0, -direction)).as_index()] = None;
444                        } else if mv.move_type == MoveType::LongCastle {
445                            if mv.piece.color == Color::White {
446                                self.board[Position('a', '1').as_index()] = None;
447                                self.board[Position('d', '1').as_index()] =
448                                    Some(Piece::new(PieceType::Rook, Color::White));
449                            } else {
450                                self.board[Position('a', '8').as_index()] = None;
451                                self.board[Position('d', '8').as_index()] =
452                                    Some(Piece::new(PieceType::Rook, Color::Black));
453                            }
454                        } else if mv.move_type == MoveType::ShortCastle {
455                            if mv.piece.color == Color::White {
456                                self.board[Position('h', '1').as_index()] = None;
457                                self.board[Position('f', '1').as_index()] =
458                                    Some(Piece::new(PieceType::Rook, Color::White));
459                            } else {
460                                self.board[Position('h', '8').as_index()] = None;
461                                self.board[Position('f', '8').as_index()] =
462                                    Some(Piece::new(PieceType::Rook, Color::Black));
463                            }
464                        }
465                        // FIXME: circular relationship in those function. Dirty fix was used by checking bool get_protected when checking if check
466                        //  get_all_protected_squares has to be run before pieces_attacking_king right now
467                        self.protected_squares = self.get_all_protected_squares(false); // as it does not matter if the piece is protected as the king never can move into check
468                        self.pieces_attacking_king = self.pieces_attacking_king(false); // as it does not matter if the piece is protected as the king never can move into check
469
470                        if let Some(captured_piece) = mv.captured_piece {
471                            self.captured[mv.piece.color as usize].push(captured_piece);
472                        }
473                        if (mv.piece.piece_type == PieceType::King
474                            || mv.piece.piece_type == PieceType::Rook)
475                            && (self.able_to_long_castle[mv.piece.color as usize]
476                                || self.able_to_short_castle[mv.piece.color as usize])
477                        {
478                            if mv.piece.piece_type == PieceType::King {
479                                self.able_to_short_castle[mv.piece.color as usize] = false;
480                                self.able_to_long_castle[mv.piece.color as usize] = false;
481                            } else {
482                                let long_caste_pos: Position = if mv.piece.color == Color::White {
483                                    Position('a', '1')
484                                } else {
485                                    Position('a', '8')
486                                };
487                                let short_caste_pos: Position = if mv.piece.color == Color::White {
488                                    Position('h', '1')
489                                } else {
490                                    Position('h', '8')
491                                };
492                                if mv.from == long_caste_pos {
493                                    self.able_to_long_castle[mv.piece.color as usize] = false;
494                                } else if mv.from == short_caste_pos {
495                                    self.able_to_short_castle[mv.piece.color as usize] = false;
496                                }
497                            }
498                        }
499
500                        if !(mv.captured_piece.is_some() || mv.piece.piece_type == PieceType::Pawn)
501                        {
502                            self.number_of_moves_without_captures_or_pawn_moves += 1;
503                        } else {
504                            self.number_of_moves_without_captures_or_pawn_moves = 0;
505                        }
506
507                        self.history.push(mv);
508
509                        let board = self.board;
510                        let all_possible_moves = self.get_all_possible_moves();
511
512                        let key = (self.turn, board, all_possible_moves);
513                        if self.number_of_repeated_board_states.contains_key(&key) {
514                            let num_pos = self.number_of_repeated_board_states[&key];
515                            self.number_of_repeated_board_states
516                                .insert(key, num_pos + 1);
517                        } else {
518                            self.number_of_repeated_board_states.insert(key, 1);
519                        }
520
521                        if self.no_possible_moves(self.turn) {
522                            return if self.check(self.turn) {
523                                Some(UserOutput::CheckMate)
524                            } else {
525                                Some(UserOutput::StaleMate)
526                            };
527                        }
528
529                        if self.is_a_draw() {
530                            return Some(UserOutput::Draw);
531                        }
532
533                        None
534                    }
535                    None => Some(UserOutput::InvalidMove),
536                }
537            }
538            UserInput::Promotion(piece, pos) => {
539                self.turn = self.turn.invert();
540                self.board[pos.as_index()] = Some(*piece);
541
542                // FIXME: circular relationship in those function. Dirty fix was used by checking bool get_protected when checking if check
543                //  get_all_protected_squares has to be run before pieces_attacking_king right now
544                self.protected_squares = self.get_all_protected_squares(true);
545                self.pieces_attacking_king = self.pieces_attacking_king(true);
546
547                if self.no_possible_moves(self.turn) {
548                    return if self.check(self.turn) {
549                        Some(UserOutput::CheckMate)
550                    } else {
551                        Some(UserOutput::StaleMate)
552                    };
553                }
554
555                None
556            }
557            _ => {
558                unreachable!()
559            }
560        }
561    }
562
563    #[must_use]
564    pub fn get_all_currently_valid_moves(&self) -> Vec<Move> {
565        let all_possible_moves = ALL_POSSIBLE_SQUARES.par_iter().flat_map(|(x, y)| {
566            let mut all_possible_moves = Vec::new();
567            if let Some(piece) = &self.board[Position(*x, *y).as_index()] {
568                if piece.color == self.turn {
569                    all_possible_moves = self.get_valid_moves(Position(*x, *y));
570                }
571            }
572            all_possible_moves
573        });
574        all_possible_moves.collect()
575    }
576
577    #[must_use]
578    pub fn get_valid_moves(&self, pos: Position) -> Vec<Move> {
579        self.possible_moves(pos, false, true)
580    }
581
582    #[inline]
583    #[must_use] pub fn check(&self, color: Color) -> bool {
584        !self.pieces_attacking_king[color as usize].is_empty()
585    }
586}
587
588// NOTE: all the private functions are used by the game logic
589impl Game {
590    fn obstacles_in_one_move(&self, pos: Position) -> Option<Obstacle> {
591        let Some(index) = pos.try_as_index() else {
592            return Some(Obstacle::OutOfBoundary);
593        };
594        self.board[index].map(|p| Obstacle::Piece(p.color))
595    }
596
597    fn can_short_castle(&self, color: Color) -> bool {
598        match color {
599            Color::Black => {
600                !self.check(color)
601                    && self.able_to_short_castle[color as usize]
602                    && self.board[Position('f', '8').as_index()].is_none()
603                    && self.board[Position('g', '8').as_index()].is_none()
604                    && !self.pos_protected(Position('f', '8'), color.invert())
605                    && !self.pos_protected(Position('g', '8'), color.invert())
606            }
607            Color::White => {
608                !self.check(color)
609                    && self.able_to_short_castle[color as usize]
610                    && self.board[Position('f', '1').as_index()].is_none()
611                    && self.board[Position('g', '1').as_index()].is_none()
612                    && !self.pos_protected(Position('f', '1'), color.invert())
613                    && !self.pos_protected(Position('g', '1'), color.invert())
614            }
615        }
616    }
617
618    fn can_long_castle(&self, color: Color) -> bool {
619        match color {
620            Color::Black => {
621                !self.check(color)
622                    && self.able_to_long_castle[color as usize]
623                    && self.board[Position('b', '8').as_index()].is_none()
624                    && self.board[Position('c', '8').as_index()].is_none()
625                    && self.board[Position('d', '8').as_index()].is_none()
626                    && !self.pos_protected(Position('c', '8'), color.invert())
627                    && !self.pos_protected(Position('d', '8'), color.invert())
628            }
629            Color::White => {
630                !self.check(color)
631                    && self.able_to_long_castle[color as usize]
632                    && self.board[Position('b', '1').as_index()].is_none()
633                    && self.board[Position('c', '1').as_index()].is_none()
634                    && self.board[Position('d', '1').as_index()].is_none()
635                    && !self.pos_protected(Position('c', '1'), color.invert())
636                    && !self.pos_protected(Position('d', '1'), color.invert())
637            }
638        }
639    }
640
641    fn get_all_protected_squares(&self, filter_pinned: bool) -> [Vec<Position>; COLOR_COUNT] {
642        let protected_squares_white = Mutex::new(Vec::new());
643        let protected_squares_black = Mutex::new(Vec::new());
644        protected_squares_white.lock().unwrap().reserve(64);
645        protected_squares_black.lock().unwrap().reserve(64);
646        ALL_POSSIBLE_SQUARES.par_iter().for_each(|(x, y)| {
647            if let Some(piece) = &self.board[Position(*x, *y).as_index()] {
648                let possible_moves = self.possible_moves(Position(*x, *y), true, filter_pinned);
649                for m in possible_moves {
650                    if piece.color == Color::White {
651                        protected_squares_white.lock().unwrap().push(m.to);
652                    } else {
653                        protected_squares_black.lock().unwrap().push(m.to);
654                    }
655                }
656            }
657        });
658
659        [
660            protected_squares_white.into_inner().unwrap(),
661            protected_squares_black.into_inner().unwrap(),
662        ]
663    }
664
665    fn pos_protected(&self, pos: Position, color: Color) -> bool {
666        for pos_protected in &self.protected_squares[color as usize] {
667            if pos == *pos_protected {
668                return true;
669            }
670        }
671        false
672    }
673
674    fn get_moves_in_one_direction(
675        &self,
676        x_path: &[i8],
677        y_path: &[i8],
678        pos: Position,
679        piece: Piece,
680        get_protected: bool,
681    ) -> Vec<Move> {
682        let mut moves = Vec::new();
683
684        let mut traversed_squares = vec![pos];
685        for (x, y) in x_path.iter().zip(y_path) {
686            let new_pos = pos.add((*x, *y));
687            let obstacle = self.obstacles_in_one_move(new_pos);
688
689            if let Some(obstacle) = obstacle {
690                match obstacle {
691                    Obstacle::Piece(obstacle_color)
692                        if get_protected || obstacle_color != piece.color =>
693                    {
694                        traversed_squares.push(new_pos);
695                        moves.push(Move {
696                            piece,
697                            move_type: MoveType::Normal,
698                            from: pos,
699                            to: new_pos,
700                            traversed_squares: traversed_squares.clone(),
701                            captured_piece: self.board[new_pos.as_index()],
702                        });
703                    }
704                    _ => {}
705                }
706                // if there is an obstacle we cannot go further:
707                break;
708            }
709
710            traversed_squares.push(new_pos);
711            moves.push(Move {
712                piece,
713                move_type: MoveType::Normal,
714                from: pos,
715                to: new_pos,
716                traversed_squares: traversed_squares.clone(),
717                captured_piece: self.board[new_pos.as_index()],
718            });
719        }
720        moves
721    }
722
723    fn possible_horizontal_vertical_moves(
724        &self,
725        pos: Position,
726        piece: Piece,
727        get_protected: bool,
728    ) -> Vec<Move> {
729        HORIZONTAL_DIRECTIONS
730            .par_iter() // Convert to a parallel iterator
731            .flat_map(|(x_range, y_range)| {
732                self.get_moves_in_one_direction(x_range, y_range, pos, piece, get_protected)
733            })
734            .collect()
735    }
736
737    fn possible_diagonal_moves(
738        &self,
739        pos: Position,
740        piece: Piece,
741        get_protected: bool,
742    ) -> Vec<Move> {
743        DIAGONAL_DIRECTIONS
744            .par_iter() // Convert to a parallel iterator
745            .flat_map(|(x_range, y_range)| {
746                self.get_moves_in_one_direction(x_range, y_range, pos, piece, get_protected)
747            })
748            .collect()
749    }
750
751    fn pieces_attacking_king(
752        &self,
753        filter_pinned: bool,
754    ) -> [Vec<(Piece, Vec<Position>)>; COLOR_COUNT] {
755        let pieces_attacking_white = Mutex::new(Vec::new());
756        let pieces_attacking_black = Mutex::new(Vec::new());
757        pieces_attacking_white.lock().unwrap().reserve(16);
758        pieces_attacking_black.lock().unwrap().reserve(16);
759        ALL_POSSIBLE_SQUARES.par_iter().for_each(|(x, y)| {
760            let moves = self.possible_moves(Position(*x, *y), false, filter_pinned);
761            for mv in moves {
762                if let Some(piece) = mv.captured_piece {
763                    if piece.piece_type == PieceType::King {
764                        if mv.piece.color == Color::White {
765                            pieces_attacking_black
766                                .lock()
767                                .unwrap()
768                                .push((mv.piece, mv.traversed_squares));
769                        } else {
770                            pieces_attacking_white
771                                .lock()
772                                .unwrap()
773                                .push((mv.piece, mv.traversed_squares));
774                        }
775                    }
776                }
777            }
778        });
779
780        [
781            pieces_attacking_white.into_inner().unwrap(),
782            pieces_attacking_black.into_inner().unwrap(),
783        ]
784    }
785
786    fn no_possible_moves(&self, color: Color) -> bool {
787        for x in 'a'..='h' {
788            for y in '1'..='8' {
789                let moves = self.possible_moves(Position(x, y), false, true);
790                if !moves.is_empty() && moves[0].piece.color == color {
791                    return false;
792                }
793            }
794        }
795        true
796    }
797
798    fn possbile_pawn_moves(&self, pos: Position, piece: Piece, get_protected: bool) -> Vec<Move> {
799        debug_assert_eq!(piece.piece_type, PieceType::Pawn);
800
801        let mut moves = Vec::new();
802
803        let (direction, rel_pos_to_iter) = if piece.color == Color::White {
804            (1, if pos.1 == '2' { 1..3 } else { 1..2 })
805        } else {
806            (-1, if pos.1 == '7' { 1..3 } else { 1..2 })
807        };
808
809        // Normal moves
810        if !get_protected {
811            for y in rel_pos_to_iter {
812                let new_pos = pos.add((0, direction * y));
813                match self.obstacles_in_one_move(new_pos) {
814                    None => {
815                        moves.push(Move {
816                            piece,
817                            move_type: MoveType::Normal,
818                            from: pos,
819                            to: new_pos,
820                            traversed_squares: vec![pos, new_pos],
821                            captured_piece: self.board[new_pos.as_index()],
822                        });
823                    }
824                    _ => {
825                        break;
826                    }
827                }
828            }
829        }
830
831        // check if able to capture a piece
832        for new_pos_int in [(1, direction), (-1, direction)] {
833            let new_pos = pos.add(new_pos_int);
834            match self.obstacles_in_one_move(new_pos) {
835                Some(Obstacle::Piece(other_piece_color)) => {
836                    if get_protected || other_piece_color != piece.color {
837                        moves.push(Move {
838                            piece,
839                            move_type: MoveType::Normal,
840                            from: pos,
841                            to: new_pos,
842                            traversed_squares: vec![pos, new_pos],
843                            captured_piece: self.board[new_pos.as_index()],
844                        });
845                    }
846                }
847                None => {
848                    if get_protected {
849                        moves.push(Move {
850                            piece,
851                            move_type: MoveType::Normal,
852                            from: pos,
853                            to: new_pos,
854                            traversed_squares: vec![pos, new_pos],
855                            captured_piece: self.board[new_pos.as_index()],
856                        });
857                    }
858                }
859                Some(Obstacle::OutOfBoundary) => {} // do nothing
860            }
861        }
862
863        // Enpassant
864        if !get_protected {
865            if let Some(last_move) = self.history.last() {
866                if last_move.piece.piece_type == PieceType::Pawn
867                    && (last_move.from.1 as i8 - last_move.to.1 as i8).abs() == 2
868                    && (last_move.to == pos.add((1, 0)) || last_move.to == pos.add((-1, 0)))
869                {
870                    let new_pos = if last_move.to == pos.add((1, 0)) {
871                        pos.add((1, direction))
872                    } else {
873                        pos.add((-1, direction))
874                    };
875                    moves.push(Move {
876                        piece,
877                        move_type: MoveType::Enpassant,
878                        from: pos,
879                        to: new_pos,
880                        traversed_squares: vec![pos, new_pos],
881                        captured_piece: self.board[new_pos.add((0, -direction)).as_index()],
882                    });
883                }
884            }
885        }
886
887        moves
888    }
889
890    fn possible_knight_moves(&self, pos: Position, piece: Piece, get_protected: bool) -> Vec<Move> {
891        let mut moves = Vec::new();
892        let dxdys: [(i8, i8); 8] = [
893            (2, 1),
894            (2, -1),
895            (-2, 1),
896            (-2, -1),
897            (1, 2),
898            (-1, 2),
899            (1, -2),
900            (-1, -2),
901        ];
902
903        for dxdy in dxdys {
904            let new_pos = pos.add(dxdy);
905            match self.obstacles_in_one_move(new_pos) {
906                None => {
907                    moves.push(Move {
908                        piece,
909                        move_type: MoveType::Jump,
910                        from: pos,
911                        to: new_pos,
912                        traversed_squares: vec![pos, new_pos],
913                        captured_piece: self.board[new_pos.as_index()],
914                    });
915                }
916                Some(Obstacle::Piece(obstacle_color)) => {
917                    if get_protected || piece.color != obstacle_color {
918                        moves.push(Move {
919                            piece,
920                            move_type: MoveType::Jump,
921                            from: pos,
922                            to: new_pos,
923                            traversed_squares: vec![pos, new_pos],
924                            captured_piece: self.board[new_pos.as_index()],
925                        });
926                    }
927                }
928                Some(Obstacle::OutOfBoundary) => {
929                    // nothing to do
930                }
931            }
932        }
933        moves
934    }
935
936    fn possible_queen_moves(&self, pos: Position, piece: Piece, get_protected: bool) -> Vec<Move> {
937        QUEEN_DIRECTIONS
938            .par_iter() // Convert to a parallel iterator
939            .flat_map(|(x_range, y_range)| {
940                self.get_moves_in_one_direction(x_range, y_range, pos, piece, get_protected)
941            })
942            .collect()
943    }
944
945    fn possible_king_moves(&self, pos: Position, piece: Piece, get_protected: bool) -> Vec<Move> {
946        let mut moves = Vec::with_capacity(8);
947        for (x, y) in [
948            (-1, 1),
949            (0, 1),
950            (1, 1),
951            (1, 0),
952            (1, -1),
953            (0, -1),
954            (-1, -1),
955            (-1, 0),
956        ] {
957            let new_pos = pos.add((x, y));
958            match self.obstacles_in_one_move(new_pos) {
959                None => {
960                    if get_protected || !self.pos_protected(new_pos, piece.color.invert()) {
961                        moves.push(Move {
962                            piece,
963                            move_type: MoveType::Normal,
964                            from: pos,
965                            to: new_pos,
966                            traversed_squares: vec![pos, new_pos],
967                            captured_piece: self.board[new_pos.as_index()],
968                        });
969                    }
970                }
971                Some(Obstacle::Piece(obstacle_color)) => {
972                    if get_protected
973                        || (!self.pos_protected(new_pos, piece.color.invert())
974                            && piece.color != obstacle_color)
975                    {
976                        moves.push(Move {
977                            piece,
978                            move_type: MoveType::Normal,
979                            from: pos,
980                            to: new_pos,
981                            traversed_squares: vec![pos, new_pos],
982                            captured_piece: self.board[new_pos.as_index()],
983                        });
984                    }
985                }
986                Some(Obstacle::OutOfBoundary) => {
987                    // nothing to do
988                }
989            }
990        }
991
992        if !get_protected && self.can_long_castle(piece.color) {
993            moves.push(Move {
994                piece,
995                move_type: MoveType::LongCastle,
996                from: pos,
997                to: pos.add((-2, 0)),
998                traversed_squares: vec![pos, pos.add((-1, 0)), pos.add((-2, 0))],
999                captured_piece: self.board[pos.add((-2, 0)).as_index()],
1000            });
1001        }
1002        if !get_protected && self.can_short_castle(piece.color) {
1003            moves.push(Move {
1004                piece,
1005                move_type: MoveType::ShortCastle,
1006                from: pos,
1007                to: pos.add((2, 0)),
1008                traversed_squares: vec![pos, pos.add((1, 0)), pos.add((2, 0))],
1009                captured_piece: self.board[pos.add((2, 0)).as_index()],
1010            });
1011        }
1012        moves
1013    }
1014
1015    fn possible_moves(&self, pos: Position, get_protected: bool, filter_pinned: bool) -> Vec<Move> {
1016        let Some(piece) = self.board[pos.as_index()] else {
1017            // no piece there -> no moves
1018            return Vec::new();
1019        };
1020
1021        if !get_protected
1022            && self.check(piece.color)
1023            && self.pieces_attacking_king[piece.color as usize].len() > 1
1024            && piece.piece_type != PieceType::King
1025        {
1026            // double check: only king can move
1027            return Vec::new();
1028        }
1029
1030        let mut moves = match piece.piece_type {
1031            PieceType::King => self.possible_king_moves(pos, piece, get_protected),
1032
1033            PieceType::Queen => self.possible_queen_moves(pos, piece, get_protected),
1034
1035            PieceType::Rook => self.possible_horizontal_vertical_moves(pos, piece, get_protected),
1036
1037            PieceType::Bishop => self.possible_diagonal_moves(pos, piece, get_protected),
1038
1039            PieceType::Knight => self.possible_knight_moves(pos, piece, get_protected),
1040
1041            PieceType::Pawn => self.possbile_pawn_moves(pos, piece, get_protected),
1042        };
1043
1044        if !get_protected && self.check(piece.color) && piece.piece_type != PieceType::King {
1045            // filter out moves that do not protect the king. Only for pieces other than the king.
1046            debug_assert_eq!(
1047                1,
1048                self.pieces_attacking_king[piece.color as usize].len(),
1049                "More than one piece attacking the king"
1050            );
1051            let (_, pos) = self.pieces_attacking_king[piece.color as usize][0].clone();
1052            moves = moves
1053                .into_par_iter()
1054                .filter(|x| pos.contains(&x.to))
1055                .collect();
1056        }
1057
1058        if filter_pinned {
1059            moves = moves
1060                .into_par_iter()
1061                .filter(|x| self.king_in_check_after_move(x))
1062                .collect();
1063        }
1064
1065        moves
1066    }
1067
1068    fn king_in_check_after_move(&self, mv: &Move) -> bool {
1069        // NOTE: We also consider the King here such that he does not move into a check
1070        // for example when the King moves in the same direction as the line of attack of a Rook
1071        let mut game_after_move = self.clone();
1072        game_after_move.turn = game_after_move.turn.invert();
1073        // update position
1074        game_after_move.board[mv.from.as_index()] = None;
1075        game_after_move.board[mv.to.as_index()] = Some(mv.piece);
1076        if mv.move_type == MoveType::Enpassant {
1077            let direction = if mv.piece.color == Color::White {
1078                1
1079            } else {
1080                -1
1081            };
1082            game_after_move.board[mv.to.add((0, -direction)).as_index()] = None;
1083        } else if mv.move_type == MoveType::LongCastle {
1084            if mv.piece.color == Color::White {
1085                game_after_move.board[Position('a', '1').as_index()] = None;
1086                game_after_move.board[Position('d', '1').as_index()] =
1087                    Some(Piece::new(PieceType::Rook, Color::White));
1088            } else {
1089                game_after_move.board[Position('a', '8').as_index()] = None;
1090                game_after_move.board[Position('d', '8').as_index()] =
1091                    Some(Piece::new(PieceType::Rook, Color::Black));
1092            }
1093        } else if mv.move_type == MoveType::ShortCastle {
1094            if mv.piece.color == Color::White {
1095                game_after_move.board[Position('h', '1').as_index()] = None;
1096                game_after_move.board[Position('f', '1').as_index()] =
1097                    Some(Piece::new(PieceType::Rook, Color::White));
1098            } else {
1099                game_after_move.board[Position('h', '8').as_index()] = None;
1100                game_after_move.board[Position('f', '8').as_index()] =
1101                    Some(Piece::new(PieceType::Rook, Color::Black));
1102            }
1103        }
1104        game_after_move.protected_squares = game_after_move.get_all_protected_squares(false);
1105        game_after_move.pieces_attacking_king = game_after_move.pieces_attacking_king(false);
1106        game_after_move.pieces_attacking_king[mv.piece.color as usize].is_empty()
1107    }
1108
1109    fn get_move_if_valid(&self, from: Position, to: Position) -> Option<Move> {
1110        match self.obstacles_in_one_move(from) {
1111            Some(Obstacle::Piece(piece_color)) => {
1112                if self.turn == piece_color {
1113                    let matching_moves: Vec<Move> = self
1114                        .possible_moves(from, false, true)
1115                        .into_par_iter()
1116                        .filter(|x| x.to == to)
1117                        .collect();
1118                    if matching_moves.is_empty() {
1119                        None
1120                    } else {
1121                        debug_assert_eq!(1, matching_moves.len());
1122                        Some(matching_moves[0].clone())
1123                    }
1124                } else {
1125                    None
1126                }
1127            }
1128            _ => None,
1129        }
1130    }
1131
1132    fn get_all_possible_moves(&self) -> Vec<Move> {
1133        let all_possible_moves = ALL_POSSIBLE_SQUARES.par_iter().flat_map(|(x, y)| {
1134            let mut all_possible_moves = Vec::new();
1135            if self.board[Position(*x, *y).as_index()].is_some() {
1136                all_possible_moves = self.possible_moves(Position(*x, *y), true, true);
1137            }
1138            all_possible_moves
1139        });
1140        all_possible_moves.collect()
1141    }
1142
1143    fn is_a_draw(&self) -> bool {
1144        if self.number_of_moves_without_captures_or_pawn_moves >= 50 {
1145            true
1146        } else {
1147            self.number_of_repeated_board_states
1148                .clone()
1149                .into_iter()
1150                .filter(|(_, num)| *num >= 3)
1151                .peekable()
1152                .peek()
1153                .is_some()
1154        }
1155    }
1156}