Skip to main content

tic_tac_toe_board/
lib.rs

1mod bitboard;
2
3use bitboard::Bitboard;
4use std::{fmt, io};
5
6/// A TacTacToe board
7#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Default)]
8pub struct TicTacToe(Bitboard);
9
10impl TicTacToe {
11    pub fn new() -> TicTacToe {
12        TicTacToe(Bitboard::new())
13    }
14
15    pub fn print_to(self, mut out: impl io::Write) -> io::Result<()> {
16        let f = |i| self.0.field(CellIndex(i));
17
18        write!(
19            out,
20            "-------\n\
21             |{}|{}|{}|\n\
22             |-----|\n\
23             |{}|{}|{}|\n\
24             |-----|\n\
25             |{}|{}|{}|\n\
26             -------",
27            f(0),
28            f(1),
29            f(2),
30            f(3),
31            f(4),
32            f(5),
33            f(6),
34            f(7),
35            f(8)
36        )
37    }
38
39    /// Iterator over all fields which are not occupied by a stone of either player
40    pub fn open_fields(&self) -> impl Iterator<Item = CellIndex> + use<'_> {
41        (0..9)
42            .map(CellIndex)
43            .filter(move |&i| self.0.field(i) == Cell::Empty)
44    }
45
46    pub fn state(&self) -> TicTacToeState {
47        let stones = self.0.stones();
48        let player = stones % 2;
49        match (self.0.victory(), player) {
50            (true, 0) => TicTacToeState::VictoryPlayerTwo,
51            (true, 1) => TicTacToeState::VictoryPlayerOne,
52            (false, 0) => TicTacToeState::TurnPlayerOne,
53            _ => {
54                if stones == 9 {
55                    TicTacToeState::Draw
56                } else {
57                    TicTacToeState::TurnPlayerTwo
58                }
59            }
60        }
61    }
62
63    /// Places a stone for the current player in the specified Cell. Panics if cell is not empty
64    pub fn play_move(&mut self, &mov: &CellIndex) {
65        assert!(self.0.field(mov) == Cell::Empty);
66        let new_state = match self.state() {
67            TicTacToeState::TurnPlayerOne => Cell::PlayerOne,
68            TicTacToeState::TurnPlayerTwo => Cell::PlayerTwo,
69            _ => panic!("Tic Tac Toe game is already finished."),
70        };
71        self.0.mark_cell(mov, new_state);
72    }
73}
74
75#[derive(Clone, Copy, PartialEq, Eq, Debug)]
76pub enum TicTacToeState {
77    VictoryPlayerOne,
78    VictoryPlayerTwo,
79    Draw,
80    TurnPlayerOne,
81    TurnPlayerTwo,
82}
83
84impl TicTacToeState {
85    /// `true` if the game is finished, `false` if it is still ongoing
86    pub fn is_terminal(self) -> bool {
87        match self {
88            TicTacToeState::VictoryPlayerOne
89            | TicTacToeState::VictoryPlayerTwo
90            | TicTacToeState::Draw => true,
91            TicTacToeState::TurnPlayerOne | Self::TurnPlayerTwo => false,
92        }
93    }
94}
95
96/// State of a cell in a TicTacToe Board
97#[derive(Clone, Copy, PartialEq, Eq, Debug)]
98enum Cell {
99    /// Field is not captured by either player
100    Empty,
101    /// Field contains a stone from Player 1
102    PlayerOne,
103    /// Field contains a stone from Player 1
104    PlayerTwo,
105}
106
107impl fmt::Display for Cell {
108    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109        let c = match self {
110            Cell::Empty => " ",
111            Cell::PlayerOne => "X",
112            Cell::PlayerTwo => "O",
113        };
114        write!(f, "{}", c)
115    }
116}
117
118/// Field are enumerated 0..=8. Top left is zero. Bottom right is 8.
119///
120/// ```custom
121/// 0 1 2
122/// 3 4 5
123/// 6 7 8
124/// ```
125#[derive(Clone, Copy, PartialEq, Eq, Debug)]
126pub struct CellIndex(u8);
127
128impl CellIndex {
129    /// Create a new cell index from a number between 0 and 8. Panics for values >= 9.
130    ///
131    /// ```custom
132    /// 0 1 2
133    /// 3 4 5
134    /// 6 7 8
135    /// ```
136    pub fn new(index: u8) -> CellIndex {
137        assert!(index < 9);
138        CellIndex(index)
139    }
140
141    pub fn row(self) -> u8 {
142        self.0 / 3
143    }
144
145    pub fn column(self) -> u8 {
146        self.0 % 3
147    }
148}
149
150impl std::str::FromStr for CellIndex {
151    type Err = &'static str;
152
153    fn from_str(source: &str) -> Result<CellIndex, &'static str> {
154        match source.as_bytes().first() {
155            Some(v @ b'0'..=b'8') => Ok(CellIndex(v - b'0')),
156            _ => Err("Only digits from 0 to 8 count as valid moves."),
157        }
158    }
159}
160
161impl fmt::Display for CellIndex {
162    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
163        write!(f, "cell index: {}", self.0)
164    }
165}
166
167impl From<u8> for CellIndex {
168    fn from(source: u8) -> CellIndex {
169        match source {
170            i @ 0..=8 => CellIndex(i),
171            _ => panic!("Only digits from 0 to 8 can be used as index into a tic tac toe field."),
172        }
173    }
174}
175
176#[cfg(test)]
177mod test {
178
179    use super::*;
180
181    #[test]
182    fn empty_board() {
183        let board = TicTacToe::new();
184        let mut buf = Vec::new();
185        board.print_to(&mut buf).unwrap();
186
187        let expected = "-------\n\
188                        | | | |\n\
189                        |-----|\n\
190                        | | | |\n\
191                        |-----|\n\
192                        | | | |\n\
193                        -------";
194
195        assert_eq!(String::from_utf8(buf).unwrap(), expected);
196    }
197
198    #[test]
199    fn board_with_two_stones() {
200        let mut board = TicTacToe::new();
201        board.0.mark_cell(CellIndex(4), Cell::PlayerOne);
202        board.0.mark_cell(CellIndex(6), Cell::PlayerTwo);
203        let mut buf = Vec::new();
204        board.print_to(&mut buf).unwrap();
205
206        let expected = "-------\n\
207                        | | | |\n\
208                        |-----|\n\
209                        | |X| |\n\
210                        |-----|\n\
211                        |O| | |\n\
212                        -------";
213
214        assert_eq!(String::from_utf8(buf).unwrap(), expected);
215    }
216
217    #[test]
218    fn victory_condition_player_two() {
219        // -------
220        // | | |X|
221        // |-----|
222        // | |X|X|
223        // |-----|
224        // |O|O|O|
225        // -------
226        let mut game = TicTacToe::new();
227        game.play_move(&CellIndex::new(4));
228        game.play_move(&CellIndex::new(6));
229        game.play_move(&CellIndex::new(2));
230        game.play_move(&CellIndex::new(8));
231        game.play_move(&CellIndex::new(5));
232        game.play_move(&CellIndex::new(7));
233
234        assert_eq!(game.state(), TicTacToeState::VictoryPlayerTwo);
235    }
236}