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#[derive(Debug, Default)]
17pub struct Engine {
18 pub attack_table: AttackTable,
19}
20
21impl 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}