1use itertools::iproduct;
11use std::fmt;
12use std::io;
13
14#[derive(Debug, Default, Clone, PartialEq)]
16pub enum Space {
17 #[default]
18 None,
19 Cross,
20 Circle,
21}
22
23impl fmt::Display for Space {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 let d = match &self {
26 Space::None => ' ',
27 Space::Cross => 'X',
28 Space::Circle => 'O',
29 };
30 write!(f, "{}", d)
31 }
32}
33
34impl From<Space> for char {
35 fn from(s: Space) -> Self {
36 format!("{}", s).chars().next().unwrap()
37 }
38}
39
40#[derive(Debug, Default, Clone)]
42struct TicTacToe {
43 grid: [[Space; 3]; 3],
44 victory: Space,
45}
46
47impl fmt::Display for TicTacToe {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 for (line_nb, line) in self.grid.clone().into_iter().enumerate() {
50 writeln!(f, "{} | {} | {}", line[0], line[1], line[2])?;
51 if line_nb < 2 {
52 writeln!(f, "--+---+--")?
53 }
54 }
55 Ok(())
56 }
57}
58
59impl TicTacToe {
60 fn play(&mut self, player: Space, place: usize) -> Result<(), String> {
74 if place == 0 || place > 9 {
75 return Err("Invalid place".into());
77 }
78 let place = place - 1;
79 let coords = (place / 3, place % 3);
80 if self.grid[coords.0][coords.1] != Space::None {
81 return Err("Already occupied".into());
83 }
84 self.grid[coords.0][coords.1] = player.clone();
85 Ok(())
86 }
87
88 fn victory(&mut self) -> Space {
89 if self.victory != Space::None {
90 return self.victory.clone();
91 }
92 self.victory = self.compute_victory();
93 self.victory.clone()
94 }
95
96 fn compute_victory(&self) -> Space {
97 for p in [Space::Cross, Space::Circle] {
98 for c in 0..3 {
99 if (0..3).map(|u| &self.grid[c][u]).cloned().all(|x| x == p) {
100 return p;
102 }
103 if (0..3).map(|u| &self.grid[u][c]).cloned().all(|x| x == p) {
104 return p;
106 }
107 }
108 if (0..3).map(|u| &self.grid[u][u]).cloned().all(|x| x == p) {
109 return p;
111 }
112 if (0..3)
113 .map(|u| &self.grid[u][2 - u])
114 .cloned()
115 .all(|x| x == p)
116 {
117 return p;
119 }
120 }
121 Space::None
122 }
123}
124
125#[derive(Debug, Default)]
126struct GiantTicTacToe {
127 grid: [[TicTacToe; 3]; 3],
128}
129
130impl fmt::Display for GiantTicTacToe {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 write!(f, "{}", &self.to_grid(None).unwrap())
133 }
134}
135
136impl GiantTicTacToe {
137 fn to_grid(&self, playable: Option<usize>) -> Result<String, String> {
150 let mut grid = empty_giant_grid();
152
153 for (l, x) in self.grid.iter().enumerate() {
155 for (c, g) in x.iter().enumerate() {
156 let grid_offset = (l * 12 + 1, c * 12 + 1);
157 for (line, column) in iproduct!(0..3, 0..3) {
158 let coords = (4 * line + grid_offset.0, 4 * column + grid_offset.1);
159 grid[coords.0][coords.1] = g.grid[line][column].clone().into();
160 }
161 }
162 }
163
164 if let Some(g) = playable {
166 if g < 10 && g > 0 {
167 let g = g - 1;
168 let offset = (g / 3 * 12, g % 3 * 12);
169 for n in 0..9 {
170 let c = ((n / 3) * 4, (n % 3) * 4);
171 grid[offset.0 + c.0][offset.1 + c.1] =
172 format!("{}", n + 1).chars().next().unwrap();
173 }
174 }
175 }
176
177 let mut lines: Vec<String> = Vec::new();
179 for line in grid.iter() {
180 lines.push(line.iter().collect())
181 }
182 let result = lines.join("\n");
183 Ok(result)
184 }
185
186 fn play(&mut self, player: Space, grid: usize, cell: usize) -> Result<(), String> {
187 if grid == 0 || grid > 9 {
188 return Err(format!(
189 "invalid grid number, expected a number between 1 and 9 included, got {grid}"
190 ));
191 }
192 if cell == 0 || cell > 9 {
193 return Err(format!(
194 "invalid cell number, expected a number between 1 and 9 included, got {cell}"
195 ));
196 }
197 let grid = grid - 1;
198 self.grid[grid / 3][grid % 3].play(player, cell)
199 }
200
201 fn victories(&self) -> TicTacToe {
202 let mut t = TicTacToe::default();
203 for (a, b) in iproduct!(0..3, 0..3) {
204 t.grid[a][b] = self.grid[a][b].clone().victory();
205 }
206 t
207 }
208
209 fn victory(&self) -> Space {
210 self.victories().victory()
211 }
212}
213
214fn empty_giant_grid() -> [[char; 3 * 12]; 3 * 12] {
215 let mut grid = [[' '; 3 * 12]; 3 * 12];
216 for (x, y) in iproduct!(0..3, 0..3) {
217 let offset = (x * 12, y * 12);
218
219 for l in 0..2 {
221 for c in 0..11 {
222 grid[3 + l * 4 + offset.0][c + offset.1] = '-'
223 }
224 }
225
226 for c in 0..2 {
228 for l in 0..11 {
229 grid[l + offset.0][3 + c * 4 + offset.1] = '|'
230 }
231 }
232
233 grid[offset.0 + 3][offset.1 + 3] = '+';
235 grid[offset.0 + 7][offset.1 + 3] = '+';
236 grid[offset.0 + 3][offset.1 + 7] = '+';
237 grid[offset.0 + 7][offset.1 + 7] = '+';
238 }
239 grid
240}
241
242fn read_num() -> usize {
243 let mut read = String::new();
244 println!("choice:");
245 loop {
246 let _ = io::stdin()
247 .read_line(&mut read)
248 .expect("unable to read line");
249
250 if let Ok(res) = read.trim().parse::<usize>() {
251 if res > 0 && res < 10 {
252 return res;
253 } else {
254 println!("expected a number between 0 and 9, got {res}");
255 }
256 } else {
257 println!("expected a number between 0 and 9");
258 }
259 read.clear()
260 }
261}
262
263pub fn run_game() -> Space {
267 let mut board = GiantTicTacToe::default();
268 let mut grid = 0;
269 let mut cell;
270 let players = [Space::Cross, Space::Circle].into_iter().cycle();
271 println!("{board}");
272 for player in players {
273 println!("next player: {player}");
274 if grid == 0 || grid > 9 {
275 println!("choose a grid:");
276 grid = read_num();
277 }
278 println!("{}", board.to_grid(Some(grid)).unwrap());
279 println!("giant victory");
280 println!("{}", board.victories());
281 println!("{player}: where to play?");
282 cell = read_num();
283 match board.play(player, grid, cell) {
284 Ok(_) => grid = cell,
285 Err(s) => {
286 println!("error while playing: {s}")
287 }
288 };
289
290 if board.victory() != Space::None {
291 break;
292 }
293 }
294 println!("{board}");
295 println!("{}", board.victories());
296 println!("victory: {}", board.victory());
297 board.victory()
298}