haitaka_types/
piece.rs

1//! [`Piece`] representation
2use crate::*;
3
4#[cfg(not(feature = "std"))]
5extern crate alloc;
6
7#[cfg(not(feature = "std"))]
8use alloc::string::String;
9
10#[cfg(feature = "std")]
11use std::string::String;
12
13use core::fmt::*;
14
15crate::helpers::simple_enum! {
16    /// Shogi piece types.
17    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
18    pub enum Piece {
19        /// A pawn.
20        Pawn,
21        /// A lance.
22        Lance,
23        /// A knight.
24        Knight,
25        /// A silver general.
26        Silver,
27        /// A bishop.
28        Bishop,
29        /// A rook.
30        Rook,
31        /// A gold general.
32        Gold,
33        /// A king.
34        King,
35        /// Tokin (promoted pawn)
36        Tokin,
37        /// Promoted lance.
38        PLance,
39        /// Promoted knight.
40        PKnight,
41        /// Promoted silver.
42        PSilver,
43        /// Promoted bishop.
44        PBishop,
45        /// Promoted rook.
46        PRook
47    }
48}
49
50// The piece representation in SFEN strings requires either one or
51// two chars. Black/Black pieces are indicated by uppercase letters;
52// White/White pieces by lowerase. Promoted pieces are indicated by
53// a '+' prefix.
54
55crate::helpers::simple_error! {
56    /// The value was not a valid [`Piece`].
57    pub struct PieceParseError = "The value is not a valid Piece.";
58}
59
60impl Piece {
61    /// Number of simple, unpromoted piece types other than King.
62    pub const HAND_NUM: usize = 7;
63
64    /// Max number of pieces for a piece type to have in hand
65    pub const MAX_HAND: [u8; Self::NUM] = [
66        18, // Pawn
67        4,  // Lance
68        4,  // Knight
69        4,  // Silver
70        2,  // Bishop
71        2,  // Rook
72        4,  // Gold
73        0, 0, 0, 0, 0, 0, 0,
74    ];
75
76    // piece -> promoted piece (promoted pieces map to themselves)
77    const PROMOTED: [Self; Self::NUM] = [
78        Piece::Tokin,
79        Piece::PLance,
80        Piece::PKnight,
81        Piece::PSilver,
82        Piece::PBishop,
83        Piece::PRook,
84        Piece::Gold,
85        Piece::King,
86        Piece::Tokin,
87        Piece::PLance,
88        Piece::PKnight,
89        Piece::PSilver,
90        Piece::PBishop,
91        Piece::PRook,
92    ];
93
94    // promoted piece -> piece (unpromoted pieces map to themselves)
95    const UNPROMOTED: [Self; Self::NUM] = [
96        Piece::Pawn,
97        Piece::Lance,
98        Piece::Knight,
99        Piece::Silver,
100        Piece::Bishop,
101        Piece::Rook,
102        Piece::Gold,
103        Piece::King,
104        Piece::Pawn,
105        Piece::Lance,
106        Piece::Knight,
107        Piece::Silver,
108        Piece::Bishop,
109        Piece::Rook,
110    ];
111
112    /// Is this piece a promoted piece?
113    #[inline(always)]
114    pub const fn is_promoted(self) -> bool {
115        (self as usize) >= Self::Tokin as usize
116    }
117
118    /// Is this piece a non-promoted piece?
119    #[inline(always)]
120    pub const fn is_unpromoted(self) -> bool {
121        (self as usize) < Self::Tokin as usize
122    }
123
124    /// Can this piece ever promote?
125    #[inline(always)]
126    pub const fn is_promotable(self) -> bool {
127        (self as usize) < Self::Gold as usize
128    }
129
130    /// Can this piece with given color promote on the given square?
131    ///
132    /// # Examples
133    ///
134    /// ```
135    /// use haitaka_types::*;
136    /// assert!(Piece::Pawn.can_promote(Color::Black, Square::C1));
137    /// assert!(Piece::Pawn.can_promote(Color::White, Square::G1));
138    /// assert!(! Piece::Pawn.can_promote(Color::Black, Square::H7));
139    /// assert!(! Piece::Pawn.can_promote(Color::White, Square::C3));
140    /// ```
141    #[inline(always)]
142    pub const fn can_promote(self, color: Color, square: Square) -> bool {
143        if !self.is_promotable() {
144            false
145        } else {
146            let rank = square.rank() as usize;
147            match color {
148                Color::White => rank > 5,
149                Color::Black => rank < 3,
150            }
151        }
152    }
153
154    /// Must this piece with given color promote on the given square?
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use haitaka_types::*;
160    /// assert!(Piece::Pawn.must_promote(Color::Black, Square::A3));
161    /// assert!(Piece::Lance.must_promote(Color::Black, Square::A3));
162    /// assert!(Piece::Knight.must_promote(Color::Black, Square::A3));
163    ///
164    /// assert!(Piece::Pawn.must_promote(Color::White, Square::I3));
165    /// assert!(Piece::Lance.must_promote(Color::White, Square::I3));
166    /// assert!(Piece::Knight.must_promote(Color::White, Square::I3));
167    ///
168    /// assert!(!Piece::Pawn.must_promote(Color::Black, Square::B3));
169    /// assert!(!Piece::Lance.must_promote(Color::Black, Square::B3));
170    /// assert!(Piece::Knight.must_promote(Color::Black, Square::B3));
171    ///
172    /// assert!(!Piece::Pawn.must_promote(Color::White, Square::H3));
173    /// assert!(!Piece::Lance.must_promote(Color::White, Square::H3));
174    /// assert!(Piece::Knight.must_promote(Color::White, Square::H3));
175    /// ```
176    #[inline(always)]
177    pub const fn must_promote(self, color: Color, square: Square) -> bool {
178        let rank = square.rank() as usize;
179        if 1 < rank && rank < 7 {
180            return false;
181        }
182        match color {
183            Color::White => match self {
184                Self::Pawn | Self::Lance => rank == 8,
185                Self::Knight => rank >= 7,
186                _ => false,
187            },
188            Color::Black => match self {
189                Self::Pawn | Self::Lance => rank == 0,
190                Self::Knight => rank <= 1,
191                _ => false,
192            },
193        }
194    }
195
196    /// Can this piece with given color be dropped on the given square?
197    /// If the piece must promote on the square, it can not be dropped there.
198    ///
199    /// This function does not perform any occupancy check.
200    /// It assumes the given square is empty.
201    ///
202    #[inline(always)]
203    pub const fn can_drop(self, color: Color, square: Square) -> bool {
204        !self.must_promote(color, square)
205    }
206
207    /// Promote this piece.
208    ///
209    /// Never panics. If the piece cannot be promoted, it the same piece is returned.
210    #[inline(always)]
211    pub const fn promote(self) -> Self {
212        Self::PROMOTED[self as usize]
213    }
214
215    /// Unpromote this piece.
216    ///
217    /// Never panics. If the piece is an unpromoted piece, the same piece is returned.
218    #[inline(always)]
219    pub const fn unpromote(self) -> Self {
220        Self::UNPROMOTED[self as usize]
221    }
222
223    pub fn try_from_char(c: char) -> Option<(Self, Color)> {
224        match c {
225            'p' => Some((Self::Pawn, Color::White)),
226            'l' => Some((Self::Lance, Color::White)),
227            'n' => Some((Self::Knight, Color::White)),
228            's' => Some((Self::Silver, Color::White)),
229            'g' => Some((Self::Gold, Color::White)),
230            'r' => Some((Self::Rook, Color::White)),
231            'b' => Some((Self::Bishop, Color::White)),
232            'k' => Some((Self::King, Color::White)),
233            'P' => Some((Self::Pawn, Color::Black)),
234            'L' => Some((Self::Lance, Color::Black)),
235            'N' => Some((Self::Knight, Color::Black)),
236            'S' => Some((Self::Silver, Color::Black)),
237            'G' => Some((Self::Gold, Color::Black)),
238            'R' => Some((Self::Rook, Color::Black)),
239            'B' => Some((Self::Bishop, Color::Black)),
240            'K' => Some((Self::King, Color::Black)),
241            _ => None,
242        }
243    }
244
245    pub fn try_from_str(s: &str) -> Option<(Self, Color)> {
246        let mut chars = s.chars();
247        let first = chars.next()?; // Get the first character
248        let second = chars.next(); // Get the second character, if any
249
250        match (first, second) {
251            // Promoted pieces (e.g., "+p", "+l")
252            ('+', Some(c)) => match c {
253                'p' => Some((Self::Tokin, Color::White)),
254                'l' => Some((Self::PLance, Color::White)),
255                'n' => Some((Self::PKnight, Color::White)),
256                's' => Some((Self::PSilver, Color::White)),
257                'b' => Some((Self::PBishop, Color::White)),
258                'r' => Some((Self::PRook, Color::White)),
259                'P' => Some((Self::Tokin, Color::Black)),
260                'L' => Some((Self::PLance, Color::Black)),
261                'N' => Some((Self::PKnight, Color::Black)),
262                'S' => Some((Self::PSilver, Color::Black)),
263                'B' => Some((Self::PBishop, Color::Black)),
264                'R' => Some((Self::PRook, Color::Black)),
265                _ => None,
266            },
267            // Unpromoted pieces (e.g., "p", "l")
268            (c, None) => match c {
269                'p' => Some((Self::Pawn, Color::White)),
270                'l' => Some((Self::Lance, Color::White)),
271                'n' => Some((Self::Knight, Color::White)),
272                's' => Some((Self::Silver, Color::White)),
273                'g' => Some((Self::Gold, Color::White)),
274                'r' => Some((Self::Rook, Color::White)),
275                'b' => Some((Self::Bishop, Color::White)),
276                'k' => Some((Self::King, Color::White)),
277                'P' => Some((Self::Pawn, Color::Black)),
278                'L' => Some((Self::Lance, Color::Black)),
279                'N' => Some((Self::Knight, Color::Black)),
280                'S' => Some((Self::Silver, Color::Black)),
281                'G' => Some((Self::Gold, Color::Black)),
282                'R' => Some((Self::Rook, Color::Black)),
283                'B' => Some((Self::Bishop, Color::Black)),
284                'K' => Some((Self::King, Color::Black)),
285                _ => None,
286            },
287            // Invalid input
288            _ => None,
289        }
290    }
291
292    pub fn to_str(self, color: Color) -> String {
293        let s: &str = match self {
294            Self::Pawn => "p",
295            Self::Lance => "l",
296            Self::Knight => "n",
297            Self::Silver => "s",
298            Self::Bishop => "b",
299            Self::Rook => "r",
300            Self::Gold => "g",
301            Self::King => "k",
302            Self::Tokin => "+p",
303            Self::PLance => "+l",
304            Self::PKnight => "+n",
305            Self::PSilver => "+s",
306            Self::PBishop => "+b",
307            Self::PRook => "+r",
308        };
309
310        if color == Color::Black {
311            s.to_uppercase()
312        } else {
313            String::from(s)
314        }
315    }
316}
317
318impl core::str::FromStr for Piece {
319    type Err = PieceParseError;
320
321    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
322        Piece::try_from_str(s)
323            .map(|(piece, _color)| piece) // Extract the `Piece` from the tuple
324            .ok_or(PieceParseError) // Convert `Option` to `Result`
325    }
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
329pub struct ColoredPiece {
330    pub piece: Piece,
331    pub color: Color,
332}
333
334impl core::str::FromStr for ColoredPiece {
335    type Err = PieceParseError;
336
337    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
338        Piece::try_from_str(s)
339            .map(|(piece, color)| ColoredPiece { piece, color })
340            .ok_or(PieceParseError)
341    }
342}
343
344impl core::fmt::Display for ColoredPiece {
345    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
346        write!(f, "{}", self.piece.to_str(self.color))
347    }
348}