use super::Color;
use std::convert::TryFrom;
use std::fmt;
use std::str::FromStr;
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct PieceType(i8);
const _: () = {
assert!(core::mem::size_of::<PieceType>() == 1);
assert!(core::mem::align_of::<PieceType>() == 1);
};
impl PieceType {
pub const NONE: Self = Self(0);
pub const PAWN: Self = Self(1);
pub const LANCE: Self = Self(2);
pub const KNIGHT: Self = Self(3);
pub const SILVER: Self = Self(4);
pub const BISHOP: Self = Self(5);
pub const ROOK: Self = Self(6);
pub const GOLD: Self = Self(7);
pub const KING: Self = Self(8);
pub const PRO_PAWN: Self = Self(9);
pub const PRO_LANCE: Self = Self(10);
pub const PRO_KNIGHT: Self = Self(11);
pub const PRO_SILVER: Self = Self(12);
pub const HORSE: Self = Self(13);
pub const DRAGON: Self = Self(14);
pub const GOLD_LIKE: Self = Self(15);
pub const COUNT: usize = 16;
pub const PIECE_TYPE_PROMOTE: i8 = 8;
pub const PIECE_HAND_ZERO: Self = Self(1);
pub const HAND_TABLE_SIZE: usize = 8;
#[inline]
#[must_use]
pub const fn new(value: i8) -> Self {
Self(value)
}
#[must_use]
pub const fn is_promotable(self) -> bool {
self.0 >= Self::PAWN.0 && self.0 <= Self::ROOK.0
}
#[must_use]
pub const fn is_promoted(self) -> bool {
self.0 >= Self::PRO_PAWN.0
}
#[must_use]
pub const fn is_unpromotable(self) -> bool {
self.0 >= Self::GOLD.0
}
#[must_use]
pub const fn promote(self) -> Self {
if self.is_promotable() { Self(self.0 + Self::PIECE_TYPE_PROMOTE) } else { self }
}
#[must_use]
pub const fn demote(self) -> Self {
match self.0 {
9..=14 => Self(self.0 - Self::PIECE_TYPE_PROMOTE), _ => self, }
}
#[must_use]
pub fn is_valid(self) -> bool {
usize::try_from(self.0).map(|idx| idx < Self::COUNT).unwrap_or(false)
}
#[inline]
#[must_use]
pub const fn raw(self) -> i8 {
self.0
}
#[inline]
#[must_use]
#[allow(clippy::cast_sign_loss)]
pub const fn to_index(self) -> usize {
self.0 as usize
}
#[inline]
#[must_use]
pub const fn is_minor(self) -> bool {
const MINOR_MASK: u32 = (1 << 2)
| (1 << 3)
| (1 << 4)
| (1 << 7)
| (1 << 9)
| (1 << 10)
| (1 << 11)
| (1 << 12);
let idx = self.0 as u32;
idx < 32 && (MINOR_MASK >> idx) & 1 != 0
}
#[inline]
pub fn iter() -> impl DoubleEndedIterator<Item = Self> + ExactSizeIterator {
(0..15).map(Self::new)
}
#[inline]
#[must_use]
pub fn raw_pieces() -> impl DoubleEndedIterator<Item = Self> + ExactSizeIterator {
[
Self::PAWN,
Self::LANCE,
Self::KNIGHT,
Self::SILVER,
Self::BISHOP,
Self::ROOK,
Self::GOLD,
Self::KING,
]
.into_iter()
}
#[inline]
#[must_use]
pub fn hand_pieces() -> impl DoubleEndedIterator<Item = Self> + ExactSizeIterator {
[Self::PAWN, Self::LANCE, Self::KNIGHT, Self::SILVER, Self::BISHOP, Self::ROOK, Self::GOLD]
.into_iter()
}
}
impl Default for PieceType {
fn default() -> Self {
Self::NONE
}
}
impl fmt::Display for PieceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match *self {
Self::PAWN => "P",
Self::LANCE => "L",
Self::KNIGHT => "N",
Self::SILVER => "S",
Self::BISHOP => "B",
Self::ROOK => "R",
Self::GOLD => "G",
Self::KING => "K",
Self::PRO_PAWN => "+P",
Self::PRO_LANCE => "+L",
Self::PRO_KNIGHT => "+N",
Self::PRO_SILVER => "+S",
Self::HORSE => "+B",
Self::DRAGON => "+R",
_ => "?",
};
write!(f, "{s}")
}
}
impl FromStr for PieceType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"P" => Ok(Self::PAWN),
"L" => Ok(Self::LANCE),
"N" => Ok(Self::KNIGHT),
"S" => Ok(Self::SILVER),
"B" => Ok(Self::BISHOP),
"R" => Ok(Self::ROOK),
"G" => Ok(Self::GOLD),
"K" => Ok(Self::KING),
"+P" => Ok(Self::PRO_PAWN),
"+L" => Ok(Self::PRO_LANCE),
"+N" => Ok(Self::PRO_KNIGHT),
"+S" => Ok(Self::PRO_SILVER),
"+B" => Ok(Self::HORSE),
"+R" => Ok(Self::DRAGON),
_ => Err(format!("invalid piece type: {s}")),
}
}
}
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Hash)]
pub struct Piece(i8);
const _: () = {
assert!(core::mem::size_of::<Piece>() == 1);
assert!(core::mem::align_of::<Piece>() == 1);
};
impl Piece {
pub const NONE: Self = Self(0);
pub const B_PAWN: Self = Self(1);
pub const B_LANCE: Self = Self(2);
pub const B_KNIGHT: Self = Self(3);
pub const B_SILVER: Self = Self(4);
pub const B_BISHOP: Self = Self(5);
pub const B_ROOK: Self = Self(6);
pub const B_GOLD: Self = Self(7);
pub const B_KING: Self = Self(8);
pub const B_PRO_PAWN: Self = Self(9);
pub const B_PRO_LANCE: Self = Self(10);
pub const B_PRO_KNIGHT: Self = Self(11);
pub const B_PRO_SILVER: Self = Self(12);
pub const B_HORSE: Self = Self(13);
pub const B_DRAGON: Self = Self(14);
pub const B_GOLD_LIKE: Self = Self(15);
pub const W_PAWN: Self = Self(17);
pub const W_LANCE: Self = Self(18);
pub const W_KNIGHT: Self = Self(19);
pub const W_SILVER: Self = Self(20);
pub const W_BISHOP: Self = Self(21);
pub const W_ROOK: Self = Self(22);
pub const W_GOLD: Self = Self(23);
pub const W_KING: Self = Self(24);
pub const W_PRO_PAWN: Self = Self(25);
pub const W_PRO_LANCE: Self = Self(26);
pub const W_PRO_KNIGHT: Self = Self(27);
pub const W_PRO_SILVER: Self = Self(28);
pub const W_HORSE: Self = Self(29);
pub const W_DRAGON: Self = Self(30);
pub const W_GOLD_LIKE: Self = Self(31);
pub const COUNT: usize = 32;
pub const PROMOTE_OFFSET: i8 = 8;
pub const COLOR_BIT: i8 = 16;
pub const HAND_TABLE_SIZE: usize = 8;
pub const RAW_COUNT: usize = 8;
#[inline]
#[must_use]
pub const fn new(value: i8) -> Self {
Self(value)
}
#[must_use]
pub const fn from_parts(color: Color, piece_type: PieceType) -> Self {
let base = piece_type.0;
if color.raw() == Color::WHITE.raw() { Self(base | Self::COLOR_BIT) } else { Self(base) }
}
#[must_use]
pub const fn color(self) -> Color {
if (self.0 & Self::COLOR_BIT) != 0 { Color::WHITE } else { Color::BLACK }
}
#[must_use]
pub const fn piece_type(self) -> PieceType {
PieceType::new(self.0 & 15)
}
#[inline]
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
#[must_use]
pub const fn promote(self) -> Self {
let pt = self.piece_type();
if pt.is_promotable() { Self(self.0 + Self::PROMOTE_OFFSET) } else { self }
}
#[must_use]
pub const fn is_promoted(self) -> bool {
self.piece_type().is_promoted()
}
#[must_use]
pub const fn is_unpromotable(self) -> bool {
self.piece_type().is_unpromotable()
}
#[must_use]
pub const fn base_piece_type(self) -> PieceType {
PieceType::new(self.0 & 7)
}
#[must_use]
pub const fn unpromoted_piece(self) -> Self {
Self(self.0 & !Self::PROMOTE_OFFSET)
}
#[must_use]
pub const fn is_long_range(self) -> bool {
matches!(
self.piece_type(),
PieceType::LANCE
| PieceType::BISHOP
| PieceType::ROOK
| PieceType::HORSE
| PieceType::DRAGON
)
}
#[must_use]
pub fn is_valid(self) -> bool {
if self.0 == Self::NONE.0 {
return true;
}
let pt = self.piece_type();
pt.is_valid()
&& pt.raw() != 0
&& self.0 >= 0
&& usize::try_from(self.0).map(|idx| idx < Self::COUNT).unwrap_or(false)
}
#[inline]
#[must_use]
pub const fn raw(self) -> i8 {
self.0
}
#[inline]
#[must_use]
#[allow(clippy::cast_sign_loss)]
pub const fn to_index(self) -> usize {
self.0 as usize
}
}
impl fmt::Display for Piece {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if *self == Self::NONE {
return write!(f, " ");
}
let piece_text = self.piece_type().to_string();
let display = if self.color() == Color::WHITE {
piece_text.to_lowercase()
} else {
piece_text
};
write!(f, "{display}")
}
}
impl FromStr for Piece {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == " " {
return Ok(Self::NONE);
}
let (is_promoted, base_str) =
s.strip_prefix('+').map_or((false, s), |stripped| (true, stripped));
let is_white = base_str.chars().next().is_some_and(char::is_lowercase);
let color = if is_white { Color::WHITE } else { Color::BLACK };
let upper = base_str.to_uppercase();
let pt_str = if is_promoted { format!("+{upper}") } else { upper };
let piece_type = PieceType::from_str(&pt_str)?;
Ok(Self::from_parts(color, piece_type))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_piece_type_constants() {
assert_eq!(PieceType::NONE.raw(), 0);
assert_eq!(PieceType::PAWN.raw(), 1);
assert_eq!(PieceType::LANCE.raw(), 2);
assert_eq!(PieceType::KNIGHT.raw(), 3);
assert_eq!(PieceType::SILVER.raw(), 4);
assert_eq!(PieceType::BISHOP.raw(), 5);
assert_eq!(PieceType::ROOK.raw(), 6);
assert_eq!(PieceType::GOLD.raw(), 7);
assert_eq!(PieceType::KING.raw(), 8);
assert_eq!(PieceType::PRO_PAWN.raw(), 9);
assert_eq!(PieceType::PRO_LANCE.raw(), 10);
assert_eq!(PieceType::PRO_KNIGHT.raw(), 11);
assert_eq!(PieceType::PRO_SILVER.raw(), 12);
assert_eq!(PieceType::HORSE.raw(), 13);
assert_eq!(PieceType::DRAGON.raw(), 14);
assert_eq!(PieceType::PIECE_TYPE_PROMOTE, 8);
assert_eq!(PieceType::COUNT, 16);
assert_eq!(PieceType::PIECE_HAND_ZERO.raw(), 1);
assert_eq!(PieceType::HAND_TABLE_SIZE, 8);
}
#[test]
fn test_piece_type_promote() {
assert_eq!(PieceType::PAWN.promote(), PieceType::PRO_PAWN);
assert_eq!(PieceType::LANCE.promote(), PieceType::PRO_LANCE);
assert_eq!(PieceType::KNIGHT.promote(), PieceType::PRO_KNIGHT);
assert_eq!(PieceType::SILVER.promote(), PieceType::PRO_SILVER);
assert_eq!(PieceType::BISHOP.promote(), PieceType::HORSE);
assert_eq!(PieceType::ROOK.promote(), PieceType::DRAGON);
assert_eq!(PieceType::GOLD.promote(), PieceType::GOLD);
assert_eq!(PieceType::KING.promote(), PieceType::KING);
assert_eq!(PieceType::PRO_PAWN.promote(), PieceType::PRO_PAWN);
assert_eq!(PieceType::HORSE.promote(), PieceType::HORSE);
}
#[test]
fn test_piece_type_is_promotable() {
assert!(PieceType::PAWN.is_promotable());
assert!(PieceType::LANCE.is_promotable());
assert!(PieceType::KNIGHT.is_promotable());
assert!(PieceType::SILVER.is_promotable());
assert!(PieceType::BISHOP.is_promotable());
assert!(PieceType::ROOK.is_promotable());
assert!(!PieceType::GOLD.is_promotable());
assert!(!PieceType::KING.is_promotable());
assert!(!PieceType::PRO_PAWN.is_promotable());
assert!(!PieceType::HORSE.is_promotable());
}
#[test]
fn test_piece_type_is_ok() {
for i in 0..16 {
assert!(PieceType(i).is_valid());
}
assert!(!PieceType(-1).is_valid());
assert!(!PieceType(16).is_valid());
assert!(!PieceType(100).is_valid());
}
#[test]
fn test_piece_type_display_to_string() {
let cases = [
(PieceType::PAWN, "P"),
(PieceType::LANCE, "L"),
(PieceType::KNIGHT, "N"),
(PieceType::SILVER, "S"),
(PieceType::BISHOP, "B"),
(PieceType::ROOK, "R"),
(PieceType::GOLD, "G"),
(PieceType::KING, "K"),
(PieceType::PRO_PAWN, "+P"),
(PieceType::PRO_LANCE, "+L"),
(PieceType::PRO_KNIGHT, "+N"),
(PieceType::PRO_SILVER, "+S"),
(PieceType::HORSE, "+B"),
(PieceType::DRAGON, "+R"),
];
for (piece_type, expected) in cases {
assert_eq!(piece_type.to_string(), expected);
}
}
#[test]
fn test_piece_type_display_round_trip() {
let cases = [
PieceType::PAWN,
PieceType::LANCE,
PieceType::KNIGHT,
PieceType::SILVER,
PieceType::BISHOP,
PieceType::ROOK,
PieceType::GOLD,
PieceType::KING,
PieceType::PRO_PAWN,
PieceType::PRO_LANCE,
PieceType::PRO_KNIGHT,
PieceType::PRO_SILVER,
PieceType::HORSE,
PieceType::DRAGON,
];
for pt in cases {
let s = pt.to_string();
let parsed = PieceType::from_str(&s).unwrap();
assert_eq!(pt, parsed);
}
}
#[test]
fn test_piece_type_from_str_invalid() {
for invalid in ["X", "", "++P"] {
assert!(PieceType::from_str(invalid).is_err());
}
}
#[test]
fn test_piece_constants_black() {
assert_eq!(Piece::B_PAWN.raw(), 1);
assert_eq!(Piece::B_LANCE.raw(), 2);
assert_eq!(Piece::B_KNIGHT.raw(), 3);
assert_eq!(Piece::B_SILVER.raw(), 4);
assert_eq!(Piece::B_BISHOP.raw(), 5);
assert_eq!(Piece::B_ROOK.raw(), 6);
assert_eq!(Piece::B_GOLD.raw(), 7);
assert_eq!(Piece::B_KING.raw(), 8);
assert_eq!(Piece::B_PRO_PAWN.raw(), 9);
assert_eq!(Piece::B_PRO_LANCE.raw(), 10);
assert_eq!(Piece::B_PRO_KNIGHT.raw(), 11);
assert_eq!(Piece::B_PRO_SILVER.raw(), 12);
assert_eq!(Piece::B_HORSE.raw(), 13);
assert_eq!(Piece::B_DRAGON.raw(), 14);
}
#[test]
fn test_piece_constants_white() {
assert_eq!(Piece::W_PAWN.raw(), 17);
assert_eq!(Piece::W_LANCE.raw(), 18);
assert_eq!(Piece::W_KNIGHT.raw(), 19);
assert_eq!(Piece::W_SILVER.raw(), 20);
assert_eq!(Piece::W_BISHOP.raw(), 21);
assert_eq!(Piece::W_ROOK.raw(), 22);
assert_eq!(Piece::W_GOLD.raw(), 23);
assert_eq!(Piece::W_KING.raw(), 24);
assert_eq!(Piece::W_PRO_PAWN.raw(), 25);
assert_eq!(Piece::W_PRO_LANCE.raw(), 26);
assert_eq!(Piece::W_PRO_KNIGHT.raw(), 27);
assert_eq!(Piece::W_PRO_SILVER.raw(), 28);
assert_eq!(Piece::W_HORSE.raw(), 29);
assert_eq!(Piece::W_DRAGON.raw(), 30);
}
#[test]
fn test_piece_constant_flags() {
assert_eq!(Piece::NONE.raw(), 0);
assert_eq!(Piece::COLOR_BIT, 16);
assert_eq!(Piece::PROMOTE_OFFSET, 8);
assert_eq!(Piece::COUNT, 32);
}
#[test]
fn test_piece_make_and_decompose() {
for color in [Color::BLACK, Color::WHITE] {
for pt in [
PieceType::PAWN,
PieceType::LANCE,
PieceType::KNIGHT,
PieceType::SILVER,
PieceType::BISHOP,
PieceType::ROOK,
PieceType::GOLD,
PieceType::KING,
PieceType::PRO_PAWN,
PieceType::HORSE,
] {
let piece = Piece::from_parts(color, pt);
assert_eq!(piece.color(), color);
assert_eq!(piece.piece_type(), pt);
}
}
assert_eq!(Piece::from_parts(Color::BLACK, PieceType::PAWN), Piece::B_PAWN);
assert_eq!(Piece::from_parts(Color::WHITE, PieceType::PAWN), Piece::W_PAWN);
assert_eq!(Piece::from_parts(Color::BLACK, PieceType::KING), Piece::B_KING);
assert_eq!(Piece::from_parts(Color::WHITE, PieceType::KING), Piece::W_KING);
}
#[test]
fn test_piece_promote() {
assert_eq!(Piece::B_PAWN.promote(), Piece::B_PRO_PAWN);
assert_eq!(Piece::W_PAWN.promote(), Piece::W_PRO_PAWN);
assert_eq!(Piece::B_BISHOP.promote(), Piece::B_HORSE);
assert_eq!(Piece::W_ROOK.promote(), Piece::W_DRAGON);
assert_eq!(Piece::B_GOLD.promote(), Piece::B_GOLD);
assert_eq!(Piece::W_KING.promote(), Piece::W_KING);
assert_eq!(Piece::B_PRO_PAWN.promote(), Piece::B_PRO_PAWN);
assert_eq!(Piece::W_HORSE.promote(), Piece::W_HORSE);
}
#[test]
fn test_piece_is_ok() {
assert!(Piece::NONE.is_valid());
assert!(Piece::B_PAWN.is_valid());
assert!(Piece::W_KING.is_valid());
assert!(Piece::B_DRAGON.is_valid());
assert!(Piece::W_DRAGON.is_valid());
assert!(!Piece(-1).is_valid());
assert!(!Piece(32).is_valid()); assert!(!Piece(33).is_valid());
assert!(!Piece(100).is_valid());
assert!(!Piece(16).is_valid()); }
#[test]
fn test_piece_display_to_string() {
let cases = [
(Piece::NONE, " "),
(Piece::B_PAWN, "P"),
(Piece::B_LANCE, "L"),
(Piece::B_KNIGHT, "N"),
(Piece::B_SILVER, "S"),
(Piece::B_BISHOP, "B"),
(Piece::B_ROOK, "R"),
(Piece::B_GOLD, "G"),
(Piece::B_KING, "K"),
(Piece::B_PRO_PAWN, "+P"),
(Piece::B_HORSE, "+B"),
(Piece::B_DRAGON, "+R"),
(Piece::W_PAWN, "p"),
(Piece::W_LANCE, "l"),
(Piece::W_KNIGHT, "n"),
(Piece::W_SILVER, "s"),
(Piece::W_BISHOP, "b"),
(Piece::W_ROOK, "r"),
(Piece::W_GOLD, "g"),
(Piece::W_KING, "k"),
(Piece::W_PRO_PAWN, "+p"),
(Piece::W_HORSE, "+b"),
(Piece::W_DRAGON, "+r"),
];
for (piece, expected) in cases {
assert_eq!(piece.to_string(), expected);
}
}
#[test]
fn test_piece_display_round_trip() {
let cases = [
Piece::NONE,
Piece::B_PAWN,
Piece::W_PAWN,
Piece::B_KING,
Piece::W_KING,
Piece::B_DRAGON,
Piece::W_DRAGON,
];
for piece in cases {
let s = piece.to_string();
let parsed = Piece::from_str(&s).unwrap();
assert_eq!(piece, parsed);
}
}
#[test]
fn test_piece_display_from_str_invalid() {
for invalid in ["X", ""] {
assert!(Piece::from_str(invalid).is_err());
}
}
}