1#![allow(missing_docs)]
4
5use core::slice;
6use std::fmt::Write as FmtWrite;
7use std::ops::{Index, IndexMut};
8use std::time::Duration;
9use std::{
10 fmt::{self, Display, Formatter},
11 io,
12};
13
14use super::Game;
15use crossterm::event::{self, KeyModifiers};
16use crossterm::style::{Color, ContentStyle, Stylize};
17use crossterm::terminal::Clear;
18use crossterm::{cursor, QueueableCommand};
19use terminity_widgets::widgets::frame::Frame;
20use terminity_widgets::widgets::text::{Align, Text};
21use terminity_widgets::{frame, Widget};
22use Tile::*;
23
24#[derive(Debug)]
25pub struct SuperTTT();
26
27impl Game for SuperTTT {
28 fn run(&self, out: &mut dyn io::Write) -> io::Result<()> {
29 GameState::new(out).run()
30 }
31}
32
33type Player = u8;
34
35struct GameState<'a> {
36 pub out: &'a mut dyn io::Write,
37 pub area: Frame<(u8, u8), Zone, GameArea>,
38 pub selected: Selection,
39 pub player: u8,
40 pub text: Text<7>,
41}
42
43#[derive(Debug, Copy, Clone)]
44struct Selection {
45 ty: SelectType,
46 x: u8,
47 y: u8,
48}
49
50#[derive(Debug, Copy, Clone, PartialEq, Eq)]
51enum SelectType {
52 SelCell(u8, u8),
53 Zone,
54}
55
56#[derive(Debug)]
57struct Zone {
58 pub values: [Tile; 9],
59 pub winner: Option<Tile>,
60 pub selected: bool,
61}
62
63impl Widget for Zone {
64 fn displ_line(&self, f: &mut Formatter<'_>, line: usize) -> std::fmt::Result {
65 let mut style = ContentStyle::new();
66 if let Some(winner) = self.winner {
67 style.background_color = Some(winner.get_color());
68 style.foreground_color = Some(Color::Black);
69 }
70 if self.selected {
71 style.background_color = Some(Color::Grey);
72 style.foreground_color = style.background_color;
73 }
74 for cell_x in 0..3 {
75 f.write_fmt(format_args!("{}", &style.apply(' ').to_string()))?;
76 let cell = self[(cell_x, line as u8)];
77 let mut styled_cell = style.apply(cell).bold();
78 if style.foreground_color == None {
79 styled_cell = styled_cell.with(cell.get_color()).bold();
80 }
81 f.write_fmt(format_args!("{}", styled_cell))?;
82 }
83 f.write_fmt(format_args!("{}", &style.apply(' ').to_string()))?;
84 Ok(())
85 }
86 fn size(&self) -> (usize, usize) {
87 (7, 3)
88 }
89}
90
91#[derive(Debug, Default)]
92struct GameArea([Zone; 9]);
93
94impl Index<(u8, u8)> for GameArea {
95 type Output = Zone;
96 fn index(&self, (x, y): (u8, u8)) -> &Self::Output {
97 &self.0[(x + 3 * y) as usize]
98 }
99}
100impl IndexMut<(u8, u8)> for GameArea {
101 fn index_mut(&mut self, (x, y): (u8, u8)) -> &mut Self::Output {
102 &mut self.0[(x + 3 * y) as usize]
103 }
104}
105
106impl GameArea {
107 fn iter(&self) -> slice::Iter<Zone> {
108 self.0.iter()
109 }
110}
111
112#[derive(Debug, Copy, Clone, Eq, PartialEq)]
113#[repr(u8)]
114enum Tile {
115 X = 'x' as u8,
116 O = 'o' as u8,
117 Empty = ' ' as u8,
118}
119
120impl Tile {
121 fn from_player(player: Player) -> Self {
122 if player == 0 {
123 X
124 } else if player == 1 {
125 O
126 } else {
127 panic!("Whut?")
128 }
129 }
130 fn get_color(&self) -> Color {
131 match self {
132 X => Color::Red,
133 O => Color::Blue,
134 Empty => Color::White,
135 }
136 }
137}
138
139impl Display for Tile {
140 fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
141 fmt.write_char(*self as u8 as char)
142 }
143}
144
145impl Default for Zone {
146 fn default() -> Self {
147 Self { values: [Empty; 9], winner: None, selected: false }
148 }
149}
150
151impl Index<(u8, u8)> for Zone {
152 type Output = Tile;
153 fn index(&self, (x, y): (u8, u8)) -> &Self::Output {
154 &self.values[(x + 3 * y) as usize]
155 }
156}
157impl IndexMut<(u8, u8)> for Zone {
158 fn index_mut(&mut self, (x, y): (u8, u8)) -> &mut Self::Output {
159 &mut self.values[(x + 3 * y) as usize]
160 }
161}
162
163impl<'a> GameState<'a> {
164 fn new(out: &'a mut dyn io::Write) -> Self {
165 let mut area: GameArea = Default::default();
166 area[(1, 1)].selected = true;
167 Self {
168 out,
169 selected: Selection { ty: SelectType::Zone, x: 1, y: 1 },
170 player: 0,
171 area: frame!(
172 area => {
173 '0': (0, 0), '1': (1, 0), '2': (2, 0),
174 '3': (0, 1), '4': (1, 1), '5': (2, 1),
175 '6': (0, 2), '7': (1, 2), '8': (2, 2),
176 }
177 " #-------#-------#-------# "
178 " |0000000|1111111|2222222| "
179 " |0000000|1111111|2222222| "
180 " |0000000|1111111|2222222| "
181 " #-------#-------#-------# "
182 " |3333333|4444444|5555555| "
183 " |3333333|4444444|5555555| "
184 " |3333333|4444444|5555555| "
185 " #-------#-------#-------# "
186 " |6666666|7777777|8888888| "
187 " |6666666|7777777|8888888| "
188 " |6666666|7777777|8888888| "
189 " #-------#-------#-------# "
190 ),
191 text: Text {
192 content: [
193 "".to_owned(),
194 "".to_owned(),
195 "Welcome to Super tic tac toe!".to_owned(),
196 "Choose in which zone you will play first. You won't be able to cancel!"
197 .to_owned(),
198 "".to_owned(),
199 "".to_owned(),
200 "".to_owned(),
201 ],
202 align: Align::Center,
203 padding: ' ',
204 width: 70,
205 },
206 }
207 }
208
209 fn run(&mut self) -> crossterm::Result<()> {
210 use event::{Event::Key, KeyCode::*, KeyEvent, KeyEventKind::*};
211 self.disp()?;
212 let winner = loop {
213 let coords = (self.selected.x, self.selected.y);
214 self.area[coords].selected = false;
215 match event::read()? {
216 Key(KeyEvent { code: Left, kind: Press, .. }) => {
217 if self.selected.x > 0 {
218 self.selected.x -= 1
219 }
220 }
221 Key(KeyEvent { code: Right, kind: Press, .. }) => {
222 if self.selected.x < 2 {
223 self.selected.x += 1
224 }
225 }
226 Key(KeyEvent { code: Up, kind: Press, .. }) => {
227 if self.selected.y > 0 {
228 self.selected.y -= 1
229 }
230 }
231 Key(KeyEvent { code: Down, kind: Press, .. }) => {
232 if self.selected.y < 2 {
233 self.selected.y += 1
234 }
235 }
236 Key(KeyEvent { code: Enter, kind: Press, .. }) => match self.selected.ty {
237 SelectType::Zone => {
238 self.text.clear();
239 if let Some(winner) = self.area[(self.selected.x, self.selected.y)].winner {
240 self.text[2] = if winner == Empty {
241 format!("Nope, no more free tile over here.")
242 } else {
243 format!("Nope, you can't! The zone is already won by {}.", winner)
244 };
245 self.text[3] = "Choose in which zone you will play.".to_string();
246 } else {
247 self.selected.ty =
248 SelectType::SelCell(self.selected.x, self.selected.y);
249 self.selected.x = 1;
250 self.selected.y = 1;
251 self.text[2] = "Right.".to_owned();
252 self.text[3] = "Which tile?".to_owned();
253 }
254 }
255 SelectType::SelCell(zone_x, zone_y) => {
256 match self.play(zone_x, zone_y, self.selected.x, self.selected.y) {
257 Ok(None) => {
258 self.text.clear();
259 self.text[2] = "Really guys? That's a draw.".to_owned();
260 self.text[3] = "Well played though, that was intense!".to_owned();
261 break Ok(None);
262 }
263 Ok(Some(winner)) => {
264 self.text.clear();
265 self.text[2] =
266 "WOOOOOHOOOOO!!!! Seems like we have a winner!".to_owned();
267 self.text[3] = format!("Well done player {}!", self.player + 1);
268 self.text[4] = format!(
269 "Player {}, maybe you wanna ask a rematch?",
270 (self.player + 1) % 2 + 1
271 );
272 break Ok(Some(winner));
273 }
274 Err(true) => {
275 self.text.clear();
276 self.text[2] = "Done.".to_owned();
277 self.text[3] = "Where to play now?".to_owned();
278 if self.area[(self.selected.x, self.selected.y)].winner == None {
279 self.selected.ty =
280 SelectType::SelCell(self.selected.x, self.selected.y);
281 self.selected.x = 1;
282 self.selected.y = 1;
283 } else {
284 self.selected.ty = SelectType::Zone;
285 self.selected.x = 1;
286 self.selected.y = 1;
287 }
288 self.player = (1 + self.player) % 2;
289 }
290 Err(false) => {
291 self.text.clear();
292 self.text[2] =
293 "Sneaky one, but you can't play where someone already played!"
294 .to_owned();
295 self.text[3] = "Choose on which tile you'll play.".to_string();
296 }
297 }
298 }
299 },
300 Key(KeyEvent { code: Char('c'), kind: Press, modifiers, .. }) => {
301 if modifiers.contains(KeyModifiers::CONTROL) {
302 self.text.clear();
303 self.text[2] = "Exiting the game....".to_owned();
304 break Err(());
305 }
306 }
307 _ => (),
308 }
309 if self.selected.ty == SelectType::Zone {
310 let coords = (self.selected.x, self.selected.y);
311 self.area[coords].selected = true;
312 }
313 self.disp()?;
314 };
315 if winner == Err(()) {
316 return Ok(());
317 }
318 let texts = [
319 "Press any key to exit ",
320 "Press any key to exit. ",
321 "Press any key to exit.. ",
322 "Press any key to exit...",
323 ];
324 let mut i = 0;
325 loop {
326 self.text[6] = texts[i].to_owned();
327 i = (i + 1) % texts.len();
328 self.disp()?;
329 self.out.queue(crossterm::cursor::Hide)?;
330 self.out.flush()?;
331 if event::poll(Duration::from_millis(600))? {
332 break;
333 }
334 }
335 Ok(())
336 }
337
338 fn play(&mut self, z_x: u8, z_y: u8, cx: u8, cy: u8) -> Result<Option<Player>, bool> {
339 let cell_type = Tile::from_player(self.player);
340
341 let cell = &mut self.area[(z_x, z_y)][(cx, cy)];
342 if *cell != Empty {
343 return Err(false);
344 }
345 *cell = cell_type;
346
347 if cell_type == self.area[(z_x, z_y)][((cx + 1) % 3, cy)]
349 && cell_type == self.area[(z_x, z_y)][((cx + 2) % 3, cy)]
350 || cell_type == self.area[(z_x, z_y)][(cx, (cy + 1) % 3)]
352 && cell_type == self.area[(z_x, z_y)][(cx, (cy + 2) % 3)]
353 || cx == cy
355 && cell_type == self.area[(z_x, z_y)][((cx + 1) % 3, (cy + 1) % 3)]
356 && cell_type == self.area[(z_x, z_y)][((cx + 2) % 3, (cy + 2) % 3)]
357 || cx + cy == 2
359 && cell_type == self.area[(z_x, z_y)][((cx + 1) % 3, (cy + 2) % 3)]
360 && cell_type == self.area[(z_x, z_y)][((cx + 2) % 3, (cy + 1) % 3)]
361 {
362 self.area[(z_x, z_y)].winner = Some(cell_type);
364
365 if Some(cell_type) == self.area[((z_x + 1) % 3, z_y)].winner
367 && Some(cell_type) == self.area[((z_x + 2) % 3, z_y)].winner
368 || Some(cell_type) == self.area[(z_x, (z_y + 1) % 3)].winner
370 && Some(cell_type) == self.area[(z_x, (z_y + 2) % 3)].winner
371 || z_x == z_y
373 && Some(cell_type) == self.area[((z_x + 1) % 3, (z_y + 1) % 3)].winner
374 && Some(cell_type) == self.area[((z_x + 2) % 3, (z_y + 2) % 3)].winner
375 || z_x + z_y == 2
377 && Some(cell_type) == self.area[((z_x + 1) % 3, (z_y + 2) % 3)].winner
378 && Some(cell_type) == self.area[((z_x + 2) % 3, (z_y + 1) % 3)].winner
379 {
380 return Ok(Some(self.player));
381 }
382 } else if self.area[(z_x, z_y)].values.iter().all(|c| *c != Empty) {
383 self.area[(z_x, z_y)].winner = Some(Empty);
384 }
385
386 if self.area[(z_x, z_y)].winner != None && self.area.iter().all(|z| z.winner != None) {
387 Ok(None)
388 } else {
389 Err(true)
390 }
391 }
392
393 fn disp(&mut self) -> io::Result<()> {
394 self.text[0] = format!(
395 "Turn to player {} ({})",
396 self.player + 1,
397 Tile::from_player(self.player)
398 .to_string()
399 .with(Tile::from_player(self.player).get_color())
400 .bold()
401 );
402
403 self.out.queue(cursor::MoveTo(0, 0))?;
404 write!(self.out, "{}", self.area)?;
405 self.out.queue(cursor::MoveTo(0, 13))?;
406 write!(self.out, "{}", self.text)?;
407 self.out.queue(Clear(crossterm::terminal::ClearType::FromCursorDown))?;
409
410 if let Selection { ty: SelectType::SelCell(zx, zy), x, y } = self.selected {
411 let (mut x_index, mut y_index) = self.area.find_pos(&(zx, zy)).unwrap();
412 y_index += y as usize;
413 x_index += 1 + 2 * x as usize;
414 self.out.queue(cursor::MoveTo(x_index as u16, y_index as u16))?.queue(cursor::Show)?;
415 } else {
416 self.out.queue(cursor::Hide)?;
417 }
418 self.out.flush()?;
419 Ok(())
420 }
421}