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 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]), (backward, [0i8; BOARD_SIZE]), ([0i8; BOARD_SIZE], forward), ([0i8; BOARD_SIZE], backward), ]
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), (forward, backward), (backward, forward), (backward, backward), ]
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 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
348impl 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 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 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 self.protected_squares = self.get_all_protected_squares(false); self.pieces_attacking_king = self.pieces_attacking_king(false); 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 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
588impl 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 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() .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() .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 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 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) => {} }
861 }
862
863 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 }
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() .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 }
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 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 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 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 let mut game_after_move = self.clone();
1072 game_after_move.turn = game_after_move.turn.invert();
1073 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}