sttt/
board.rs

1use std::fmt::{Debug, Display};
2use std::hash::Hash;
3
4use internal_iterator::InternalIterator;
5use rand::Rng;
6
7use crate::symmetry::Symmetry;
8
9#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
10pub enum Player {
11    A,
12    B,
13}
14
15#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
16pub enum Outcome {
17    WonBy(Player),
18    Draw,
19}
20
21/// The representation of a game state.
22pub trait Board: Debug + Display + Clone + Eq + Hash + Send + Sync where for<'a> Self: BoardAvailableMoves<'a, Self> {
23    type Move: Debug + Copy + Eq + Ord + Hash + Send + Sync;
24    type Symmetry: Symmetry;
25
26    /// Whether the player who plays a move can lose by playing that move.
27    /// Symbolically whether `b.won_by() == Some(Winner::Player(b.next_player()))` can ever be true.
28    /// This may be pessimistic, returning `true` is always correct.
29    fn can_lose_after_move() -> bool;
30
31    /// Return minimum and maximum possible games lengths. These bounds may be pessimistic,
32    /// returning `(0, None)` is always correct.
33    fn game_length_bounds() -> (u32, Option<u32>);
34
35    /// Return the next player to make a move.
36    /// If the board is done this is the player that did not play the last move for consistency.
37    fn next_player(&self) -> Player;
38
39    /// Return whether the given move is available. Panics if this board is done.
40    fn is_available_move(&self, mv: Self::Move) -> bool;
41
42    /// Pick a random move from the `available_moves` with a uniform distribution. Panics if this board is done.
43    /// Can be overridden for better performance.
44    fn random_available_move(&self, rng: &mut impl Rng) -> Self::Move {
45        let count = self.available_moves().count();
46        let index = rng.gen_range(0..count);
47        self.available_moves().nth(index).unwrap()
48    }
49
50    /// Play the move `mv`, modifying this board.
51    /// Panics if this board is done or if the move is not available or valid for this board.
52    fn play(&mut self, mv: Self::Move);
53
54    /// Clone this board, play `mv` on it and return the new board.
55    fn clone_and_play(&self, mv: Self::Move) -> Self {
56        let mut next = self.clone();
57        next.play(mv);
58        next
59    }
60
61    /// The outcome of this board, is `None` when this games is not done yet.
62    fn outcome(&self) -> Option<Outcome>;
63
64    /// Whether this games is done.
65    fn is_done(&self) -> bool {
66        self.outcome().is_some()
67    }
68
69    /// Map this board under the given symmetry.
70    fn map(&self, sym: Self::Symmetry) -> Self;
71
72    /// Map a move under the given symmetry.
73    fn map_move(sym: Self::Symmetry, mv: Self::Move) -> Self::Move;
74}
75
76/// Trait to fake generic associated types, can be removed once that's stable.
77/// See https://github.com/rust-lang/rust/issues/44265.
78pub trait BoardAvailableMoves<'a, B: Board> {
79    type MoveIterator: InternalIterator<Item=B::Move>;
80    type AllMoveIterator: InternalIterator<Item=B::Move>;
81
82    /// All theoretically possible moves, for any possible board.
83    /// Moves returned by `available_moves` will always be a subset of these moves.
84    /// The ordering must be consistent with `available_moves`.
85    fn all_possible_moves() -> Self::AllMoveIterator;
86
87    /// Return an iterator over available moves, is always nonempty. No guarantees are made about the ordering except
88    /// that it stays consistent when the board is not modified.
89    /// Panics if this board is done.
90    fn available_moves(&'a self) -> Self::MoveIterator;
91}
92
93impl Player {
94    pub fn other(self) -> Player {
95        match self {
96            Player::A => Player::B,
97            Player::B => Player::A,
98        }
99    }
100
101    pub fn index(self) -> u8 {
102        match self {
103            Player::A => 0,
104            Player::B => 1,
105        }
106    }
107
108    pub fn sign(self, pov: Player) -> i8 {
109        if self == pov { 1 } else { -1 }
110    }
111}
112
113impl Outcome {
114    pub fn other(self) -> Outcome {
115        match self {
116            Outcome::WonBy(player) => Outcome::WonBy(player.other()),
117            Outcome::Draw => Outcome::Draw,
118        }
119    }
120
121    pub fn unwrap_player(self) -> Player {
122        match self {
123            Outcome::WonBy(player) => player,
124            Outcome::Draw => panic!("Expected a player, got {:?}", self),
125        }
126    }
127
128    pub fn sign(self, pov: Player) -> i8 {
129        match self {
130            Outcome::WonBy(player) => player.sign(pov),
131            Outcome::Draw => 0,
132        }
133    }
134
135    pub fn inf_sign(self, pov: Player) -> f32 {
136        match self {
137            Outcome::WonBy(player) => player.sign(pov) as f32 * f32::INFINITY,
138            Outcome::Draw => 0.0,
139        }
140    }
141}
142