Skip to main content

atomic_movegen/
types.rs

1use std::fmt;
2use std::ops;
3
4/// A square on a chessboard indexed `A1` (0) through `H8` (63), plus `NONE`.
5///
6/// Layout: `A1` = 0, `B1` = 1, …, `H1` = 7, `A2` = 8, …, `H8` = 63.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
8#[repr(u8)]
9pub enum Square {
10    A1,
11    B1,
12    C1,
13    D1,
14    E1,
15    F1,
16    G1,
17    H1,
18    A2,
19    B2,
20    C2,
21    D2,
22    E2,
23    F2,
24    G2,
25    H2,
26    A3,
27    B3,
28    C3,
29    D3,
30    E3,
31    F3,
32    G3,
33    H3,
34    A4,
35    B4,
36    C4,
37    D4,
38    E4,
39    F4,
40    G4,
41    H4,
42    A5,
43    B5,
44    C5,
45    D5,
46    E5,
47    F5,
48    G5,
49    H5,
50    A6,
51    B6,
52    C6,
53    D6,
54    E6,
55    F6,
56    G6,
57    H6,
58    A7,
59    B7,
60    C7,
61    D7,
62    E7,
63    F7,
64    G7,
65    H7,
66    A8,
67    B8,
68    C8,
69    D8,
70    E8,
71    F8,
72    G8,
73    H8,
74    NONE,
75}
76
77pub const SQ_A1: Square = Square::A1;
78pub const SQ_B1: Square = Square::B1;
79pub const SQ_C1: Square = Square::C1;
80pub const SQ_D1: Square = Square::D1;
81pub const SQ_E1: Square = Square::E1;
82pub const SQ_F1: Square = Square::F1;
83pub const SQ_G1: Square = Square::G1;
84pub const SQ_H1: Square = Square::H1;
85pub const SQ_A2: Square = Square::A2;
86pub const SQ_B2: Square = Square::B2;
87pub const SQ_C2: Square = Square::C2;
88pub const SQ_D2: Square = Square::D2;
89pub const SQ_E2: Square = Square::E2;
90pub const SQ_F2: Square = Square::F2;
91pub const SQ_G2: Square = Square::G2;
92pub const SQ_H2: Square = Square::H2;
93pub const SQ_A3: Square = Square::A3;
94pub const SQ_B3: Square = Square::B3;
95pub const SQ_C3: Square = Square::C3;
96pub const SQ_D3: Square = Square::D3;
97pub const SQ_E3: Square = Square::E3;
98pub const SQ_F3: Square = Square::F3;
99pub const SQ_G3: Square = Square::G3;
100pub const SQ_H3: Square = Square::H3;
101pub const SQ_A4: Square = Square::A4;
102pub const SQ_B4: Square = Square::B4;
103pub const SQ_C4: Square = Square::C4;
104pub const SQ_D4: Square = Square::D4;
105pub const SQ_E4: Square = Square::E4;
106pub const SQ_F4: Square = Square::F4;
107pub const SQ_G4: Square = Square::G4;
108pub const SQ_H4: Square = Square::H4;
109pub const SQ_A5: Square = Square::A5;
110pub const SQ_B5: Square = Square::B5;
111pub const SQ_C5: Square = Square::C5;
112pub const SQ_D5: Square = Square::D5;
113pub const SQ_E5: Square = Square::E5;
114pub const SQ_F5: Square = Square::F5;
115pub const SQ_G5: Square = Square::G5;
116pub const SQ_H5: Square = Square::H5;
117pub const SQ_A6: Square = Square::A6;
118pub const SQ_B6: Square = Square::B6;
119pub const SQ_C6: Square = Square::C6;
120pub const SQ_D6: Square = Square::D6;
121pub const SQ_E6: Square = Square::E6;
122pub const SQ_F6: Square = Square::F6;
123pub const SQ_G6: Square = Square::G6;
124pub const SQ_H6: Square = Square::H6;
125pub const SQ_A7: Square = Square::A7;
126pub const SQ_B7: Square = Square::B7;
127pub const SQ_C7: Square = Square::C7;
128pub const SQ_D7: Square = Square::D7;
129pub const SQ_E7: Square = Square::E7;
130pub const SQ_F7: Square = Square::F7;
131pub const SQ_G7: Square = Square::G7;
132pub const SQ_H7: Square = Square::H7;
133pub const SQ_A8: Square = Square::A8;
134pub const SQ_B8: Square = Square::B8;
135pub const SQ_C8: Square = Square::C8;
136pub const SQ_D8: Square = Square::D8;
137pub const SQ_E8: Square = Square::E8;
138pub const SQ_F8: Square = Square::F8;
139pub const SQ_G8: Square = Square::G8;
140pub const SQ_H8: Square = Square::H8;
141/// Number of squares on a chessboard.
142pub const SQUARE_NB: usize = 64;
143/// Number of files on a chessboard.
144pub const FILE_NB: usize = 8;
145/// Number of ranks on a chessboard.
146pub const RANK_NB: usize = 8;
147
148// Consolidation point for Square-by-index lookup.
149pub(crate) const SQUARES: [Square; 64] = [
150    Square::A1,
151    Square::B1,
152    Square::C1,
153    Square::D1,
154    Square::E1,
155    Square::F1,
156    Square::G1,
157    Square::H1,
158    Square::A2,
159    Square::B2,
160    Square::C2,
161    Square::D2,
162    Square::E2,
163    Square::F2,
164    Square::G2,
165    Square::H2,
166    Square::A3,
167    Square::B3,
168    Square::C3,
169    Square::D3,
170    Square::E3,
171    Square::F3,
172    Square::G3,
173    Square::H3,
174    Square::A4,
175    Square::B4,
176    Square::C4,
177    Square::D4,
178    Square::E4,
179    Square::F4,
180    Square::G4,
181    Square::H4,
182    Square::A5,
183    Square::B5,
184    Square::C5,
185    Square::D5,
186    Square::E5,
187    Square::F5,
188    Square::G5,
189    Square::H5,
190    Square::A6,
191    Square::B6,
192    Square::C6,
193    Square::D6,
194    Square::E6,
195    Square::F6,
196    Square::G6,
197    Square::H6,
198    Square::A7,
199    Square::B7,
200    Square::C7,
201    Square::D7,
202    Square::E7,
203    Square::F7,
204    Square::G7,
205    Square::H7,
206    Square::A8,
207    Square::B8,
208    Square::C8,
209    Square::D8,
210    Square::E8,
211    Square::F8,
212    Square::G8,
213    Square::H8,
214];
215
216/// A file (column) on a chessboard.
217#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
218#[repr(u8)]
219#[non_exhaustive]
220pub enum File {
221    A,
222    B,
223    C,
224    D,
225    E,
226    F,
227    G,
228    H,
229}
230
231impl File {
232    pub const NB: usize = 8;
233}
234
235/// A rank (row) on a chessboard.
236#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
237#[repr(u8)]
238#[non_exhaustive]
239pub enum Rank {
240    R1,
241    R2,
242    R3,
243    R4,
244    R5,
245    R6,
246    R7,
247    R8,
248}
249
250impl Rank {
251    pub const NB: usize = 8;
252}
253
254/// Return the file of a square.
255pub fn file_of(s: Square) -> File {
256    static FILES: [File; 8] = [
257        File::A,
258        File::B,
259        File::C,
260        File::D,
261        File::E,
262        File::F,
263        File::G,
264        File::H,
265    ];
266    FILES[(s as u8 & 7) as usize]
267}
268
269/// Return the rank of a square.
270pub fn rank_of(s: Square) -> Rank {
271    static RANKS: [Rank; 8] = [
272        Rank::R1,
273        Rank::R2,
274        Rank::R3,
275        Rank::R4,
276        Rank::R5,
277        Rank::R6,
278        Rank::R7,
279        Rank::R8,
280    ];
281    RANKS[((s as u8 >> 3) & 7) as usize]
282}
283
284/// Construct a square from a file and rank.
285pub fn make_square(f: File, r: Rank) -> Square {
286    let idx = (r as usize) * 8 + (f as usize);
287    SQUARES[idx]
288}
289
290/// A set of squares represented as a 64-bit bitboard.
291///
292/// Bit `n` corresponds to [`Square`] with discriminant `n`. Supports standard
293/// bitwise operators (`&`, `|`, `^`, `!`, `<<`, `>>`) as well as set-wise
294/// operations with [`Square`] values.
295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296pub struct Bitboard(pub u64);
297
298impl Bitboard {
299    /// The empty bitboard (no squares set).
300    pub const EMPTY: Bitboard = Bitboard(0);
301
302    /// Returns `true` if no squares are set.
303    #[must_use]
304    pub fn is_empty(self) -> bool {
305        self.0 == 0
306    }
307
308    /// Return the number of squares set in the bitboard.
309    #[must_use]
310    pub fn count(self) -> u32 {
311        self.0.count_ones()
312    }
313
314    /// Return the least-significant (lowest-index) set square.
315    ///
316    /// # Panics
317    /// Panics in debug mode if the bitboard is empty.
318    #[must_use]
319    pub fn lsb(self) -> Square {
320        debug_assert!(!self.is_empty());
321        let idx = self.0.trailing_zeros() as u8;
322        // SAFETY: trailing_zeros() returns 0..63 when self is non-empty.
323        // All discriminants 0..63 are valid Square values.
324        unsafe { std::mem::transmute(idx) }
325    }
326
327    /// Extract and remove the least-significant set square.
328    pub fn pop_lsb(&mut self) -> Square {
329        let sq = self.lsb();
330        self.0 &= self.0 - 1;
331        sq
332    }
333
334    /// Returns `true` if more than one square is set.
335    #[must_use]
336    pub fn more_than_one(self) -> bool {
337        self.0 & (self.0 - 1) != 0
338    }
339
340    /// Return a bitboard with only the given square set.
341    /// Returns [`EMPTY`](Self::EMPTY) for [`Square::NONE`].
342    pub fn square_bb(sq: Square) -> Bitboard {
343        if sq == Square::NONE {
344            return Bitboard::EMPTY;
345        }
346        Bitboard(1u64 << (sq as u8))
347    }
348}
349
350impl ops::BitAnd for Bitboard {
351    type Output = Bitboard;
352    fn bitand(self, rhs: Bitboard) -> Bitboard {
353        Bitboard(self.0 & rhs.0)
354    }
355}
356
357impl ops::BitOr for Bitboard {
358    type Output = Bitboard;
359    fn bitor(self, rhs: Bitboard) -> Bitboard {
360        Bitboard(self.0 | rhs.0)
361    }
362}
363
364impl ops::BitXor for Bitboard {
365    type Output = Bitboard;
366    fn bitxor(self, rhs: Bitboard) -> Bitboard {
367        Bitboard(self.0 ^ rhs.0)
368    }
369}
370
371impl ops::Not for Bitboard {
372    type Output = Bitboard;
373    fn not(self) -> Bitboard {
374        Bitboard(!self.0)
375    }
376}
377
378impl ops::Shl<usize> for Bitboard {
379    type Output = Bitboard;
380    fn shl(self, rhs: usize) -> Bitboard {
381        Bitboard(self.0 << rhs)
382    }
383}
384
385impl ops::Shr<usize> for Bitboard {
386    type Output = Bitboard;
387    fn shr(self, rhs: usize) -> Bitboard {
388        Bitboard(self.0 >> rhs)
389    }
390}
391
392impl ops::BitAnd<Square> for Bitboard {
393    type Output = Bitboard;
394    fn bitand(self, rhs: Square) -> Bitboard {
395        self & Bitboard::square_bb(rhs)
396    }
397}
398
399impl ops::BitOr<Square> for Bitboard {
400    type Output = Bitboard;
401    fn bitor(self, rhs: Square) -> Bitboard {
402        self | Bitboard::square_bb(rhs)
403    }
404}
405
406impl ops::BitXor<Square> for Bitboard {
407    type Output = Bitboard;
408    fn bitxor(self, rhs: Square) -> Bitboard {
409        self ^ Bitboard::square_bb(rhs)
410    }
411}
412
413impl ops::Sub<Square> for Bitboard {
414    type Output = Bitboard;
415    fn sub(self, rhs: Square) -> Bitboard {
416        self & !Bitboard::square_bb(rhs)
417    }
418}
419
420impl ops::BitAnd<Square> for Square {
421    type Output = Bitboard;
422    fn bitand(self, rhs: Square) -> Bitboard {
423        Bitboard::square_bb(self) & rhs
424    }
425}
426
427impl ops::BitOr<Square> for Square {
428    type Output = Bitboard;
429    fn bitor(self, rhs: Square) -> Bitboard {
430        Bitboard::square_bb(self) | rhs
431    }
432}
433
434/// A side in a chess game.
435#[derive(Debug, Clone, Copy, PartialEq, Eq)]
436#[non_exhaustive]
437pub enum Color {
438    White = 0,
439    Black = 1,
440}
441
442impl Color {
443    pub const NB: usize = 2;
444
445    /// Return the opposite color.
446    pub fn flip(self) -> Color {
447        match self {
448            Color::White => Color::Black,
449            Color::Black => Color::White,
450        }
451    }
452}
453
454/// A piece type (pawn, knight, bishop, rook, queen, or commoner).
455///
456/// In atomic chess, a *commoner* moves like a king and is pseudo-royal:
457/// losing all commoners loses the game.
458#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
459#[repr(u8)]
460#[non_exhaustive]
461pub enum PieceType {
462    Pawn = 0,
463    Knight = 1,
464    Bishop = 2,
465    Rook = 3,
466    Queen = 4,
467    Commoner = 5,
468}
469
470impl PieceType {
471    pub const NB: usize = 6;
472}
473
474/// A colored piece, packed into a single byte.
475///
476/// Encoding: `(color << 3) | (type + 1)` so that `0` can represent
477/// [`NO_PIECE`].
478#[derive(Debug, Clone, Copy, PartialEq, Eq)]
479pub struct Piece(u8);
480
481impl Piece {
482    // Encode as (color << 3) | (pt as u8 + 1) to avoid NO_PIECE = 0 conflict
483    pub const fn from_parts(color: Color, pt: PieceType) -> Piece {
484        Piece(((color as u8) << 3) | ((pt as u8) + 1))
485    }
486
487    /// Return the color of this piece.
488    #[must_use]
489    pub fn color(self) -> Color {
490        if self.0 & 8 == 0 {
491            Color::White
492        } else {
493            Color::Black
494        }
495    }
496
497    /// Return the piece type.
498    #[must_use]
499    pub fn type_of(self) -> PieceType {
500        let inner = (self.0 & 7) - 1;
501        debug_assert!(
502            inner < 6,
503            "Piece::type_of called with invalid Piece encoding: inner={}",
504            inner
505        );
506        // SAFETY: inner is 0..5 after masking and decrementing a valid Piece.
507        // All discriminants 0..5 are valid PieceType values.
508        unsafe { std::mem::transmute(inner) }
509    }
510
511    /// Return the ASCII character for this piece.
512    ///
513    /// Upper-case for white (`P`, `N`, `B`, `R`, `Q`, `C`), lower-case for black.
514    #[must_use]
515    pub fn ascii_char(self) -> char {
516        let t = match self.type_of() {
517            PieceType::Pawn => 'P',
518            PieceType::Knight => 'N',
519            PieceType::Bishop => 'B',
520            PieceType::Rook => 'R',
521            PieceType::Queen => 'Q',
522            PieceType::Commoner => 'C',
523        };
524        match self.color() {
525            Color::White => t,
526            Color::Black => t.to_ascii_lowercase(),
527        }
528    }
529}
530
531impl fmt::Display for Piece {
532    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
533        if self.0 == 0 {
534            return write!(f, "--");
535        }
536        let c = match self.color() {
537            Color::White => "W",
538            Color::Black => "B",
539        };
540        write!(f, "{}{}", c, self.ascii_char().to_ascii_uppercase())
541    }
542}
543
544pub const NO_PIECE: Piece = Piece(0);
545pub const W_PAWN: Piece = Piece::from_parts(Color::White, PieceType::Pawn);
546pub const W_KNIGHT: Piece = Piece::from_parts(Color::White, PieceType::Knight);
547pub const W_BISHOP: Piece = Piece::from_parts(Color::White, PieceType::Bishop);
548pub const W_ROOK: Piece = Piece::from_parts(Color::White, PieceType::Rook);
549pub const W_QUEEN: Piece = Piece::from_parts(Color::White, PieceType::Queen);
550pub const W_COMMONER: Piece = Piece::from_parts(Color::White, PieceType::Commoner);
551pub const B_PAWN: Piece = Piece::from_parts(Color::Black, PieceType::Pawn);
552pub const B_KNIGHT: Piece = Piece::from_parts(Color::Black, PieceType::Knight);
553pub const B_BISHOP: Piece = Piece::from_parts(Color::Black, PieceType::Bishop);
554pub const B_ROOK: Piece = Piece::from_parts(Color::Black, PieceType::Rook);
555pub const B_QUEEN: Piece = Piece::from_parts(Color::Black, PieceType::Queen);
556pub const B_COMMONER: Piece = Piece::from_parts(Color::Black, PieceType::Commoner);
557
558/// Construct a [`Piece`] from a color and piece type.
559pub fn make_piece(color: Color, pt: PieceType) -> Piece {
560    Piece::from_parts(color, pt)
561}
562
563/// The type of a chess move (normal, promotion, en-passant, castling).
564#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
565#[non_exhaustive]
566pub enum MoveType {
567    Normal = 0,
568    Promotion = 1,
569    EnPassant = 2,
570    Castling = 3,
571}
572
573/// A chess move packed into a 16-bit integer.
574///
575/// Bit layout (0-indexed LSB): `to_sq:6 | from_sq:6 | type:2 | promotion_type:2`.
576/// - `promotion_type` is valid only when `type == Promotion`.
577#[derive(Debug, Clone, Copy, PartialEq, Eq)]
578pub struct Move(u16);
579
580impl Move {
581    /// A sentinel value representing "no move".
582    pub const NONE: Move = Move(0);
583    /// A sentinel null move (from_sq=1, to_sq=1).
584    pub const NULL: Move = Move(1 + (1 << 6));
585
586    /// Return the origin square of this move.
587    #[must_use]
588    pub fn from_sq(self) -> Square {
589        let idx = ((self.0 >> 6) & 0x3f) as u8;
590        // SAFETY: (self.0 >> 6) & 0x3f extracts a 6-bit field (0..63).
591        // All discriminants 0..63 are valid Square values.
592        unsafe { std::mem::transmute(idx) }
593    }
594
595    /// Return the destination square of this move.
596    #[must_use]
597    pub fn to_sq(self) -> Square {
598        let idx = (self.0 & 0x3f) as u8;
599        // SAFETY: self.0 & 0x3f extracts a 6-bit field (0..63).
600        // All discriminants 0..63 are valid Square values.
601        unsafe { std::mem::transmute(idx) }
602    }
603
604    /// Return the move type (normal, promotion, en-passant, castling).
605    #[must_use]
606    pub fn move_type(self) -> MoveType {
607        match (self.0 >> 12) & 3 {
608            0 => MoveType::Normal,
609            1 => MoveType::Promotion,
610            2 => MoveType::EnPassant,
611            _ => MoveType::Castling,
612        }
613    }
614
615    /// Return the promotion piece type (valid only for promotion moves).
616    #[must_use]
617    pub fn promotion_type(self) -> PieceType {
618        static TYPES: [PieceType; 4] = [
619            PieceType::Knight,
620            PieceType::Bishop,
621            PieceType::Rook,
622            PieceType::Queen,
623        ];
624        TYPES[((self.0 >> 14) & 3) as usize]
625    }
626
627    /// Construct a normal (non-promotion) move.
628    pub fn make_move(from: Square, to: Square) -> Move {
629        Move(((from as u16) << 6) | (to as u16))
630    }
631
632    /// Construct a promotion move. `pt` should be a non-pawn piece type.
633    pub fn make_promotion(from: Square, to: Square, pt: PieceType) -> Move {
634        let pt_bits = match pt {
635            PieceType::Knight => 0u16,
636            PieceType::Bishop => 1u16,
637            PieceType::Rook => 2u16,
638            PieceType::Queen => 3u16,
639            _ => 0u16,
640        };
641        Move((pt_bits << 14) | (1 << 12) | ((from as u16) << 6) | (to as u16))
642    }
643
644    /// Construct an en-passant capture move.
645    pub fn make_enpassant(from: Square, to: Square) -> Move {
646        Move((2 << 12) | ((from as u16) << 6) | (to as u16))
647    }
648
649    /// Construct a castling move.
650    pub fn make_castling(from: Square, to: Square) -> Move {
651        Move((3 << 12) | ((from as u16) << 6) | (to as u16))
652    }
653}
654
655/// A direction on the chessboard (used for stepping computations).
656#[derive(Debug, Clone, Copy, PartialEq, Eq)]
657pub enum Direction {
658    North = 8,
659    East = 1,
660    South = -8,
661    West = -1,
662    NorthEast = 9,
663    NorthWest = 7,
664    SouthEast = -7,
665    SouthWest = -9,
666}
667
668impl ops::Add<Direction> for Square {
669    type Output = Square;
670    fn add(self, rhs: Direction) -> Square {
671        let idx = (self as i16) + (rhs as i16);
672        if (0..64).contains(&idx) {
673            SQUARES[idx as usize]
674        } else {
675            Square::NONE
676        }
677    }
678}
679
680impl ops::Sub<Direction> for Square {
681    type Output = Square;
682    fn sub(self, rhs: Direction) -> Square {
683        let idx = (self as i16) - (rhs as i16);
684        if (0..64).contains(&idx) {
685            SQUARES[idx as usize]
686        } else {
687            Square::NONE
688        }
689    }
690}
691
692/// Maximum number of moves that can be generated for any atomic chess position.
693///
694/// The absolute upper bound is well below 256:
695/// - At most 64 squares with attackers
696/// - Perft at depth 1 on the most complex legal positions yields < 150 moves
697/// - 256 provides a comfortable safety margin.
698pub const MAX_MOVES: usize = 256;
699
700/// A fixed-capacity, stack-allocated list of `Move` values.
701///
702/// This is a drop-in replacement for `Vec<Move>` in move-generation hot paths.
703/// It avoids heap allocation, eliminates dynamic capacity checks, and improves
704/// cache locality by keeping the entire move list on the stack.
705#[derive(Debug, Clone)]
706pub struct MoveList {
707    moves: [Move; MAX_MOVES],
708    len: usize,
709}
710
711impl MoveList {
712    /// Creates an empty `MoveList`.
713    #[inline]
714    pub fn new() -> Self {
715        MoveList {
716            moves: [Move::NONE; MAX_MOVES],
717            len: 0,
718        }
719    }
720
721    /// Returns the number of moves currently stored.
722    #[inline]
723    pub fn len(&self) -> usize {
724        self.len
725    }
726
727    /// Returns `true` if the list contains zero moves.
728    #[inline]
729    pub fn is_empty(&self) -> bool {
730        self.len == 0
731    }
732
733    /// Appends a move to the end of the list.
734    ///
735    /// # Panics
736    /// Panics in debug mode if the list is full (`len == MAX_MOVES`).
737    /// In release mode, exceeding the capacity silently overwrites (unreachable
738    /// in practice due to the move-count bound).
739    #[inline]
740    pub fn push(&mut self, m: Move) {
741        debug_assert!(self.len < MAX_MOVES, "MoveList overflow");
742        // Safe: unreachable in practice due to move-count bound.
743        if self.len < MAX_MOVES {
744            self.moves[self.len] = m;
745            self.len += 1;
746        }
747    }
748
749    /// Sets the length directly (caller must ensure `len <= MAX_MOVES` and
750    /// that elements beyond `len` are unused).
751    #[inline]
752    pub(crate) fn set_len(&mut self, len: usize) {
753        debug_assert!(len <= MAX_MOVES, "MoveList::set_len overflow");
754        self.len = len;
755    }
756
757    /// Returns the stored moves as a slice.
758    #[inline]
759    pub fn as_slice(&self) -> &[Move] {
760        &self.moves[..self.len]
761    }
762
763    /// Returns the stored moves as a mutable slice (for sorting, etc.).
764    #[inline]
765    pub fn as_mut_slice(&mut self) -> &mut [Move] {
766        let len = self.len;
767        &mut self.moves[..len]
768    }
769}
770
771/// Convert a `Square` to its algebraic notation string (e.g. `Square::E2` -> `"e2"`).
772///
773/// This is a convenience helper exposed for the crate's own example binaries;
774/// downstream consumers should prefer formatting squares via their own display
775/// logic. Not covered by semantic versioning guarantees.
776#[doc(hidden)]
777pub fn sq_str(sq: Square) -> String {
778    let files = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
779    let idx = sq as usize;
780    format!("{}{}", files[idx % 8], (idx / 8 + 1))
781}
782
783/// Parse a square in algebraic notation (e.g. `"e2"`) into a `Square`.
784///
785/// This is a convenience helper exposed for the crate's own example binaries;
786/// downstream consumers should prefer more robust parsers. Not covered by
787/// semantic versioning guarantees.
788#[doc(hidden)]
789pub fn parse_sq(s: &str) -> Square {
790    if s.len() < 2 {
791        return Square::A1;
792    }
793    let file = match s.chars().next().unwrap() {
794        'a' => 0,
795        'b' => 1,
796        'c' => 2,
797        'd' => 3,
798        'e' => 4,
799        'f' => 5,
800        'g' => 6,
801        'h' => 7,
802        _ => 0,
803    };
804    let rank = match s.chars().nth(1).unwrap() {
805        '1' => Rank::R1,
806        '2' => Rank::R2,
807        '3' => Rank::R3,
808        '4' => Rank::R4,
809        '5' => Rank::R5,
810        '6' => Rank::R6,
811        '7' => Rank::R7,
812        '8' => Rank::R8,
813        _ => Rank::R1,
814    };
815    make_square(
816        match file {
817            0 => File::A,
818            1 => File::B,
819            2 => File::C,
820            3 => File::D,
821            4 => File::E,
822            5 => File::F,
823            6 => File::G,
824            7 => File::H,
825            _ => unreachable!(),
826        },
827        rank,
828    )
829}
830
831impl Default for MoveList {
832    fn default() -> Self {
833        Self::new()
834    }
835}
836
837/// Iteration over `&MoveList` yields `Move` by value (cheap copy).
838impl<'a> IntoIterator for &'a MoveList {
839    type Item = Move;
840    type IntoIter = MoveListIter<'a>;
841
842    fn into_iter(self) -> MoveListIter<'a> {
843        MoveListIter {
844            iter: self.as_slice().iter(),
845        }
846    }
847}
848
849/// An iterator over the moves in a [`MoveList`].
850///
851/// Obtained via [`IntoIterator`] on `&MoveList`. Yields [`Move`] by value.
852pub struct MoveListIter<'a> {
853    iter: std::slice::Iter<'a, Move>,
854}
855
856impl<'a> Iterator for MoveListIter<'a> {
857    type Item = Move;
858
859    #[inline]
860    fn next(&mut self) -> Option<Move> {
861        self.iter.next().copied()
862    }
863
864    #[inline]
865    fn size_hint(&self) -> (usize, Option<usize>) {
866        self.iter.size_hint()
867    }
868}
869
870impl<'a> ExactSizeIterator for MoveListIter<'a> {}
871
872impl ops::Index<usize> for MoveList {
873    type Output = Move;
874
875    #[inline]
876    fn index(&self, index: usize) -> &Move {
877        &self.moves[index]
878    }
879}
880
881impl ops::IndexMut<usize> for MoveList {
882    #[inline]
883    fn index_mut(&mut self, index: usize) -> &mut Move {
884        &mut self.moves[index]
885    }
886}
887
888#[cfg(test)]
889mod tests {
890    use super::*;
891
892    #[test]
893    fn test_square_conversion() {
894        let sq = make_square(File::A, Rank::R1);
895        assert_eq!(sq, Square::A1);
896        assert_eq!(file_of(sq), File::A);
897        assert_eq!(rank_of(sq), Rank::R1);
898
899        let sq = make_square(File::H, Rank::R8);
900        assert_eq!(sq, Square::H8);
901        assert_eq!(file_of(sq), File::H);
902        assert_eq!(rank_of(sq), Rank::R8);
903    }
904
905    #[test]
906    fn test_bitboard_ops() {
907        let b1 = Bitboard(0xFF);
908        let b2 = Bitboard(0x0F);
909        assert_eq!((b1 & b2).0, 0x0F);
910        assert_eq!((b1 | b2).0, 0xFF);
911        assert_eq!((!b1).0, !0xFFu64);
912        assert_eq!(Bitboard::square_bb(Square::A1).0, 1);
913        assert_eq!(Bitboard::square_bb(Square::H8).0, 1u64 << 63);
914    }
915
916    #[test]
917    fn test_move_encoding() {
918        let m = Move::make_move(Square::A2, Square::A4);
919        assert_eq!(m.from_sq(), Square::A2);
920        assert_eq!(m.to_sq(), Square::A4);
921        assert_eq!(m.move_type(), MoveType::Normal);
922
923        let m = Move::make_enpassant(Square::C5, Square::D6);
924        assert_eq!(m.move_type(), MoveType::EnPassant);
925
926        let m = Move::make_castling(Square::E1, Square::H1);
927        assert_eq!(m.move_type(), MoveType::Castling);
928
929        let m = Move::make_promotion(Square::A7, Square::A8, PieceType::Queen);
930        assert_eq!(m.move_type(), MoveType::Promotion);
931        assert_eq!(m.promotion_type(), PieceType::Queen);
932    }
933
934    #[test]
935    fn test_piece() {
936        let wp = make_piece(Color::White, PieceType::Pawn);
937        assert_eq!(wp.color(), Color::White);
938        assert_eq!(wp.type_of(), PieceType::Pawn);
939
940        let bk = make_piece(Color::Black, PieceType::Commoner);
941        assert_eq!(bk.color(), Color::Black);
942        assert_eq!(bk.type_of(), PieceType::Commoner);
943    }
944}