Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions pleco/src/core/piece_move.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@

use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
use std::fmt;
use std::str::FromStr;

use super::sq::SQ;
use super::*;
Expand Down Expand Up @@ -146,6 +147,77 @@ impl fmt::Display for BitMove {
}
}

impl FromStr for BitMove {
type Err = BitMoveFromStrError;

/// Parses a `BitMove` from a UCI move string (e.g., "e2e4", "a7a8q").
///
/// The format is: source square, destination square, and an optional promotion piece
/// character ('n', 'b', 'r', 'q').
///
/// Note: Without board context, only the source, destination, and promotion information
/// can be determined. Flags such as capture, en passant, castle, or double pawn push
/// cannot be inferred from the string alone.
///
/// # Examples
///
/// ```rust
/// use pleco::BitMove;
/// use std::str::FromStr;
///
/// let mv = BitMove::from_str("e2e4").unwrap();
/// assert_eq!(mv.to_string(), "e2e4");
///
/// let promo = BitMove::from_str("a7a8q").unwrap();
/// assert!(promo.is_promo());
/// ```
fn from_str(s: &str) -> Result<Self, Self::Err> {
let len = s.len();
if len < 4 || len > 5 {
return Err(BitMoveFromStrError);
}
let src = s[0..2].parse::<SQ>().map_err(|_| BitMoveFromStrError)?;
let dst = s[2..4].parse::<SQ>().map_err(|_| BitMoveFromStrError)?;

if len == 5 {
let promo_char = s.as_bytes()[4];
let prom = match promo_char {
b'n' => PieceType::N,
b'b' => PieceType::B,
b'r' => PieceType::R,
b'q' => PieceType::Q,
_ => return Err(BitMoveFromStrError),
};
// Capture status cannot be determined without board context
Ok(BitMove::init(PreMoveInfo {
src,
dst,
flags: MoveFlag::Promotion {
capture: false,
prom,
},
}))
} else {
// Without board context, flags like capture, en passant, castle,
// or double pawn push cannot be inferred from the string alone
Ok(BitMove::make(BitMove::FLAG_QUIET, src, dst))
}
}
}

/// Error type for parsing a `BitMove` from a string.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BitMoveFromStrError;

impl fmt::Display for BitMoveFromStrError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"invalid move string, expected format like 'e2e4' or 'a7a8q'"
)
}
}

// https://chessprogramming.wikispaces.com/Encoding+Moves
impl BitMove {
pub const FLAG_QUIET: u16 = 0b0000;
Expand Down
44 changes: 44 additions & 0 deletions pleco/src/core/sq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ use super::*;
use std::fmt;
use std::mem::transmute;
use std::ops::*;
use std::str::FromStr;

// TODO: Investigate possibility of using an Enum instead

Expand Down Expand Up @@ -392,3 +393,46 @@ impl fmt::Display for SQ {
write!(f, "{}", SQ_DISPLAY[self.0 as usize])
}
}

impl FromStr for SQ {
type Err = SQFromStrError;

/// Parses a `SQ` from a string in algebraic notation (e.g., "a1", "h8").
///
/// # Examples
///
/// ```rust
/// use pleco::SQ;
/// use std::str::FromStr;
///
/// let sq = SQ::from_str("e4").unwrap();
/// assert_eq!(sq, SQ::E4);
/// ```
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = s.as_bytes();
if bytes.len() != 2 {
return Err(SQFromStrError);
}
let file = bytes[0];
let rank = bytes[1];
if !(b'a'..=b'h').contains(&file) || !(b'1'..=b'8').contains(&rank) {
return Err(SQFromStrError);
}
let file_idx = file - b'a';
let rank_idx = rank - b'1';
Ok(SQ(rank_idx * 8 + file_idx))
}
}

/// Error type for parsing a `SQ` from a string.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SQFromStrError;

impl fmt::Display for SQFromStrError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"invalid square string, expected format like 'a1' through 'h8'"
)
}
}
72 changes: 72 additions & 0 deletions pleco/tests/move_generating.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use pleco::board::{Board, RandBoard};
use pleco::core::piece_move::*;
use pleco::core::*;
use pleco::SQ;
use std::str::FromStr;

#[test]
fn test_movegen_captures() {
Expand Down Expand Up @@ -333,3 +334,74 @@ fn all_move_flags() -> Vec<MoveFlag> {
move_flags.push(MoveFlag::QuietMove);
move_flags
}

#[test]
fn sq_from_str() {
assert_eq!(SQ::from_str("a1").unwrap(), SQ::A1);
assert_eq!(SQ::from_str("h8").unwrap(), SQ::H8);
assert_eq!(SQ::from_str("e4").unwrap(), SQ::E4);
assert_eq!(SQ::from_str("d7").unwrap(), SQ::D7);
assert!(SQ::from_str("").is_err());
assert!(SQ::from_str("a").is_err());
assert!(SQ::from_str("a9").is_err());
assert!(SQ::from_str("i1").is_err());
assert!(SQ::from_str("a1b").is_err());
}

#[test]
fn sq_from_str_roundtrip() {
for i in 0..64u8 {
let sq = SQ(i);
let s = sq.to_string();
let parsed = SQ::from_str(&s).unwrap();
assert_eq!(sq, parsed);
}
}

#[test]
fn bitmove_from_str_quiet() {
let mv = BitMove::from_str("e2e4").unwrap();
assert_eq!(mv.get_src(), SQ::E2);
assert_eq!(mv.get_dest(), SQ::E4);
assert!(!mv.is_promo());
assert_eq!(mv.to_string(), "e2e4");
}

#[test]
fn bitmove_from_str_promotion() {
let mv = BitMove::from_str("a7a8q").unwrap();
assert_eq!(mv.get_src(), SQ::A7);
assert_eq!(mv.get_dest(), SQ::A8);
assert!(mv.is_promo());
assert_eq!(mv.promo_piece(), PieceType::Q);

let mv = BitMove::from_str("b7b8n").unwrap();
assert!(mv.is_promo());
assert_eq!(mv.promo_piece(), PieceType::N);

let mv = BitMove::from_str("c7c8b").unwrap();
assert!(mv.is_promo());
assert_eq!(mv.promo_piece(), PieceType::B);

let mv = BitMove::from_str("d7d8r").unwrap();
assert!(mv.is_promo());
assert_eq!(mv.promo_piece(), PieceType::R);
}

#[test]
fn bitmove_from_str_invalid() {
assert!(BitMove::from_str("").is_err());
assert!(BitMove::from_str("e2").is_err());
assert!(BitMove::from_str("e2e4e6").is_err());
assert!(BitMove::from_str("e2e9").is_err());
assert!(BitMove::from_str("e2e4x").is_err());
}

#[test]
fn bitmove_from_str_roundtrip_quiet() {
let original = BitMove::make_quiet(SQ::E2, SQ::E4);
let s = original.to_string();
let parsed = BitMove::from_str(&s).unwrap();
assert_eq!(parsed.get_src(), original.get_src());
assert_eq!(parsed.get_dest(), original.get_dest());
}