blunders_engine/boardrepr/
mailbox.rs

1//! A [mailbox](https://www.chessprogramming.org/Mailbox) is a square-centric
2//! representation of a chess board.
3//!
4//! A Mailbox is an array of size Files x Ranks where each index may contain a
5//! chess piece or be empty.
6
7use std::fmt::{self, Display};
8use std::ops::{Index, IndexMut};
9
10use crate::boardrepr::PieceSets;
11use crate::coretypes::{
12    Color, Piece, PieceKind, Square, SquareIndexable, NUM_FILES, NUM_RANKS, NUM_SQUARES,
13};
14
15/// Classic 8x8 square board representation of Chess board.
16/// Mailbox is Square-Centric, meaning it indexes by square to get a piece,
17/// as opposed to a PieceSets which indexes by piece to get squares.
18/// Index starts at A1.
19/// A1 = idx 0
20/// B1 = idx 1
21/// A2 = idx 8
22/// H7 = idx 63
23#[derive(Debug, Clone, Eq, PartialEq)]
24pub struct Mailbox {
25    board: [Option<Piece>; Self::SIZE],
26}
27
28impl Mailbox {
29    pub const FILES: usize = NUM_FILES;
30    pub const RANKS: usize = NUM_RANKS;
31    pub const SIZE: usize = NUM_SQUARES;
32
33    /// Creates an empty Mailbox, where all squares are None.
34    pub fn new() -> Self {
35        Mailbox {
36            board: [None; Mailbox::SIZE],
37        }
38    }
39
40    /// Create Mailbox with pieces arranged in starting chess position.
41    pub fn start_position() -> Self {
42        use Color::*;
43        use PieceKind::*;
44        use Square::*;
45        let mut mb = Self::new();
46
47        mb[A1] = Some(Piece::new(White, Rook));
48        mb[B1] = Some(Piece::new(White, Knight));
49        mb[C1] = Some(Piece::new(White, Bishop));
50        mb[D1] = Some(Piece::new(White, Queen));
51        mb[E1] = Some(Piece::new(White, King));
52        mb[F1] = Some(Piece::new(White, Bishop));
53        mb[G1] = Some(Piece::new(White, Knight));
54        mb[H1] = Some(Piece::new(White, Rook));
55        for square in [A2, B2, C2, D2, E2, F2, G2, H2] {
56            mb[square] = Some(Piece::new(White, Pawn));
57        }
58        mb[A8] = Some(Piece::new(Black, Rook));
59        mb[B8] = Some(Piece::new(Black, Knight));
60        mb[C8] = Some(Piece::new(Black, Bishop));
61        mb[D8] = Some(Piece::new(Black, Queen));
62        mb[E8] = Some(Piece::new(Black, King));
63        mb[F8] = Some(Piece::new(Black, Bishop));
64        mb[G8] = Some(Piece::new(Black, Knight));
65        mb[H8] = Some(Piece::new(Black, Rook));
66        for square in [A7, B7, C7, D7, E7, F7, G7, H7] {
67            mb[square] = Some(Piece::new(Black, Pawn));
68        }
69
70        mb
71    }
72
73    pub fn board(&self) -> &[Option<Piece>; Self::SIZE] {
74        &self.board
75    }
76
77    /// Returns pretty-printed chess board representation of Self.
78    /// The chess board has borders and file/rank indicators.
79    pub fn pretty(&self) -> String {
80        const RANK_SEP: &'static str = "+---+---+---+---+---+---+---+---+\n";
81        let mut pretty = String::with_capacity(626); // Measured in test.
82
83        pretty.push_str(RANK_SEP);
84        for rank in (0..Self::RANKS).rev() {
85            pretty.push_str("| ");
86
87            for file in 0..Self::FILES {
88                pretty.push(match self[rank * Self::RANKS + file] {
89                    Some(piece) => char::from(piece),
90                    None => ' ',
91                });
92                pretty.push_str(" | ");
93            }
94            pretty.push_str(&(rank + 1).to_string());
95            pretty.push('\n');
96            pretty.push_str(RANK_SEP);
97        }
98        pretty.push_str("  a   b   c   d   e   f   g   h\n");
99
100        pretty
101    }
102}
103
104/// Be careful with accessing with usize, as usize value out of bounds will panic.
105impl Index<usize> for Mailbox {
106    type Output = Option<Piece>;
107    fn index(&self, idx: usize) -> &Self::Output {
108        &self.board[idx]
109    }
110}
111
112impl IndexMut<usize> for Mailbox {
113    fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
114        &mut self.board[idx]
115    }
116}
117
118impl<I: SquareIndexable> Index<I> for Mailbox {
119    type Output = Option<Piece>;
120    fn index(&self, idx: I) -> &Self::Output {
121        &self.board[idx.idx()]
122    }
123}
124
125impl<I: SquareIndexable> IndexMut<I> for Mailbox {
126    fn index_mut(&mut self, idx: I) -> &mut Self::Output {
127        &mut self.board[idx.idx()]
128    }
129}
130
131impl From<&PieceSets> for Mailbox {
132    fn from(pieces: &PieceSets) -> Mailbox {
133        let mut mb = Mailbox::new();
134
135        for color in Color::iter() {
136            for piece_kind in PieceKind::iter() {
137                let piece = Piece::new(color, piece_kind);
138                pieces[&piece]
139                    .into_iter()
140                    .for_each(|square| mb[square] = Some(piece));
141            }
142        }
143        mb
144    }
145}
146
147/// Default value is that of a standard starting chess position.
148impl Default for Mailbox {
149    fn default() -> Self {
150        Mailbox::start_position()
151    }
152}
153
154impl Display for Mailbox {
155    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
156        write!(f, "{}", self.pretty())
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn display_start_position_mailbox() {
166        let mb = Mailbox::start_position();
167
168        println!("{}", mb.pretty().len());
169        println!("{}", mb);
170    }
171}