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}