use core::num::NonZeroU8;
use crate::common::write_ascii_byte;
use crate::{Color, PieceKind, ToUsi};
#[repr(transparent)]
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub struct Piece(NonZeroU8);
#[repr(transparent)]
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub struct OptionPiece(u8);
impl Piece {
#[must_use]
pub const fn new(piece_kind: PieceKind, color: Color) -> Self {
let disc = piece_kind as u8;
let value = disc
+ match color {
Color::Black => 0,
Color::White => 16,
};
Piece(unsafe { NonZeroU8::new_unchecked(value) })
}
#[no_mangle]
pub extern "C" fn Piece_new(piece_kind: PieceKind, color: Color) -> Self {
Self::new(piece_kind, color)
}
#[must_use]
#[inline(always)]
pub fn to_parts(self) -> (PieceKind, Color) {
let data = self.0.get();
let disc = data & 15;
(
unsafe { PieceKind::from_u8_unchecked(disc) },
if data >= 16 {
Color::White
} else {
Color::Black
},
)
}
#[must_use]
#[export_name = "Piece_piece_kind"]
#[inline(always)]
pub extern "C" fn piece_kind(self) -> PieceKind {
self.to_parts().0
}
#[must_use]
#[export_name = "Piece_color"]
#[inline(always)]
pub extern "C" fn color(self) -> Color {
self.to_parts().1
}
#[must_use]
#[inline(always)]
pub fn as_u8(self) -> u8 {
self.0.get()
}
#[must_use]
pub fn promote(self) -> Option<Piece> {
let (piece_kind, color) = self.to_parts();
Some(Self::new(piece_kind.promote()?, color))
}
#[no_mangle]
#[inline(always)]
pub extern "C" fn Piece_promote(self) -> OptionPiece {
OptionPiece::from(self.promote())
}
#[must_use]
pub fn unpromote(self) -> Option<Piece> {
let (piece_kind, color) = self.to_parts();
Some(Self::new(piece_kind.unpromote()?, color))
}
#[no_mangle]
#[inline(always)]
pub extern "C" fn Piece_unpromote(self) -> OptionPiece {
OptionPiece::from(self.unpromote())
}
#[inline(always)]
pub(crate) unsafe fn from_u8_unchecked(value: u8) -> Self {
Self(NonZeroU8::new_unchecked(value))
}
#[cfg_attr(docsrs, doc(cfg(feature = "experimental")))]
#[cfg(feature = "experimental")]
#[inline]
pub const fn array_index(self) -> usize {
let result = self.0.get() as usize - 1;
if result >= Self::NUM {
unsafe { core::hint::unreachable_unchecked() };
}
result
}
#[cfg_attr(docsrs, doc(cfg(feature = "experimental")))]
#[cfg(feature = "experimental")]
pub const NUM: usize = 31;
pub fn all() -> [Self; 28] {
let mut result = [Self::new(PieceKind::Pawn, Color::Black); 28];
let piece_kinds = PieceKind::all();
let mut index = 0;
let colors = Color::all();
for &piece_kind in &piece_kinds {
for &color in &colors {
*unsafe { result.get_unchecked_mut(index) } = Piece::new(piece_kind, color);
index += 1;
}
}
result
}
}
impl_ord_for_single_field!(Piece);
impl_hash_for_single_field!(Piece);
impl From<Option<Piece>> for OptionPiece {
#[inline(always)]
fn from(arg: Option<Piece>) -> Self {
Self(match arg {
Some(result) => result.0.get(),
None => 0,
})
}
}
impl From<OptionPiece> for Option<Piece> {
#[inline(always)]
fn from(arg: OptionPiece) -> Self {
Some(Piece(NonZeroU8::new(arg.0)?))
}
}
impl_ord_for_single_field!(OptionPiece);
impl_hash_for_single_field!(OptionPiece);
impl ToUsi for Piece {
fn to_usi<W: core::fmt::Write>(&self, sink: &mut W) -> core::fmt::Result {
let (piece_kind, color) = self.to_parts();
match (piece_kind, color) {
(piece_kind, color) if piece_kind as u8 >= PieceKind::ProPawn as u8 => {
let table = b"+P+L+N+S+B+R+p+l+n+s+b+r";
let index = match color {
Color::Black => 0,
Color::White => 6,
};
let index = index + piece_kind as usize - PieceKind::ProPawn as usize;
debug_assert!(index < 12);
sink.write_str(unsafe {
core::str::from_utf8_unchecked(table.get_unchecked(2 * index..2 * index + 2))
})
}
(piece_kind, color) => {
debug_assert!(piece_kind as u8 <= PieceKind::King as u8);
let symbols = b"PLNSGBRKplnsgbrk";
let offset = match color {
Color::Black => 0,
Color::White => 8,
};
let c = *unsafe { symbols.get_unchecked(offset + piece_kind as usize - 1) };
unsafe { write_ascii_byte(sink, c) }
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn to_parts_works() {
let piece_kinds = PieceKind::all();
let colors = Color::all();
for &piece_kind in &piece_kinds {
for &color in &colors {
let piece = Piece::new(piece_kind, color);
let (piece_kind0, color0) = piece.to_parts();
assert_eq!(piece_kind0, piece_kind);
assert_eq!(color0, color);
}
}
}
fn to_usi_reference<W: core::fmt::Write>(this: &Piece, sink: &mut W) -> core::fmt::Result {
let (piece_kind, color) = this.to_parts();
match (piece_kind, color) {
(PieceKind::Pawn, Color::Black) => sink.write_char('P'),
(PieceKind::Pawn, Color::White) => sink.write_char('p'),
(PieceKind::Lance, Color::Black) => sink.write_char('L'),
(PieceKind::Lance, Color::White) => sink.write_char('l'),
(PieceKind::Knight, Color::Black) => sink.write_char('N'),
(PieceKind::Knight, Color::White) => sink.write_char('n'),
(PieceKind::Silver, Color::Black) => sink.write_char('S'),
(PieceKind::Silver, Color::White) => sink.write_char('s'),
(PieceKind::Gold, Color::Black) => sink.write_char('G'),
(PieceKind::Gold, Color::White) => sink.write_char('g'),
(PieceKind::Bishop, Color::Black) => sink.write_char('B'),
(PieceKind::Bishop, Color::White) => sink.write_char('b'),
(PieceKind::Rook, Color::Black) => sink.write_char('R'),
(PieceKind::Rook, Color::White) => sink.write_char('r'),
(PieceKind::King, Color::Black) => sink.write_char('K'),
(PieceKind::King, Color::White) => sink.write_char('k'),
(PieceKind::ProPawn, Color::Black) => sink.write_str("+P"),
(PieceKind::ProPawn, Color::White) => sink.write_str("+p"),
(PieceKind::ProLance, Color::Black) => sink.write_str("+L"),
(PieceKind::ProLance, Color::White) => sink.write_str("+l"),
(PieceKind::ProKnight, Color::Black) => sink.write_str("+N"),
(PieceKind::ProKnight, Color::White) => sink.write_str("+n"),
(PieceKind::ProSilver, Color::Black) => sink.write_str("+S"),
(PieceKind::ProSilver, Color::White) => sink.write_str("+s"),
(PieceKind::ProBishop, Color::Black) => sink.write_str("+B"),
(PieceKind::ProBishop, Color::White) => sink.write_str("+b"),
(PieceKind::ProRook, Color::Black) => sink.write_str("+R"),
(PieceKind::ProRook, Color::White) => sink.write_str("+r"),
}
}
#[test]
fn to_usi_works() {
for piece in Piece::all() {
let mut actual = String::new();
piece.to_usi(&mut actual).unwrap();
let mut expected = String::new();
to_usi_reference(&piece, &mut expected).unwrap();
assert_eq!(actual, expected);
}
}
}