1use crate::board::{Board, SIZE_0X88};
2use crate::constants::{ATTACKS, COLOR_MASK, KNIGHT_DELTAS, QUEEN_DELTAS};
3use crate::error::Error;
4use crate::move_gen::MoveGen;
5use crate::piece::{PType, Piece};
6use crate::square::Square;
7use crate::utils;
8use std::cmp;
9use std::collections::{HashMap, HashSet};
10
11#[derive(Clone, Copy, PartialEq, Debug)]
12#[repr(u8)]
13pub enum Color {
14 WHITE = 0,
15 BLACK = 128,
16}
17
18impl TryFrom<&str> for Color {
19 type Error = Error;
20 fn try_from(value: &str) -> Result<Self, Self::Error> {
21 match value {
22 "w" => Ok(Color::WHITE),
23 "b" => Ok(Color::BLACK),
24 _ => Err(Error::InvalidColor),
25 }
26 }
27}
28
29impl From<Color> for &'static str {
30 fn from(color: Color) -> &'static str {
31 match color {
32 Color::WHITE => "w",
33 Color::BLACK => "b",
34 }
35 }
36}
37
38#[derive(Clone, PartialEq, Debug)]
39pub struct Move {
40 pub from: Square,
41 pub to: Square,
42 pub promotion_piece: Option<Piece>,
43}
44
45impl Move {
47 pub fn new(from: Square, to: Square, promotion_piece: Option<Piece>) -> Self {
48 Self {
49 from,
50 to,
51 promotion_piece,
52 }
53 }
54
55 pub fn from_str(from: &str, to: &str, promotion_piece: Option<Piece>) -> Self {
56 Self {
57 from: from.try_into().unwrap(),
58 to: to.try_into().unwrap(),
59 promotion_piece,
60 }
61 }
62}
63
64#[derive(Default, Clone)]
65struct CastlingRights {
66 white_kingside: bool,
67 white_queenside: bool,
68 black_kingside: bool,
69 black_queenside: bool,
70}
71
72struct HistoryEntry {
73 m: Move,
74 piece: Piece,
75 castling_rights: CastlingRights,
76 en_passant_sq: Option<Square>,
77 half_moves: u8,
78 full_moves: u8,
79 is_queenside_castle: bool,
80 is_kingside_castle: bool,
81 has_moved: bool,
82 unique_positions: HashMap<String, u8>,
83 side_to_move: Color,
84 fen: String,
85 is_capture: bool,
86 check_rays: HashSet<Square>,
87}
88
89pub struct Capture {
90 sq: Square,
91 piece: Piece,
92}
93
94pub struct GameState {
95 pub board: Board,
96 pub en_passant_sq: Option<Square>,
97 pub side_to_move: Color,
98 pub is_in_check: bool,
99 pub white_king_square: Option<Square>,
100 pub black_king_square: Option<Square>,
101 check_rays: HashSet<Square>,
102 castling_rights: CastlingRights,
103
104 pub captures: Vec<Capture>,
105 unique_positions: HashMap<String, u8>,
106
107 half_moves: u8,
108 full_moves: u8,
109 debug: bool,
110
111 history: Vec<HistoryEntry>,
112
113 has_moved: bool,
114 captures_count: usize,
115 castle_count: usize,
116 check_count: usize,
117}
118
119impl GameState {
120 pub fn new() -> Self {
121 Self {
122 side_to_move: Color::WHITE,
123 board: Board::new(),
124 en_passant_sq: None,
125 is_in_check: false,
126 check_rays: HashSet::new(),
127 white_king_square: None,
128 black_king_square: None,
129 castling_rights: CastlingRights::default(),
130 captures: vec![],
131 debug: false,
132 full_moves: 0,
133 half_moves: 0,
134 unique_positions: HashMap::new(),
135 has_moved: false,
136 captures_count: 0,
137 castle_count: 0,
138 check_count: 0,
139
140 history: vec![],
141 }
142 }
143
144 pub fn play_move(&mut self, m: Move) -> Result<(), Error> {
145 if !self.is_occupied(&m.from) {
146 return Err(Error::UnknownMove);
147 }
148
149 if self
150 .board
151 .get(&m.from)
152 .expect("piece must be present after the is_occupied check")
153 .color
154 != self.side_to_move
155 {
156 return Err(Error::MustWaitForTurn);
157 }
158
159 let legal_moves = MoveGen::moves_for_square(self, m.from);
161 if !legal_moves.contains(&m) || legal_moves.len() == 0 {
162 return Err(Error::IllegalMove);
163 }
164
165 self.make_move(m);
166 self.change_turn();
167 self.update_king_attacks();
168
169 Ok(())
170 }
171
172 pub fn make_move(&mut self, m: Move) {
174 let piece = self
175 .board
176 .get(&m.from)
177 .expect("a piece must be present in make_move")
178 .clone();
179
180 let mut is_capture = false;
181 if self.is_capture(&m) {
182 if self.is_enpassant_capture(&m) {
183 let delta: i8 = match piece.color {
184 Color::WHITE => -16,
185 Color::BLACK => 16,
186 };
187 let to =
188 m.to.add(delta)
189 .expect("en passant square must be valid here");
190
191 self.board.remove(&to);
192
193 self.captures.push(Capture {
194 sq: to,
195 piece: Piece::new(
196 PType::PAWN,
197 if piece.color == Color::WHITE {
198 Color::BLACK
199 } else {
200 Color::WHITE
201 },
202 ),
203 });
204 } else {
205 self.captures.push(Capture {
206 sq: m.to.clone(),
207 piece: self
208 .board
209 .get(&m.to)
210 .expect("capture must have a captured piece")
211 .clone(),
212 });
213 }
214 is_capture = true;
215 }
216
217 let mut is_queenside_castle = false;
219 let mut is_kingside_castle = false;
220 if self.board.get(&m.from).unwrap().p_type == PType::KING {
221 if self.is_castling_kingside(&m) {
222 is_kingside_castle = true;
223 self.board
225 .remove(&m.to.add(1).expect("castling square must be valid"));
226 self.set(
228 Piece::new(PType::ROOK, piece.color),
229 &m.to.add(-1).expect("castling square must be valid"),
230 );
231 self.castle_count += 1;
232 } else if self.is_castling_queenside(&m) {
233 is_queenside_castle = true;
234 self.board
236 .remove(&m.to.add(-2).expect("castling square must be valid"));
237 self.set(
239 Piece::new(PType::ROOK, piece.color),
240 &m.to.add(1).expect("castling square must be valid"),
241 );
242 self.castle_count += 1;
243 }
244 }
245
246 self.history.push(HistoryEntry {
248 m: m.clone(),
249 piece: piece.clone(),
250 castling_rights: self.castling_rights.clone(),
251 full_moves: self.full_moves,
252 half_moves: self.half_moves,
253 en_passant_sq: self.en_passant_sq,
254 is_kingside_castle,
255 is_queenside_castle,
256 has_moved: self.has_moved,
257 unique_positions: self.unique_positions.clone(),
258 side_to_move: self.side_to_move,
259 fen: self.get_fen(),
260 is_capture,
261 check_rays: self.check_rays.clone(),
262 });
263
264 if self.is_enpassant_move(&m) {
265 let delta: i8 = match piece.color {
266 Color::WHITE => -16,
267 Color::BLACK => 16,
268 };
269
270 self.en_passant_sq = Some(m.to.add(delta).expect("en passant square must be valid"));
271 } else {
272 self.en_passant_sq = None;
273 }
274
275 self.has_moved = true;
276 self.full_moves += 1;
277
278 if piece.p_type == PType::PAWN || self.is_capture(&m) {
279 self.half_moves = 0;
280 } else {
281 self.half_moves += 1;
282 }
283
284 if let Some(ref promo_piece) = m.promotion_piece {
285 self.set(promo_piece.clone(), &m.to);
286 } else {
287 self.set(piece.clone(), &m.to);
288 }
289
290 self.board.remove(&m.from);
291 self.update_castling_rights(&piece, &m);
292 self.update_positions();
293 }
294
295 pub fn undo(&mut self) {
296 if let Some(entry) = self.history.pop() {
297 let piece = entry.piece;
298
299 self.set(piece.clone(), &entry.m.from);
300 self.board.remove(&entry.m.to);
301
302 if entry.is_kingside_castle {
303 self.board.remove(&entry.m.from.add(1).unwrap());
305 self.set(
306 Piece::new(PType::ROOK, piece.color),
307 &entry.m.from.add(3).unwrap(),
308 );
309 } else if entry.is_queenside_castle {
310 self.board.remove(&entry.m.from.add(-1).unwrap());
311 self.set(
312 Piece::new(PType::ROOK, piece.color),
313 &entry.m.from.add(-4).unwrap(),
314 );
315 }
316
317 self.full_moves = entry.full_moves;
318 self.half_moves = entry.half_moves;
319 self.has_moved = entry.has_moved;
320 self.unique_positions = entry.unique_positions;
321 self.side_to_move = entry.side_to_move;
322 self.castling_rights = entry.castling_rights;
323 self.en_passant_sq = entry.en_passant_sq;
324 self.check_rays = entry.check_rays;
325 self.is_in_check = self.check_rays.len() > 0;
326
327 if entry.is_capture {
328 let c = self.captures.pop().expect("capture MUST be in history");
329 self.board.set(c.piece, &c.sq);
330 }
331 };
332 }
333
334 fn update_king_attacks(&mut self) {
335 let king_sq: Square = self.get_current_king_sq().expect("no king to check");
336
337 self.check_rays = self.get_attack_rays(king_sq);
339 self.is_in_check = self.check_rays.len() > 0;
340 }
341
342 fn get_attack_rays(&self, sq: Square) -> HashSet<Square> {
343 #[allow(non_snake_case)]
344 let SLIDING_ATTACK_DELTAS = QUEEN_DELTAS;
345 let mut temp: Square;
346 let mut attack_rays: HashSet<Square> = HashSet::new();
347
348 for delta in SLIDING_ATTACK_DELTAS {
349 let mut attacker_present = false;
350 let mut visting_squares: HashSet<Square> = HashSet::new();
351 temp = sq;
352
353 while let Ok(to_sq) = temp.add(*delta) {
354 let Some(piece) = self.board.get(&to_sq) else {
355 temp = to_sq;
356 visting_squares.insert(to_sq);
357 continue;
358 };
359
360 if piece.color == self.side_to_move {
361 break; }
363
364 let diff = to_sq.0 as i16 - sq.0 as i16 + 119;
365 let attack_mask = ATTACKS[diff as usize];
366
367 if attack_mask == 0 {
368 break; }
370
371 if piece.p_type == PType::PAWN {
372 let piece_bits = (piece.p_type as u8) | (piece.color as u8);
373
374 if (piece_bits & attack_mask) != 0
375 && (piece.color as u8) == (attack_mask & COLOR_MASK)
376 {
377 attacker_present = true;
378 visting_squares.insert(to_sq);
379 }
380 } else {
381 if ((piece.p_type as u8) & attack_mask) != 0 {
382 attacker_present = true;
383 visting_squares.insert(to_sq);
384 }
385 }
386
387 break;
388 }
389
390 if attacker_present {
391 attack_rays.extend(&visting_squares);
392 }
393 }
394
395 for delta in KNIGHT_DELTAS {
397 if let Ok(to_sq) = sq.add(*delta) {
398 let Some(piece) = self.board.get(&to_sq) else {
399 continue;
400 };
401
402 if piece.p_type == PType::KNIGHT && piece.color != self.side_to_move {
403 attack_rays.insert(to_sq);
404 }
405 }
406 }
407
408 return attack_rays;
409 }
410
411 pub fn is_attacked(&self, sq: Square) -> bool {
412 return self.get_attack_rays(sq).len() > 0;
413 }
414
415 pub fn get_current_king_sq(&self) -> Option<Square> {
416 if self.side_to_move == Color::WHITE {
417 return self.white_king_square;
418 } else {
419 return self.black_king_square;
420 }
421 }
422
423 pub fn get_castling_rights(&self) -> (bool, bool) {
424 if self.side_to_move == Color::WHITE {
425 return (
426 self.castling_rights.white_kingside,
427 self.castling_rights.white_queenside,
428 );
429 } else {
430 return (
431 self.castling_rights.black_kingside,
432 self.castling_rights.black_queenside,
433 );
434 }
435 }
436
437 fn update_castling_rights(&mut self, piece: &Piece, m: &Move) {
438 let a1: Square = "a1".try_into().unwrap();
439 let h1: Square = "h1".try_into().unwrap();
440 let a8: Square = "a8".try_into().unwrap();
441 let h8: Square = "h8".try_into().unwrap();
442
443 if self.board.get(&a1) != Some(&Piece::new(PType::ROOK, Color::WHITE)) {
444 self.castling_rights.white_queenside = false;
445 }
446 if self.board.get(&h1) != Some(&Piece::new(PType::ROOK, Color::WHITE)) {
447 self.castling_rights.white_kingside = false;
448 }
449 if self.board.get(&a8) != Some(&Piece::new(PType::ROOK, Color::BLACK)) {
450 self.castling_rights.black_queenside = false;
451 }
452 if self.board.get(&h8) != Some(&Piece::new(PType::ROOK, Color::BLACK)) {
453 self.castling_rights.black_kingside = false;
454 }
455
456 if piece.p_type == PType::KING {
457 if piece.color == Color::WHITE {
458 self.castling_rights.white_kingside = false;
459 self.castling_rights.white_queenside = false;
460 } else {
461 self.castling_rights.black_kingside = false;
462 self.castling_rights.black_queenside = false;
463 }
464
465 return;
466 }
467
468 if piece.p_type != PType::ROOK {
469 return;
470 }
471
472 let kingside_rook_sq = match piece.color {
473 Color::WHITE => "h1",
474 Color::BLACK => "h8",
475 };
476
477 let queenside_rook_sq = match piece.color {
478 Color::WHITE => "a1",
479 Color::BLACK => "a8",
480 };
481
482 if m.from.get_notation() == kingside_rook_sq {
483 if piece.color == Color::WHITE {
484 self.castling_rights.white_kingside = false;
485 } else {
486 self.castling_rights.black_kingside = false;
487 }
488 } else if m.from.get_notation() == queenside_rook_sq {
489 if piece.color == Color::WHITE {
490 self.castling_rights.white_queenside = false;
491 } else {
492 self.castling_rights.black_queenside = false;
493 }
494 }
495 }
496
497 fn update_king_sq(&mut self, sq: Square, color: Color) {
498 if color == Color::WHITE {
499 self.white_king_square = Some(sq);
500 } else {
501 self.black_king_square = Some(sq);
502 }
503 }
504
505 fn update_positions(&mut self) {
506 let fen = self.get_fen();
507 let fen_parts: Vec<&str> = fen.split(" ").collect();
508
509 *self
510 .unique_positions
511 .entry(fen_parts[0].to_string())
512 .or_insert(0) += 1;
513 }
514
515 fn change_turn(&mut self) {
516 if self.side_to_move == Color::WHITE {
517 self.side_to_move = Color::BLACK;
518 } else {
519 self.side_to_move = Color::WHITE;
520 }
521 }
522
523 pub fn reset(&mut self) {
524 self.side_to_move = Color::WHITE;
525 self.board = Board::new();
526 self.en_passant_sq = None;
527 self.is_in_check = false;
528 self.check_rays = HashSet::new();
529 self.white_king_square = None;
530 self.black_king_square = None;
531 self.castling_rights = CastlingRights::default();
532 self.captures = vec![];
533 self.debug = false;
534 self.full_moves = 0;
535 self.half_moves = 0;
536 self.unique_positions = HashMap::new();
537 self.has_moved = false;
538 }
539
540 fn is_draw(&mut self) -> bool {
541 self.is_stalemate()
542 || self.is_threefold_repetition()
543 || self.is_50_moves()
544 || self.is_insufficient_material()
545 }
546
547 fn is_stalemate(&mut self) -> bool {
548 !self.is_in_check && MoveGen::moves(self, self.side_to_move).len() == 0
549 }
550
551 fn is_insufficient_material(&self) -> bool {
554 let mut w_knights = 0;
555 let mut b_knights = 0;
556 let mut w_bishops = 0;
557 let mut b_bishops = 0;
558 let mut w_light_square_bishops = 0;
559 let mut w_dark_square_bishops = 0;
560 let mut b_light_square_bishops = 0;
561 let mut b_dark_square_bishops = 0;
562 let mut knights = 0;
563 let mut bishops = 0;
564
565 for idx in 0..SIZE_0X88 {
566 let idx = idx as u8;
567
568 if utils::is_valid_idx(idx).is_err() {
569 continue;
570 }
571
572 let Some(piece) = self.board.get(&idx) else {
573 continue;
574 };
575
576 if piece.p_type == PType::PAWN
577 || piece.p_type == PType::ROOK
578 || piece.p_type == PType::QUEEN
579 {
580 return false;
581 }
582
583 let square: Square = idx.try_into().unwrap();
584
585 if piece.color == Color::WHITE {
586 if piece.p_type == PType::KNIGHT {
587 w_knights += 1;
588 knights += 1;
589 }
590
591 if piece.p_type == PType::BISHOP {
592 if square.color() == Color::WHITE {
593 w_light_square_bishops += 1;
594 } else {
595 w_dark_square_bishops += 1;
596 }
597
598 w_bishops += 1;
599 bishops += 1;
600 }
601 } else {
602 if piece.p_type == PType::KNIGHT {
603 b_knights += 1;
604 knights += 1;
605 }
606
607 if piece.p_type == PType::BISHOP {
608 if square.color() == Color::WHITE {
609 b_light_square_bishops += 1;
610 } else {
611 b_dark_square_bishops += 1;
612 }
613
614 b_bishops += 1;
615 bishops += 1;
616 }
617 }
618 }
619
620 if knights == 0 && bishops == 0 {
622 return true;
623 }
624
625 if (knights == 1 && bishops == 0) || (bishops == 1 && knights == 0) {
627 return true;
628 }
629
630 if (w_dark_square_bishops == 1 && b_dark_square_bishops == 1)
642 || (w_light_square_bishops == 1 && b_light_square_bishops == 1)
643 {
644 return true;
645 }
646
647 if w_dark_square_bishops != 0 && b_dark_square_bishops != 0 && knights == 0 {
648 return true;
649 }
650
651 if w_light_square_bishops != 0 && b_light_square_bishops != 0 && knights == 0 {
652 return true;
653 }
654
655 false
656 }
657
658 fn is_50_moves(&self) -> bool {
659 self.half_moves >= 100
660 }
661
662 fn is_threefold_repetition(&self) -> bool {
664 for (_, position_count) in &self.unique_positions {
665 if *position_count >= 3 {
666 return true;
667 }
668 }
669
670 false
671 }
672
673 fn is_checkmate(&mut self) -> bool {
674 self.is_in_check && MoveGen::moves(self, self.side_to_move).len() == 0
675 }
676
677 pub fn is_occupied(&self, sq: &Square) -> bool {
678 return self.board.get(sq).is_some();
679 }
680
681 fn is_friendly(&self, piece: &Piece) -> bool {
682 return piece.color == self.side_to_move;
683 }
684
685 fn is_enpassant_capture(&self, m: &Move) -> bool {
686 self.board.get(&m.from).unwrap().p_type == PType::PAWN && Some(m.to) == self.en_passant_sq
687 }
688
689 fn is_enpassant_move(&self, m: &Move) -> bool {
690 let p = self.board.get(&m.from).unwrap();
691
692 if p.p_type != PType::PAWN {
693 return false;
694 }
695
696 if (m.to.0 as i16 - m.from.0 as i16).abs() != 32 {
697 return false;
698 }
699
700 for d in &[1, -1] {
701 if let Ok(to) = m.to.add(*d) {
702 if let Some(piece) = self.board.get(&to) {
703 if piece.p_type == PType::PAWN && piece.color != p.color {
704 return true;
705 }
706 }
707 }
708 }
709
710 return false;
711 }
712
713 fn is_capture(&self, m: &Move) -> bool {
714 let Some(p) = self.board.get(&m.to) else {
715 let a = self.is_enpassant_capture(m);
716 return a;
717 };
718 p.color != self.side_to_move
719 }
720
721 pub fn is_castling(&self, m: &Move) -> bool {
722 (m.to.0 as i16 - m.from.0 as i16).abs() == 2
723 }
735
736 pub fn is_castling_kingside(&self, m: &Move) -> bool {
737 let a = m.from.get_notation();
738 (a == "e1" || a == "e8") && (m.to.0 as i16 - m.from.0 as i16) == 2
739 }
740
741 pub fn is_castling_queenside(&self, m: &Move) -> bool {
742 let a = m.from.get_notation();
743 (a == "e1" || a == "e8") && (m.to.0 as i16 - m.from.0 as i16) == -2
744 }
745
746 fn set(&mut self, piece: Piece, sq: &Square) {
747 if piece.p_type == PType::KING {
748 self.update_king_sq(sq.clone(), piece.color);
749 }
750 self.board.set(piece, sq);
751 }
752
753 pub fn load_fen(&mut self, fen: &str) {
755 let fen_parts: Vec<&str> = fen.split(" ").collect();
756
757 let mut ranks: Vec<&str> = fen_parts[0].split("/").collect();
758 ranks.reverse();
759
760 for rank_idx in 0..ranks.len() {
761 let mut file_idx: u8 = 0;
762
763 for c in ranks[rank_idx].chars() {
764 let sq = Square::new((rank_idx as u8).into(), file_idx.into());
765
766 match c {
767 '1'..='8' => {
768 file_idx += (c.to_digit(10).unwrap() as usize - 1) as u8;
769 }
770 _ => {
771 self.set(
772 c.to_string()
773 .as_str()
774 .try_into()
775 .expect("invalid FEN characters"),
776 &sq,
777 );
778 }
779 };
780
781 file_idx += 1;
782 }
783 }
784
785 match fen_parts[1] {
787 "w" => self.side_to_move = Color::WHITE,
788 "b" => self.side_to_move = Color::BLACK,
789 _ => panic!("invalid FEN turn"),
790 }
791
792 for castling_right in fen_parts[2].chars() {
794 match castling_right {
795 'K' => {
796 self.castling_rights.white_kingside = true;
797 }
798 'Q' => {
799 self.castling_rights.white_queenside = true;
800 }
801 'k' => {
802 self.castling_rights.black_kingside = true;
803 }
804 'q' => {
805 self.castling_rights.black_queenside = true;
806 }
807
808 '-' => {
809 self.castling_rights.white_kingside = false;
810 self.castling_rights.white_queenside = false;
811 self.castling_rights.black_kingside = false;
812 self.castling_rights.black_kingside = false;
813 }
814 _ => panic!("cant load fen castling rights"),
815 }
816 }
817
818 let en_passant_square = fen_parts[3];
820
821 match en_passant_square {
822 "-" => {
823 self.en_passant_sq = None;
824 }
825 _ => {
826 self.en_passant_sq = Some(
827 en_passant_square
828 .try_into()
829 .expect("invalid en passant square in FEN"),
830 );
831 }
832 }
833
834 self.half_moves = fen_parts[4].parse().expect("can't parse FEN half moves");
835 self.full_moves = fen_parts[5].parse().expect("can't parse FEN full moves");
836
837 *self
838 .unique_positions
839 .entry(fen_parts[0].to_string())
840 .or_insert(0) += 1;
841
842 self.update_king_attacks();
843 }
844
845 pub fn get_fen(&self) -> String {
847 let mut empty_count: u8 = 0;
848
849 let mut rank = String::new();
850 let mut ranks: Vec<String> = vec![];
851
852 for idx in 0..128 {
853 if utils::is_valid_idx(idx).is_err() {
854 continue;
855 }
856
857 if let Some(piece) = self.board.get(&idx) {
858 if empty_count != 0 {
859 rank.push_str(empty_count.to_string().as_str());
860 empty_count = 0;
861 }
862
863 let p: String = piece.clone().into();
864 rank.push_str(&p);
865 } else {
866 empty_count += 1;
867 }
868
869 if (idx + 1) % 8 == 0 {
870 if empty_count != 0 {
871 rank.push_str(empty_count.to_string().as_str());
872 empty_count = 0;
873 }
874
875 ranks.push(rank);
876 rank = String::new();
877 }
878 }
879
880 ranks.reverse();
881 let en_passant_sq = if let Some(sq) = self.en_passant_sq {
882 sq.get_notation()
883 } else {
884 "-".to_string()
885 };
886
887 let half_moves = self.half_moves;
888 let full_moves = if self.has_moved {
889 cmp::max(1, self.full_moves - 1)
890 } else {
891 self.full_moves
892 };
893
894 let mut castling_rights = String::new();
895 if self.castling_rights.white_kingside {
896 castling_rights.push_str("K");
897 }
898
899 if self.castling_rights.white_queenside {
900 castling_rights.push_str("Q");
901 }
902
903 if self.castling_rights.black_kingside {
904 castling_rights.push_str("k");
905 }
906
907 if self.castling_rights.black_queenside {
908 castling_rights.push_str("q")
909 }
910
911 if castling_rights.is_empty() {
912 castling_rights.push_str("-");
913 }
914
915 let turn: &str = self.side_to_move.into();
916
917 vec![
919 ranks.join("/"),
920 turn.to_string(),
921 castling_rights,
922 en_passant_sq,
923 half_moves.to_string(),
924 full_moves.to_string(),
925 ]
926 .join(" ")
927 }
928
929 pub fn get_boar(&self) -> Board {
930 self.board.clone()
931 }
932
933 pub fn perft(&mut self, depth: u8, log: bool) -> usize {
934 let mut nodes = 0;
935 let mut count = 0;
936
937 if depth <= 0 {
938 return 1;
939 }
940
941 let moves = MoveGen::moves(self, self.side_to_move);
942
943 for _move in moves {
944 let from = _move.from.get_notation();
945 let to = _move.to.get_notation();
946
947 match self.play_move(_move.clone()) {
948 Ok(_) => {}
949 Err(e) => {
950 panic!("{}", e)
951 }
952 }
953 count = self.perft(depth - 1, false);
960
961 nodes += count;
962
963 if log {
964 if let Some(promotion_piece) = _move.promotion_piece {
965 match promotion_piece.p_type {
966 PType::BISHOP => {
967 println!("{} {}", format!("{}{}b", from, to), count);
968 }
969 PType::KNIGHT => {
970 println!("{} {}", format!("{}{}n", from, to), count);
971 }
972 PType::ROOK => {
973 println!("{} {}", format!("{}{}r", from, to), count);
974 }
975 PType::QUEEN => {
976 println!("{} {}", format!("{}{}q", from, to), count);
977 }
978 _ => panic!("invalid promotion piece"),
979 }
980 } else {
981 println!("{} {}", format!("{}{}", from, to), count);
982 }
983 }
984
985 self.undo();
986 }
987
988 return nodes;
989 }
990}
991
992pub struct Chess {
993 state: GameState,
994}
995
996impl Chess {
997 pub fn new() -> Self {
998 Self {
999 state: GameState::new(),
1000 }
1001 }
1002
1003 pub fn play_move(&mut self, m: Move) -> Result<(), Error> {
1004 Ok(self.state.play_move(m)?)
1005 }
1006
1007 pub fn load_fen(&mut self, fen: &str) -> Result<(), Error> {
1008 self.state.load_fen(fen);
1009
1010 Ok(())
1011 }
1012
1013 pub fn get_fen(&self) -> String {
1014 self.state.get_fen()
1015 }
1016
1017 pub fn get_turn(&self) -> Color {
1018 self.state.side_to_move
1019 }
1020
1021 pub fn set_turn(&mut self, color: Color) {
1022 self.state.side_to_move = color;
1023 }
1024
1025 pub fn get_board_ptr(&self) -> *const Option<Piece> {
1026 self.state.board.get_board_ptr()
1027 }
1028}
1029
1030#[cfg(test)]
1031mod tests {
1032 use super::*;
1033 use crate::piece::*;
1034 use crate::square::*;
1035
1036 #[test]
1037 fn in_check() {
1038 let mut state = GameState::new();
1039 let white_king_sq = Square::new(Rank::Five, File::C);
1040 let attacker_sq = Square::new(Rank::Three, File::E);
1041 let attacker2_sq = Square::new(Rank::Eight, File::F);
1042
1043 let king = Piece::new(PType::KING, Color::WHITE);
1044 let attacker = Piece::new(PType::QUEEN, Color::BLACK);
1045 let attacker2 = Piece::new(PType::BISHOP, Color::BLACK);
1046
1047 state.set(king, &white_king_sq);
1048 state.set(attacker, &attacker_sq);
1049 state.set(attacker2, &attacker2_sq);
1050 state.set(
1051 Piece::new(PType::PAWN, Color::BLACK),
1052 &Square::new(Rank::Six, File::B),
1053 );
1054
1055 state.update_king_attacks();
1056
1057 assert!(state.is_in_check);
1058
1059 let mut squares: Vec<String> = state.check_rays.iter().map(|m| m.get_notation()).collect();
1060
1061 squares.sort();
1062 let mut expected = ["d6", "e7", "f8", "b6", "d4", "e3"];
1063 expected.sort();
1064
1065 assert_eq!(squares, expected);
1066
1067 let blocker = Piece::new(PType::PAWN, Color::WHITE);
1068 let blocker_sq = Square::new(Rank::Six, File::D);
1069 state.board.set(blocker, &blocker_sq);
1070
1071 state.update_king_attacks();
1072
1073 assert!(state.is_in_check);
1074
1075 let mut squares: Vec<String> = state.check_rays.iter().map(|m| m.get_notation()).collect();
1076
1077 squares.sort();
1078 let mut expected = ["d4", "e3", "b6"];
1079 expected.sort();
1080
1081 assert_eq!(squares, expected);
1082 }
1083
1084 #[test]
1085 fn load_fen() {
1086 let mut state = GameState::new();
1087 state.load_fen("8/2b5/1N6/2K3qk/8/8/8/8 w - - 0 1");
1088
1089 assert_eq!(
1090 state
1091 .board
1092 .get(&Square::new(Rank::Five, File::C))
1093 .expect("piece must exist here")
1094 .p_type,
1095 PType::KING
1096 );
1097
1098 assert_eq!(
1099 state
1100 .board
1101 .get(&Square::new(Rank::Five, File::G))
1102 .expect("piece must exist here")
1103 .p_type,
1104 PType::QUEEN
1105 );
1106 }
1107
1108 #[test]
1109 fn get_fen() {
1110 let mut state = GameState::new();
1111 let fens = [
1112 "8/2b5/1N6/2K3qk/8/8/8/8 w - - 0 1",
1113 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
1114 "rn1qkb1r/p1b1pppp/4n3/1p1p2B1/Q1N5/3B1N2/PPPPPPPP/R3K2R w KQkq - 0 1",
1115 "k7/8/8/8/8/8/8/7K w - - 0 1",
1116 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
1117 "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1",
1118 "1nbqkbn1/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/1NBQKBN1 b - - 1 2",
1119 ];
1120
1121 for fen in fens {
1122 state.load_fen(fen);
1123
1124 assert_eq!(fen, state.get_fen());
1125
1126 state.reset();
1127 }
1128
1129 let mut state = GameState::new();
1130 state.load_fen("4k3/8/8/8/5p2/8/4P3/4K3 w - - 0 1");
1131 assert!(state.play_move(Move::from_str("e2", "e4", None)).is_ok());
1132 assert_eq!(state.get_fen(), "4k3/8/8/8/4Pp2/8/8/4K3 b - e3 0 1");
1133
1134 }
1140
1141 struct TestCase {
1142 fen: String,
1143 moves: Vec<Move>,
1144 expected: String,
1145 }
1146
1147 impl TestCase {
1148 fn new(fen: &str, moves: Vec<Move>, expected: &str) -> Self {
1149 Self {
1150 fen: fen.to_string(),
1151 moves,
1152 expected: expected.to_string(),
1153 }
1154 }
1155 }
1156
1157 #[test]
1158 fn undo() {
1159 let tests = vec![
1160 TestCase::new(
1161 "r3k2r/8/8/8/6pP/8/8/R3K2R w KQkq - 0 1",
1162 vec![Move::from_str("e1", "g1", None)],
1163 "r3k2r/8/8/8/6pP/8/8/R3K2R w KQkq - 0 1",
1164 ),
1165 TestCase::new(
1166 "r3k2r/8/8/8/6pP/8/8/R3K2R w KQkq - 0 1",
1167 vec![Move::from_str("e1", "c1", None)],
1168 "r3k2r/8/8/8/6pP/8/8/R3K2R w KQkq - 0 1",
1169 ),
1170 TestCase::new(
1171 "r3k2r/8/8/8/6pP/8/8/R3K2R b KQkq - 0 1",
1172 vec![Move::from_str("e8", "g8", None)],
1173 "r3k2r/8/8/8/6pP/8/8/R3K2R b KQkq - 0 1",
1174 ),
1175 TestCase::new(
1176 "r3k2r/8/2q5/1P6/6p1/8/7P/R3K2R w KQkq - 0 1",
1177 vec![Move::from_str("h2", "h4", None)],
1178 "r3k2r/8/2q5/1P6/6p1/8/7P/R3K2R w KQkq - 0 1",
1179 ),
1180 TestCase::new(
1181 "r3k2r/8/2q5/1P6/6p1/8/7P/R3K2R w KQkq - 0 1",
1182 vec![Move::from_str("b5", "c6", None)],
1183 "r3k2r/8/2q5/1P6/6p1/8/7P/R3K2R w KQkq - 0 1",
1184 ),
1185 TestCase::new(
1187 "r3k2r/8/2q5/1P6/6pP/8/8/R3K2R b KQkq h3 0 1",
1188 vec![Move::from_str("g4", "h3", None)],
1189 "r3k2r/8/2q5/1P6/6pP/8/8/R3K2R b KQkq h3 0 1",
1190 ),
1191 TestCase::new(
1193 "r3k2r/5P2/2q5/1P6/6p1/7P/8/R3K2R w KQkq - 0 1",
1194 vec![Move::from_str(
1195 "f7",
1196 "f8",
1197 Some(Piece::new(PType::QUEEN, Color::WHITE)),
1198 )],
1199 "r3k2r/5P2/2q5/1P6/6p1/7P/8/R3K2R w KQkq - 0 1",
1200 ),
1201 ];
1202
1203 let mut state = GameState::new();
1204
1205 for test in tests {
1206 state.reset();
1207 state.load_fen(&test.fen);
1208
1209 for m in test.moves {
1210 assert!(state.play_move(m).is_ok());
1211 state.undo();
1212 }
1213
1214 assert_eq!(state.get_fen(), test.expected);
1215 assert!(state.captures.is_empty());
1216 }
1217 }
1218
1219 #[test]
1220 fn play_move() {
1221 let tests = vec![
1222 TestCase::new(
1223 "rnb1kbnr/pppppppp/1q6/4Q2B/8/2N4B/P1PPP2P/R3K2R w KQkq - 0 1",
1224 vec![
1225 Move::from_str("e1", "f1", None),
1226 Move::from_str("e8", "d8", None),
1227 ],
1228 "rnbk1bnr/pppppppp/1q6/4Q2B/8/2N4B/P1PPP2P/R4K1R w - - 2 2",
1229 ),
1230 TestCase::new(
1231 "r1bk1b1r/1ppppp1p/1q1n3n/p3Q1pB/8/2NP3B/P1PKP2P/1R4R1 w - - 0 1",
1232 vec![
1233 Move::from_str("e5", "e7", None),
1234 Move::from_str("d8", "e7", None),
1235 ],
1236 "r1b2b1r/1pppkp1p/1q1n3n/p5pB/8/2NP3B/P1PKP2P/1R4R1 w - - 0 2",
1237 ),
1238 ];
1239
1240 let mut state = GameState::new();
1241
1242 for test in tests {
1243 state.reset();
1244 state.load_fen(&test.fen);
1245
1246 for m in test.moves {
1247 assert!(state.play_move(m).is_ok());
1248 }
1249
1250 assert_eq!(state.get_fen(), test.expected);
1251 }
1252 }
1253
1254 #[test]
1255 fn enpassant() {
1256 let tests = vec![
1257 TestCase::new(
1258 "1k6/8/8/8/5p2/8/4P3/4KR2 w - - 0 1",
1259 vec![Move::from_str("e2", "e4", None)],
1260 "1k6/8/8/8/4Pp2/8/8/4KR2 b - e3 0 1",
1261 ),
1262 TestCase::new(
1263 "1k6/8/2p5/8/8/8/4P3/4KR2 w - - 0 1",
1264 vec![Move::from_str("e2", "e4", None)],
1265 "1k6/8/2p5/8/4P3/8/8/4KR2 b - - 0 1",
1266 ),
1267 TestCase::new(
1268 "1k6/8/8/8/6p1/8/7P/4KR2 w - - 0 1",
1269 vec![Move::from_str("h2", "h4", None)],
1270 "1k6/8/8/8/6pP/8/8/4KR2 b - h3 0 1",
1271 ),
1272 ];
1273
1274 let mut state = GameState::new();
1275
1276 for test in tests {
1277 state.reset();
1278 state.load_fen(&test.fen);
1279
1280 for m in test.moves {
1281 assert!(state.play_move(m).is_ok());
1282 }
1283
1284 assert_eq!(state.get_fen(), test.expected);
1285 }
1286 }
1287
1288 #[test]
1289 fn checkmate() {
1290 let fens = vec![
1292 "8/5r2/4K1q1/4p3/3k4/8/8/8 w - - 0 7",
1293 "4r2r/p6p/1pnN2p1/kQp5/3pPq2/3P4/PPP3PP/R5K1 b - - 0 2",
1294 "r3k2r/ppp2p1p/2n1p1p1/8/2B2P1q/2NPb1n1/PP4PP/R2Q3K w kq - 0 8",
1295 "8/6R1/pp1r3p/6p1/P3R1Pk/1P4P1/7K/8 b - - 0 4",
1296 ];
1297
1298 for fen in fens {
1299 let mut state = GameState::new();
1300 state.load_fen(fen);
1301
1302 assert!(state.is_checkmate());
1303 }
1304 }
1305
1306 #[test]
1307 fn insufficient_material() {
1308 let fens = vec![
1310 "8/8/8/8/8/8/8/k6K w - - 0 1",
1311 "8/2N5/8/8/8/8/8/k6K w - - 0 1",
1312 "8/2b5/8/8/8/8/8/k6K w - - 0 1",
1313 "8/b7/3B4/8/8/8/8/k6K w - - 0 1",
1314 "8/b1B1b1B1/1b1B1b1B/8/8/8/8/1k5K w - - 0 1",
1315 ];
1316
1317 for fen in fens {
1318 let mut state = GameState::new();
1319 state.load_fen(fen);
1320
1321 assert!(state.is_insufficient_material());
1322 }
1323
1324 let fens = vec![
1325 "8/2p5/8/8/8/8/8/k6K w - - 0 1",
1326 "5k1K/7B/8/6b1/8/8/8/8 b - - 0 1",
1327 "7K/5k1N/8/6b1/8/8/8/8 b - - 0 1",
1328 "7K/5k1N/8/4n3/8/8/8/8 b - - 0 1",
1329 ];
1330
1331 for fen in fens {
1332 let mut state = GameState::new();
1333 state.load_fen(fen);
1334
1335 assert!(!state.is_insufficient_material());
1336 }
1337 }
1338
1339 #[test]
1340 fn stalemate() {
1341 let fens = vec![
1343 "1R6/8/8/8/8/8/7R/k6K b - - 0 1",
1344 "8/8/5k2/p4p1p/P4K1P/1r6/8/8 w - - 0 2",
1345 ];
1346
1347 for fen in fens {
1348 let mut state = GameState::new();
1349 state.load_fen(fen);
1350
1351 assert!(state.is_stalemate());
1352 }
1353
1354 let mut state = GameState::new();
1355 state.load_fen("R3k3/8/4K3/8/8/8/8/8 b - - 0 1");
1356
1357 assert!(!state.is_stalemate());
1358 }
1359
1360 #[test]
1361 fn threefold_repetition() {
1362 let mut state = GameState::new();
1363 state.load_fen("8/pp3p1k/2p2q1p/3r1P2/5R2/7P/P1P1QP2/7K b - - 2 30");
1364 let moves = vec![
1365 Move::from_str("f6", "e5", None),
1366 Move::from_str("e2", "h5", None),
1367 Move::from_str("e5", "f6", None),
1368 Move::from_str("h5", "e2", None),
1369 Move::from_str("d5", "e5", None),
1370 Move::from_str("e2", "d3", None),
1371 Move::from_str("e5", "d5", None),
1372 Move::from_str("d3", "e2", None),
1373 ];
1374
1375 for m in moves {
1376 assert!(state.play_move(m).is_ok());
1377 }
1378
1379 assert!(state.is_threefold_repetition());
1380 }
1381
1382 fn perft() {
1384 struct Perft {
1385 fen: &'static str,
1386 depth: u8,
1387 expected: usize,
1388 }
1389
1390 let tests = [
1391 Perft {
1392 fen: "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1",
1393 depth: 3,
1394 expected: 2812,
1395 },
1396 Perft {
1397 fen: "rnbqkbnr/p3pppp/2p5/1pPp4/3P4/8/PP2PPPP/RNBQKBNR w KQkq b6 0 4",
1398 depth: 3,
1399 expected: 23509,
1400 },
1401 Perft {
1402 fen: "r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 101",
1403 depth: 3,
1404 expected: 89890,
1405 },
1406 Perft {
1407 fen: "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8",
1408 depth: 3,
1409 expected: 62379,
1410 },
1411 Perft {
1412 fen: "r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 1",
1413 depth: 4,
1414 expected: 422333,
1415 },
1416 Perft {
1417 fen: "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1",
1418 depth: 3,
1419 expected: 97862,
1420 },
1421 Perft {
1422 fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
1423 depth: 4,
1424 expected: 197281,
1425 },
1426 ];
1427 let mut state = GameState::new();
1428
1429 for test in tests {
1430 state.reset();
1431 state.load_fen(&test.fen);
1432 assert_eq!(state.perft(test.depth, false), test.expected);
1433 }
1434 }
1435}