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#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
15#[repr(u8)]
16pub enum MoveKind {
17 #[default]
19 Null = 0,
20 Simple = 1,
22 CastlingKingside = 2,
24 CastlingQueenside = 3,
26 PawnDouble = 4,
28 Enpassant = 5,
30 PromoteKnight = 6,
32 PromoteBishop = 7,
34 PromoteRook = 8,
36 PromoteQueen = 9,
38}
39
40#[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#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
52pub enum Style {
53 San,
55 SanUtf8,
57 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 #[inline]
143 pub fn promote(self) -> Option<Piece> {
144 let piece: PromotePiece = self.try_into().ok()?;
145 Some(piece.into())
146 }
147
148 #[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#[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#[derive(Debug, Clone, Error, Eq, PartialEq)]
208pub enum ValidateError {
209 #[error("move is not semi-legal")]
211 NotSemiLegal,
212 #[error("move is not legal")]
214 NotLegal,
215}
216
217#[derive(Debug, Clone, Error, Eq, PartialEq)]
219pub enum CreateError {
220 #[error("move is not well-formed")]
222 NotWellFormed,
223}
224
225impl Move {
226 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 #[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 #[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 #[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 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 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 #[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 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 pub unsafe fn is_legal_unchecked(&self, b: &Board) -> bool {
321 Checker::new(b, NilPrechecker).is_legal(*self)
322 }
323
324 #[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 #[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 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 pub fn is_well_formed(&self) -> bool {
366 if self.kind == MoveKind::Null {
367 return *self == Move::NULL;
368 }
369
370 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 #[inline]
440 pub const fn kind(&self) -> MoveKind {
441 self.kind
442 }
443
444 #[inline]
448 pub const fn src(&self) -> Coord {
449 self.src
450 }
451
452 #[inline]
456 pub const fn dst(&self) -> Coord {
457 self.dst
458 }
459
460 #[inline]
464 pub const fn src_cell(&self) -> Cell {
465 self.src_cell
466 }
467
468 #[inline]
470 pub fn uci(&self) -> uci::Move {
471 (*self).into()
472 }
473
474 #[inline]
478 pub fn san(&self, b: &Board) -> Result<san::Move, ValidateError> {
479 san::Move::from_move(*self, b)
480 }
481
482 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
534pub 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#[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 }
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 }
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
825pub 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
846pub 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}