game_player/state.rs
1//! Game State Module
2//!
3//! This module implements the state components and traits, providing the necessary interface for the game-specific state and
4//! logic.
5
6/// IDs of the players in a two-player game.
7///
8/// This enumeration defines the two possible players. The numeric values (0 and 1) can be used for array indexing and other
9/// performance-critical operations. These are provided for convenience and are not required to be used.
10///
11/// # Examples
12///
13/// ```rust
14/// # use game_player::PlayerId;
15/// let current_player = PlayerId::ALICE;
16/// let player_index = current_player as usize; // 0
17/// ```
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum PlayerId {
20 ALICE = 0,
21 BOB = 1,
22}
23
24impl PlayerId {
25 /// Returns the other player
26 ///
27 /// # Examples
28 ///
29 /// ```rust
30 /// # use game_player::PlayerId;
31 /// assert_eq!(PlayerId::ALICE.other(), PlayerId::BOB);
32 /// assert_eq!(PlayerId::BOB.other(), PlayerId::ALICE);
33 /// ```
34 pub fn other(self) -> Self {
35 match self {
36 PlayerId::ALICE => PlayerId::BOB,
37 PlayerId::BOB => PlayerId::ALICE,
38 }
39 }
40}
41
42/// A trait representing the state.
43///
44/// This trait defines the core interface that a game-specific state must implement.
45///
46/// # Core Concepts
47/// ## Fingerprinting
48/// A state must provide a unique fingerprint (hash) that can be used for:
49/// - Transposition tables in game tree search
50/// - Duplicate position detection
51/// - State caching and memoization
52///
53/// ## Turn Management
54/// The trait tracks which player should move next, enabling:
55/// - Alternating play enforcement
56/// - Player-specific evaluation functions
57/// - Turn-based game logic
58///
59/// ## State Transitions
60/// Game states can store references to expected responses, allowing:
61/// - Pre-computed move sequences
62/// - Principal variation storage
63/// - Game tree navigation
64///
65/// # Examples
66///
67/// ```rust
68/// # use game_player::{State, PlayerId};
69///
70/// #[derive(Clone, Default)]
71/// struct MyAction;
72///
73/// #[derive(Clone, Copy)]
74/// struct MyGameState {
75/// board: [u8; 64],
76/// current_player: PlayerId,
77/// game_over: bool,
78/// // other game-specific fields...
79/// }
80///
81/// impl State for MyGameState {
82/// type Action = MyAction;
83/// fn fingerprint(&self) -> u64 {
84/// // Generate unique hash for this position
85/// // Implementation depends on game specifics
86/// 42 // placeholder
87/// }
88///
89/// fn whose_turn(&self) -> u8 {
90/// self.current_player as u8
91/// }
92///
93/// fn is_terminal(&self) -> bool {
94/// self.game_over
95/// }
96///
97/// fn apply(&self, _action: &MyAction) -> Self {
98/// MyGameState {
99/// board: self.board,
100/// current_player: self.current_player.other(),
101/// game_over: false,
102/// }
103/// }
104/// }
105/// ```
106pub trait State: Sized {
107 /// The type representing actions/moves in this game
108 type Action: Clone;
109
110 /// Returns a unique fingerprint (hash) for this state.
111 ///
112 /// The fingerprint must be statistically unique across all possible game states to avoid hash collisions in transposition
113 /// tables and state caches. Identical game positions must always produce identical fingerprints.
114 ///
115 /// # Implementation Requirements
116 /// - **Deterministic**: Same position always produces same fingerprint
117 /// - **Collision-resistant**: Different positions should produce different and uncorrelated fingerprints
118 /// - **Fast**: Called frequently during game tree search
119 /// - **Position-dependent**: Only depends on the current state and independent of move history.
120 ///
121 /// # Returns
122 /// A 64-bit unsigned integer representing the unique fingerprint
123 ///
124 /// # Examples
125 ///
126 /// ```rust
127 /// # use game_player::{State, PlayerId};
128 /// # #[derive(Clone, Default)]
129 /// # struct MyAction;
130 /// # #[derive(Clone, Copy)]
131 /// # struct MyGameState { current_player: PlayerId }
132 /// # impl State for MyGameState {
133 /// # type Action = MyAction;
134 /// # fn fingerprint(&self) -> u64 { 42 }
135 /// # fn whose_turn(&self) -> u8 { self.current_player as u8 }
136 /// # fn is_terminal(&self) -> bool { false }
137 /// # fn apply(&self, _action: &Self::Action) -> Self { *self }
138 /// # }
139 /// # fn create_initial_state() -> MyGameState { MyGameState { current_player: PlayerId::ALICE } }
140 /// let state = create_initial_state();
141 /// let fingerprint = state.fingerprint();
142 ///
143 /// // Same position should produce same fingerprint
144 /// let same_state = create_initial_state();
145 /// assert_eq!(fingerprint, same_state.fingerprint());
146 /// ```
147 fn fingerprint(&self) -> u64;
148
149 /// Returns the ID of the player whose turn it is to move.
150 ///
151 /// # Returns
152 /// The id of the player of the player who should move next
153 ///
154 /// # Examples
155 /// ```rust
156 /// # use game_player::{State, PlayerId};
157 /// # #[derive(Clone, Default)]
158 /// # struct MyAction;
159 /// # #[derive(Clone, Copy)]
160 /// # struct MyGameState { current_player: PlayerId }
161 /// # impl State for MyGameState {
162 /// # type Action = MyAction;
163 /// # fn fingerprint(&self) -> u64 { 42 }
164 /// # fn whose_turn(&self) -> u8 { self.current_player as u8 }
165 /// # fn is_terminal(&self) -> bool { false }
166 /// # fn apply(&self, _action: &Self::Action) -> Self { *self }
167 /// # }
168 /// let state = MyGameState { current_player: PlayerId::ALICE };
169 /// match state.whose_turn() {
170 /// 0 => println!("Alice to move"), // PlayerId::ALICE as u8
171 /// 1 => println!("Bob to move"), // PlayerId::BOB as u8
172 /// _ => unreachable!(),
173 /// }
174 /// ```
175 fn whose_turn(&self) -> u8;
176
177 /// Checks if the game cannot continue.
178 ///
179 /// # Returns
180 /// `true` if the game cannot continue, `false` otherwise.
181 ///
182 /// # Examples
183 /// ```rust
184 /// # use game_player::{State, PlayerId};
185 /// # #[derive(Clone, Default)]
186 /// # struct MyAction;
187 /// # #[derive(Clone, Copy)]
188 /// # struct MyGameState { game_is_over: bool }
189 /// # impl State for MyGameState {
190 /// # type Action = MyAction;
191 /// # fn fingerprint(&self) -> u64 { 42 }
192 /// # fn whose_turn(&self) -> u8 { 0 }
193 /// # fn is_terminal(&self) -> bool { self.game_is_over }
194 /// # fn apply(&self, _action: &Self::Action) -> Self { *self }
195 /// # }
196 /// let state = MyGameState { game_is_over: true };
197 /// assert!(state.is_terminal());
198 /// ```
199 fn is_terminal(&self) -> bool;
200
201 /// Applies an action to the current state, returning a new state as a result of the action.
202 ///
203 /// This method creates a new state by applying the given action to the current state.
204 /// The original state remains unchanged (immutable transformation).
205 ///
206 /// # Arguments
207 /// * `action` - The action to apply to the current state
208 ///
209 /// # Returns
210 /// A new state representing the position after applying the action
211 ///
212 /// # Examples
213 /// ```rust
214 /// # use game_player::{State, PlayerId};
215 /// #
216 /// # #[derive(Debug, Clone, Default)]
217 /// # struct MyAction { move_type: String }
218 /// #
219 /// # struct MyGameState {
220 /// # current_player: PlayerId,
221 /// # move_count: u32,
222 /// # game_over: bool
223 /// # }
224 /// #
225 /// # impl State for MyGameState {
226 /// # type Action = MyAction;
227 /// # fn fingerprint(&self) -> u64 {
228 /// # (self.current_player as u64) << 32 | self.move_count as u64
229 /// # }
230 /// # fn whose_turn(&self) -> u8 { self.current_player as u8 }
231 /// # fn is_terminal(&self) -> bool { self.game_over }
232 /// # fn apply(&self, action: &Self::Action) -> Self {
233 /// # MyGameState {
234 /// # current_player: self.current_player.other(),
235 /// # move_count: self.move_count + 1,
236 /// # game_over: self.move_count >= 10,
237 /// # }
238 /// # }
239 /// # }
240 ///
241 /// let initial_state = MyGameState {
242 /// current_player: PlayerId::ALICE,
243 /// move_count: 0,
244 /// game_over: false
245 /// };
246 /// let action = MyAction { move_type: "play_tile".to_string() };
247 ///
248 /// let new_state = initial_state.apply(&action);
249 ///
250 /// // State should be updated
251 /// assert_eq!(new_state.whose_turn(), PlayerId::BOB as u8);
252 /// assert_ne!(new_state.fingerprint(), initial_state.fingerprint());
253 ///
254 /// // Original state unchanged
255 /// assert_eq!(initial_state.whose_turn(), PlayerId::ALICE as u8);
256 /// ```
257 fn apply(&self, action: &Self::Action) -> Self;
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn test_player_id_other() {
266 assert_eq!(PlayerId::ALICE.other(), PlayerId::BOB);
267 assert_eq!(PlayerId::BOB.other(), PlayerId::ALICE);
268 }
269
270 #[test]
271 fn test_player_id_values() {
272 assert_eq!(PlayerId::ALICE as u8, 0);
273 assert_eq!(PlayerId::BOB as u8, 1);
274 }
275}