two_player/games/
fibonacci_nim.rs1use crate::{player::Player, state::State};
4
5#[derive(Debug, Clone)]
6pub struct FibonacciNimState {
7 remaining: usize,
8 quota: usize,
9 turn: Player,
10}
11
12impl FibonacciNimState {
13 pub fn initial(start: usize) -> Self {
14 Self {
15 remaining: start,
16 quota: start - 1,
17 turn: Player::FirstPlayer,
18 }
19 }
20
21 pub fn remaining(&self) -> usize {
22 self.remaining
23 }
24
25 pub fn quota(&self) -> usize {
26 self.quota
27 }
28}
29
30impl State for FibonacciNimState {
31 fn turn(&self) -> Player {
32 self.turn
33 }
34
35 fn winner(&self) -> Option<Player> {
36 if self.remaining == 0 {
37 Some(self.turn.opposite())
38 } else {
39 None
40 }
41 }
42
43 fn valid_moves(&self) -> impl Iterator<Item = Self> {
44 (1..=self.quota).map(|i| {
45 let remaining = self.remaining - i;
46 Self {
47 remaining,
48 quota: (2 * i).min(remaining),
49 turn: self.turn.opposite(),
50 }
51 })
52 }
53}
54
55#[cfg(test)]
56mod test {
57 use super::*;
58 use crate::valuation::best_move_finite;
59
60 #[test]
61 fn test_initial() {
62 let initial = FibonacciNimState::initial(5);
63 assert_eq!(initial.turn(), Player::FirstPlayer);
64 assert_eq!(initial.winner(), None);
65 assert_eq!(initial.valid_moves().count(), 4);
66 }
67
68 #[test]
69 fn test_small_game() {
70 let state = FibonacciNimState::initial(2);
71 assert_eq!(state.turn(), Player::FirstPlayer);
72 assert_eq!(state.winner(), None);
73 let moves: Vec<_> = state.valid_moves().collect();
74 assert_eq!(moves.len(), 1, "{state:?} -> {moves:?}");
75
76 let state = &moves[0];
77 assert_eq!(state.turn(), Player::SecondPlayer);
78 assert_eq!(state.winner(), None);
79 let moves: Vec<_> = state.valid_moves().collect();
80 assert_eq!(moves.len(), 1, "{state:?} -> {moves:?}");
81
82 let state = &moves[0];
83 assert_eq!(state.winner(), Some(Player::SecondPlayer), "{state:?}");
84 }
85
86 #[test]
87 fn test_best_move() {
88 let fib = [2, 3, 5, 8, 13, 21];
89
90 for n in 2..22 {
91 let initial = FibonacciNimState::initial(n);
92 let expected_winner = if fib.contains(&n) {
93 Player::SecondPlayer
94 } else {
95 Player::FirstPlayer
96 };
97 let Some((valuation, best_move)) = best_move_finite(&initial) else {
98 panic!("No best move for {initial:?}");
99 };
100 assert_eq!(
101 valuation.winner, expected_winner,
102 "{initial:?} -> ({valuation:?}, {best_move:?})"
103 );
104 }
105 }
106}