Skip to main content

chessnut_move/protocol/
position.rs

1// Copyright 2026 Daymon Littrell-Reyes
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//   http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Canonical chess positions and piece identities.
16
17#[cfg(doc)]
18use crate::protocol;
19use crate::protocol::{SQUARE_COUNT, Square};
20
21/// Number of packed bytes used to encode all 64 board squares.
22pub const BOARD_STATE_LENGTH: usize = 32;
23
24/// Piece occupancy for all 64 squares.
25///
26/// The internal array uses canonical A1-to-H8 indexing. Use
27/// [`Position::piece_at`] and [`Position::set_piece`] when the square is known
28/// by file and rank.
29///
30/// Realtime board updates carry positions through
31/// [`BoardEvent::PositionChanged`][protocol::BoardEvent::PositionChanged].
32///
33/// # Examples
34///
35/// ```
36/// use chessnut_move::protocol::{
37///     Color, File, Piece, PieceKind, Position, Rank, SQUARE_COUNT, Square,
38/// };
39///
40/// let mut position = Position::new([None; SQUARE_COUNT]);
41/// let e4 = Square::new(File::E, Rank::Four);
42/// position.set_piece(
43///     e4,
44///     Some(Piece {
45///         color: Color::White,
46///         kind: PieceKind::Pawn,
47///     }),
48/// );
49/// assert!(position.piece_at(e4).is_some());
50/// ```
51#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
52pub struct Position {
53  squares: [Option<Piece>; SQUARE_COUNT],
54}
55
56impl Position {
57  /// Creates a position from squares in canonical A1-to-H8 order.
58  ///
59  /// Index zero represents A1 and index 63 represents H8.
60  ///
61  /// # Examples
62  ///
63  /// ```
64  /// use chessnut_move::protocol::{
65  ///     Color, File, Piece, PieceKind, Position, Rank, SQUARE_COUNT, Square,
66  /// };
67  ///
68  /// let white_king = Piece {
69  ///     color: Color::White,
70  ///     kind: PieceKind::King,
71  /// };
72  /// let e1 = Square::new(File::E, Rank::One);
73  /// let mut squares = [None; SQUARE_COUNT];
74  /// squares[e1.index()] = Some(white_king);
75  ///
76  /// let position = Position::new(squares);
77  /// assert_eq!(position.piece_at(e1), Some(white_king));
78  /// ```
79  pub fn new(squares: [Option<Piece>; SQUARE_COUNT]) -> Self {
80    Self { squares }
81  }
82
83  /// Returns the piece occupying a square, or `None` when it is empty.
84  ///
85  /// # Examples
86  ///
87  /// ```
88  /// use chessnut_move::protocol::{
89  ///     BoardEvent, File, PieceKind, Rank, Square,
90  /// };
91  ///
92  /// fn pawn_reached_e4(event: BoardEvent) -> bool {
93  ///     let BoardEvent::PositionChanged(position) = event else {
94  ///         return false;
95  ///     };
96  ///
97  ///     position
98  ///         .piece_at(Square::new(File::E, Rank::Four))
99  ///         .is_some_and(|piece| piece.kind == PieceKind::Pawn)
100  /// }
101  /// # let _ = pawn_reached_e4;
102  /// ```
103  pub const fn piece_at(&self, square: Square) -> Option<Piece> {
104    self.squares[square.index()]
105  }
106
107  /// Replaces the piece occupying a square.
108  ///
109  /// Pass `None` to empty the square.
110  ///
111  /// # Examples
112  ///
113  /// ```
114  /// use chessnut_move::protocol::{
115  ///     AutoMoveMode, Command, File, Position, Rank, Square,
116  /// };
117  ///
118  /// fn move_e2_to_e4(mut observed: Position) -> Command {
119  ///     let e2 = Square::new(File::E, Rank::Two);
120  ///     let e4 = Square::new(File::E, Rank::Four);
121  ///     let pawn = observed.piece_at(e2);
122  ///
123  ///     observed.set_piece(e2, None);
124  ///     observed.set_piece(e4, pawn);
125  ///     Command::auto_move(observed, AutoMoveMode::Normal)
126  /// }
127  /// # let _ = move_e2_to_e4;
128  /// ```
129  pub fn set_piece(&mut self, square: Square, piece: Option<Piece>) {
130    self.squares[square.index()] = piece;
131  }
132
133  /// Returns canonical square storage for private wire encoders.
134  pub(crate) const fn squares(&self) -> &[Option<Piece>; SQUARE_COUNT] {
135    &self.squares
136  }
137}
138
139/// A chess piece described by its [color] and [kind].
140///
141/// # Examples
142///
143/// ```
144/// use chessnut_move::protocol::{Color, Piece, PieceKind};
145///
146/// let queen = Piece {
147///     color: Color::Black,
148///     kind: PieceKind::Queen,
149/// };
150/// ```
151///
152/// [color]: Color
153/// [kind]: PieceKind
154#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
155pub struct Piece {
156  /// Side to which the piece belongs.
157  pub color: Color,
158
159  /// Chess role of the piece.
160  pub kind: PieceKind,
161}
162
163/// Side to which a chess piece belongs.
164#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
165pub enum Color {
166  /// White side.
167  White,
168
169  /// Black side.
170  Black,
171}
172
173/// Chess role of a piece.
174#[derive(PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Debug, Hash)]
175pub enum PieceKind {
176  /// The Pawn piece on a Chess board.
177  Pawn,
178
179  /// The Knight piece on a Chess board.
180  Knight,
181
182  /// The Bishop piece on a Chess board.
183  Bishop,
184
185  /// The Rook piece on a Chess board.
186  Rook,
187
188  /// The Queen piece on a Chess board.
189  Queen,
190
191  /// The King piece on a Chess board.
192  King,
193}
194
195#[cfg(test)]
196mod tests {
197  use super::*;
198  use crate::protocol::{File, Rank};
199
200  #[test]
201  fn pieces_are_accessed_by_square() {
202    let square = Square::new(File::E, Rank::Four);
203    let piece = Piece {
204      color: Color::White,
205      kind: PieceKind::Pawn,
206    };
207    let mut position = Position::new([None; SQUARE_COUNT]);
208
209    position.set_piece(square, Some(piece));
210
211    assert_eq!(position.piece_at(square), Some(piece));
212  }
213}