[moves] Add several macros to help with testing: ply! and assert_move_list!

ply! implements a small DSL for writing moves in code using a natural-ish
algebraic notation.

assert_move_list! takes a generator and an expected list of moves and asserts
that they're equal. This macro is mostly a copy from one I wrote earlier in the
position crate.
This commit is contained in:
Eryn Wells 2025-05-25 11:04:49 -07:00
parent 09bf17d66b
commit 3f3842c7c8
4 changed files with 90 additions and 0 deletions

View file

@ -2,6 +2,9 @@
mod pawn;
#[cfg(test)]
mod testing;
pub use pawn::PawnMoveGenerator;
use crate::Move;
@ -11,6 +14,12 @@ pub struct GeneratedMove {
pub(crate) ply: Move,
}
impl std::fmt::Display for GeneratedMove {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.ply.fmt(f)
}
}
impl From<Move> for GeneratedMove {
fn from(value: Move) -> Self {
GeneratedMove { ply: value }

View file

@ -0,0 +1,31 @@
// Eryn Wells <eryn@erynwells.me>
#[macro_export]
macro_rules! assert_move_list {
($generator:expr, [ $($expected:expr),* $(,)? ]) => {
{
let generated_moves: std::collections::HashSet<$crate::GeneratedMove> = $generator.collect();
let expected_moves: std::collections::HashSet<$crate::GeneratedMove> = [
$($expected.into(),)*
].into();
assert_eq!(
generated_moves,
expected_moves,
"\n\tMatching: {:?}\n\tGenerated, not expected: {:?}\n\tExpected, not generated: {:?}",
generated_moves
.intersection(&expected_moves)
.map(|mv| format!("{}", mv))
.collect::<Vec<String>>(),
generated_moves
.difference(&expected_moves)
.map(|mv| format!("{}", mv))
.collect::<Vec<String>>(),
expected_moves
.difference(&generated_moves)
.map(|mv| format!("{}", mv))
.collect::<Vec<String>>(),
);
}
};
}

View file

@ -9,4 +9,5 @@ mod moves;
pub use builder::{Builder, Error as BuildMoveError, Result as BuildMoveResult};
pub use defs::{Kind, PromotionShape};
pub use generators::GeneratedMove;
pub use moves::Move;

View file

@ -4,6 +4,54 @@ use crate::defs::{Kind, PromotionShape};
use chessfriend_core::{Rank, Shape, Square, Wing};
use std::fmt;
#[macro_export]
macro_rules! ply {
($origin:ident - $target:ident) => {
$crate::Move::quiet(
chessfriend_core::Square::$origin,
chessfriend_core::Square::$target,
)
};
($origin:ident -- $target:ident) => {
$crate::Move::double_push(
chessfriend_core::Square::$origin,
chessfriend_core::Square::$target,
)
};
($origin:ident x $target:ident) => {
$crate::Move::capture(
chessfriend_core::Square::$origin,
chessfriend_core::Square::$target,
)
};
($origin:ident x $target:ident e$(.)?p$(.)?) => {
$crate::Move::en_passant_capture(
chessfriend_core::Square::$origin,
chessfriend_core::Square::$target,
)
};
($origin:ident x $target:ident = $promotion:ident) => {
$crate::Move::capture_promotion(
chessfriend_core::Square::$origin,
chessfriend_core::Square::$target,
$crate::PromotionShape::$promotion,
)
};
($origin:ident - $target:ident = $promotion:ident) => {
$crate::Move::promotion(
chessfriend_core::Square::$origin,
chessfriend_core::Square::$target,
$crate::PromotionShape::$promotion,
)
};
(0-0) => {
$crate::Move::castle(chessfriend_core::Wing::KingSide)
};
(0-0-0) => {
$crate::Move::castle(chessfriend_core::Wing::QueenSide)
};
}
/// A single player's move. In game theory parlance, this is a "ply".
///
/// ## TODO
@ -39,6 +87,7 @@ impl Move {
Move(origin_bits(origin) | target_bits(target) | flag_bits)
}
#[must_use]
pub fn capture_promotion(origin: Square, target: Square, shape: PromotionShape) -> Self {
let flag_bits = Kind::CapturePromotion as u16;
let shape_bits = shape as u16;