1mod bitboard;
2
3use bitboard::Bitboard;
4use std::{fmt, io};
5
6#[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 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 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 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#[derive(Clone, Copy, PartialEq, Eq, Debug)]
98enum Cell {
99 Empty,
101 PlayerOne,
103 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#[derive(Clone, Copy, PartialEq, Eq, Debug)]
126pub struct CellIndex(u8);
127
128impl CellIndex {
129 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 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}