use shogi_core::{Color, Hand, PartialPosition, Piece, Square};
use crate::{Error, FromUsi, Result};
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl FromUsi for shogi_core::Position {
fn parse_usi_slice(s: &[u8]) -> Result<(&[u8], Self)> {
use shogi_core::Move;
let (s, partial) = bind!(PartialPosition::parse_usi_slice(s));
let orig = s;
let s = match parse_many_whitespaces(s) {
Ok(s) => s,
Err(_) => return Ok((s, shogi_core::Position::arbitrary_position(partial))),
};
if s.get(..5) != Some(b"moves") {
return Ok((orig, shogi_core::Position::arbitrary_position(partial)));
}
let mut s = unsafe { s.get_unchecked(5..) };
let mut position = shogi_core::Position::arbitrary_position(partial);
let mut side = position.side_to_move();
loop {
let orig = s;
let next = match parse_many_whitespaces(s) {
Ok(next) => next,
Err(_) => return Ok((s, position)),
};
let (next, mv) = match shogi_core::Move::parse_usi_slice(next) {
Ok((next, mv)) => (next, mv),
Err(_) => return Ok((orig, position)),
};
let mv = match mv {
Move::Drop { piece, to } => {
let piece = Piece::new(piece.piece_kind(), side);
Move::Drop { piece, to }
}
_ => mv,
};
let result = position.make_move(mv);
s = next;
if result.is_some() {
side = side.flip();
}
}
}
}
impl FromUsi for PartialPosition {
fn parse_usi_slice(mut s: &[u8]) -> Result<(&[u8], Self)> {
use core::cmp::min;
if s.len() >= 8 {
let (startpos, rest) = s.split_at(8); if startpos == b"startpos" {
return Ok((rest, PartialPosition::startpos()));
}
}
let mut position = PartialPosition::empty();
let orig = s;
if s.get(..4) != Some(b"sfen") {
return Err(Error::InvalidInput {
from: 0,
to: min(4, s.len()),
description: "invalid token: `sfen` was expected",
});
}
s = unsafe { s.get_unchecked(4..) };
let mut s = try_with_progress!(parse_many_whitespaces(s), orig.len() - s.len());
for i in 0..9 {
let (slash, row) = try_with_progress!(parse_row(s), orig.len() - s.len());
for j in 0..9 {
position.piece_set(
unsafe { Square::new(9 - j, i + 1).unwrap_unchecked() },
row[j as usize],
);
}
s = slash;
if i < 8 {
if s.get(0).copied() != Some(b'/') {
return Err(Error::InvalidInput {
from: orig.len() - slash.len(),
to: orig.len() - slash.len() + min(s.len(), 1),
description: "`/` was expected",
});
}
s = unsafe { s.get_unchecked(1..) };
}
}
if s.get(0).copied() != Some(b' ') {
return Err(Error::InvalidInput {
from: orig.len() - s.len(),
to: orig.len() - s.len() + min(s.len(), 1),
description: "` ` (whitespace) was expected",
});
}
let s = unsafe { s.get_unchecked(1..) };
let (s, side) = try_with_progress!(Color::parse_usi_slice(s), orig.len() - s.len());
position.side_to_move_set(side);
if s.get(0).copied() != Some(b' ') {
return Err(Error::InvalidInput {
from: orig.len() - s.len(),
to: orig.len() - s.len() + 1,
description: "` ` (whitespace) was expected",
});
}
let s = unsafe { s.get_unchecked(1..) };
let (s, hand) = try_with_progress!(<[Hand; 2]>::parse_usi_slice(s), orig.len() - s.len());
*position.hand_of_a_player_mut(Color::Black) = hand[0];
*position.hand_of_a_player_mut(Color::White) = hand[1];
if s.get(0).copied() != Some(b' ') {
return Ok((s, position));
}
let mut s = unsafe { s.get_unchecked(1..) };
let mut count: u16 = 0;
while matches!(s.get(0).copied(), Some(b'0'..=b'9')) {
let digit = (*unsafe { s.get_unchecked(0) } - b'0') as u16;
count = count.saturating_mul(10).saturating_add(digit);
s = unsafe { s.get_unchecked(1..) };
}
let _ = position.ply_set(count);
Ok((s, position))
}
}
fn parse_many_whitespaces(s: &[u8]) -> Result<&[u8]> {
use core::cmp::min;
let mut s = if let Some((&b' ', s)) = s.split_first() {
s
} else {
return Err(Error::InvalidInput {
from: 0,
to: min(1, s.len()),
description: "` ` (whitespace) was expected",
});
};
while let Some((&b' ', rest)) = s.split_first() {
s = rest;
}
Ok(s)
}
fn parse_row(s: &[u8]) -> Result<(&[u8], [Option<Piece>; 9])> {
let mut this_row = s;
let mut seen = 0; let mut result = [None; 9];
while let Some((&first, next)) = this_row.split_first() {
if !(seen <= 90 && first != b'/' && first != b' ') {
break;
}
if matches!(first, b'1'..=b'9') {
seen += first - b'0';
this_row = next;
continue;
}
let (next, piece) =
try_with_progress!(Piece::parse_usi_slice(this_row), s.len() - this_row.len());
if seen < 9 {
result[seen as usize] = Some(piece);
}
seen += 1;
this_row = next;
}
if seen != 9 {
return Err(Error::InvalidInput {
from: 0,
to: s.len() - this_row.len(),
description: "exactly 9 squares are expected",
});
}
Ok((this_row, result))
}
#[no_mangle]
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub unsafe extern "C" fn Position_parse_usi_slice(s: *const u8) -> *mut shogi_core::Position {
let length = crate::common::strlen(s);
let slice = core::slice::from_raw_parts(s, length);
match shogi_core::Position::parse_usi_slice(slice) {
Ok((_slice, resulting_data)) => {
let returned = alloc::boxed::Box::new(resulting_data);
alloc::boxed::Box::leak(returned)
}
Err(_) => core::ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn PartialPosition_parse_usi_slice(
position: &mut PartialPosition,
s: *const u8,
) -> isize {
crate::common::make_parse_usi_slice_c(position, s)
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::borrow::ToOwned;
use shogi_core::Position;
#[test]
fn position_works() {
let s = "sfen lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1";
let (rem, pos) = Position::parse_usi_slice(s.as_bytes()).unwrap();
assert!(rem.is_empty());
let pos_as_str = pos.to_sfen_owned();
assert_eq!(s, "sfen ".to_owned() + &pos_as_str);
}
#[test]
fn partial_position_works() {
let s = "sfen lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1";
let (rem, pos) = PartialPosition::parse_usi_slice(s.as_bytes()).unwrap();
assert!(rem.is_empty());
let pos_as_str = pos.to_sfen_owned();
assert_eq!(s, "sfen ".to_owned() + &pos_as_str);
}
}