update interface for text records
This commit is contained in:
parent
0d8b9cc9cf
commit
149aa841c9
5 changed files with 198 additions and 179 deletions
|
|
@ -13,7 +13,7 @@ time that no illegal move is played, with no runtime checks and no potential pan
|
||||||
use eschac::prelude::*;
|
use eschac::prelude::*;
|
||||||
|
|
||||||
// read a position from a text record
|
// read a position from a text record
|
||||||
let setup = "7k/4P1rp/5Q2/5p2/1Pp1bP2/8/r4K1P/6R1 w - -".parse::<Setup>()?;
|
let setup = Setup::from_text_record("7k/4P1rp/5Q2/5p2/1Pp1bP2/8/r4K1P/6R1 w - -")?;
|
||||||
let position = setup.into_position()?;
|
let position = setup.into_position()?;
|
||||||
|
|
||||||
// read a move in algebraic notation
|
// read a move in algebraic notation
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
//! use eschac::prelude::*;
|
//! use eschac::prelude::*;
|
||||||
//!
|
//!
|
||||||
//! // read a position from a text record
|
//! // read a position from a text record
|
||||||
//! let setup = "7k/4P1rp/5Q2/5p2/1Pp1bP2/8/r4K1P/6R1 w - -".parse::<Setup>()?;
|
//! let setup = Setup::from_text_record("7k/4P1rp/5Q2/5p2/1Pp1bP2/8/r4K1P/6R1 w - -")?;
|
||||||
//! let position = setup.into_position()?;
|
//! let position = setup.into_position()?;
|
||||||
//!
|
//!
|
||||||
//! // read a move in algebraic notation
|
//! // read a move in algebraic notation
|
||||||
|
|
|
||||||
|
|
@ -82,16 +82,28 @@ impl Position {
|
||||||
/// ```
|
/// ```
|
||||||
/// # use eschac::setup::Setup;
|
/// # use eschac::setup::Setup;
|
||||||
/// # |s: &str| -> Option<eschac::position::Position> {
|
/// # |s: &str| -> Option<eschac::position::Position> {
|
||||||
/// s.parse::<Setup>().ok().and_then(|pos| pos.into_position().ok())
|
/// Setup::from_text_record(s).ok().and_then(|pos| pos.into_position().ok())
|
||||||
/// # };
|
/// # };
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_text_record(s: &str) -> Option<Self> {
|
pub fn from_text_record(s: &str) -> Option<Self> {
|
||||||
s.parse::<Setup>()
|
Setup::from_text_record(s)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|pos| pos.into_position().ok())
|
.and_then(|pos| pos.into_position().ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the text record of the position.
|
||||||
|
///
|
||||||
|
/// This is a shortcut for:
|
||||||
|
/// ```
|
||||||
|
/// # |position: eschac::position::Position| {
|
||||||
|
/// position.as_setup().to_text_record()
|
||||||
|
/// # };
|
||||||
|
#[inline]
|
||||||
|
pub fn to_text_record(&self) -> String {
|
||||||
|
self.as_setup().to_text_record()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns all the legal moves on the position.
|
/// Returns all the legal moves on the position.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn legal_moves<'l>(&'l self) -> Moves<'l> {
|
pub fn legal_moves<'l>(&'l self) -> Moves<'l> {
|
||||||
|
|
@ -469,7 +481,7 @@ impl Position {
|
||||||
impl std::fmt::Debug for Position {
|
impl std::fmt::Debug for Position {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
f.debug_tuple("Position")
|
f.debug_tuple("Position")
|
||||||
.field(&self.as_setup().to_string())
|
.field(&TextRecord(self.as_setup()))
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
264
src/setup.rs
264
src/setup.rs
|
|
@ -13,13 +13,14 @@ use crate::position::*;
|
||||||
/// It must be validated and converted to a [`Position`] using the [`Setup::into_position`] method
|
/// It must be validated and converted to a [`Position`] using the [`Setup::into_position`] method
|
||||||
/// before generating moves.
|
/// before generating moves.
|
||||||
///
|
///
|
||||||
/// This type implements [`FromStr`](std::str::FromStr) and [`Display`](std::fmt::Display) to parse
|
/// ## Text description
|
||||||
/// and print positions from text records.
|
|
||||||
///
|
///
|
||||||
/// Forsyth-Edwards Notation (FEN) is typically used to describe chess positions as text. eschac
|
/// Forsyth-Edwards Notation (FEN) is typically used to describe chess positions as text. eschac
|
||||||
/// uses a slightly different notation, which simply removes the last two fields of the FEN string
|
/// uses a slightly different notation, which simply removes the last two fields of the FEN record
|
||||||
/// (i.e. the halfmove clock and the fullmove number) as the [`Position`] type does not keep
|
/// (i.e. the halfmove clock and the fullmove number) as the [`Position`] type does not keep
|
||||||
/// track of those.
|
/// track of those. For example, the starting position is recorded as
|
||||||
|
/// `rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -`. [`Setup::from_text_record`] and
|
||||||
|
/// [`Setup::to_text_record`] can be used to read and write these records.
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Setup {
|
pub struct Setup {
|
||||||
pub(crate) w: Bitboard,
|
pub(crate) w: Bitboard,
|
||||||
|
|
@ -48,9 +49,23 @@ impl Setup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads a position from a text record.
|
||||||
|
///
|
||||||
|
/// This is a shortcut for:
|
||||||
|
/// ```
|
||||||
|
/// # use eschac::setup::Setup;
|
||||||
|
/// # |record: &str| {
|
||||||
|
/// Setup::from_ascii_record(record.as_bytes())
|
||||||
|
/// # };
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn from_text_record(record: &str) -> Result<Self, ParseRecordError> {
|
||||||
|
Self::from_ascii_record(record.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
/// Reads a position from an ascii record.
|
/// Reads a position from an ascii record.
|
||||||
pub fn from_ascii(s: &[u8]) -> Result<Self, ParseSetupError> {
|
pub fn from_ascii_record(record: &[u8]) -> Result<Self, ParseRecordError> {
|
||||||
let mut s = s.iter().copied().peekable();
|
let mut s = record.iter().copied().peekable();
|
||||||
let mut setup = Setup::new();
|
let mut setup = Setup::new();
|
||||||
(|| {
|
(|| {
|
||||||
let mut accept_empty_square = true;
|
let mut accept_empty_square = true;
|
||||||
|
|
@ -85,19 +100,14 @@ impl Setup {
|
||||||
}
|
}
|
||||||
(rank == 0).then_some(())?;
|
(rank == 0).then_some(())?;
|
||||||
(file == 8).then_some(())?;
|
(file == 8).then_some(())?;
|
||||||
Some(())
|
|
||||||
})()
|
|
||||||
.ok_or(ParseSetupError::InvalidBoard)?;
|
|
||||||
(|| {
|
|
||||||
match s.next()? {
|
match s.next()? {
|
||||||
b'w' => setup.set_turn(Color::White),
|
b'w' => setup.set_turn(Color::White),
|
||||||
b'b' => setup.set_turn(Color::Black),
|
b'b' => setup.set_turn(Color::Black),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
(s.next()? == b' ').then_some(())
|
(s.next()? == b' ').then_some(())?;
|
||||||
})()
|
|
||||||
.ok_or(ParseSetupError::InvalidTurn)?;
|
|
||||||
(|| {
|
|
||||||
if s.next_if_eq(&b'-').is_none() {
|
if s.next_if_eq(&b'-').is_none() {
|
||||||
if s.next_if_eq(&b'K').is_some() {
|
if s.next_if_eq(&b'K').is_some() {
|
||||||
setup.set_castling_rights(Color::White, CastlingSide::Short, true);
|
setup.set_castling_rights(Color::White, CastlingSide::Short, true);
|
||||||
|
|
@ -112,10 +122,8 @@ impl Setup {
|
||||||
setup.set_castling_rights(Color::Black, CastlingSide::Long, true);
|
setup.set_castling_rights(Color::Black, CastlingSide::Long, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(s.next()? == b' ').then_some(())
|
(s.next()? == b' ').then_some(())?;
|
||||||
})()
|
|
||||||
.ok_or(ParseSetupError::InvalidCastlingRights)?;
|
|
||||||
(|| {
|
|
||||||
match s.next()? {
|
match s.next()? {
|
||||||
b'-' => (),
|
b'-' => (),
|
||||||
file => setup.set_en_passant_target_square(Some(Square::new(
|
file => setup.set_en_passant_target_square(Some(Square::new(
|
||||||
|
|
@ -123,10 +131,95 @@ impl Setup {
|
||||||
Rank::from_ascii(s.next()?)?,
|
Rank::from_ascii(s.next()?)?,
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
s.next().is_none().then_some(())
|
s.next().is_none().then_some(())?;
|
||||||
|
Some(setup)
|
||||||
})()
|
})()
|
||||||
.ok_or(ParseSetupError::InvalidEnPassantTargetSquare)?;
|
.ok_or_else(|| ParseRecordError {
|
||||||
Ok(setup)
|
byte: record.len() - s.len(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the text record of the position.
|
||||||
|
pub fn to_text_record(&self) -> String {
|
||||||
|
let mut record = String::with_capacity(81);
|
||||||
|
self.write_text_record(&mut record).unwrap();
|
||||||
|
record
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the text record of the position.
|
||||||
|
pub fn write_text_record<W>(&self, w: &mut W) -> std::fmt::Result
|
||||||
|
where
|
||||||
|
W: std::fmt::Write,
|
||||||
|
{
|
||||||
|
for rank in Rank::all().into_iter().rev() {
|
||||||
|
let mut count = 0;
|
||||||
|
for file in File::all() {
|
||||||
|
match self.get(Square::new(file, rank)) {
|
||||||
|
Some(piece) => {
|
||||||
|
if count > 0 {
|
||||||
|
w.write_char(char::from_u32('0' as u32 + count).unwrap())?;
|
||||||
|
}
|
||||||
|
count = 0;
|
||||||
|
w.write_char(match piece.color {
|
||||||
|
Color::White => piece.role.to_char_uppercase(),
|
||||||
|
Color::Black => piece.role.to_char_lowercase(),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count > 0 {
|
||||||
|
w.write_char(char::from_u32('0' as u32 + count).unwrap())?;
|
||||||
|
}
|
||||||
|
if rank != Rank::First {
|
||||||
|
w.write_char('/')?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.write_char(' ')?;
|
||||||
|
|
||||||
|
w.write_char(match self.turn {
|
||||||
|
Color::White => 'w',
|
||||||
|
Color::Black => 'b',
|
||||||
|
})?;
|
||||||
|
|
||||||
|
w.write_char(' ')?;
|
||||||
|
|
||||||
|
let mut no_castle_available = true;
|
||||||
|
if self.castling_rights(Color::White, CastlingSide::Short) {
|
||||||
|
w.write_char('K')?;
|
||||||
|
no_castle_available = false;
|
||||||
|
}
|
||||||
|
if self.castling_rights(Color::White, CastlingSide::Long) {
|
||||||
|
w.write_char('Q')?;
|
||||||
|
no_castle_available = false;
|
||||||
|
}
|
||||||
|
if self.castling_rights(Color::Black, CastlingSide::Short) {
|
||||||
|
w.write_char('k')?;
|
||||||
|
no_castle_available = false;
|
||||||
|
}
|
||||||
|
if self.castling_rights(Color::Black, CastlingSide::Long) {
|
||||||
|
w.write_char('q')?;
|
||||||
|
no_castle_available = false;
|
||||||
|
}
|
||||||
|
if no_castle_available {
|
||||||
|
w.write_char('-')?;
|
||||||
|
}
|
||||||
|
|
||||||
|
w.write_char(' ')?;
|
||||||
|
|
||||||
|
match self.en_passant.try_into_square() {
|
||||||
|
Some(sq) => {
|
||||||
|
w.write_str(sq.to_str())?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
w.write_char('-')?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the occupancy of a square.
|
/// Returns the occupancy of a square.
|
||||||
|
|
@ -424,119 +517,28 @@ impl TryFrom<Setup> for Position {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Setup {
|
pub(crate) struct TextRecord<'a>(pub(crate) &'a Setup);
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
impl<'a> std::fmt::Display for TextRecord<'a> {
|
||||||
f.debug_tuple("Setup").field(&self.to_string()).finish()
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.write_text_record(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<'a> std::fmt::Debug for TextRecord<'a> {
|
||||||
impl std::fmt::Display for Setup {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
f.write_char('"')?;
|
||||||
for rank in Rank::all().into_iter().rev() {
|
self.0.write_text_record(f)?;
|
||||||
let mut count = 0;
|
f.write_char('"')?;
|
||||||
for file in File::all() {
|
|
||||||
match self.get(Square::new(file, rank)) {
|
|
||||||
Some(piece) => {
|
|
||||||
if count > 0 {
|
|
||||||
f.write_char(char::from_u32('0' as u32 + count).unwrap())?;
|
|
||||||
}
|
|
||||||
count = 0;
|
|
||||||
f.write_char(match piece.color {
|
|
||||||
Color::White => piece.role.to_char_uppercase(),
|
|
||||||
Color::Black => piece.role.to_char_lowercase(),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
f.write_char(char::from_u32('0' as u32 + count).unwrap())?;
|
|
||||||
}
|
|
||||||
if rank != Rank::First {
|
|
||||||
f.write_char('/')?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.write_char(' ')?;
|
|
||||||
|
|
||||||
f.write_char(match self.turn {
|
|
||||||
Color::White => 'w',
|
|
||||||
Color::Black => 'b',
|
|
||||||
})?;
|
|
||||||
|
|
||||||
f.write_char(' ')?;
|
|
||||||
|
|
||||||
let mut no_castle_available = true;
|
|
||||||
if self.castling_rights(Color::White, CastlingSide::Short) {
|
|
||||||
f.write_char('K')?;
|
|
||||||
no_castle_available = false;
|
|
||||||
}
|
|
||||||
if self.castling_rights(Color::White, CastlingSide::Long) {
|
|
||||||
f.write_char('Q')?;
|
|
||||||
no_castle_available = false;
|
|
||||||
}
|
|
||||||
if self.castling_rights(Color::Black, CastlingSide::Short) {
|
|
||||||
f.write_char('k')?;
|
|
||||||
no_castle_available = false;
|
|
||||||
}
|
|
||||||
if self.castling_rights(Color::Black, CastlingSide::Long) {
|
|
||||||
f.write_char('q')?;
|
|
||||||
no_castle_available = false;
|
|
||||||
}
|
|
||||||
if no_castle_available {
|
|
||||||
f.write_char('-')?;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.write_char(' ')?;
|
|
||||||
|
|
||||||
match self.en_passant.try_into_square() {
|
|
||||||
Some(sq) => {
|
|
||||||
f.write_str(sq.to_str())?;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
write!(f, "-")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for Setup {
|
impl std::fmt::Debug for Setup {
|
||||||
type Err = ParseSetupError;
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
#[inline]
|
f.debug_tuple("Setup").field(&TextRecord(&self)).finish()
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
Self::from_ascii(s.as_bytes())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error when trying to parse a position record.
|
|
||||||
///
|
|
||||||
/// The variant indicates the field that caused the error.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum ParseSetupError {
|
|
||||||
InvalidBoard,
|
|
||||||
InvalidTurn,
|
|
||||||
InvalidCastlingRights,
|
|
||||||
InvalidEnPassantTargetSquare,
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for ParseSetupError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let details = match self {
|
|
||||||
Self::InvalidBoard => "board",
|
|
||||||
Self::InvalidTurn => "turn",
|
|
||||||
Self::InvalidCastlingRights => "castling rights",
|
|
||||||
Self::InvalidEnPassantTargetSquare => "en passant target square",
|
|
||||||
};
|
|
||||||
write!(f, "invalid text record ({details})")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::error::Error for ParseSetupError {}
|
|
||||||
|
|
||||||
/// An invalid position.
|
/// An invalid position.
|
||||||
///
|
///
|
||||||
/// This is an illegal position that can't be represented with the [`Position`] type.
|
/// This is an illegal position that can't be represented with the [`Position`] type.
|
||||||
|
|
@ -549,7 +551,7 @@ impl std::fmt::Display for IllegalPosition {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let setup = &self.setup;
|
let setup = &self.setup;
|
||||||
write!(f, "`{setup}` is illegal:")?;
|
write!(f, "`{}` is illegal:", &TextRecord(setup))?;
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
for reason in self.reasons {
|
for reason in self.reasons {
|
||||||
if !first {
|
if !first {
|
||||||
|
|
@ -654,3 +656,17 @@ impl std::fmt::Display for IllegalPositionReason {
|
||||||
f.write_str(self.to_str())
|
f.write_str(self.to_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An error when trying to parse a position record.
|
||||||
|
///
|
||||||
|
/// The variant indicates the field that caused the error.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct ParseRecordError {
|
||||||
|
pub byte: usize,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for ParseRecordError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "invalid text record (at byte {})", self.byte)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for ParseRecordError {}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,7 @@ static P6: &'static str = "r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QP
|
||||||
fn recursive_check_aux(position: Position, depth: usize) {
|
fn recursive_check_aux(position: Position, depth: usize) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
position,
|
position,
|
||||||
position
|
Position::from_text_record(&position.as_setup().to_text_record()).unwrap(),
|
||||||
.as_setup()
|
|
||||||
.to_string()
|
|
||||||
.parse::<Setup>()
|
|
||||||
.unwrap()
|
|
||||||
.into_position()
|
|
||||||
.unwrap(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(passed) = position.pass() {
|
if let Some(passed) = position.pass() {
|
||||||
|
|
@ -75,7 +69,7 @@ fn recursive_check_aux(position: Position, depth: usize) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn recursive_check(record: &str) {
|
fn recursive_check(record: &str) {
|
||||||
recursive_check_aux(record.parse::<Setup>().unwrap().into_position().unwrap(), 4);
|
recursive_check_aux(Position::from_text_record(record).unwrap(), 4);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn recursive_check_1() {
|
fn recursive_check_1() {
|
||||||
|
|
@ -104,34 +98,29 @@ fn recursive_check_6() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn setup() {
|
fn setup() {
|
||||||
assert_eq!(Position::new().as_setup().to_string(), P1);
|
assert_eq!(Position::new().as_setup().to_text_record(), P1);
|
||||||
assert_eq!(Setup::new().to_string(), "8/8/8/8/8/8/8/8 w - -");
|
assert_eq!(Setup::new().to_text_record(), "8/8/8/8/8/8/8/8 w - -");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"8/8/8/8/1Pp5/8/R1k5/K7 w - b3"
|
Setup::from_text_record("8/8/8/8/1Pp5/8/R1k5/K7 w - b3")
|
||||||
.parse::<Setup>()
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string(),
|
.to_text_record(),
|
||||||
"8/8/8/8/1Pp5/8/R1k5/K7 w - b3",
|
"8/8/8/8/1Pp5/8/R1k5/K7 w - b3",
|
||||||
);
|
);
|
||||||
|
|
||||||
for (record, err) in [
|
for (record, byte) in [
|
||||||
("", ParseSetupError::InvalidBoard),
|
("", 0),
|
||||||
(" w - -", ParseSetupError::InvalidBoard),
|
(" w - -", 1),
|
||||||
("8/8/8/8/8/8/8 w - -", ParseSetupError::InvalidBoard),
|
("8/8/8/8/8/8/8 w - -", 14),
|
||||||
("1/1/1/1/1/1/1/1 w - -", ParseSetupError::InvalidBoard),
|
("1/1/1/1/1/1/1/1 w - -", 2),
|
||||||
(
|
("44/44/44/44/44/44/44/44 w - -", 2),
|
||||||
"44/44/44/44/44/44/44/44 w - -",
|
("8/8/8/8/8/8/8/8/8 w - -", 16),
|
||||||
ParseSetupError::InvalidBoard,
|
("p8/8/8/8/8/8/8/8 w - -", 2),
|
||||||
),
|
("8/8/8/8/8/8/8/8 - - - ", 17),
|
||||||
("8/8/8/8/8/8/8/8/8 w - -", ParseSetupError::InvalidBoard),
|
("8/8/8/8/8/8/8/8 w QQQQ -", 20),
|
||||||
("p8/8/8/8/8/8/8/8 w - -", ParseSetupError::InvalidBoard),
|
|
||||||
("8/8/8/8/8/8/8/8 - - - ", ParseSetupError::InvalidTurn),
|
|
||||||
(
|
|
||||||
"8/8/8/8/8/8/8/8 w QQQQ -",
|
|
||||||
ParseSetupError::InvalidCastlingRights,
|
|
||||||
),
|
|
||||||
] {
|
] {
|
||||||
assert_eq!(record.parse::<Setup>(), Err(err), "{record}");
|
let res = Setup::from_text_record(record);
|
||||||
|
assert!(res.is_err());
|
||||||
|
assert_eq!(res.unwrap_err().byte, byte, "{record}");
|
||||||
}
|
}
|
||||||
for (record, reason) in [
|
for (record, reason) in [
|
||||||
(
|
(
|
||||||
|
|
@ -192,12 +181,12 @@ fn setup() {
|
||||||
),
|
),
|
||||||
] {
|
] {
|
||||||
assert!(
|
assert!(
|
||||||
record.parse::<Setup>().map(|record| record.to_string()) == Ok(record.to_string()),
|
Setup::from_text_record(record).map(|setup| setup.to_text_record())
|
||||||
|
== Ok(record.to_string()),
|
||||||
"{record}",
|
"{record}",
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
record
|
Setup::from_text_record(record)
|
||||||
.parse::<Setup>()
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_position()
|
.into_position()
|
||||||
.is_err_and(|e| e.reasons.contains(reason)),
|
.is_err_and(|e| e.reasons.contains(reason)),
|
||||||
|
|
@ -212,28 +201,34 @@ fn setup() {
|
||||||
"3kr3/8/8/8/4P3/8/8/4K3 b - e3",
|
"3kr3/8/8/8/4P3/8/8/4K3 b - e3",
|
||||||
"8/8/8/3k4/3P4/8/8/3RK3 b - d3",
|
"8/8/8/3k4/3P4/8/8/3RK3 b - d3",
|
||||||
] {
|
] {
|
||||||
assert!(record.parse::<Setup>().is_ok(), "{record}");
|
assert!(Setup::from_text_record(record).is_ok(), "{record}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Setup::from_text_record(
|
||||||
|
"p1p1p1p1/p1p1p1p1/p1p1p1p1/p1p1p1p1/p1p1p1p1/p1p1p1p1/p1p1p1p1/p1p1p1p1 w KQkq a1"
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.to_text_record()
|
||||||
|
.len(),
|
||||||
|
81
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mirror() {
|
fn mirror() {
|
||||||
assert_eq!(Position::new().pass(), Some(Position::new().mirror()));
|
assert_eq!(Position::new().pass(), Some(Position::new().mirror()));
|
||||||
let position = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b Kq e3"
|
let position =
|
||||||
.parse::<Setup>()
|
Position::from_text_record("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b Kq e3")
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.into_position()
|
let mirror =
|
||||||
.unwrap();
|
Position::from_text_record("rnbqkbnr/pppp1ppp/8/4p3/8/8/PPPPPPPP/RNBQKBNR w Qk e6")
|
||||||
let mirror = "rnbqkbnr/pppp1ppp/8/4p3/8/8/PPPPPPPP/RNBQKBNR w Qk e6"
|
.unwrap();
|
||||||
.parse::<Setup>()
|
|
||||||
.unwrap()
|
|
||||||
.into_position()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(mirror, position.mirror());
|
assert_eq!(mirror, position.mirror());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perft_aux(record: &str, tests: &[u128]) {
|
fn perft_aux(record: &str, tests: &[u128]) {
|
||||||
let position = record.parse::<Setup>().unwrap().into_position().unwrap();
|
let position = Position::from_text_record(record).unwrap();
|
||||||
for (depth, value) in tests.iter().copied().enumerate() {
|
for (depth, value) in tests.iter().copied().enumerate() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
position.perft(depth),
|
position.perft(depth),
|
||||||
|
|
@ -272,11 +267,7 @@ fn perft_6() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn san() {
|
fn san() {
|
||||||
let position = "8/2KN1p2/5p2/3N1B1k/5PNp/7P/7P/8 w - -"
|
let position = Position::from_text_record("8/2KN1p2/5p2/3N1B1k/5PNp/7P/7P/8 w - -").unwrap();
|
||||||
.parse::<Setup>()
|
|
||||||
.unwrap()
|
|
||||||
.into_position()
|
|
||||||
.unwrap();
|
|
||||||
let san1 = "N7xf6#".parse::<San>().unwrap();
|
let san1 = "N7xf6#".parse::<San>().unwrap();
|
||||||
let m1 = san1.to_move(&position).unwrap();
|
let m1 = san1.to_move(&position).unwrap();
|
||||||
let san2 = "N5xf6#".parse::<San>().unwrap();
|
let san2 = "N5xf6#".parse::<San>().unwrap();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue