blunders_engine/
position.rs

1//! Holds Position struct, the most important data structure for the engine.
2//! Position represents a chess position.
3//! Positions and moves are assumed to be strictly legal,
4//! and have undefined behavior for illegal activity.
5
6use std::fmt::{self, Display};
7
8use crate::bitboard::Bitboard;
9use crate::boardrepr::PieceSets;
10use crate::coretypes::{
11    Castling, Color, Move, MoveCount, MoveInfo, MoveKind, Piece, PieceKind, Square,
12};
13use crate::coretypes::{Color::*, PieceKind::*, Square::*};
14use crate::error::{self, ErrorKind};
15use crate::fen::Fen;
16use crate::movegen as mg;
17use crate::movelist::{MoveHistory, MoveList};
18
19/// Game contains information for an in progress game:
20/// The base position the game started from, the sequence of moves that were
21/// played, and the current position.
22#[derive(Debug, Clone, Eq, PartialEq)]
23pub struct Game {
24    pub base_position: Position,
25    pub moves: MoveHistory,
26    pub position: Position,
27}
28
29impl Game {
30    /// Create a new Game from a base position and a sequence of moves.
31    /// This generates the current position by applying the sequence of moves to the base.
32    /// If a move in the move history was illegal, Err is returned.
33    pub fn new(base_position: Position, moves: MoveHistory) -> error::Result<Self> {
34        let mut position = base_position.clone();
35
36        for move_ in &moves {
37            let maybe_move_info = position.do_legal_move(*move_);
38            if maybe_move_info.is_none() {
39                return Err(ErrorKind::GameIllegalMove.into());
40            }
41        }
42
43        Ok(Self {
44            base_position,
45            moves,
46            position,
47        })
48    }
49
50    /// Create a new game in the standard chess start position.
51    pub fn start_position() -> Self {
52        Self::from(Position::start_position())
53    }
54}
55
56/// Convert a position to a Game with no past moves.
57impl From<Position> for Game {
58    fn from(position: Position) -> Self {
59        Self::new(position, MoveHistory::new()).unwrap()
60    }
61}
62
63/// During position.do_move, there are a number of variables
64/// that are updated in one direction, which are restored from backups in MoveInfo
65/// during position.undo_move. Instead of each MoveInfo keeping its own repetitive copy
66/// of this undone info, they should be saved separately.
67#[derive(Debug, Copy, Clone, Eq, PartialEq)]
68pub struct Cache {
69    pub(crate) castling: Castling,
70    pub(crate) en_passant: Option<Square>,
71    pub(crate) halfmoves: MoveCount,
72    // Number of times active player is in check, either 0, 1, or 2.
73    // pub(crate) checks: u8,
74    // Checks? Occupied per side?
75}
76
77impl Cache {
78    /// Return a cache with garbage values.
79    pub fn illegal() -> Self {
80        Self {
81            castling: Castling::NONE,
82            en_passant: None,
83            halfmoves: 1,
84        }
85    }
86}
87
88impl From<&Position> for Cache {
89    fn from(position: &Position) -> Self {
90        Self {
91            castling: position.castling,
92            en_passant: position.en_passant,
93            halfmoves: position.halfmoves,
94        }
95    }
96}
97
98/// struct Position
99/// A complete data set that can represent any chess position.
100/// # Members:
101/// * pieces - a piece-centric setwise container of all basic chess piece positions.
102/// * player - Color of player whose turn it is. AKA: "side_to_move".
103/// * castling - Castling rights for both players.
104/// * en_passant - Indicates if en passant is possible, and for which square.
105/// * halfmoves - Tracker for 50 move draw rule. Resets after capture/pawn move.
106/// * fullmoves - Starts at 1, increments after each black player's move.
107#[derive(Debug, Copy, Clone, Eq, PartialEq)]
108pub struct Position {
109    pub(crate) pieces: PieceSets,
110    pub(crate) player: Color,
111    pub(crate) castling: Castling,
112    pub(crate) en_passant: Option<Square>,
113    pub(crate) halfmoves: MoveCount,
114    pub(crate) fullmoves: MoveCount,
115}
116
117impl Position {
118    /// Standard chess start position.
119    pub fn start_position() -> Self {
120        Self {
121            pieces: PieceSets::start_position(),
122            player: Color::White,
123            castling: Castling::start_position(),
124            en_passant: None,
125            halfmoves: 0,
126            fullmoves: 1,
127        }
128    }
129
130    /// Const getters.
131    pub fn pieces(&self) -> &PieceSets {
132        &self.pieces
133    }
134    pub fn player(&self) -> &Color {
135        &self.player
136    }
137    pub fn castling(&self) -> &Castling {
138        &self.castling
139    }
140    pub fn en_passant(&self) -> &Option<Square> {
141        &self.en_passant
142    }
143    pub fn halfmoves(&self) -> &MoveCount {
144        &self.halfmoves
145    }
146    pub fn fullmoves(&self) -> &MoveCount {
147        &self.fullmoves
148    }
149
150    /// Return the number of moves played in this game so far, from the fullmove counter.
151    pub fn moves_played(&self) -> MoveCount {
152        self.fullmoves * 2
153            - 1
154            - match self.player {
155                Color::White => 1,
156                Color::Black => 0,
157            }
158    }
159
160    /// Returns the age of this position.
161    /// Age is the same as moves_played(), except wrapped into a u8.
162    pub(crate) fn age(&self) -> u8 {
163        (self.moves_played() % u8::MAX as MoveCount) as u8
164    }
165
166    /// Create a new position where the relative position is the same for the active player,
167    /// but the player gets switched.
168    /// This is equivalent to a vertical flip and color swap for all pieces,
169    /// along with castling rights, player and en-passant.
170    ///
171    /// This is useful for checking that an evaluation function scores the same scenario
172    /// presented to either player.
173    pub fn color_flip(&self) -> Self {
174        let mut flipped = self.clone();
175
176        // For each piece, flip its rank and color
177        let mut pieces = PieceSets::new();
178        for color in Color::iter() {
179            for piece_kind in PieceKind::iter() {
180                for sq in self.pieces[(color, piece_kind)] {
181                    let flipped_sq = Square::from((sq.file(), sq.rank().flip()));
182                    pieces[(!color, piece_kind)].set_square(flipped_sq);
183                }
184            }
185        }
186        flipped.pieces = pieces;
187
188        // Flip side to move
189        flipped.player = !self.player;
190
191        // Flip castling rights
192        let mut cr = Castling::NONE;
193        self.castling
194            .has(Castling::W_KING)
195            .then(|| cr.set(Castling::B_KING));
196        self.castling
197            .has(Castling::W_QUEEN)
198            .then(|| cr.set(Castling::B_QUEEN));
199        self.castling
200            .has(Castling::B_KING)
201            .then(|| cr.set(Castling::W_KING));
202        self.castling
203            .has(Castling::B_QUEEN)
204            .then(|| cr.set(Castling::W_QUEEN));
205        flipped.castling = cr;
206
207        // Flip ep passant square
208        flipped.en_passant = self
209            .en_passant
210            .map(|sq| Square::from((sq.file(), sq.rank().flip())));
211
212        debug_assert!(flipped.pieces().is_valid());
213        debug_assert!(flipped.castling().is_mask_valid());
214        flipped
215    }
216
217    /// Returns true if the positions are the same, in context of FIDE laws for position repetition.
218    /// They are the same if the player to move, piece kind and color per square, en passant,
219    /// and castling rights are the same.
220    pub fn is_same_as(&self, other: &Self) -> bool {
221        self.player == other.player
222            && self.castling == other.castling
223            && self.en_passant == other.en_passant
224            && self.pieces == other.pieces
225    }
226
227    /// Returns true if the fifty-move rule has been reached by this position, indicating that it is drawn.
228    /// `num_legal_moves`: number of legal moves for this position.
229    pub fn fifty_move_rule(&self, num_legal_moves: usize) -> bool {
230        self.halfmoves >= 100 && num_legal_moves != 0
231    }
232
233    /// Generate a MoveInfo for this position from a given Move.
234    pub fn move_info(&self, move_: Move) -> MoveInfo {
235        let moved_piece_kind = self
236            .pieces
237            .on_player_square(self.player, move_.from)
238            .expect("no piece on `from` square");
239
240        let mut move_kind = MoveKind::Quiet;
241
242        // Check for Capture. Each mode below is mutually exclusive.
243        if let Some(pk) = self.pieces.on_player_square(!self.player, move_.to) {
244            move_kind = MoveKind::Capture(pk);
245        }
246        // Check for EnPassant.
247        else if moved_piece_kind == Pawn {
248            if let Some(ep_square) = self.en_passant {
249                if move_.to == ep_square {
250                    move_kind = MoveKind::EnPassant;
251                }
252            }
253        }
254        // Check for Castling
255        else if moved_piece_kind == King {
256            match (move_.from, move_.to) {
257                (E1, C1) | (E1, G1) | (E8, C8) | (E8, G8) => move_kind = MoveKind::Castle,
258                _ => (),
259            };
260        }
261
262        MoveInfo::new(move_, moved_piece_kind, move_kind)
263    }
264
265    /// Returns this position's cached state.
266    pub fn cache(&self) -> Cache {
267        Cache::from(self)
268    }
269
270    /// Halfmoves is set to zero after a capture or pawn move, incremented otherwise.
271    /// There is no unset because this value is cached.
272    fn step_halfmoves(&mut self, move_info: &MoveInfo) {
273        let is_pawn_move = move_info.piece_kind == Pawn;
274        let is_capture = matches!(move_info.move_kind, MoveKind::Capture(_));
275
276        match is_pawn_move || is_capture {
277            true => self.halfmoves = 0,
278            false => self.halfmoves += 1,
279        }
280    }
281
282    /// Fullmoves is incremented after each Black player's move.
283    fn step_fullmoves(&mut self) {
284        if *self.player() == Black {
285            self.fullmoves += 1;
286        }
287    }
288
289    // Fullmoves increments after Black move, so decrements before White move.
290    // It starts at 1, so cannot go below that.
291    fn unstep_fullmoves(&mut self) {
292        if *self.player() == White && *self.fullmoves() > 1 {
293            self.fullmoves -= 1;
294        }
295    }
296
297    /// Apply a move to self, in place.
298    /// `do_move` does not check if the move is legal or not,
299    /// it simply executes it while assuming legality.
300    /// Castling is described by moving king 2 squares, as defined in UCI protocol.
301    /// Assumptions:
302    /// There is an active player's piece on from square.
303    /// There is no active player's piece on to square.
304    /// A double king move from starting position is a castling.
305    /// Current behavior:
306    /// Removes from square from active player piece on that square.
307    /// Removes to square from all passive player pieces.
308    /// Panics if from square has no active player piece.
309    pub fn do_move(&mut self, move_: Move) -> MoveInfo {
310        let move_info = self.move_info(move_);
311        self.do_move_info(move_info);
312        move_info
313    }
314
315    /// Incrementally apply a move to self, in place.
316    /// This assumes the given move_info is legal.
317    pub fn do_move_info(&mut self, move_info: MoveInfo) {
318        let player = *self.player();
319        let active_piece = Piece::new(player, move_info.piece_kind);
320
321        // These always get updated, regardless of move.
322        self.step_halfmoves(&move_info);
323        self.step_fullmoves();
324        self.en_passant = None;
325        self.pieces[active_piece].clear_square(move_info.from);
326        self.player = !self.player;
327
328        // If promoting, place promoting piece. Otherwise place active piece.
329        if let Some(promoting_piece_kind) = move_info.promotion {
330            let promoting_piece = Piece::new(player, promoting_piece_kind);
331            self.pieces[promoting_piece].set_square(move_info.to);
332        } else {
333            self.pieces[active_piece].set_square(move_info.to);
334        }
335
336        // Handle all special moves.
337        match move_info.move_kind {
338            // Clear opposing player's captured piece.
339            MoveKind::Capture(piece_kind) => {
340                let captured_piece = Piece::new(!player, piece_kind);
341                self.pieces[captured_piece].clear_square(move_info.to);
342            }
343            // Remove captured pawn near the en-passant square.
344            MoveKind::EnPassant => {
345                let to = Bitboard::from(move_info.to);
346                let captured_pawn = mg::pawn_single_pushes(to, !player);
347                self.pieces[(!player, Pawn)].remove(&captured_pawn);
348            }
349            // Move Rook to castling square and clear castling rights.
350            MoveKind::Castle => {
351                let castling_rook_squares = match (move_info.from, move_info.to) {
352                    (E1, G1) => (H1, F1), // White Kingside
353                    (E1, C1) => (A1, D1), // White Queenside
354                    (E8, G8) => (H8, F8), // Black Kingside
355                    (E8, C8) => (A8, D8), // Black Queenside
356                    _ => panic!("move_kind is Castle however squares are illegal"),
357                };
358                let (clear, set) = castling_rook_squares;
359                let active_rook = (active_piece.color, Rook);
360                self.pieces[active_rook].clear_square(clear);
361                self.pieces[active_rook].set_square(set);
362
363                self.castling.clear_color(player);
364            }
365
366            // Handle special quiet case where pawn double jumps.
367            MoveKind::Quiet => {
368                if move_info.piece_kind == Pawn {
369                    let from = Bitboard::from(move_info.from);
370                    let to = Bitboard::from(move_info.to);
371                    let from_start_row = from & (Bitboard::RANK_2 | Bitboard::RANK_7);
372                    let to_jump_row = to & (Bitboard::RANK_4 | Bitboard::RANK_5);
373
374                    if !from_start_row.is_empty() && !to_jump_row.is_empty() {
375                        let pawn = Bitboard::from(move_info.from);
376                        self.en_passant = mg::pawn_single_pushes(pawn, player).get_lowest_square();
377                    }
378                }
379            }
380        };
381
382        // If any corner square is moved from or in to, remove those castling rights.
383        // This covers active player moving rook, and passive player losing a rook.
384        let moved_rights = match move_info.from {
385            A1 => Castling::W_QUEEN,
386            A8 => Castling::B_QUEEN,
387            H1 => Castling::W_KING,
388            H8 => Castling::B_KING,
389            _ => Castling::NONE,
390        };
391        let captured_rights = match move_info.to {
392            A1 => Castling::W_QUEEN,
393            A8 => Castling::B_QUEEN,
394            H1 => Castling::W_KING,
395            H8 => Castling::B_KING,
396            _ => Castling::NONE,
397        };
398        self.castling.clear(moved_rights | captured_rights);
399
400        // If King has moved, remove all castling rights.
401        if move_info.piece_kind == King {
402            self.castling.clear_color(player);
403        }
404
405        debug_assert!(self.pieces().is_valid());
406    }
407
408    /// Undo the application of a move, in place.
409    pub fn undo_move(&mut self, move_info: MoveInfo, cache: Cache) {
410        self.unstep_fullmoves();
411        self.player = !self.player;
412        self.castling = cache.castling;
413        self.en_passant = cache.en_passant;
414        self.halfmoves = cache.halfmoves;
415
416        // Side-to-move of self is currently set to player who made original move.
417        // If the player captured a piece, need to restore captured piece.
418        // If player did en-passant, need to restore captured piece.
419        // If player castled, need to restore king and rook positions.
420        // If player promoted, need to remove promoted piece on to square, add original piece to from square.
421        let player = *self.player();
422
423        // Restore explicitly moved piece of move's active player.
424        let moved_piece = Piece::new(player, move_info.piece_kind);
425        self.pieces[moved_piece].set_square(move_info.from);
426        self.pieces[moved_piece].clear_square(move_info.to);
427        if let Some(promoted) = move_info.promotion {
428            self.pieces[(player, promoted)].clear_square(move_info.to);
429        }
430
431        // Handle special MoveKind cases.
432        match move_info.move_kind {
433            MoveKind::Capture(piece_kind) => {
434                self.pieces[(!player, piece_kind)].set_square(move_info.to);
435            }
436
437            MoveKind::Castle => {
438                // Identify what kind of castle.
439                let (rook_from, rook_to) = match move_info.to {
440                    C1 => (A1, D1), // White Queenside
441                    G1 => (H1, F1), // White Kingside
442                    C8 => (A8, D8), // Black Queenside
443                    G8 => (H8, F8), // Black Kingside
444                    _ => panic!("MoveKind is Castle but Move is not a castling move."),
445                };
446                // Restore Rook position before castling.
447                self.pieces[(player, Rook)].set_square(rook_from);
448                self.pieces[(player, Rook)].clear_square(rook_to);
449            }
450
451            MoveKind::EnPassant => {
452                let ep_square = cache
453                    .en_passant
454                    .expect("MoveKind is EnPassant, but en_passant square is not set.");
455                let ep_bb = Bitboard::from(ep_square);
456                let original_bb = mg::pawn_single_pushes(ep_bb, !player);
457                self.pieces[(!player, Pawn)] |= original_bb;
458            }
459
460            _ => (),
461        }
462        debug_assert!(self.pieces().is_valid());
463    }
464
465    /// Checks if move is legal before applying it.
466    /// If move is legal, the move is applied and returns the resulting MoveInfo.
467    /// Otherwise, no action is taken and returns None.
468    /// This is best used as a CLI function, not in the engine.
469    pub fn do_legal_move(&mut self, move_: Move) -> Option<MoveInfo> {
470        if self.is_legal_move(move_) {
471            Some(self.do_move(move_))
472        } else {
473            None
474        }
475    }
476
477    /// Check if the current position is checkmated.
478    /// Returns true if it is mate, false otherwise.
479    pub fn is_checkmate(&self) -> bool {
480        let legal_moves = self.get_legal_moves();
481        if legal_moves.len() == 0 && self.is_in_check() {
482            true
483        } else {
484            false
485        }
486    }
487
488    /// Check if the current position is stalemated.
489    /// Returns true if it is stalemate, false otherwise.
490    pub fn is_stalemate(&self) -> bool {
491        let legal_moves = self.get_legal_moves();
492        if legal_moves.len() == 0 && !self.is_in_check() {
493            true
494        } else {
495            false
496        }
497    }
498
499    /// Generates a new Position from applying move on current Position.
500    pub fn make_move(&self, move_: Move) -> Self {
501        let mut position_clone: Position = self.clone();
502        position_clone.do_move(move_);
503        position_clone
504    }
505
506    /// Checks if given move is legal for current position.
507    pub fn is_legal_move(&self, move_: Move) -> bool {
508        let legal_moves = self.get_legal_moves();
509        legal_moves.contains(&move_)
510    }
511
512    /// Returns true if active player's king is in any check.
513    pub fn is_in_check(&self) -> bool {
514        self.num_active_king_checks() > 0
515    }
516
517    /// Returns tuple representing if current player's king is in single or double check.
518    /// Tuple format: (is_in_single_check, is_in_double_check).
519    pub fn active_king_checks(&self) -> (bool, bool) {
520        let num_checks = self.num_active_king_checks();
521        let single_check = num_checks >= 1;
522        let double_check = num_checks >= 2;
523        (single_check, double_check)
524    }
525
526    pub fn num_active_king_checks(&self) -> u32 {
527        let king_bb = self.pieces[(self.player, King)];
528        let king_square = king_bb.get_lowest_square().unwrap();
529        let king_attackers = self.attackers_to(king_square, !self.player);
530        king_attackers.count_squares()
531    }
532
533    /// Returns bitboard with positions of all pieces of a player attacking a square.
534    /// Assumes there is no overlap for pieces of a color.
535    pub fn attackers_to(&self, target: Square, attacking: Color) -> Bitboard {
536        let pawns = self.pieces[(attacking, Pawn)];
537        let knights = self.pieces[(attacking, Knight)];
538        let king = self.pieces[(attacking, King)];
539        let bishops = self.pieces[(attacking, Bishop)];
540        let rooks = self.pieces[(attacking, Rook)];
541        let queens = self.pieces[(attacking, Queen)];
542
543        let occupied = self.pieces().occupied();
544
545        mg::pawn_attackers_to(target, pawns, attacking)
546            | mg::knight_attackers_to(target, knights)
547            | mg::king_attackers_to(target, king)
548            | mg::bishop_attackers_to(target, bishops, occupied)
549            | mg::rook_attackers_to(target, rooks, occupied)
550            | mg::queen_attackers_to(target, queens, occupied)
551    }
552
553    /// Returns true if target square is attacked by any piece of attacking color.
554    pub fn is_attacked_by(&self, target: Square, attacking: Color) -> bool {
555        self.attackers_to(target, attacking).count_squares() > 0
556    }
557
558    /// Returns bitboard with all squares attacked by a player's pieces.
559    pub fn attacks(&self, attacking: Color, occupied: Bitboard) -> Bitboard {
560        let pawns = self.pieces[(attacking, Pawn)];
561        let knights = self.pieces[(attacking, Knight)];
562        let king = self.pieces[(attacking, King)];
563        let rooks = self.pieces[(attacking, Rook)];
564        let bishops = self.pieces[(attacking, Bishop)];
565        let queens = self.pieces[(attacking, Queen)];
566
567        mg::pawn_attacks(pawns, attacking)
568            | mg::knight_attacks(knights)
569            | mg::king_attacks(king)
570            | mg::slide_attacks(queens, rooks, bishops, occupied)
571    }
572
573    /// Returns a list of all legal moves for active player in current position.
574    /// This operation is expensive.
575    /// Notes:
576    /// If En-Passant, need to check for sliding piece check discovery.
577    /// If king is in check, number of moves are restricted.
578    /// If king is pinned, number of moves are restricted.
579    pub fn get_legal_moves(&self) -> MoveList {
580        let (single_check, double_check) = self.active_king_checks();
581
582        if double_check {
583            self.generate_legal_double_check_moves()
584        } else if single_check {
585            self.generate_legal_single_check_moves()
586        } else {
587            self.generate_legal_no_check_moves()
588        }
589    }
590
591    /// Generate a list of all legal capture moves the active player can make in
592    /// the current position.
593    //pub fn get_legal_captures(&self) -> MoveInfoList {
594    //    let (single_check, double_check) = self.active_king_checks();
595
596    //    if double_check {
597    //        self.generate_legal_double_check_captures()
598    //    } else if single_check {
599    //        self.generate_legal_single_check_captures()
600    //    } else {
601    //        self.generate_legal_no_check_captures()
602    //    }
603    //}
604
605    /// Generate king moves assuming double check.
606    /// Only the king can move when in double check.
607    fn generate_legal_double_check_moves(&self) -> MoveList {
608        let king = self.pieces[(self.player, King)];
609
610        // Generate bitboard with all squares attacked by passive player.
611        // Sliding pieces x-ray king.
612        let passive_player = !self.player;
613        let occupied_without_king = self.pieces.occupied() & !king;
614        let attacked = self.attacks(passive_player, occupied_without_king);
615
616        // Filter illegal moves from pseudo-legal king moves.
617        // King cannot move into attacked square, or into piece of same color.
618        let mut possible_moves = mg::king_attacks(king);
619        possible_moves.remove(&attacked);
620        possible_moves.remove(&self.pieces.color_occupied(self.player));
621
622        // Convert remaining move squares into Move structs.
623        let mut legal_moves = MoveList::new(); // Eight max possible moves.
624        let from = king.get_lowest_square().unwrap();
625        for to in possible_moves {
626            legal_moves.push(Move::new(from, to, None));
627        }
628
629        legal_moves
630    }
631
632    /// Generate moves assuming active player is in single check.
633    fn generate_legal_single_check_moves(&self) -> MoveList {
634        // Can capture checking piece with non-absolute-pinned piece,
635        // move king to non-attacked squares,
636        // block checking piece with non-absolute-pinned piece
637        let mut legal_moves: MoveList = MoveList::new();
638
639        let king = self.pieces[(self.player, King)];
640        let king_square = king.get_lowest_square().unwrap();
641        let passive_player = !self.player;
642        let us = self.pieces.color_occupied(self.player);
643        let them = self.pieces.color_occupied(passive_player);
644        let occupied = self.pieces.occupied();
645
646        // Generate all legal king moves.
647        let occupied_without_king = occupied & !king;
648        let attacked_xray_king = self.attacks(passive_player, occupied_without_king);
649        let mut possible_moves = mg::king_attacks(king);
650        possible_moves.remove(&attacked_xray_king);
651        possible_moves.remove(&us);
652        for to in possible_moves {
653            legal_moves.push(Move::new(king_square, to, None));
654        }
655
656        // Notes
657        // Only sliding pieces can cause absolute pins and pins in general.
658        // If a piece is absolutely pinned, it can only move along pinned direction.
659        // a pinning piece must already pseudo attack the king to absolutely pin.
660        // If there are multiple in between pieces, there is no pin.
661        // Once a piece is known to be pinned, how to determine where it can move?
662        // Algorithm:
663        // For each sliding piece, check if it pseudo checks the king.
664        // If it does, need to find if there is a single piece between them of active color.
665        // Sliding checker can be blocked or captured with non-pinned piece.
666        // If not sliding, then checker can be captured with non-pinned piece.
667        // TODO: Make more efficient (change from verifying by making move).
668        let queens = self.pieces[(self.player, Queen)];
669        let rooks = self.pieces[(self.player, Rook)];
670        let bishops = self.pieces[(self.player, Bishop)];
671        let knights = self.pieces[(self.player, Knight)];
672        let pawns = self.pieces[(self.player, Pawn)];
673
674        let mut pseudo_moves = MoveList::new();
675        mg::queen_pseudo_moves(&mut pseudo_moves, queens, occupied, us);
676        mg::rook_pseudo_moves(&mut pseudo_moves, rooks, occupied, us);
677        mg::bishop_pseudo_moves(&mut pseudo_moves, bishops, occupied, us);
678        mg::knight_pseudo_moves(&mut pseudo_moves, knights, us);
679        mg::pawn_pseudo_moves(
680            &mut pseudo_moves,
681            pawns,
682            self.player,
683            occupied,
684            them,
685            self.en_passant,
686        );
687
688        let mut position = self.clone();
689        let cache = position.cache();
690        pseudo_moves
691            .into_iter()
692            .filter(|pseudo_move| {
693                let move_info = position.do_move(*pseudo_move);
694                let is_legal = !position.is_attacked_by(king_square, passive_player);
695                position.undo_move(move_info, cache);
696                is_legal
697            })
698            .for_each(|legal_move| legal_moves.push(legal_move));
699
700        legal_moves
701    }
702
703    /// Generate moves assuming active player is not in check.
704    fn generate_legal_no_check_moves(&self) -> MoveList {
705        // moves:
706        // move absolutely-pinned piece along pin direction
707        // Castling with no pieces or attacked squares between
708        // en-passant with horizontal move test and conditionally pinned?
709        // king not attacked by pawns, knights, kings.
710        // For non king moves, only need to consider leaving absolute pin.
711        // For king moves, need to consider all attacked squares.
712        // Most positions will have fewer moves than this capacity.
713        let mut legal_moves = MoveList::new();
714
715        let king = self.pieces[(self.player, King)];
716        let king_square = king.get_lowest_square().unwrap();
717        let passive_player = !self.player;
718        let us = self.pieces.color_occupied(self.player);
719        let them = self.pieces.color_occupied(passive_player);
720        let occupied = us | them;
721        let attacked = self.attacks(passive_player, occupied);
722
723        let (absolute_pins, _pinned_moves) = {
724            let queens = self.pieces[(passive_player, Queen)];
725            let rooks = self.pieces[(passive_player, Rook)];
726            let bishops = self.pieces[(passive_player, Bishop)];
727
728            mg::absolute_pins(king_square, us, them, queens | rooks, queens | bishops)
729        };
730
731        // Generate all normal Queen, Rook, Bishop, Knight moves.
732        // Generate all normal and special Pawn moves (single/double push, attacks, ep).
733        let queens = self.pieces[(self.player, Queen)];
734        let rooks = self.pieces[(self.player, Rook)];
735        let bishops = self.pieces[(self.player, Bishop)];
736        let knights = self.pieces[(self.player, Knight)];
737        let pawns = self.pieces[(self.player, Pawn)];
738
739        // Generate strictly legal moves using pinned data.
740        let knights_free = knights & !absolute_pins;
741        let bishops_free = bishops & !absolute_pins;
742        let queens_free = queens & !absolute_pins;
743        let rooks_free = rooks & !absolute_pins;
744        mg::knight_pseudo_moves(&mut legal_moves, knights_free, us);
745        mg::bishop_pseudo_moves(&mut legal_moves, bishops_free, occupied, us);
746        mg::queen_pseudo_moves(&mut legal_moves, queens_free, occupied, us);
747        mg::rook_pseudo_moves(&mut legal_moves, rooks_free, occupied, us);
748
749        // Generate all normal legal king moves.
750        let mut king_tos = mg::king_attacks(king);
751        king_tos.remove(&us);
752        king_tos.remove(&attacked);
753        for to in king_tos {
754            legal_moves.push(Move::new(king_square, to, None));
755        }
756
757        // Generate pseudo moves and check for legality with "do/undo".
758        let mut pseudo_moves = MoveList::new();
759        let bishops_pinned = bishops & absolute_pins;
760        let rooks_pinned = rooks & absolute_pins;
761        let queens_pinned = queens & absolute_pins;
762
763        mg::queen_pseudo_moves(&mut pseudo_moves, queens_pinned, occupied, us);
764        mg::rook_pseudo_moves(&mut pseudo_moves, rooks_pinned, occupied, us);
765        mg::bishop_pseudo_moves(&mut pseudo_moves, bishops_pinned, occupied, us);
766        mg::pawn_pseudo_moves(
767            &mut pseudo_moves,
768            pawns,
769            self.player,
770            occupied,
771            them,
772            self.en_passant,
773        );
774
775        let mut position = self.clone();
776        let cache = position.cache();
777        pseudo_moves
778            .into_iter()
779            .filter(|pseudo_move| {
780                let move_info = position.do_move(*pseudo_move);
781                let is_legal = !position.is_attacked_by(king_square, passive_player);
782                position.undo_move(move_info, cache);
783                is_legal
784            })
785            .for_each(|legal_move| legal_moves.push(legal_move));
786
787        // Generate Castling moves
788        // Check if current player can castle. If can, for each side that can castle,
789        // check if there are any pieces between king and castling rook.
790        // check if king will pass through an attacked square.
791        mg::legal_castling_moves(
792            &mut legal_moves,
793            self.player,
794            self.castling,
795            occupied,
796            attacked,
797        );
798
799        legal_moves
800    }
801
802    // Generate all captures possible while in double check, where only king can move.
803    // fn generate_legal_double_check_captures(&self) -> MoveInfoList {
804    //     let king_bb = self.pieces[(self.player, King)];
805    //     let enemy = !self.player;
806
807    //     // Generate bitboard with all squares attacked by enemy player.
808    //     // Remove king so enemy attacks x-ray the king.
809    //     let occupied_without_king = self.pieces.occupied() & !king_bb;
810    //     let attacked = self.attacks(enemy, occupied_without_king);
811
812    //     // Extract only legal captures by removing attacked squares and non-enemy squares.
813    //     let mut possible_captures = mg::king_attacks(king_bb);
814    //     possible_captures.remove(&attacked);
815    //     possible_captures.remove(&!self.pieces.color_occupied(enemy));
816
817    //     let mut legal_captures = MoveInfoList::new();
818
819    //     // Convert each capture into a MoveInfo.
820    //     let from = king_bb.get_lowest_square().unwrap();
821    //     for to in possible_captures {
822    //         let captured_pk = self.pieces.on_player_square(enemy, to).unwrap();
823    //         let move_kind = MoveKind::Capture(captured_pk);
824
825    //         legal_captures.push(MoveInfo::new(
826    //             Move::new(from, to, None),
827    //             King,
828    //             move_kind,
829    //             self.castling,
830    //             self.en_passant,
831    //             self.halfmoves,
832    //         ));
833    //     }
834
835    //     legal_captures
836    // }
837}
838
839/// Defaults to standard chess start position.
840impl Default for Position {
841    fn default() -> Self {
842        Self::start_position()
843    }
844}
845
846/// Displays pretty-printed chess board and Fen string representing Position.
847impl Display for Position {
848    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
849        // print: position, FEN string
850        write!(f, "{}\n Fen: {}\n", self.pieces, self.to_fen())
851    }
852}
853
854#[cfg(test)]
855mod tests {
856    use super::*;
857
858    #[test]
859    fn pretty_print_position() {
860        let start_pos = Position::start_position();
861        println!("{}", start_pos);
862    }
863
864    #[test]
865    fn do_move_with_legal_move() {
866        let move1 = Move::new(E2, E4, None);
867        let move1_piece = Piece::new(White, Pawn);
868        let mut position = Position::start_position();
869        position.do_move(move1);
870        assert!(position.pieces[&move1_piece].has_square(E4));
871        assert!(!position.pieces[&move1_piece].has_square(E2));
872    }
873
874    #[test]
875    fn undo_move_with_legal_move() {
876        {
877            // Start position
878            let pos = Position::start_position();
879            let cache = pos.cache();
880            let mut pos_moved = pos.clone();
881            let move_ = Move::new(E2, E4, None);
882            let move_info = pos_moved.do_move(move_);
883            pos_moved.undo_move(move_info, cache);
884            assert_eq!(pos, pos_moved);
885            assert_eq!(Move::from(move_info), move_);
886            assert_eq!(move_info.piece_kind, Pawn);
887            assert_eq!(move_info.move_kind, MoveKind::Quiet);
888            assert_eq!(cache.castling, Castling::ALL);
889            assert_eq!(cache.en_passant, None);
890        }
891        {
892            // En passant
893            let pos = Position::parse_fen(
894                "rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3",
895            )
896            .unwrap();
897            let mut pos_moved = pos.clone();
898            let cache = pos_moved.cache();
899            let move_ = Move::new(E5, F6, None);
900            let move_info = pos_moved.do_move(move_);
901            pos_moved.undo_move(move_info, cache);
902            assert_eq!(pos, pos_moved);
903            assert_eq!(Move::from(move_info), move_);
904            assert_eq!(move_info.piece_kind, Pawn);
905            assert_eq!(move_info.move_kind, MoveKind::EnPassant);
906            assert_eq!(cache.castling, Castling::ALL);
907            assert_eq!(cache.en_passant, Some(F6));
908        }
909        {
910            // Kingside Castling
911            let pos = Position::parse_fen(
912                "rn1qkbnr/ppp3pp/5p2/8/2Bp2b1/5N2/PPPP1PPP/RNBQK2R w KQkq - 2 6",
913            )
914            .unwrap();
915            let mut pos_moved = pos.clone();
916            let cache = pos_moved.cache();
917            let move_ = Move::new(E1, G1, None);
918            let move_info = pos_moved.do_move(move_);
919            pos_moved.undo_move(move_info, cache);
920            assert_eq!(pos, pos_moved);
921            assert_eq!(Move::from(move_info), move_);
922            assert_eq!(move_info.piece_kind, King);
923            assert_eq!(move_info.move_kind, MoveKind::Castle);
924            assert_eq!(cache.castling, Castling::ALL);
925            assert_eq!(cache.en_passant, None);
926        }
927    }
928
929    #[test]
930    fn king_checks() {
931        let check1_1 = Position::parse_fen("8/8/8/8/3K3r/8/8/8 w - - 0 1").unwrap();
932        let check1_2 =
933            Position::parse_fen("rnb1kbnr/ppp1pppp/8/3p4/1qPPP3/8/PP3PPP/RNBQKBNR w KQkq - 1 4")
934                .unwrap();
935        let check2_1 = Position::parse_fen("3q4/8/4b3/3k4/4P1n1/8/3Q4/2R5 b - - 0 1").unwrap();
936        let check4_1 =
937            Position::parse_fen("6b1/2r1r3/pp4n1/4K2r/2p5/7p/1p1q2q1/4r2r w - - 0 1").unwrap();
938        let check5_1 = Position::parse_fen("4r3/8/2b2n2/5p2/4K3/5q2/8/8 w - - 0 1").unwrap();
939        let check5_2 = Position::parse_fen("8/8/5n2/3brp2/Q3K2q/5P2/3N4/1B2R3 w - - 0 1").unwrap();
940
941        assert_eq!(check1_1.num_active_king_checks(), 1);
942        assert_eq!(check1_2.num_active_king_checks(), 1);
943        assert_eq!(check2_1.num_active_king_checks(), 2);
944        assert_eq!(check4_1.num_active_king_checks(), 4);
945        assert_eq!(check5_1.num_active_king_checks(), 5);
946        assert_eq!(check5_2.num_active_king_checks(), 5);
947    }
948
949    #[test]
950    fn legal_double_check_moves() {
951        let pos0_1 = Position::parse_fen("4R2k/7p/6p1/8/8/2B5/8/1K6 b - - 0 1").unwrap();
952        let pos1_1 = Position::parse_fen("8/5K2/8/3Qk3/4R3/8/8/8 b - - 0 1").unwrap();
953        let pos3_1 = Position::parse_fen("8/2k5/8/8/4Kr2/4r3/8/8 w - - 0 1").unwrap();
954
955        let moves0_1 = pos0_1.generate_legal_double_check_moves();
956        let moves1_1 = pos1_1.generate_legal_double_check_moves();
957        let moves3_1 = pos3_1.generate_legal_double_check_moves();
958        assert_eq!(moves0_1.len(), 0);
959        assert_eq!(moves1_1.len(), 1);
960        assert_eq!(moves3_1.len(), 3);
961
962        assert!(moves1_1.contains(&Move::new(E5, D5, None)));
963
964        assert!(moves3_1.contains(&Move::new(E4, D5, None)));
965        assert!(moves3_1.contains(&Move::new(E4, E3, None)));
966        assert!(moves3_1.contains(&Move::new(E4, F4, None)));
967    }
968
969    #[test]
970    fn checkmated() {
971        {
972            let pos1 =
973                Position::parse_fen("rnb1k1nr/ppp2ppp/4p3/8/P7/1Pb3BQ/3qPPPP/4KBNR w Kkq - 0 14")
974                    .unwrap();
975            let moves1 = pos1.get_legal_moves();
976            assert_eq!(moves1.len(), 0);
977        }
978
979        {
980            let pos2 =
981                Position::parse_fen("r4r1k/1b3p1p/pp2pQ2/2p5/P1B3R1/3P3P/2q3P1/7K b - - 0 26")
982                    .unwrap();
983            let moves2 = pos2.get_legal_moves();
984            assert_eq!(moves2.len(), 0);
985        }
986    }
987
988    #[test]
989    fn stalemated() {
990        let pos1 = Position::parse_fen("8/8/8/8/p7/P3k3/4p3/4K3 w - - 1 2").unwrap();
991
992        let moves1 = pos1.get_legal_moves();
993        assert_eq!(moves1.len(), 0);
994    }
995
996    #[test]
997    fn color_flipped_eq() {
998        // Manually check flipped positions.
999        // Flipping one gets the other, and vice-versa.
1000        let w_giuoco = Position::parse_fen(
1001            "r1bqk1nr/pppp1ppp/2n5/2b1p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 6 4",
1002        )
1003        .unwrap();
1004        let b_giuoco = Position::parse_fen(
1005            "rnbqk2r/pppp1ppp/5n2/2b1p3/2B1P3/2N5/PPPP1PPP/R1BQK1NR b KQkq - 6 4",
1006        )
1007        .unwrap();
1008
1009        assert_eq!(w_giuoco.color_flip(), b_giuoco);
1010        assert_eq!(b_giuoco.color_flip(), w_giuoco);
1011    }
1012
1013    #[test]
1014    fn moves_played() {
1015        let mut pos = Position::start_position();
1016        let line = vec![
1017            Move::new(D2, D4, None),
1018            Move::new(D7, D5, None),
1019            Move::new(E2, E4, None),
1020            Move::new(D5, E4, None),
1021            Move::new(B1, C3, None),
1022            Move::new(F7, F5, None),
1023            Move::new(C1, F4, None),
1024        ];
1025
1026        assert_eq!(pos.moves_played(), 0);
1027
1028        for (moves_played, move_) in line.into_iter().enumerate() {
1029            let res = pos.do_legal_move(move_);
1030            assert!(res.is_some());
1031            assert_eq!(pos.moves_played(), moves_played as MoveCount + 1);
1032        }
1033    }
1034}