nimlib/
game.rs

1//! The primary game structs are in this module;  
2//! For game logic, see [crate::nimbers].
3
4use std::{
5    fmt::{Debug, Display},
6    ops::BitXor,
7};
8
9use serde::{Deserialize, Serialize};
10
11use crate::nimbers;
12
13/// # A Nim game
14///
15/// This struct uses [NimRule]s to calculate the nimber of the position.
16#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
17pub struct NimGame {
18    /// The rules of the game (e.g. which numbers of coins can be taken)
19    pub(crate) rules: Vec<NimRule>,
20
21    /// The stacks of the game, represented as their current heights
22    pub(crate) stacks: Vec<Stack>,
23
24    /// The number of coins in the pool of player A  
25    /// (ignored for now)
26    pub(crate) coins_a: u64,
27
28    /// The number of coins in the pool of player B  
29    /// (ignored for now)
30    pub(crate) coins_b: u64,
31}
32
33impl NimGame {
34    /// Get the stacks currently in the game
35    ///
36    /// Retrieves the position as a shared reference to vector of [Stack]s.
37    pub fn get_stacks(&self) -> &Vec<Stack> {
38        &self.stacks
39    }
40}
41
42impl Default for NimGame {
43    fn default() -> Self {
44        Self {
45            rules: vec![NimRule {
46                take: TakeSize::List(vec![1, 2, 3]),
47                split: Split::Never,
48            }],
49            stacks: vec![Stack(10)],
50            coins_a: 0,
51            coins_b: 0,
52        }
53    }
54}
55
56impl NimGame {
57    /// Create a new Nim game with the given rules and stacks
58    ///
59    /// # Examples
60    ///
61    /// ```
62    /// use nimlib::{NimGame, NimRule, Split, Stack, TakeSize};
63    ///
64    /// let simple_rules: Vec<NimRule> = vec![NimRule {
65    ///     take: TakeSize::List(vec![1, 2, 3]),
66    ///     split: Split::Never,
67    /// }];
68    ///
69    /// let stacks: Vec<Stack> = vec![Stack(10)];
70    ///
71    /// let game = NimGame::new(simple_rules, stacks);
72    /// ```
73    pub fn new(rules: Vec<NimRule>, stacks: Vec<Stack>) -> Self {
74        // TODO allow pool coins to be set
75
76        Self {
77            rules,
78            stacks,
79            ..Default::default()
80        }
81    }
82
83    /// Calculate the nimber of the position using the MEX & XOR rules
84    pub fn calculate_nimber(&self) -> Nimber {
85        // FIXME handle pool coins
86
87        self.stacks.iter().fold(Nimber(0), |nimber, stack| {
88            nimber ^ stack.calculate_nimber(&self.rules, 0)
89        })
90    }
91}
92
93/// Represents a stack of coins; specifically its height.  
94/// Simply wraps a [u64].
95#[repr(transparent)]
96#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
97pub struct Stack(pub u64);
98
99impl Stack {
100    /// Calculate the nimber of the stack using the MEX & XOR rules
101    ///
102    /// For now, `pool_coins` must be 0.
103    pub fn calculate_nimber(&self, rules: impl AsRef<Vec<NimRule>>, pool_coins: u64) -> Nimber {
104        nimbers::calculate_nimber_for_height(self.0, rules.as_ref(), pool_coins)
105    }
106}
107
108/// A nimber.  
109/// Simply wraps a [u64].
110#[repr(transparent)]
111#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
112pub struct Nimber(pub u64);
113
114impl BitXor for Nimber {
115    type Output = Nimber;
116
117    fn bitxor(self, rhs: Nimber) -> Nimber {
118        Nimber(self.0 ^ rhs.0)
119    }
120}
121
122impl Display for Nimber {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        write!(f, "*{}", self.0)
125    }
126}
127
128impl Debug for Nimber {
129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130        write!(f, "*{}", self.0)
131    }
132}
133
134/// Specifies if a player may/must split a stack into two non-empty stacks after taking coins
135#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
136pub enum Split {
137    /// Splitting the stack is not allowed
138    Never,
139
140    /// The stack may be split into two non-empty stacks after taking coins
141    Optional,
142
143    /// The stack must be split into two non-empty stacks after taking coins
144    Always,
145}
146
147/// Specifies the number of coins that can be taken from a stack in a single move according to a rule.
148#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
149pub enum TakeSize {
150    /// A list of possible numbers which may be taken from a stack in a single move,
151    /// if enough coins are available.
152    ///
153    /// E.g. `[1, 2, 3]`, `[3, 6, 10]`, or `[42]`
154    List(Vec<u64>),
155
156    /// Any number of coins less than or equal to the stack height may be taken.
157    Any,
158
159    /// The player may place coins into the stack from their pool (none are taken),  
160    /// For use with Poker-Nim
161    Place,
162}
163
164/// A rule for a Nim game.  
165/// This struct specifies a set of possible moves for a player.  
166#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
167pub struct NimRule {
168    /// Specifies the number of coins that can be taken from a stack in a single move
169    pub take: TakeSize,
170
171    /// Specifies whether the player may/must split a stack into two stacks
172    pub split: Split,
173}
174
175/// A Nim move, generally represented, not connected to a position,
176/// or a specific game.
177///
178/// For example, remove 3 coins from the first stack without splitting.
179/// This does not include information about the current game state,
180/// or if a non-empty first
181#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
182pub enum NimAction {
183    /// A move which takes coins from a stack, possibly splitting it
184    Take(TakeAction),
185
186    /// A move which places coins onto a stack from the player's pool
187    ///
188    /// For use with Poker-Nim
189    Place(PlaceAction),
190}
191
192/// A move which takes coins from a stack
193///
194/// (placing them into the player's pool, when used with Poker-Nim)
195#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
196pub struct TakeAction {
197    /// The index of the stack to take coins from
198    pub stack_index: usize,
199
200    /// The number of coins to take from the stack
201    pub amount: u64,
202
203    /// If (and possibly how) the stack should be split after taking coins
204    pub split: NimSplit,
205}
206
207/// A move which places coins onto a stack from the player's pool
208///
209/// For use with Poker-Nim.
210#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
211pub struct PlaceAction {
212    /// The index of the stack to place coins onto
213    pub stack_index: usize,
214
215    /// The number of coins to place onto the stack,  
216    /// taken from the player's pool
217    pub amount: u64,
218}
219
220/// Represents a possible split of a stack into two non-empty stacks in a [NimAction::Take] move
221///
222/// This struct represents the resulting split (if any) of a stack after a [TakeAction] is applied.
223#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
224pub enum NimSplit {
225    /// The resulting stacks after a split
226    Yes(Stack, Stack),
227
228    /// The stack was not split
229    No,
230}