diff --git a/board/src/board_provider.rs b/board/src/board_provider.rs new file mode 100644 index 0000000..a653bfc --- /dev/null +++ b/board/src/board_provider.rs @@ -0,0 +1,18 @@ +// Eryn Wells + +use crate::Board; + +pub trait BoardProvider { + fn board(&self) -> &Board; + fn board_mut(&mut self) -> &mut Board; +} + +impl BoardProvider for Board { + fn board(&self) -> &Board { + self + } + + fn board_mut(&mut self) -> &mut Board { + self + } +} diff --git a/board/src/lib.rs b/board/src/lib.rs index 1df6832..451bc23 100644 --- a/board/src/lib.rs +++ b/board/src/lib.rs @@ -9,10 +9,12 @@ pub mod macros; pub mod movement; pub mod sight; +mod board_provider; mod check; mod piece_sets; pub use board::Board; +pub use board_provider::BoardProvider; pub use castle::Parameters as CastleParameters; pub use castle::Rights as CastleRights; pub use piece_sets::{PlacePieceError, PlacePieceStrategy}; diff --git a/moves/src/lib.rs b/moves/src/lib.rs index fd74a25..89cd4b6 100644 --- a/moves/src/lib.rs +++ b/moves/src/lib.rs @@ -5,11 +5,13 @@ pub mod testing; mod builder; mod defs; +mod make_move; mod moves; mod record; pub use builder::{Builder, Error as BuildMoveError, Result as BuildMoveResult}; pub use defs::{Kind, PromotionShape}; pub use generators::GeneratedMove; +pub use make_move::{MakeMove, MakeMoveError, ValidateMove}; pub use moves::Move; pub use record::MoveRecord; diff --git a/position/src/position/make_move.rs b/moves/src/make_move.rs similarity index 55% rename from position/src/position/make_move.rs rename to moves/src/make_move.rs index cfb380a..06278ac 100644 --- a/position/src/position/make_move.rs +++ b/moves/src/make_move.rs @@ -1,11 +1,11 @@ // Eryn Wells -use crate::Position; +use crate::{Move, MoveRecord}; use chessfriend_board::{ - castle::CastleEvaluationError, movement::Movement, Board, PlacePieceError, PlacePieceStrategy, + castle::CastleEvaluationError, movement::Movement, Board, BoardProvider, PlacePieceError, + PlacePieceStrategy, }; use chessfriend_core::{Color, Piece, Rank, Square, Wing}; -use chessfriend_moves::{Move, MoveRecord}; use thiserror::Error; type MakeMoveResult = Result; @@ -60,23 +60,35 @@ pub enum MakeMoveError { PromotionRequired(Square), } -impl Position { +pub trait MakeMove { + fn make_move(&mut self, ply: Move, validate: ValidateMove) -> MakeMoveResult; +} + +trait MakeMoveInternal { + fn make_quiet_move(&mut self, ply: Move) -> MakeMoveResult; + fn make_double_push_move(&mut self, ply: Move) -> MakeMoveResult; + fn make_capture_move(&mut self, ply: Move) -> MakeMoveResult; + fn make_castle_move(&mut self, ply: Move, wing: Wing) -> MakeMoveResult; + fn make_promotion_move(&mut self, ply: Move) -> MakeMoveResult; + + fn validate_move(&self, ply: Move, validate: ValidateMove) -> Result<(), MakeMoveError>; + fn validate_active_piece(&self, ply: Move) -> Result; + + fn advance_clocks(&mut self, half_move_clock: HalfMoveClock); +} + +impl MakeMove for T { /// Make a move in the position. /// /// ## Errors /// /// If `validate` is [`ValidateMove::Yes`], perform validation of move correctness prior to /// applying the move. See [`Position::validate_move`]. - pub fn make_move(&mut self, ply: Move, validate: ValidateMove) -> Result<(), MakeMoveError> { - self.make_move_internal(ply, validate)?; - Ok(()) - } - - pub(crate) fn make_move_internal( + fn make_move( &mut self, ply: Move, validate: ValidateMove, - ) -> MakeMoveResult { + ) -> Result { if ply.is_quiet() { self.validate_move(ply, validate)?; return self.make_quiet_move(ply); @@ -105,20 +117,28 @@ impl Position { } } -impl Position { +impl MakeMoveInternal for T { fn make_quiet_move(&mut self, ply: Move) -> MakeMoveResult { + let board = self.board_mut(); + let origin = ply.origin_square(); - let piece = self + let piece = board .get_piece(origin) .ok_or(MakeMoveError::NoPiece(origin))?; let target = ply.target_square(); - self.place_piece_for_move(piece, target)?; + if piece.is_pawn() && target.rank().is_promotable_rank() { + return Err(MakeMoveError::PromotionRequired(target)); + } - self.remove_piece(origin); + board + .place_piece(piece, target, PlacePieceStrategy::PreserveExisting) + .map_err(MakeMoveError::PlacePieceError)?; - let record = self.register_move_record(ply, None); + board.remove_piece(origin); + + let record = MoveRecord::new(board, ply, None); self.advance_clocks(HalfMoveClock::Advance); @@ -126,22 +146,25 @@ impl Position { } fn make_double_push_move(&mut self, ply: Move) -> MakeMoveResult { + let board = self.board_mut(); + let origin = ply.origin_square(); - let piece = self - .board + let piece = board .remove_piece(origin) .ok_or(MakeMoveError::NoPiece(origin))?; let target = ply.target_square(); - self.place_piece_for_move(piece, target)?; + board + .place_piece(piece, target, PlacePieceStrategy::PreserveExisting) + .map_err(MakeMoveError::PlacePieceError)?; - self.board.en_passant_target = match target.rank() { + board.en_passant_target = match target.rank() { Rank::FOUR => Some(Square::from_file_rank(target.file(), Rank::THREE)), Rank::FIVE => Some(Square::from_file_rank(target.file(), Rank::SIX)), _ => unreachable!(), }; - let record = self.register_move_record(ply, None); + let record = MoveRecord::new(board, ply, None); self.advance_clocks(HalfMoveClock::Advance); @@ -152,11 +175,14 @@ impl Position { let origin_square = ply.origin_square(); let target_square = ply.target_square(); - let piece = self.get_piece_for_move(origin_square)?; + let board = self.board_mut(); + + let piece = board + .get_piece(origin_square) + .ok_or(MakeMoveError::NoPiece(origin_square))?; if ply.is_en_passant() { - let en_passant_square = self - .board + let en_passant_square = board .en_passant_target .ok_or(MakeMoveError::NoCaptureSquare)?; if target_square != en_passant_square { @@ -164,24 +190,23 @@ impl Position { } } + let board = self.board_mut(); + let capture_square = ply.capture_square().ok_or(MakeMoveError::NoCaptureSquare)?; - let captured_piece = self + let captured_piece = board .remove_piece(capture_square) .ok_or(MakeMoveError::NoCapturePiece(capture_square))?; - // Register the capture - self.captures.push(piece.color, captured_piece); - - self.remove_piece(origin_square).unwrap(); + board.remove_piece(origin_square).unwrap(); if let Some(promotion_shape) = ply.promotion_shape() { let promoted_piece = Piece::new(piece.color, promotion_shape); - self.place_piece(promoted_piece, target_square, PlacePieceStrategy::Replace)?; + board.place_piece(promoted_piece, target_square, PlacePieceStrategy::Replace)?; } else { - self.place_piece(piece, target_square, PlacePieceStrategy::Replace)?; + board.place_piece(piece, target_square, PlacePieceStrategy::Replace)?; } - let record = self.register_move_record(ply, Some(captured_piece)); + let record = MoveRecord::new(board, ply, Some(captured_piece)); self.advance_clocks(HalfMoveClock::Reset); @@ -189,20 +214,22 @@ impl Position { } fn make_castle_move(&mut self, ply: Move, wing: Wing) -> MakeMoveResult { - self.board.color_can_castle(wing, None)?; + let board = self.board_mut(); - let active_color = self.board.active_color; + board.color_can_castle(wing, None)?; + + let active_color = board.active_color; let parameters = Board::castling_parameters(wing, active_color); - let king = self.board.remove_piece(parameters.origin.king).unwrap(); - self.place_piece(king, parameters.target.king, PlacePieceStrategy::default())?; + let king = board.remove_piece(parameters.origin.king).unwrap(); + board.place_piece(king, parameters.target.king, PlacePieceStrategy::default())?; - let rook = self.board.remove_piece(parameters.origin.rook).unwrap(); - self.place_piece(rook, parameters.target.rook, PlacePieceStrategy::default())?; + let rook = board.remove_piece(parameters.origin.rook).unwrap(); + board.place_piece(rook, parameters.target.rook, PlacePieceStrategy::default())?; - self.board.castling_rights.revoke(active_color, wing); + board.castling_rights.revoke(active_color, wing); - let record = self.register_move_record(ply, None); + let record = MoveRecord::new(board, ply, None); self.advance_clocks(HalfMoveClock::Advance); @@ -210,9 +237,13 @@ impl Position { } fn make_promotion_move(&mut self, ply: Move) -> MakeMoveResult { + let board = self.board_mut(); + let origin = ply.origin_square(); - let piece = self.get_piece_for_move(origin)?; + let piece = board + .get_piece(origin) + .ok_or(MakeMoveError::NoPiece(origin))?; if !piece.is_pawn() { return Err(MakeMoveError::InvalidPiece(piece)); } @@ -223,68 +254,37 @@ impl Position { } if let Some(promotion_shape) = ply.promotion_shape() { - self.remove_piece(origin); + board.remove_piece(origin); let promoted_piece = Piece::new(piece.color, promotion_shape); - self.place_piece(promoted_piece, target, PlacePieceStrategy::PreserveExisting)?; + board.place_piece(promoted_piece, target, PlacePieceStrategy::PreserveExisting)?; } else { unreachable!( "Cannot make a promotion move with a ply that has no promotion shape: {ply:?}", ); } - let record = self.register_move_record(ply, None); + let record = MoveRecord::new(board, ply, None); self.advance_clocks(HalfMoveClock::Reset); Ok(record) } - fn register_move_record(&mut self, ply: Move, capture: Option) -> MoveRecord { - let record = MoveRecord::new(&self.board, ply, capture); - self.moves.push(record.clone()); - - record - } -} - -impl Position { - fn get_piece_for_move(&mut self, square: Square) -> Result { - self.get_piece(square).ok_or(MakeMoveError::NoPiece(square)) - } - - fn place_piece_for_move(&mut self, piece: Piece, square: Square) -> Result<(), MakeMoveError> { - if piece.is_pawn() && square.rank().is_promotable_rank() { - return Err(MakeMoveError::PromotionRequired(square)); - } - - self.place_piece(piece, square, PlacePieceStrategy::PreserveExisting) - .map_err(MakeMoveError::PlacePieceError) - } -} - -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] -enum HalfMoveClock { - Reset, - #[default] - Advance, -} - -impl Position { fn advance_clocks(&mut self, half_move_clock: HalfMoveClock) { + let board = self.board_mut(); + match half_move_clock { - HalfMoveClock::Reset => self.board.half_move_clock = 0, - HalfMoveClock::Advance => self.board.half_move_clock += 1, + HalfMoveClock::Reset => board.half_move_clock = 0, + HalfMoveClock::Advance => board.half_move_clock += 1, } - self.board.active_color = self.board.active_color.next(); + board.active_color = board.active_color.next(); - if self.board.active_color == Color::White { - self.board.full_move_number += 1; + if board.active_color == Color::White { + board.full_move_number += 1; } } -} -impl Position { fn validate_move(&self, ply: Move, validate: ValidateMove) -> Result<(), MakeMoveError> { if validate == ValidateMove::No { return Ok(()); @@ -292,13 +292,14 @@ impl Position { let active_piece = self.validate_active_piece(ply)?; + let board = self.board(); let origin_square = ply.origin_square(); let target_square = ply.target_square(); // Pawns can see squares they can't move to. So, calculating valid // squares requires a concept that includes Sight, but adds pawn pushes. // In ChessFriend, that concept is Movement. - let movement = active_piece.movement(origin_square, &self.board); + let movement = active_piece.movement(origin_square, board); if !movement.contains(target_square) { return Err(MakeMoveError::NoMove { piece: active_piece, @@ -310,7 +311,7 @@ impl Position { // TODO: En Passant capture. if let Some(capture_square) = ply.capture_square() { - if let Some(captured_piece) = self.board.get_piece(capture_square) { + if let Some(captured_piece) = board.get_piece(capture_square) { if captured_piece.color == active_piece.color { return Err(MakeMoveError::InvalidCapture(capture_square)); } @@ -325,12 +326,13 @@ impl Position { fn validate_active_piece(&self, ply: Move) -> Result { let origin_square = ply.origin_square(); - let active_piece = self - .board + let board = self.board(); + + let active_piece = board .get_piece(origin_square) .ok_or(MakeMoveError::NoPiece(origin_square))?; - if active_piece.color != self.board.active_color { + if active_piece.color != board.active_color { return Err(MakeMoveError::NonActiveColor { piece: active_piece, square: origin_square, @@ -341,159 +343,167 @@ impl Position { } } +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +enum HalfMoveClock { + Reset, + #[default] + Advance, +} + #[cfg(test)] mod tests { use super::*; - use crate::{test_position, ValidateMove}; + use crate::{Move, PromotionShape}; + use chessfriend_board::test_board; use chessfriend_core::{piece, Color, Square}; - use chessfriend_moves::{Move, PromotionShape}; type TestResult = Result<(), MakeMoveError>; #[test] fn make_quiet_move() -> TestResult { - let mut pos = test_position!(White Pawn on C2); + let mut board = test_board!(White Pawn on C2); let ply = Move::quiet(Square::C2, Square::C3); - pos.make_move(ply, ValidateMove::Yes)?; + board.make_move(ply, ValidateMove::Yes)?; - assert_eq!(pos.get_piece(Square::C2), None); - assert_eq!(pos.get_piece(Square::C3), Some(piece!(White Pawn))); - assert_eq!(pos.board.active_color, Color::Black); - assert_eq!(pos.board.half_move_clock, 1); + assert_eq!(board.get_piece(Square::C2), None); + assert_eq!(board.get_piece(Square::C3), Some(piece!(White Pawn))); + assert_eq!(board.active_color, Color::Black); + assert_eq!(board.half_move_clock, 1); - pos.board.active_color = Color::White; + board.active_color = Color::White; let ply = Move::quiet(Square::C3, Square::C4); - pos.make_move(ply, ValidateMove::Yes)?; + board.make_move(ply, ValidateMove::Yes)?; - assert_eq!(pos.get_piece(Square::C3), None); - assert_eq!(pos.get_piece(Square::C4), Some(piece!(White Pawn))); - assert_eq!(pos.board.active_color, Color::Black); - assert_eq!(pos.board.half_move_clock, 2); + assert_eq!(board.get_piece(Square::C3), None); + assert_eq!(board.get_piece(Square::C4), Some(piece!(White Pawn))); + assert_eq!(board.active_color, Color::Black); + assert_eq!(board.half_move_clock, 2); Ok(()) } #[test] fn make_invalid_quiet_pawn_move() { - let mut pos = test_position!(White Pawn on C2); + let mut board = test_board!(White Pawn on C2); let ply = Move::quiet(Square::C2, Square::D2); - let result = pos.make_move(ply, ValidateMove::Yes); + let result = board.make_move(ply, ValidateMove::Yes); assert!(result.is_err()); - assert_eq!(pos.get_piece(Square::C2), Some(piece!(White Pawn))); - assert_eq!(pos.get_piece(Square::D2), None); - assert_eq!(pos.board.active_color, Color::White); - assert_eq!(pos.board.half_move_clock, 0); + assert_eq!(board.get_piece(Square::C2), Some(piece!(White Pawn))); + assert_eq!(board.get_piece(Square::D2), None); + assert_eq!(board.active_color, Color::White); + assert_eq!(board.half_move_clock, 0); } #[test] fn make_capture_move() -> TestResult { - let mut pos = test_position![ + let mut board = test_board![ White Bishop on C2, Black Rook on F5, ]; let ply = Move::capture(Square::C2, Square::F5); - pos.make_move(ply, ValidateMove::Yes)?; + let result = board.make_move(ply, ValidateMove::Yes)?; - assert_eq!(pos.get_piece(Square::C2), None); - assert_eq!(pos.get_piece(Square::F5), Some(piece!(White Bishop))); - assert_eq!(pos.captures.last(Color::White), Some(&piece!(Black Rook))); - assert_eq!(pos.board.active_color, Color::Black); - assert_eq!(pos.board.half_move_clock, 0); + assert_eq!(result.captured_piece, Some(piece!(Black Rook))); + + assert_eq!(board.get_piece(Square::C2), None); + assert_eq!(board.get_piece(Square::F5), Some(piece!(White Bishop))); + assert_eq!(board.active_color, Color::Black); + assert_eq!(board.half_move_clock, 0); Ok(()) } #[test] fn make_en_passant_capture_move() -> TestResult { - let mut pos = test_position![ + let mut board = test_board![ Black Pawn on F4, White Pawn on E2 ]; let ply = Move::double_push(Square::E2, Square::E4); - pos.make_move(ply, ValidateMove::Yes)?; + board.make_move(ply, ValidateMove::Yes)?; - assert_eq!(pos.get_piece(Square::E2), None); - assert_eq!(pos.get_piece(Square::E4), Some(piece!(White Pawn))); + assert_eq!(board.get_piece(Square::E2), None); + assert_eq!(board.get_piece(Square::E4), Some(piece!(White Pawn))); assert_eq!( - pos.board.en_passant_target, + board.en_passant_target, Some(Square::E3), "en passant square not set" ); - assert_eq!(pos.board.active_color, Color::Black); - assert_eq!(pos.board.half_move_clock, 1); + assert_eq!(board.active_color, Color::Black); + assert_eq!(board.half_move_clock, 1); let ply = Move::en_passant_capture(Square::F4, Square::E3); - pos.make_move(ply, ValidateMove::Yes)?; + let result = board.make_move(ply, ValidateMove::Yes)?; - assert_eq!(pos.get_piece(Square::F4), None); - assert_eq!(pos.get_piece(Square::E3), Some(piece!(Black Pawn))); + assert_eq!(result.captured_piece, Some(piece!(White Pawn))); + + assert_eq!(board.get_piece(Square::F4), None); + assert_eq!(board.get_piece(Square::E3), Some(piece!(Black Pawn))); assert_eq!( - pos.get_piece(Square::E4), + board.get_piece(Square::E4), None, "capture target pawn not removed" ); - assert_eq!(pos.captures.last(Color::Black), Some(&piece!(White Pawn))); Ok(()) } #[test] fn make_last_rank_quiet_move_without_promotion() { - let mut pos = test_position!( + let mut board = test_board!( White Pawn on A7 ); let ply = Move::quiet(Square::A7, Square::A8); - let result = pos.make_move(ply, ValidateMove::Yes); + let result = board.make_move(ply, ValidateMove::Yes); assert!(result.is_err()); - assert_eq!(pos.board.active_color, Color::White); - assert_eq!(pos.get_piece(Square::A7), Some(piece!(White Pawn))); - assert_eq!(pos.get_piece(Square::A8), None); - assert_eq!(pos.board.half_move_clock, 0); + assert_eq!(board.active_color, Color::White); + assert_eq!(board.get_piece(Square::A7), Some(piece!(White Pawn))); + assert_eq!(board.get_piece(Square::A8), None); + assert_eq!(board.half_move_clock, 0); } #[test] fn make_promotion_move() -> TestResult { - let mut pos = test_position![ + let mut board = test_board![ Black Pawn on E7, White Pawn on F7, ]; let ply = Move::promotion(Square::F7, Square::F8, PromotionShape::Queen); - pos.make_move(ply, ValidateMove::Yes)?; + board.make_move(ply, ValidateMove::Yes)?; - assert_eq!(pos.get_piece(Square::F7), None); - assert_eq!(pos.get_piece(Square::F8), Some(piece!(White Queen))); - assert_eq!(pos.board.active_color, Color::Black); - assert_eq!(pos.board.half_move_clock, 0); + assert_eq!(board.get_piece(Square::F7), None); + assert_eq!(board.get_piece(Square::F8), Some(piece!(White Queen))); + assert_eq!(board.active_color, Color::Black); + assert_eq!(board.half_move_clock, 0); Ok(()) } #[test] fn make_white_kingside_castle() -> TestResult { - let mut pos = test_position![ + let mut board = test_board![ White Rook on H1, White King on E1, ]; let ply = Move::castle(Wing::KingSide); - pos.make_move(ply, ValidateMove::Yes)?; + board.make_move(ply, ValidateMove::Yes)?; - assert_eq!(pos.board.active_color, Color::Black); - assert_eq!(pos.get_piece(Square::E1), None); - assert_eq!(pos.get_piece(Square::H1), None); - assert_eq!(pos.get_piece(Square::G1), Some(piece!(White King))); - assert_eq!(pos.get_piece(Square::F1), Some(piece!(White Rook))); - assert!(!pos - .board + assert_eq!(board.active_color, Color::Black); + assert_eq!(board.get_piece(Square::E1), None); + assert_eq!(board.get_piece(Square::H1), None); + assert_eq!(board.get_piece(Square::G1), Some(piece!(White King))); + assert_eq!(board.get_piece(Square::F1), Some(piece!(White Rook))); + assert!(!board .castling_rights .color_has_right(Color::White, Wing::KingSide)); @@ -502,21 +512,20 @@ mod tests { #[test] fn make_white_queenside_castle() -> TestResult { - let mut pos = test_position![ + let mut board = test_board![ White King on E1, White Rook on A1, ]; let ply = Move::castle(Wing::QueenSide); - pos.make_move(ply, ValidateMove::Yes)?; + board.make_move(ply, ValidateMove::Yes)?; - assert_eq!(pos.board.active_color, Color::Black); - assert_eq!(pos.get_piece(Square::E1), None); - assert_eq!(pos.get_piece(Square::A1), None); - assert_eq!(pos.get_piece(Square::C1), Some(piece!(White King))); - assert_eq!(pos.get_piece(Square::D1), Some(piece!(White Rook))); - assert!(!pos - .board + assert_eq!(board.active_color, Color::Black); + assert_eq!(board.get_piece(Square::E1), None); + assert_eq!(board.get_piece(Square::A1), None); + assert_eq!(board.get_piece(Square::C1), Some(piece!(White King))); + assert_eq!(board.get_piece(Square::D1), Some(piece!(White Rook))); + assert!(!board .castling_rights .color_has_right(Color::White, Wing::QueenSide)); diff --git a/position/src/lib.rs b/position/src/lib.rs index 0307e41..931c507 100644 --- a/position/src/lib.rs +++ b/position/src/lib.rs @@ -11,4 +11,4 @@ mod testing; pub use chessfriend_board::{fen, PlacePieceError, PlacePieceStrategy}; pub use chessfriend_moves::GeneratedMove; -pub use position::{Position, ValidateMove}; +pub use position::Position; diff --git a/position/src/position.rs b/position/src/position.rs index 1e0a829..8e32030 100644 --- a/position/src/position.rs +++ b/position/src/position.rs @@ -1,7 +1,6 @@ // Eryn Wells mod captures; -mod make_move; mod unmake_move; use chessfriend_moves::{ @@ -9,14 +8,14 @@ use chessfriend_moves::{ AllPiecesMoveGenerator, BishopMoveGenerator, KingMoveGenerator, KnightMoveGenerator, PawnMoveGenerator, QueenMoveGenerator, RookMoveGenerator, }, - GeneratedMove, Move, MoveRecord, + GeneratedMove, MakeMove, Move, MoveRecord, ValidateMove, }; -pub use make_move::ValidateMove; use captures::CapturesList; use chessfriend_bitboard::BitBoard; use chessfriend_board::{ - display::DiagramFormatter, fen::ToFenStr, Board, PlacePieceError, PlacePieceStrategy, + display::DiagramFormatter, fen::ToFenStr, Board, BoardProvider, PlacePieceError, + PlacePieceStrategy, }; use chessfriend_core::{Color, Piece, Shape, Square}; use std::fmt; @@ -142,6 +141,16 @@ impl ToFenStr for Position { } } +impl BoardProvider for Position { + fn board(&self) -> &Board { + &self.board + } + + fn board_mut(&mut self) -> &mut Board { + &mut self.board + } +} + impl PartialEq for Position { fn eq(&self, other: &Self) -> bool { self.board == other.board diff --git a/position/src/testing.rs b/position/src/testing.rs index 1439dee..2b1be8e 100644 --- a/position/src/testing.rs +++ b/position/src/testing.rs @@ -1,7 +1,6 @@ // Eryn Wells -use crate::position::MakeMoveError; -use chessfriend_moves::BuildMoveError; +use chessfriend_moves::{BuildMoveError, MakeMoveError}; #[macro_export] macro_rules! assert_move_list {