use std::{
cmp::{Ord, Ordering, PartialOrd},
fmt,
};
use shakmaty::{Board, ByColor, ByRole, Piece, Role};
#[derive(Clone, Eq, PartialEq, Hash)]
pub(crate) struct MaterialSide {
by_role: ByRole<u8>,
}
impl MaterialSide {
fn empty() -> MaterialSide {
MaterialSide {
by_role: ByRole::default(),
}
}
fn from_str_part(s: &str) -> Result<MaterialSide, ()> {
let mut side = MaterialSide::empty();
for ch in s.as_bytes() {
let role = Role::from_char(char::from(*ch)).ok_or(())?;
*side.by_role.get_mut(role) += 1;
}
Ok(side)
}
pub(crate) fn count(&self) -> usize {
self.by_role.iter().map(|c| usize::from(*c)).sum()
}
pub(crate) fn has_pawns(&self) -> bool {
self.by_role.pawn > 0
}
fn unique_roles(&self) -> u8 {
self.by_role.iter().filter(|c| **c == 1).sum()
}
}
impl Ord for MaterialSide {
fn cmp(&self, other: &MaterialSide) -> Ordering {
self.count()
.cmp(&other.count())
.then(self.by_role.king.cmp(&other.by_role.king))
.then(self.by_role.queen.cmp(&other.by_role.queen))
.then(self.by_role.rook.cmp(&other.by_role.rook))
.then(self.by_role.bishop.cmp(&other.by_role.bishop))
.then(self.by_role.knight.cmp(&other.by_role.knight))
.then(self.by_role.pawn.cmp(&other.by_role.pawn))
}
}
impl PartialOrd for MaterialSide {
fn partial_cmp(&self, other: &MaterialSide) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for MaterialSide {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (role, count) in self.by_role.as_ref().zip_role().into_iter().rev() {
f.write_str(&role.upper_char().to_string().repeat(usize::from(*count)))?;
}
Ok(())
}
}
impl fmt::Debug for MaterialSide {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.count() > 0 {
<Self as fmt::Display>::fmt(self, f)
} else {
f.write_str("-")
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Material {
pub(crate) by_color: ByColor<MaterialSide>,
}
impl Material {
fn empty() -> Material {
Material {
by_color: ByColor::new_with(|_| MaterialSide::empty()),
}
}
pub fn from_board(board: &Board) -> Material {
Material {
by_color: ByColor::new_with(|color| MaterialSide {
by_role: board.material_side(color),
}),
}
}
pub(crate) fn from_iter<I>(iter: I) -> Material
where
I: IntoIterator<Item = Piece>,
{
let mut material = Material::empty();
for piece in iter {
*material
.by_color
.get_mut(piece.color)
.by_role
.get_mut(piece.role) += 1;
}
material
}
pub(crate) fn from_str(s: &str) -> Result<Material, ()> {
if s.len() > 64 + 1 {
return Err(());
}
let (white, black) = s.split_once('v').ok_or(())?;
Ok(Material {
by_color: ByColor {
white: MaterialSide::from_str_part(white)?,
black: MaterialSide::from_str_part(black)?,
},
})
}
pub(crate) fn count(&self) -> usize {
self.by_color.iter().map(|side| side.count()).sum()
}
pub(crate) fn is_symmetric(&self) -> bool {
self.by_color.white == self.by_color.black
}
pub(crate) fn has_pawns(&self) -> bool {
self.by_color.iter().any(|side| side.has_pawns())
}
pub(crate) fn unique_pieces(&self) -> u8 {
self.by_color.iter().map(|side| side.unique_roles()).sum()
}
pub(crate) fn min_like_man(&self) -> u8 {
self.by_color
.iter()
.flat_map(|side| side.by_role.iter())
.copied()
.filter(|c| 2 <= *c)
.min()
.unwrap_or(0)
}
pub(crate) fn into_flipped(self) -> Material {
Material {
by_color: self.by_color.into_flipped(),
}
}
pub(crate) fn into_normalized(self) -> Material {
Material {
by_color: self.by_color.into_normalized(),
}
}
}
impl fmt::Display for Material {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}v{}", self.by_color.white, self.by_color.black)
}
}