use sashite_epin::Identifier as Piece;
use sashite_sin::{Identifier as Style, Side};
use crate::error::ParseError;
use crate::hands::HandIter;
use crate::parse::{self, Parsed};
use crate::shape::Shape;
use crate::token::epin_token;
#[cfg(feature = "alloc")]
use crate::limits::MAX_DIMENSIONS;
#[cfg(feature = "alloc")]
use sashite_qi::{Player, Qi};
#[derive(Debug, Clone, Copy)]
pub struct Feen<'a> {
inner: Parsed<'a>,
}
impl<'a> Feen<'a> {
pub fn parse(input: &'a str) -> Result<Self, ParseError> {
Ok(Self {
inner: parse::parse(input)?,
})
}
#[must_use]
pub fn is_valid(input: &str) -> bool {
parse::parse(input).is_ok()
}
#[must_use]
pub const fn shape(&self) -> Shape {
self.inner.shape
}
#[must_use]
pub const fn square_count(&self) -> u32 {
self.inner.shape.square_count()
}
#[must_use]
pub const fn piece_count(&self) -> u32 {
self.inner
.board_pieces
.saturating_add(self.inner.hand_pieces)
}
#[must_use]
pub const fn board_piece_count(&self) -> u32 {
self.inner.board_pieces
}
#[must_use]
pub const fn hand_piece_count(&self) -> u32 {
self.inner.hand_pieces
}
pub(crate) const fn placement_field(&self) -> &'a [u8] {
self.inner.placement
}
pub(crate) const fn hands_field(&self) -> &'a [u8] {
self.inner.hands
}
#[must_use]
pub const fn active_side(&self) -> Side {
self.inner.active.side()
}
#[must_use]
pub const fn inactive_side(&self) -> Side {
self.inner.inactive.side()
}
#[must_use]
pub const fn active_style(&self) -> Style {
self.inner.active
}
#[must_use]
pub const fn inactive_style(&self) -> Style {
self.inner.inactive
}
#[must_use]
pub const fn first_style(&self) -> Style {
if self.inner.active.is_first() {
self.inner.active
} else {
self.inner.inactive
}
}
#[must_use]
pub const fn second_style(&self) -> Style {
if self.inner.active.is_second() {
self.inner.active
} else {
self.inner.inactive
}
}
#[must_use]
pub fn squares(&self) -> SquareIter<'a> {
SquareIter::new(self.inner.placement)
}
#[must_use]
pub fn first_hand(&self) -> HandIter<'a> {
HandIter::new(split_hands(self.inner.hands).0)
}
#[must_use]
pub fn second_hand(&self) -> HandIter<'a> {
HandIter::new(split_hands(self.inner.hands).1)
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn to_qi(&self) -> Qi<Piece, Style> {
let shape = self.shape();
let dim_sizes = shape.dimensions();
let mut dims = [0usize; MAX_DIMENSIONS];
for (slot, &size) in dims.iter_mut().zip(dim_sizes) {
*slot = usize::from(size);
}
let turn = match self.active_side() {
Side::First => Player::First,
Side::Second => Player::Second,
};
let placements = self
.squares()
.enumerate()
.filter_map(|(index, square)| square.map(|piece| (index, Some(piece))));
let first_hand = self.first_hand().map(|item| {
(
item.piece(),
i32::try_from(item.count()).unwrap_or(i32::MAX),
)
});
let second_hand = self.second_hand().map(|item| {
(
item.piece(),
i32::try_from(item.count()).unwrap_or(i32::MAX),
)
});
Qi::new(
&dims[..dim_sizes.len()],
self.first_style(),
self.second_style(),
)
.and_then(|qi| qi.board_diff(placements))
.and_then(|qi| qi.first_hand_diff(first_hand))
.and_then(|qi| qi.second_hand_diff(second_hand))
.map(|qi| qi.with_turn(turn))
.expect("a validated FEEN always yields a valid Qi position")
}
}
fn split_hands(hands: &[u8]) -> (&[u8], &[u8]) {
match hands.iter().position(|&b| b == b'/') {
Some(at) => (&hands[..at], &hands[at + 1..]),
None => (hands, &[]),
}
}
#[derive(Debug)]
pub struct SquareIter<'a> {
bytes: &'a [u8],
pos: usize,
empties: u32,
}
impl<'a> SquareIter<'a> {
pub(crate) const fn new(bytes: &'a [u8]) -> Self {
Self {
bytes,
pos: 0,
empties: 0,
}
}
}
impl Iterator for SquareIter<'_> {
type Item = Option<Piece>;
fn next(&mut self) -> Option<Self::Item> {
if self.empties > 0 {
self.empties -= 1;
return Some(None);
}
while self.pos < self.bytes.len() && self.bytes[self.pos] == b'/' {
self.pos += 1;
}
if self.pos >= self.bytes.len() {
return None;
}
if self.bytes[self.pos].is_ascii_digit() {
let mut value: u32 = 0;
while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_digit() {
value = value
.saturating_mul(10)
.saturating_add(u32::from(self.bytes[self.pos] - b'0'));
self.pos += 1;
}
self.empties = value.saturating_sub(1);
Some(None)
} else {
match epin_token(&self.bytes[self.pos..]) {
Some((len, id)) => {
self.pos += len;
Some(Some(id))
}
None => None,
}
}
}
}