Skip to main content

giga_chess/
engine.rs

1use crate::engine::attack_table::AttackTable;
2use crate::game::bit_board::BitBoard;
3use crate::game::chess_board::ChessBoard;
4use crate::game::chess_move::{ChessMove, ChessMoveType};
5use crate::game::color::Color;
6use crate::game::piece::Piece;
7use crate::game::state::GameState;
8use crate::game::status::GameStatus;
9use std::sync::Arc;
10
11pub mod attack_table;
12pub mod magic_numbers;
13
14/// The chess engine which has knowledge of the chess rules.\
15/// On initialization it generates all static data (like attack tables), so it's best to only initialize it only once (hence the Arc<>).
16#[derive(Debug, Default)]
17pub struct Engine {
18    pub attack_table: AttackTable,
19}
20
21// ToDo: Check for insufficient material, 3-fold repetition
22impl Engine {
23    pub fn initialize() -> Arc<Self> {
24        Arc::new(Self {
25            attack_table: AttackTable::build(),
26        })
27    }
28
29    pub fn generate_moves(&self, game_state: &GameState) -> (Vec<ChessMove>, GameStatus) {
30        if game_state.half_moves >= 50 {
31            return (vec![], GameStatus::DrawFiftyMove);
32        }
33
34        let player_mask = game_state.board.get_color_bb(game_state.side_to_move);
35        let opponent_mask = game_state
36            .board
37            .get_color_bb(game_state.side_to_move.opposite());
38        let occupied_mask = player_mask | opponent_mask;
39
40        let pseudo_legal =
41            self.generate_pseudo_legal_moves(game_state, player_mask, opponent_mask, occupied_mask);
42
43        let legal = pseudo_legal
44            .into_iter()
45            .filter(|&mv| self.is_legal_move(game_state, mv))
46            .collect::<Vec<_>>();
47        if legal.is_empty() {
48            return if self.is_in_check(game_state.board, game_state.side_to_move) {
49                (legal, GameStatus::Checkmate)
50            } else {
51                (legal, GameStatus::Stalemate)
52            };
53        }
54
55        (legal, GameStatus::Running)
56    }
57
58    pub fn is_legal_move(&self, game_state: &GameState, chess_move: ChessMove) -> bool {
59        let future_board = game_state
60            .board
61            .play_move(chess_move, game_state.side_to_move);
62        !self.is_in_check(future_board, game_state.side_to_move)
63    }
64
65    pub fn is_in_check(&self, board: ChessBoard, color: Color) -> bool {
66        let king_bb = board.get_piece_bb(Piece::King, color);
67        let Some(king_square) = king_bb.get_lowest_set_bit() else {
68            return false;
69        };
70
71        let opponent_color = color.opposite();
72        self.is_square_attacked(board, king_square, opponent_color)
73    }
74
75    pub fn is_square_attacked(&self, board: ChessBoard, square: u8, opponent_color: Color) -> bool {
76        let occupied = board.get_occupied_bb();
77
78        let pawn_attacks = self
79            .attack_table
80            .get_pawn_king_attack(square, opponent_color);
81        if !(pawn_attacks & board.get_piece_bb(Piece::Pawn, opponent_color)).is_empty() {
82            return true;
83        }
84
85        let knight_attacks = self.attack_table.get_knight_attacks(square);
86        if !(knight_attacks & board.get_piece_bb(Piece::Knight, opponent_color)).is_empty() {
87            return true;
88        }
89
90        let bishop_attacks = self.attack_table.get_bishop_attacks(square, occupied);
91        if !(bishop_attacks
92            & (board.get_piece_bb(Piece::Bishop, opponent_color)
93                | board.get_piece_bb(Piece::Queen, opponent_color)))
94        .is_empty()
95        {
96            return true;
97        }
98
99        let rook_attacks = self.attack_table.get_rook_attacks(square, occupied);
100        if !(rook_attacks
101            & (board.get_piece_bb(Piece::Rook, opponent_color)
102                | board.get_piece_bb(Piece::Queen, opponent_color)))
103        .is_empty()
104        {
105            return true;
106        }
107
108        let king_attacks = self.attack_table.get_king_attacks(square);
109        if !(king_attacks & board.get_piece_bb(Piece::King, opponent_color)).is_empty() {
110            return true;
111        }
112
113        false
114    }
115
116    pub fn get_square_threats(
117        &self,
118        board: ChessBoard,
119        square: u8,
120        opponent_color: Color,
121    ) -> BitBoard {
122        let occupied = board.get_occupied_bb();
123
124        let pawn_attacks = self
125            .attack_table
126            .get_pawn_king_attack(square, opponent_color);
127        let knight_attacks = self.attack_table.get_knight_attacks(square);
128        let bishop_attacks = self.attack_table.get_bishop_attacks(square, occupied);
129        let rook_attacks = self.attack_table.get_rook_attacks(square, occupied);
130        let king_attacks = self.attack_table.get_king_attacks(square);
131
132        let pawn_threats = pawn_attacks & board.get_piece_bb(Piece::Pawn, opponent_color);
133        let knight_threats = knight_attacks & board.get_piece_bb(Piece::Knight, opponent_color);
134        let bishop_threats = bishop_attacks
135            & (board.get_piece_bb(Piece::Bishop, opponent_color)
136                | board.get_piece_bb(Piece::Queen, opponent_color));
137        let rook_threats = rook_attacks
138            & (board.get_piece_bb(Piece::Rook, opponent_color)
139                | board.get_piece_bb(Piece::Queen, opponent_color));
140        let king_threats = king_attacks & board.get_piece_bb(Piece::King, opponent_color);
141
142        pawn_threats | knight_threats | bishop_threats | rook_threats | king_threats
143    }
144
145    pub fn is_promotion(&self, game_state: &GameState, move_to: u8) -> bool {
146        game_state.side_to_move == Color::White && move_to > 55
147            || game_state.side_to_move == Color::Black && move_to < 8
148    }
149
150    pub fn get_pawn_double_push_target(
151        &self,
152        game_state: &GameState,
153        from: u8,
154        occupied_mask: BitBoard,
155    ) -> Option<u8> {
156        if game_state.side_to_move == Color::White && from > 7 && from < 16
157            || game_state.side_to_move == Color::Black && from > 47 && from < 56
158        {
159            let target_square = match game_state.side_to_move {
160                Color::White => from + 16,
161                Color::Black => from - 16,
162            };
163            if !occupied_mask.get_bit(target_square) {
164                Some(target_square)
165            } else {
166                None
167            }
168        } else {
169            None
170        }
171    }
172
173    fn generate_pseudo_legal_moves(
174        &self,
175        game_state: &GameState,
176        player_mask: BitBoard,
177        opponent_mask: BitBoard,
178        occupied_mask: BitBoard,
179    ) -> Vec<ChessMove> {
180        let mut moves = Vec::new();
181        self.generate_pawn_moves(&mut moves, game_state, opponent_mask, occupied_mask);
182        self.generate_knight_moves(&mut moves, game_state, player_mask, opponent_mask);
183        self.generate_bishop_moves(
184            &mut moves,
185            game_state,
186            player_mask,
187            opponent_mask,
188            occupied_mask,
189        );
190        self.generate_rook_moves(
191            &mut moves,
192            game_state,
193            player_mask,
194            opponent_mask,
195            occupied_mask,
196        );
197        self.generate_king_moves(&mut moves, game_state, player_mask, opponent_mask);
198        self.generate_queen_moves(
199            &mut moves,
200            game_state,
201            player_mask,
202            opponent_mask,
203            occupied_mask,
204        );
205        self.generate_possible_castling_moves(&mut moves, game_state, occupied_mask);
206        moves
207    }
208
209    fn generate_pawn_moves(
210        &self,
211        moves: &mut Vec<ChessMove>,
212        game_state: &GameState,
213        opponent_mask: BitBoard,
214        occupied_mask: BitBoard,
215    ) {
216        let pawn_bb = game_state
217            .board
218            .get_piece_bb(Piece::Pawn, game_state.side_to_move);
219
220        for from in pawn_bb.iter_set_bits() {
221            let mask = self
222                .attack_table
223                .get_pawn_mask(from, game_state.side_to_move)
224                & !occupied_mask;
225            let attack = self
226                .attack_table
227                .get_pawn_attacks(from, game_state.side_to_move);
228
229            if let Some(move_to) = mask.get_lowest_set_bit() {
230                if !self.is_promotion(game_state, move_to) {
231                    moves.push(ChessMove::new(from, move_to, ChessMoveType::Quiet));
232
233                    if let Some(double_push_target) =
234                        self.get_pawn_double_push_target(game_state, from, occupied_mask)
235                    {
236                        moves.push(ChessMove::new(
237                            from,
238                            double_push_target,
239                            ChessMoveType::DoublePawnPush,
240                        ));
241                    }
242                } else {
243                    moves.extend(ChessMove::all_promotions(from, move_to))
244                }
245            }
246
247            for move_to in attack.iter_set_bits() {
248                if Some(move_to) == game_state.en_passant_square {
249                    moves.push(ChessMove::new(from, move_to, ChessMoveType::EnPassant));
250                } else if opponent_mask.get_bit(move_to) {
251                    if !self.is_promotion(game_state, move_to) {
252                        moves.push(ChessMove::new(from, move_to, ChessMoveType::Capture));
253                    } else {
254                        moves.extend(ChessMove::all_promotions_capture(from, move_to))
255                    }
256                }
257            }
258        }
259    }
260
261    fn generate_knight_moves(
262        &self,
263        moves: &mut Vec<ChessMove>,
264        game_state: &GameState,
265        player_mask: BitBoard,
266        opponent_mask: BitBoard,
267    ) {
268        let knight_bb = game_state
269            .board
270            .get_piece_bb(Piece::Knight, game_state.side_to_move);
271        for from in knight_bb.iter_set_bits() {
272            let attack = self.attack_table.get_knight_attacks(from) & !player_mask;
273            self.push_quiet_or_capture_moves(moves, from, attack, opponent_mask);
274        }
275    }
276
277    fn generate_bishop_moves(
278        &self,
279        moves: &mut Vec<ChessMove>,
280        game_state: &GameState,
281        player_mask: BitBoard,
282        opponent_mask: BitBoard,
283        occupied_mask: BitBoard,
284    ) {
285        let bishop_bb = game_state
286            .board
287            .get_piece_bb(Piece::Bishop, game_state.side_to_move);
288        for from in bishop_bb.iter_set_bits() {
289            let attack = self.attack_table.get_bishop_attacks(from, occupied_mask) & !player_mask;
290            self.push_quiet_or_capture_moves(moves, from, attack, opponent_mask);
291        }
292    }
293
294    fn generate_rook_moves(
295        &self,
296        moves: &mut Vec<ChessMove>,
297        game_state: &GameState,
298        player_mask: BitBoard,
299        opponent_mask: BitBoard,
300        occupied_mask: BitBoard,
301    ) {
302        let rook_bb = game_state
303            .board
304            .get_piece_bb(Piece::Rook, game_state.side_to_move);
305        for from in rook_bb.iter_set_bits() {
306            let attack = self.attack_table.get_rook_attacks(from, occupied_mask) & !player_mask;
307            self.push_quiet_or_capture_moves(moves, from, attack, opponent_mask);
308        }
309    }
310
311    fn generate_king_moves(
312        &self,
313        moves: &mut Vec<ChessMove>,
314        game_state: &GameState,
315        player_mask: BitBoard,
316        opponent_mask: BitBoard,
317    ) {
318        let king_bb = game_state
319            .board
320            .get_piece_bb(Piece::King, game_state.side_to_move);
321        for from in king_bb.iter_set_bits() {
322            let attack = self.attack_table.get_king_attacks(from) & !player_mask;
323            self.push_quiet_or_capture_moves(moves, from, attack, opponent_mask);
324        }
325    }
326
327    fn generate_queen_moves(
328        &self,
329        moves: &mut Vec<ChessMove>,
330        game_state: &GameState,
331        player_mask: BitBoard,
332        opponent_mask: BitBoard,
333        occupied_mask: BitBoard,
334    ) {
335        let queen_bb = game_state
336            .board
337            .get_piece_bb(Piece::Queen, game_state.side_to_move);
338        for from in queen_bb.iter_set_bits() {
339            let attack = self.attack_table.get_queen_attacks(from, occupied_mask) & !player_mask;
340            self.push_quiet_or_capture_moves(moves, from, attack, opponent_mask);
341        }
342    }
343
344    fn generate_possible_castling_moves(
345        &self,
346        moves: &mut Vec<ChessMove>,
347        game_state: &GameState,
348        occupied_mask: BitBoard,
349    ) {
350        match game_state.side_to_move {
351            Color::White => {
352                if game_state.castling_rights.white_king_side
353                    && (occupied_mask.get_value() & 0x60) == 0
354                    && !self.is_square_attacked(
355                        game_state.board,
356                        4,
357                        game_state.side_to_move.opposite(),
358                    )
359                    && !self.is_square_attacked(
360                        game_state.board,
361                        5,
362                        game_state.side_to_move.opposite(),
363                    )
364                    && !self.is_square_attacked(
365                        game_state.board,
366                        6,
367                        game_state.side_to_move.opposite(),
368                    )
369                {
370                    moves.push(ChessMove::new(4, 6, ChessMoveType::KingCastle));
371                }
372                if game_state.castling_rights.white_queen_side
373                    && (occupied_mask.get_value() & 0xE) == 0
374                    && !self.is_square_attacked(
375                        game_state.board,
376                        2,
377                        game_state.side_to_move.opposite(),
378                    )
379                    && !self.is_square_attacked(
380                        game_state.board,
381                        3,
382                        game_state.side_to_move.opposite(),
383                    )
384                    && !self.is_square_attacked(
385                        game_state.board,
386                        4,
387                        game_state.side_to_move.opposite(),
388                    )
389                {
390                    moves.push(ChessMove::new(4, 2, ChessMoveType::QueenCastle))
391                }
392            }
393            Color::Black => {
394                if game_state.castling_rights.black_king_side
395                    && (occupied_mask.get_value() & 0x6000000000000000) == 0
396                    && !self.is_square_attacked(
397                        game_state.board,
398                        60,
399                        game_state.side_to_move.opposite(),
400                    )
401                    && !self.is_square_attacked(
402                        game_state.board,
403                        61,
404                        game_state.side_to_move.opposite(),
405                    )
406                    && !self.is_square_attacked(
407                        game_state.board,
408                        62,
409                        game_state.side_to_move.opposite(),
410                    )
411                {
412                    moves.push(ChessMove::new(60, 62, ChessMoveType::KingCastle));
413                }
414                if game_state.castling_rights.black_queen_side
415                    && (occupied_mask.get_value() & 0xE00000000000000) == 0
416                    && !self.is_square_attacked(
417                        game_state.board,
418                        58,
419                        game_state.side_to_move.opposite(),
420                    )
421                    && !self.is_square_attacked(
422                        game_state.board,
423                        59,
424                        game_state.side_to_move.opposite(),
425                    )
426                    && !self.is_square_attacked(
427                        game_state.board,
428                        60,
429                        game_state.side_to_move.opposite(),
430                    )
431                {
432                    moves.push(ChessMove::new(60, 58, ChessMoveType::QueenCastle));
433                }
434            }
435        };
436    }
437
438    fn push_quiet_or_capture_moves(
439        &self,
440        moves: &mut Vec<ChessMove>,
441        from: u8,
442        attack_mask: BitBoard,
443        opponent_mask: BitBoard,
444    ) {
445        for move_to in attack_mask.iter_set_bits() {
446            if opponent_mask.get_bit(move_to) {
447                moves.push(ChessMove::new(from, move_to, ChessMoveType::Capture));
448            } else {
449                moves.push(ChessMove::new(from, move_to, ChessMoveType::Quiet));
450            }
451        }
452    }
453}