owlchess/moves/
base.rs

1use super::{san, uci};
2use crate::bitboard::Bitboard;
3use crate::board::Board;
4use crate::legal::{Checker, NilPrechecker};
5use crate::types::{CastlingRights, CastlingSide, Cell, Color, Coord, File, Piece, Rank};
6use crate::{attack, between, castling, generic, geometry, movegen, zobrist};
7
8use std::fmt;
9use std::str::FromStr;
10
11use thiserror::Error;
12
13/// Move kind
14#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
15#[repr(u8)]
16pub enum MoveKind {
17    /// Null move
18    #[default]
19    Null = 0,
20    /// Simple move
21    Simple = 1,
22    /// Kingside castling
23    CastlingKingside = 2,
24    /// Queenside castling
25    CastlingQueenside = 3,
26    /// Double pawn move
27    PawnDouble = 4,
28    /// Enpassant
29    Enpassant = 5,
30    /// Pawn promote to knight (either non-capture or capture)
31    PromoteKnight = 6,
32    /// Pawn promote to bishop (either non-capture or capture)
33    PromoteBishop = 7,
34    /// Pawn promote to rook (either non-capture or capture)
35    PromoteRook = 8,
36    /// Pawn promote to queen (either non-capture or capture)
37    PromoteQueen = 9,
38}
39
40/// Target piece for promotion
41#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
42#[repr(u8)]
43pub enum PromotePiece {
44    Knight = 2,
45    Bishop = 3,
46    Rook = 4,
47    Queen = 5,
48}
49
50/// Move output style
51#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
52pub enum Style {
53    /// Output in SAN format, with capical Latin letters for pieces
54    San,
55    /// Output in SAN format, with Unicode chess symbols for pieces
56    SanUtf8,
57    /// Output in UCI format
58    Uci,
59}
60
61impl From<PromotePiece> for Piece {
62    #[inline]
63    fn from(p: PromotePiece) -> Self {
64        match p {
65            PromotePiece::Knight => Piece::Knight,
66            PromotePiece::Bishop => Piece::Bishop,
67            PromotePiece::Rook => Piece::Rook,
68            PromotePiece::Queen => Piece::Queen,
69        }
70    }
71}
72
73impl TryFrom<Piece> for PromotePiece {
74    type Error = ();
75
76    #[inline]
77    fn try_from(p: Piece) -> Result<Self, Self::Error> {
78        match p {
79            Piece::Knight => Ok(PromotePiece::Knight),
80            Piece::Bishop => Ok(PromotePiece::Bishop),
81            Piece::Rook => Ok(PromotePiece::Rook),
82            Piece::Queen => Ok(PromotePiece::Queen),
83            _ => Err(()),
84        }
85    }
86}
87
88impl From<CastlingSide> for MoveKind {
89    #[inline]
90    fn from(side: CastlingSide) -> Self {
91        match side {
92            CastlingSide::King => Self::CastlingKingside,
93            CastlingSide::Queen => Self::CastlingQueenside,
94        }
95    }
96}
97
98impl TryFrom<MoveKind> for CastlingSide {
99    type Error = ();
100
101    #[inline]
102    fn try_from(kind: MoveKind) -> Result<Self, Self::Error> {
103        match kind {
104            MoveKind::CastlingKingside => Ok(Self::King),
105            MoveKind::CastlingQueenside => Ok(Self::Queen),
106            _ => Err(()),
107        }
108    }
109}
110
111impl From<PromotePiece> for MoveKind {
112    #[inline]
113    fn from(kind: PromotePiece) -> Self {
114        match kind {
115            PromotePiece::Knight => Self::PromoteKnight,
116            PromotePiece::Bishop => Self::PromoteBishop,
117            PromotePiece::Rook => Self::PromoteRook,
118            PromotePiece::Queen => Self::PromoteQueen,
119        }
120    }
121}
122
123impl TryFrom<MoveKind> for PromotePiece {
124    type Error = ();
125
126    #[inline]
127    fn try_from(kind: MoveKind) -> Result<Self, Self::Error> {
128        match kind {
129            MoveKind::PromoteKnight => Ok(Self::Knight),
130            MoveKind::PromoteBishop => Ok(Self::Bishop),
131            MoveKind::PromoteRook => Ok(Self::Rook),
132            MoveKind::PromoteQueen => Ok(Self::Queen),
133            _ => Err(()),
134        }
135    }
136}
137
138impl MoveKind {
139    /// Returns the piece after promote is this move kind represents a promote
140    ///
141    /// Otherwise, returns `None`.
142    #[inline]
143    pub fn promote(self) -> Option<Piece> {
144        let piece: PromotePiece = self.try_into().ok()?;
145        Some(piece.into())
146    }
147
148    /// Returns true if move kind with the piece `piece` can exist
149    #[inline]
150    pub fn matches_piece(self, piece: Piece) -> bool {
151        match self {
152            MoveKind::Simple => true,
153            MoveKind::PawnDouble
154            | MoveKind::Enpassant
155            | MoveKind::PromoteKnight
156            | MoveKind::PromoteBishop
157            | MoveKind::PromoteRook
158            | MoveKind::PromoteQueen => piece == Piece::Pawn,
159            MoveKind::CastlingKingside | MoveKind::CastlingQueenside => piece == Piece::King,
160            MoveKind::Null => false,
161        }
162    }
163}
164
165/// Chess move
166///
167/// Represents a chess move which can be applied to the chess board.
168///
169/// Moves can have different degrees of validity:
170///
171/// - _Well-formed_. A move is considered well-formed if there exists a position in which this move
172///   is semilegal. Note that such existence is only a sufficient condition, so you may create a
173///   well-formed move which would not be semilegal in any position. It is determined by [`Move::is_well_formed()`]
174///   whether the move is well-formed.
175///
176///   Null move is explicitly well-formed.
177///
178///   Note that using non-well-formed moves other than checking them via [`Move::is_well_formed()`] or
179///   examining their fields via getters, is undefined behavior.
180///
181/// - _Semilegal_. A move is considered semi-legal if it's valid by the rules of chess, except that the king can
182///   remain under attack after such move.
183///
184///   Null move is not considered semilegal (but see the [notes below](#null-move)).
185///
186/// - _Legal_. A move is considered legal if it's semilegal plus the king doesn't remain under attack. So, such move
187///   is fully valid by the rules of chess.
188///
189/// # Null move
190///
191/// Null move is a move that just flips the move side, without changing the position. Such move doesn't exist
192/// is chess, but may be useful for chess engines, for example, to implement null move heuristics.
193///
194/// Note that this kind of moves is a special one.
195///
196/// As stated above, it is well-formed but not semilegal. So, it is not accepted by safe functions, but is accepted
197/// as a semilegal move by unsafe functions (such as [`make_move_unchecked()`]).
198#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
199pub struct Move {
200    kind: MoveKind,
201    src_cell: Cell,
202    src: Coord,
203    dst: Coord,
204}
205
206/// Error indicating that move is invalid
207#[derive(Debug, Clone, Error, Eq, PartialEq)]
208pub enum ValidateError {
209    /// Move is not semi-legal
210    #[error("move is not semi-legal")]
211    NotSemiLegal,
212    /// Move is not legal
213    #[error("move is not legal")]
214    NotLegal,
215}
216
217/// Error creating move
218#[derive(Debug, Clone, Error, Eq, PartialEq)]
219pub enum CreateError {
220    /// Move is not well-formed
221    #[error("move is not well-formed")]
222    NotWellFormed,
223}
224
225impl Move {
226    /// Null move
227    pub const NULL: Move = Move::null();
228
229    const fn null() -> Self {
230        Self {
231            kind: MoveKind::Null,
232            src_cell: Cell::EMPTY,
233            src: Coord::from_index(0),
234            dst: Coord::from_index(0),
235        }
236    }
237
238    /// Creates a castling move made by `color` with side `side`
239    #[inline]
240    pub fn from_castling(color: Color, side: CastlingSide) -> Move {
241        let rank = geometry::castling_rank(color);
242        let src = Coord::from_parts(File::E, rank);
243        let dst = match side {
244            CastlingSide::King => Coord::from_parts(File::G, rank),
245            CastlingSide::Queen => Coord::from_parts(File::C, rank),
246        };
247        Move {
248            kind: MoveKind::from(side),
249            src_cell: Cell::from_parts(color, Piece::King),
250            src,
251            dst,
252        }
253    }
254
255    /// Creates a new non-null move from its raw parts
256    ///
257    /// # Safety
258    ///
259    /// If the created move is not well formed, it is undefined behavior to do with it something other
260    /// than checking it for well-formedness via [`Move::is_well_formed()`] or examining its fields via
261    /// getters.
262    #[inline]
263    pub const unsafe fn new_unchecked(
264        kind: MoveKind,
265        src_cell: Cell,
266        src: Coord,
267        dst: Coord,
268    ) -> Move {
269        Move {
270            kind,
271            src_cell,
272            src,
273            dst,
274        }
275    }
276
277    /// Creates a move from the UCI string `s` if `b` is the positon preceding this move
278    ///
279    /// The returned move is **not** guaranteed to be semilegal.
280    #[inline]
281    pub fn from_uci(s: &str, b: &Board) -> Result<Move, uci::BasicParseError> {
282        Ok(uci::Move::from_str(s)?.into_move(b)?)
283    }
284
285    /// Same as [`Move::from_uci()`], but the returned move is guaranteed to be semilegal
286    pub fn from_uci_semilegal(s: &str, b: &Board) -> Result<Move, uci::ParseError> {
287        let res = uci::Move::from_str(s)?.into_move(b)?;
288        res.semi_validate(b)?;
289        Ok(res)
290    }
291
292    /// Same as [`Move::from_uci()`], but the returned move is guaranteed to be legal
293    pub fn from_uci_legal(s: &str, b: &Board) -> Result<Move, uci::ParseError> {
294        let res = uci::Move::from_str(s)?.into_move(b)?;
295        res.validate(b)?;
296        Ok(res)
297    }
298
299    /// Creates a move from the SAN string `s` if `b` is the positon preceding this move
300    ///
301    /// The returned move is guaranteed to be **legal**.
302    #[inline]
303    pub fn from_san(s: &str, b: &Board) -> Result<Move, san::ParseError> {
304        Ok(san::Move::from_str(s)?.into_move(b)?)
305    }
306
307    /// Returns `true` if the move is semilegal
308    pub fn is_semilegal(&self, b: &Board) -> bool {
309        match b.r.side {
310            Color::White => do_is_move_semilegal::<generic::White>(b, *self),
311            Color::Black => do_is_move_semilegal::<generic::Black>(b, *self),
312        }
313    }
314
315    /// Returns `true` if the move is legal
316    ///
317    /// # Safety
318    ///
319    /// The move must be semilegal, otherwise the behavior is undefined.
320    pub unsafe fn is_legal_unchecked(&self, b: &Board) -> bool {
321        Checker::new(b, NilPrechecker).is_legal(*self)
322    }
323
324    /// Validates whether this move is semilegal from position `b`
325    #[inline]
326    pub fn semi_validate(&self, b: &Board) -> Result<(), ValidateError> {
327        if !self.is_semilegal(b) {
328            return Err(ValidateError::NotSemiLegal);
329        }
330        Ok(())
331    }
332
333    /// Validates whether this move is legal from position `b`
334    #[inline]
335    pub fn validate(&self, b: &Board) -> Result<(), ValidateError> {
336        self.semi_validate(b)?;
337        match unsafe { self.is_legal_unchecked(b) } {
338            true => Ok(()),
339            false => Err(ValidateError::NotLegal),
340        }
341    }
342
343    /// Creates a new non-null move from its raw parts and validates it for well-formedness
344    pub fn new(
345        kind: MoveKind,
346        src_cell: Cell,
347        src: Coord,
348        dst: Coord,
349    ) -> Result<Move, CreateError> {
350        let mv = Move {
351            kind,
352            src_cell,
353            src,
354            dst,
355        };
356        mv.is_well_formed()
357            .then_some(mv)
358            .ok_or(CreateError::NotWellFormed)
359    }
360
361    /// Returns `true` if the move is well-formed
362    ///
363    /// If the move is not well-formed, you can only call getters and this function on it, other uses
364    /// are undefined behaviour.
365    pub fn is_well_formed(&self) -> bool {
366        if self.kind == MoveKind::Null {
367            return *self == Move::NULL;
368        }
369
370        // `src_cell == EMPTY` and `src == dst` are true only for null moves.
371        if self.src_cell == Cell::EMPTY || self.src == self.dst {
372            return false;
373        }
374
375        let color = self.src_cell.color().unwrap();
376        let piece = self.src_cell.piece().unwrap();
377
378        if !self.kind.matches_piece(piece) {
379            return false;
380        }
381
382        match self.kind {
383            MoveKind::Simple => match piece {
384                Piece::Pawn => {
385                    if self.src.file().index().abs_diff(self.dst.file().index()) > 1
386                        || matches!(self.src.rank(), Rank::R1 | Rank::R8)
387                        || matches!(self.dst.rank(), Rank::R1 | Rank::R8)
388                    {
389                        false
390                    } else {
391                        match color {
392                            Color::White => self.src.rank().index() == self.dst.rank().index() + 1,
393                            Color::Black => self.src.rank().index() + 1 == self.dst.rank().index(),
394                        }
395                    }
396                }
397                Piece::King => attack::king(self.src).has(self.dst),
398                Piece::Knight => attack::knight(self.src).has(self.dst),
399                Piece::Bishop => between::is_bishop_valid(self.src, self.dst),
400                Piece::Rook => between::is_rook_valid(self.src, self.dst),
401                Piece::Queen => {
402                    between::is_bishop_valid(self.src, self.dst)
403                        || between::is_rook_valid(self.src, self.dst)
404                }
405            },
406            MoveKind::CastlingKingside => {
407                let rank = geometry::castling_rank(color);
408                self.src == Coord::from_parts(File::E, rank)
409                    && self.dst == Coord::from_parts(File::G, rank)
410            }
411            MoveKind::CastlingQueenside => {
412                let rank = geometry::castling_rank(color);
413                self.src == Coord::from_parts(File::E, rank)
414                    && self.dst == Coord::from_parts(File::C, rank)
415            }
416            MoveKind::PawnDouble => {
417                self.src.file() == self.dst.file()
418                    && self.src.rank() == geometry::double_move_src_rank(color)
419                    && self.dst.rank() == geometry::double_move_dst_rank(color)
420            }
421            MoveKind::Enpassant => {
422                self.src.rank() == geometry::enpassant_src_rank(color)
423                    && self.dst.rank() == geometry::enpassant_dst_rank(color)
424                    && self.src.file().index().abs_diff(self.dst.file().index()) == 1
425            }
426            MoveKind::PromoteKnight
427            | MoveKind::PromoteBishop
428            | MoveKind::PromoteRook
429            | MoveKind::PromoteQueen => {
430                self.src.rank() == geometry::promote_src_rank(color)
431                    && self.dst.rank() == geometry::promote_dst_rank(color)
432                    && self.src.file().index().abs_diff(self.dst.file().index()) <= 1
433            }
434            MoveKind::Null => unreachable!(),
435        }
436    }
437
438    /// Returns the move kind
439    #[inline]
440    pub const fn kind(&self) -> MoveKind {
441        self.kind
442    }
443
444    /// Returns the move source square
445    ///
446    /// For null move, this function returns a square with index 0.
447    #[inline]
448    pub const fn src(&self) -> Coord {
449        self.src
450    }
451
452    /// Returns the move destination square
453    ///
454    /// For null move, this function returns a square with index 0.
455    #[inline]
456    pub const fn dst(&self) -> Coord {
457        self.dst
458    }
459
460    /// Returns the piece (alongside with its color) which is making this move.
461    ///
462    /// For null moves, [`Cell::EMPTY`] is returned.
463    #[inline]
464    pub const fn src_cell(&self) -> Cell {
465        self.src_cell
466    }
467
468    /// Converts this move into a parsed UCI representation
469    #[inline]
470    pub fn uci(&self) -> uci::Move {
471        (*self).into()
472    }
473
474    /// Converts this move into a parsed SAN representation in given position `b`
475    ///
476    /// This function returns an error if the move is not legal in the given position.
477    #[inline]
478    pub fn san(&self, b: &Board) -> Result<san::Move, ValidateError> {
479        san::Move::from_move(*self, b)
480    }
481
482    /// Returns the wrapper which helps to format the move with the given style `style`
483    ///
484    /// The resulting wrapper implements [`fmt::Display`], so can be used with
485    /// `write!()`, `println!()`, or `ToString::to_string`.
486    ///
487    /// # Example
488    ///
489    /// ```
490    /// # use owlchess::{Move, Board, MoveKind, File, Rank, Coord, Cell, Piece, Color, moves::Style};
491    /// #
492    /// let b = Board::initial();
493    /// let g1 = Coord::from_parts(File::G, Rank::R1);
494    /// let f3 = Coord::from_parts(File::F, Rank::R3);
495    /// let mv = Move::new(MoveKind::Simple, Cell::from_parts(Color::White, Piece::Knight), g1, f3).unwrap();
496    /// assert_eq!(mv.styled(&b, Style::Uci).unwrap().to_string(), "g1f3".to_string());
497    /// assert_eq!(mv.styled(&b, Style::San).unwrap().to_string(), "Nf3".to_string());
498    /// assert_eq!(mv.styled(&b, Style::SanUtf8).unwrap().to_string(), "♘f3".to_string());
499    /// ```
500    pub fn styled(&self, b: &Board, style: Style) -> Result<StyledMove, ValidateError> {
501        match style {
502            Style::Uci => Ok(StyledMove(Styled::Uci((*self).into()))),
503            Style::San => Ok(StyledMove(Styled::San(
504                san::Move::from_move(*self, b)?,
505                san::Style::Algebraic,
506            ))),
507            Style::SanUtf8 => Ok(StyledMove(Styled::San(
508                san::Move::from_move(*self, b)?,
509                san::Style::Utf8,
510            ))),
511        }
512    }
513}
514
515impl Default for Move {
516    #[inline]
517    fn default() -> Self {
518        Move::NULL
519    }
520}
521
522impl fmt::Display for Move {
523    #[inline]
524    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
525        self.uci().fmt(f)
526    }
527}
528
529enum Styled {
530    Uci(uci::Move),
531    San(san::Move, san::Style),
532}
533
534/// Wrapper to format the move with the given style
535///
536/// See [`Move::styled()`] doc for details.
537pub struct StyledMove(Styled);
538
539impl fmt::Display for StyledMove {
540    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
541        match self.0 {
542            Styled::Uci(mv) => mv.fmt(f),
543            Styled::San(mv, sty) => mv.styled(sty).fmt(f),
544        }
545    }
546}
547
548/// Metadata necessary to undo the applied move
549#[derive(Debug, Copy, Clone)]
550pub struct RawUndo {
551    hash: u64,
552    dst_cell: Cell,
553    castling: CastlingRights,
554    ep_source: Option<Coord>,
555    move_counter: u16,
556}
557
558fn update_castling(b: &mut Board, change: Bitboard) {
559    if (change & castling::ALL_SRCS).is_empty() {
560        return;
561    }
562
563    let mut castling = b.r.castling;
564    for (c, s) in [
565        (Color::White, CastlingSide::Queen),
566        (Color::White, CastlingSide::King),
567        (Color::Black, CastlingSide::Queen),
568        (Color::Black, CastlingSide::King),
569    ] {
570        if (change & castling::srcs(c, s)).is_nonempty() {
571            castling.unset(c, s);
572        }
573    }
574
575    if castling != b.r.castling {
576        b.hash ^= zobrist::castling(b.r.castling);
577        b.r.castling = castling;
578        b.hash ^= zobrist::castling(b.r.castling);
579    }
580}
581
582#[inline]
583fn do_make_pawn_double<C: generic::Color>(b: &mut Board, mv: Move, change: Bitboard, inv: bool) {
584    let pawn = Cell::from_parts(C::COLOR, Piece::Pawn);
585    if inv {
586        b.r.put(mv.src, pawn);
587        b.r.put(mv.dst, Cell::EMPTY);
588    } else {
589        b.r.put(mv.src, Cell::EMPTY);
590        b.r.put(mv.dst, pawn);
591        b.hash ^= zobrist::pieces(pawn, mv.src) ^ zobrist::pieces(pawn, mv.dst);
592    }
593    *b.color_mut(C::COLOR) ^= change;
594    *b.piece_mut(pawn) ^= change;
595    if !inv {
596        b.r.ep_source = Some(mv.dst);
597        b.hash ^= zobrist::enpassant(mv.dst);
598    }
599}
600
601#[inline]
602fn do_make_enpassant<C: generic::Color>(b: &mut Board, mv: Move, change: Bitboard, inv: bool) {
603    let taken_pos = unsafe {
604        mv.dst
605            .add_unchecked(-geometry::pawn_forward_delta(C::COLOR))
606    };
607    let taken = Bitboard::from_coord(taken_pos);
608    let our_pawn = Cell::from_parts(C::COLOR, Piece::Pawn);
609    let their_pawn = Cell::from_parts(C::COLOR.inv(), Piece::Pawn);
610    if inv {
611        b.r.put(mv.src, our_pawn);
612        b.r.put(mv.dst, Cell::EMPTY);
613        b.r.put(taken_pos, their_pawn);
614    } else {
615        b.r.put(mv.src, Cell::EMPTY);
616        b.r.put(mv.dst, our_pawn);
617        b.r.put(taken_pos, Cell::EMPTY);
618        b.hash ^= zobrist::pieces(our_pawn, mv.src)
619            ^ zobrist::pieces(our_pawn, mv.dst)
620            ^ zobrist::pieces(their_pawn, taken_pos);
621    }
622    *b.color_mut(C::COLOR) ^= change;
623    *b.piece_mut(our_pawn) ^= change;
624    *b.color_mut(C::COLOR.inv()) ^= taken;
625    *b.piece_mut(their_pawn) ^= taken;
626}
627
628#[inline]
629fn do_make_castling_kingside<C: generic::Color>(b: &mut Board, inv: bool) {
630    let king = Cell::from_parts(C::COLOR, Piece::King);
631    let rook = Cell::from_parts(C::COLOR, Piece::Rook);
632    let rank = geometry::castling_rank(C::COLOR);
633    if inv {
634        b.r.put2(File::E, rank, king);
635        b.r.put2(File::F, rank, Cell::EMPTY);
636        b.r.put2(File::G, rank, Cell::EMPTY);
637        b.r.put2(File::H, rank, rook);
638    } else {
639        b.r.put2(File::E, rank, Cell::EMPTY);
640        b.r.put2(File::F, rank, rook);
641        b.r.put2(File::G, rank, king);
642        b.r.put2(File::H, rank, Cell::EMPTY);
643        b.hash ^= zobrist::castling_delta(C::COLOR, CastlingSide::King);
644    }
645    *b.color_mut(C::COLOR) ^= Bitboard::from_raw(0xf0 << C::CASTLING_OFFSET);
646    *b.piece_mut(rook) ^= Bitboard::from_raw(0xa0 << C::CASTLING_OFFSET);
647    *b.piece_mut(king) ^= Bitboard::from_raw(0x50 << C::CASTLING_OFFSET);
648    if !inv {
649        b.hash ^= zobrist::castling(b.r.castling);
650        b.r.castling.unset_color(C::COLOR);
651        b.hash ^= zobrist::castling(b.r.castling);
652    }
653}
654
655#[inline]
656fn do_make_castling_queenside<C: generic::Color>(b: &mut Board, inv: bool) {
657    let king = Cell::from_parts(C::COLOR, Piece::King);
658    let rook = Cell::from_parts(C::COLOR, Piece::Rook);
659    let rank = geometry::castling_rank(C::COLOR);
660    if inv {
661        b.r.put2(File::A, rank, rook);
662        b.r.put2(File::C, rank, Cell::EMPTY);
663        b.r.put2(File::D, rank, Cell::EMPTY);
664        b.r.put2(File::E, rank, king);
665    } else {
666        b.r.put2(File::A, rank, Cell::EMPTY);
667        b.r.put2(File::C, rank, king);
668        b.r.put2(File::D, rank, rook);
669        b.r.put2(File::E, rank, Cell::EMPTY);
670        b.hash ^= zobrist::castling_delta(C::COLOR, CastlingSide::Queen);
671    }
672    *b.color_mut(C::COLOR) ^= Bitboard::from_raw(0x1d << C::CASTLING_OFFSET);
673    *b.piece_mut(rook) ^= Bitboard::from_raw(0x09 << C::CASTLING_OFFSET);
674    *b.piece_mut(king) ^= Bitboard::from_raw(0x14 << C::CASTLING_OFFSET);
675    if !inv {
676        b.hash ^= zobrist::castling(b.r.castling);
677        b.r.castling.unset_color(C::COLOR);
678        b.hash ^= zobrist::castling(b.r.castling);
679    }
680}
681
682fn do_make_move<C: generic::Color>(b: &mut Board, mv: Move) -> RawUndo {
683    let src_cell = mv.src_cell;
684    let dst_cell = b.get(mv.dst);
685    let undo = RawUndo {
686        hash: b.hash,
687        dst_cell,
688        castling: b.r.castling,
689        ep_source: b.r.ep_source,
690        move_counter: b.r.move_counter,
691    };
692    let src = Bitboard::from_coord(mv.src);
693    let dst = Bitboard::from_coord(mv.dst);
694    let change = src | dst;
695    let pawn = Cell::from_parts(C::COLOR, Piece::Pawn);
696    if let Some(p) = b.r.ep_source {
697        b.hash ^= zobrist::enpassant(p);
698        b.r.ep_source = None;
699    }
700    match mv.kind {
701        MoveKind::Simple => {
702            b.r.put(mv.src, Cell::EMPTY);
703            b.r.put(mv.dst, src_cell);
704            b.hash ^= zobrist::pieces(src_cell, mv.src)
705                ^ zobrist::pieces(src_cell, mv.dst)
706                ^ zobrist::pieces(dst_cell, mv.dst);
707            *b.color_mut(C::COLOR) ^= change;
708            *b.piece_mut(src_cell) ^= change;
709            *b.color_mut(C::COLOR.inv()) &= !dst;
710            *b.piece_mut(dst_cell) &= !dst;
711            if src_cell != pawn {
712                update_castling(b, change);
713            }
714        }
715        MoveKind::PawnDouble => {
716            do_make_pawn_double::<C>(b, mv, change, false);
717        }
718        MoveKind::PromoteKnight
719        | MoveKind::PromoteBishop
720        | MoveKind::PromoteRook
721        | MoveKind::PromoteQueen => {
722            let promote = Cell::from_parts(C::COLOR, mv.kind.promote().unwrap());
723            b.r.put(mv.src, Cell::EMPTY);
724            b.r.put(mv.dst, promote);
725            b.hash ^= zobrist::pieces(src_cell, mv.src)
726                ^ zobrist::pieces(promote, mv.dst)
727                ^ zobrist::pieces(dst_cell, mv.dst);
728            *b.color_mut(C::COLOR) ^= change;
729            *b.piece_mut(pawn) ^= src;
730            *b.piece_mut(promote) ^= dst;
731            *b.color_mut(C::COLOR.inv()) &= !dst;
732            *b.piece_mut(dst_cell) &= !dst;
733            update_castling(b, change);
734        }
735        MoveKind::CastlingKingside => {
736            do_make_castling_kingside::<C>(b, false);
737        }
738        MoveKind::CastlingQueenside => {
739            do_make_castling_queenside::<C>(b, false);
740        }
741        MoveKind::Null => {
742            // Do nothing.
743        }
744        MoveKind::Enpassant => {
745            do_make_enpassant::<C>(b, mv, change, false);
746        }
747    }
748
749    if dst_cell != Cell::EMPTY || src_cell == pawn {
750        b.r.move_counter = 0;
751    } else {
752        b.r.move_counter += 1;
753    }
754    b.r.side = C::COLOR.inv();
755    b.hash ^= zobrist::MOVE_SIDE;
756    if C::COLOR == Color::Black {
757        b.r.move_number += 1;
758    }
759    b.all = b.white | b.black;
760
761    undo
762}
763
764fn do_unmake_move<C: generic::Color>(b: &mut Board, mv: Move, u: RawUndo) {
765    let src = Bitboard::from_coord(mv.src);
766    let dst = Bitboard::from_coord(mv.dst);
767    let change = src | dst;
768    let src_cell = b.get(mv.dst);
769    let dst_cell = u.dst_cell;
770
771    match mv.kind {
772        MoveKind::Simple => {
773            b.r.put(mv.src, src_cell);
774            b.r.put(mv.dst, dst_cell);
775            *b.color_mut(C::COLOR) ^= change;
776            *b.piece_mut(src_cell) ^= change;
777            if dst_cell.is_occupied() {
778                *b.color_mut(C::COLOR.inv()) |= dst;
779                *b.piece_mut(dst_cell) |= dst;
780            }
781        }
782        MoveKind::PawnDouble => {
783            do_make_pawn_double::<C>(b, mv, change, true);
784        }
785        MoveKind::PromoteKnight
786        | MoveKind::PromoteBishop
787        | MoveKind::PromoteRook
788        | MoveKind::PromoteQueen => {
789            let pawn = Cell::from_parts(C::COLOR, Piece::Pawn);
790            b.r.put(mv.src, pawn);
791            b.r.put(mv.dst, dst_cell);
792            *b.color_mut(C::COLOR) ^= change;
793            *b.piece_mut(pawn) ^= src;
794            *b.piece_mut(src_cell) ^= dst;
795            if dst_cell.is_occupied() {
796                *b.color_mut(C::COLOR.inv()) |= dst;
797                *b.piece_mut(dst_cell) |= dst;
798            }
799        }
800        MoveKind::CastlingKingside => {
801            do_make_castling_kingside::<C>(b, true);
802        }
803        MoveKind::CastlingQueenside => {
804            do_make_castling_queenside::<C>(b, true);
805        }
806        MoveKind::Null => {
807            // Do nothing
808        }
809        MoveKind::Enpassant => {
810            do_make_enpassant::<C>(b, mv, change, true);
811        }
812    }
813
814    b.hash = u.hash;
815    b.r.castling = u.castling;
816    b.r.ep_source = u.ep_source;
817    b.r.move_counter = u.move_counter;
818    b.r.side = C::COLOR;
819    if C::COLOR == Color::Black {
820        b.r.move_number -= 1;
821    }
822    b.all = b.white | b.black;
823}
824
825/// Makes the move `mv` on the board `b`
826///
827/// To allow unmaking the move, a `RawUndo` instance is returned. See [`unmake_move_unchecked()`] for the
828/// details on how to unmake a move.
829///
830/// # Safety
831///
832/// The move must be either semilegal or null, otherwise the behavior is undefined.
833///
834/// If the king is under attack after the move (i.e. the board becomes invalid), it must be immediately
835/// rolled back via [`unmake_move_unchecked()`]. Doing anything other with the board before that,
836/// except unmaking the move or calling [`Board::is_opponent_king_attacked()`] is undefined behavior.
837///
838/// See docs for [`Board`] for more details.
839pub unsafe fn make_move_unchecked(b: &mut Board, mv: Move) -> RawUndo {
840    match b.r.side {
841        Color::White => do_make_move::<generic::White>(b, mv),
842        Color::Black => do_make_move::<generic::Black>(b, mv),
843    }
844}
845
846/// Unmakes the move `mv` on the board `b`
847///
848/// # Safety
849///
850/// If there exists a valid position `b_old` such that `make_move_unchecked(&mut b_old, mv)`
851/// doesn't result in undefined behavior, returns a value equal to `u`, and `b_old == b` holds
852/// after such operation, then this `b_old` is returned. Otherwise, the behavior is undefined.
853///
854/// In simpler words, you may invoke this function only from the position occured after the
855/// corresponing call to [`make_move_unchecked()`] or [`Make::make_raw()`](super::Make::make_raw).
856///
857/// Note that `b` can be an invalid position, so you can roll back an illegal move. See docs for
858/// [`Board`] or [`make_move_unchecked()`] for more details.
859pub unsafe fn unmake_move_unchecked(b: &mut Board, mv: Move, u: RawUndo) {
860    match b.r.side {
861        Color::White => do_unmake_move::<generic::Black>(b, mv, u),
862        Color::Black => do_unmake_move::<generic::White>(b, mv, u),
863    }
864}
865
866fn is_queen_semilegal(src: Coord, dst: Coord, all: Bitboard) -> bool {
867    if between::is_bishop_valid(src, dst) {
868        (between::bishop_strict(src, dst) & all).is_empty()
869    } else {
870        (between::rook_strict(src, dst) & all).is_empty()
871    }
872}
873
874fn do_is_move_semilegal<C: generic::Color>(b: &Board, mv: Move) -> bool {
875    let dst_cell = b.get(mv.dst);
876
877    if mv.kind == MoveKind::Null
878        || b.get(mv.src) != mv.src_cell
879        || mv.src_cell.color() != Some(C::COLOR)
880        || dst_cell.color() == Some(C::COLOR)
881    {
882        return false;
883    }
884
885    match mv.src_cell.piece().unwrap() {
886        Piece::Pawn => match mv.kind {
887            MoveKind::PawnDouble => {
888                let tmp_cell =
889                    unsafe { b.get(mv.src.add_unchecked(geometry::pawn_forward_delta(C::COLOR))) };
890                tmp_cell == Cell::EMPTY && dst_cell == Cell::EMPTY
891            }
892            MoveKind::Enpassant => match b.r.ep_source {
893                Some(p) => unsafe {
894                    (p == mv.src.add_unchecked(1) || p == mv.src.add_unchecked(-1))
895                        && mv.dst == p.add_unchecked(geometry::pawn_forward_delta(C::COLOR))
896                },
897                None => false,
898            },
899            _ => (mv.dst.file() == mv.src.file()) == (dst_cell == Cell::EMPTY),
900        },
901        Piece::King => match mv.kind {
902            MoveKind::CastlingKingside => {
903                b.r.castling.has(C::COLOR, CastlingSide::King)
904                    && (b.all & castling::pass(C::COLOR, CastlingSide::King)).is_empty()
905                    && !movegen::do_is_cell_attacked::<C::Inv>(b, mv.src)
906                    && !movegen::do_is_cell_attacked::<C::Inv>(b, unsafe {
907                        mv.src.add_unchecked(1)
908                    })
909            }
910            MoveKind::CastlingQueenside => {
911                b.r.castling.has(C::COLOR, CastlingSide::Queen)
912                    && (b.all & castling::pass(C::COLOR, CastlingSide::Queen)).is_empty()
913                    && !movegen::do_is_cell_attacked::<C::Inv>(b, mv.src)
914                    && !movegen::do_is_cell_attacked::<C::Inv>(b, unsafe {
915                        mv.src.add_unchecked(-1)
916                    })
917            }
918            _ => true,
919        },
920        Piece::Knight => true,
921        Piece::Bishop => (between::bishop_strict(mv.src, mv.dst) & b.all).is_empty(),
922        Piece::Rook => (between::rook_strict(mv.src, mv.dst) & b.all).is_empty(),
923        Piece::Queen => is_queen_semilegal(mv.src, mv.dst, b.all),
924    }
925}
926
927#[cfg(test)]
928mod tests {
929    use super::*;
930    use crate::board::Board;
931    use crate::moves::make::{self, Make};
932    use std::mem;
933
934    #[test]
935    fn test_size() {
936        assert_eq!(mem::size_of::<Move>(), 4);
937    }
938
939    #[test]
940    fn test_style() {
941        let b = Board::initial();
942        let mv = Move::from_uci("g1f3", &b).unwrap();
943        assert_eq!(mv.styled(&b, Style::Uci).unwrap().to_string(), "g1f3");
944        assert_eq!(mv.styled(&b, Style::San).unwrap().to_string(), "Nf3");
945        assert_eq!(mv.styled(&b, Style::SanUtf8).unwrap().to_string(), "♘f3");
946        assert_eq!(mv.uci().to_string(), "g1f3");
947        assert_eq!(mv.san(&b).unwrap().to_string(), "Nf3");
948        assert_eq!(
949            mv.san(&b).unwrap().styled(san::Style::Utf8).to_string(),
950            "♘f3"
951        );
952    }
953
954    #[test]
955    fn test_simple() {
956        let mut b = Board::initial();
957        for (mv_str, fen_str, kind) in [
958            (
959                "e2e4",
960                "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
961                MoveKind::PawnDouble,
962            ),
963            (
964                "b8c6",
965                "r1bqkbnr/pppppppp/2n5/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 1 2",
966                MoveKind::Simple,
967            ),
968            (
969                "g1f3",
970                "r1bqkbnr/pppppppp/2n5/8/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 2 2",
971                MoveKind::Simple,
972            ),
973            (
974                "e7e5",
975                "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq e6 0 3",
976                MoveKind::PawnDouble,
977            ),
978            (
979                "f1b5",
980                "r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 1 3",
981                MoveKind::Simple,
982            ),
983            (
984                "g8f6",
985                "r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 2 4",
986                MoveKind::Simple,
987            ),
988            (
989                "e1g1",
990                "r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQ1RK1 b kq - 3 4",
991                MoveKind::CastlingKingside,
992            ),
993            (
994                "f6e4",
995                "r1bqkb1r/pppp1ppp/2n5/1B2p3/4n3/5N2/PPPP1PPP/RNBQ1RK1 w kq - 0 5",
996                MoveKind::Simple,
997            ),
998        ] {
999            let m = Move::from_uci_semilegal(mv_str, &b).unwrap();
1000            assert_eq!(m.kind(), kind);
1001            b = b.make_move(m).unwrap();
1002            assert_eq!(b.as_fen(), fen_str);
1003            assert_eq!(b.raw().try_into(), Ok(b.clone()));
1004        }
1005    }
1006
1007    #[test]
1008    fn test_promote() {
1009        let mut b = Board::from_fen("1b1b1K2/2P5/8/8/7k/8/8/8 w - - 0 1").unwrap();
1010        let b_copy = b.clone();
1011
1012        for (mv_str, fen_str) in [
1013            ("c7c8q", "1bQb1K2/8/8/8/7k/8/8/8 b - - 0 1"),
1014            ("c7b8n", "1N1b1K2/8/8/8/7k/8/8/8 b - - 0 1"),
1015            ("c7d8r", "1b1R1K2/8/8/8/7k/8/8/8 b - - 0 1"),
1016        ] {
1017            let (m, u) = make::Uci(mv_str).make_raw(&mut b).unwrap();
1018            assert_eq!(b.as_fen(), fen_str);
1019            assert_eq!(b.raw().try_into(), Ok(b.clone()));
1020            unsafe { unmake_move_unchecked(&mut b, m, u) };
1021            assert_eq!(b, b_copy);
1022        }
1023    }
1024
1025    #[test]
1026    fn test_undo() {
1027        let mut b =
1028            Board::from_fen("r1bqk2r/ppp2ppp/2np1n2/1Bb1p3/4P3/2PP1N2/PP3PPP/RNBQK2R w KQkq - 0 6")
1029                .unwrap();
1030        let b_copy = b.clone();
1031
1032        for (mv_str, fen_str) in [
1033            (
1034                "e1g1",
1035                "r1bqk2r/ppp2ppp/2np1n2/1Bb1p3/4P3/2PP1N2/PP3PPP/RNBQ1RK1 b kq - 1 6",
1036            ),
1037            (
1038                "f3e5",
1039                "r1bqk2r/ppp2ppp/2np1n2/1Bb1N3/4P3/2PP4/PP3PPP/RNBQK2R b KQkq - 0 6",
1040            ),
1041            (
1042                "b2b4",
1043                "r1bqk2r/ppp2ppp/2np1n2/1Bb1p3/1P2P3/2PP1N2/P4PPP/RNBQK2R b KQkq b3 0 6",
1044            ),
1045            (
1046                "c3c4",
1047                "r1bqk2r/ppp2ppp/2np1n2/1Bb1p3/2P1P3/3P1N2/PP3PPP/RNBQK2R b KQkq - 0 6",
1048            ),
1049        ] {
1050            let (m, u) = make::Uci(mv_str).make_raw(&mut b).unwrap();
1051            assert_eq!(b.as_fen(), fen_str);
1052            assert_eq!(b.raw().try_into(), Ok(b.clone()));
1053            unsafe { unmake_move_unchecked(&mut b, m, u) };
1054            assert_eq!(b, b_copy);
1055        }
1056    }
1057
1058    #[test]
1059    fn test_pawns() {
1060        let mut b = Board::from_fen("3K4/3p4/8/3PpP2/8/5p2/6P1/2k5 w - e6 0 1").unwrap();
1061        let b_copy = b.clone();
1062
1063        for (mv_str, fen_str) in [
1064            ("g2g3", "3K4/3p4/8/3PpP2/8/5pP1/8/2k5 b - - 0 1"),
1065            ("g2g4", "3K4/3p4/8/3PpP2/6P1/5p2/8/2k5 b - g3 0 1"),
1066            ("g2f3", "3K4/3p4/8/3PpP2/8/5P2/8/2k5 b - - 0 1"),
1067            ("d5e6", "3K4/3p4/4P3/5P2/8/5p2/6P1/2k5 b - - 0 1"),
1068            ("f5e6", "3K4/3p4/4P3/3P4/8/5p2/6P1/2k5 b - - 0 1"),
1069        ] {
1070            let (m, u) = make::Uci(mv_str).make_raw(&mut b).unwrap();
1071            assert_eq!(b.as_fen(), fen_str);
1072            assert_eq!(b.raw().try_into(), Ok(b.clone()));
1073            unsafe { unmake_move_unchecked(&mut b, m, u) };
1074            assert_eq!(b, b_copy);
1075        }
1076    }
1077
1078    #[test]
1079    fn test_legal() {
1080        let b =
1081            Board::from_fen("r1bqk2r/ppp2ppp/2np1n2/1Bb1p3/4P3/2PP1N2/PP3PPP/RNBQK2R w KQkq - 0 6")
1082                .unwrap();
1083
1084        let m = Move::from_uci("e1c1", &b).unwrap();
1085        assert!(!m.is_semilegal(&b));
1086        assert_eq!(m.semi_validate(&b), Err(ValidateError::NotSemiLegal));
1087
1088        let m = Move::from_uci("b5e8", &b).unwrap();
1089        assert!(!m.is_semilegal(&b));
1090        assert_eq!(m.semi_validate(&b), Err(ValidateError::NotSemiLegal));
1091
1092        assert_eq!(
1093            Move::from_uci("a3a4", &b),
1094            Err(uci::BasicParseError::Create(CreateError::NotWellFormed))
1095        );
1096
1097        let m = Move::from_uci("e1d1", &b).unwrap();
1098        assert!(!m.is_semilegal(&b));
1099        assert_eq!(m.semi_validate(&b), Err(ValidateError::NotSemiLegal));
1100
1101        assert_eq!(
1102            Move::from_uci("c3c5", &b),
1103            Err(uci::BasicParseError::Create(CreateError::NotWellFormed))
1104        );
1105    }
1106}