tic_tac_toe/
tic_tac_toe.rs1use std::io;
8
9use line_ui::element::{Cursor, Element, IntoElement};
10use line_ui::{Renderer, Style};
11use termion::event::{Event, Key};
12use termion::input::TermRead;
13use termion::raw::IntoRawMode;
14
15#[derive(Debug, Clone, Copy, Default)]
16enum Player {
17 #[default]
18 X,
19 O,
20}
21
22#[derive(Debug, Default)]
23struct TicTacToe {
24 pub grid: [[Option<Player>; 3]; 3],
25 pub current: Player,
26}
27
28impl TicTacToe {
29 fn place(&mut self, row: usize, col: usize) {
30 if self.grid[row][col].is_some() {
31 return;
33 }
34 self.grid[row][col] = Some(self.current);
35 self.current = match self.current {
36 Player::X => Player::O,
37 Player::O => Player::X,
38 };
39 }
40
41 fn check_win(&self) -> Option<Option<Player>> {
42 for i in 0..3 {
43 for line in [
44 self.grid[i], [0, 1, 2].map(|j| self.grid[j][i]), [0, 1, 2].map(|j| self.grid[j][j]), [0, 1, 2].map(|j| self.grid[2 - j][j]), ] {
49 match line {
50 [Some(Player::X), Some(Player::X), Some(Player::X)] => {
51 return Some(Some(Player::X));
52 }
53 [Some(Player::O), Some(Player::O), Some(Player::O)] => {
54 return Some(Some(Player::O));
55 }
56 _ => {}
57 }
58 }
59 }
60
61 if self.grid.as_flattened().iter().all(Option::is_some) {
62 Some(None) } else {
64 None
65 }
66 }
67}
68
69fn main() -> io::Result<()> {
70 let stdout = io::stdout().into_raw_mode()?;
71 let mut r = Renderer::new(stdout);
72 let mut events = std::io::stdin().events();
73
74 let mut game = TicTacToe::default();
75 let (mut row, mut col): (usize, usize) = (1, 1);
76 loop {
77 r.reset()?;
79 for (i, line) in game.grid.iter().enumerate() {
80 if i != 0 {
81 r.render("--+---+--".into_element())?;
82 }
83 r.render((
84 ((row, col) == (i, 0)).then_some(Cursor),
85 render_player(line[0]),
86 " | ".into_element(),
87 ((row, col) == (i, 1)).then_some(Cursor),
88 render_player(line[1]),
89 " | ".into_element(),
90 ((row, col) == (i, 2)).then_some(Cursor),
91 render_player(line[2]),
92 ))?;
93 }
94
95 let result = game.check_win();
97 match result {
98 Some(Some(winner)) => {
99 r.render((
100 "The winner is ".into_element(),
101 render_player(Some(winner)),
102 "!".into_element(),
103 ))?;
104 }
105 Some(None) => {
106 r.render("The game is a draw.".into_element())?;
107 }
108 _ => {}
109 }
110
111 r.finish()?;
112 if result.is_some() {
113 r.leave()?;
114 break;
115 }
116
117 let Some(event) = events.next() else {
119 break;
120 };
121 match event? {
122 Event::Key(Key::Up) => row = row.checked_sub(1).unwrap_or(2),
123 Event::Key(Key::Down) => row = (row + 1) % 3,
124 Event::Key(Key::Left) => col = col.checked_sub(1).unwrap_or(2),
125 Event::Key(Key::Right) => col = (col + 1) % 3,
126 Event::Key(Key::Char(' ' | '\n' | '\r')) => game.place(row, col),
127 _ => {}
128 }
129 }
130
131 Ok(())
132}
133
134fn render_player(player: Option<Player>) -> impl Element {
135 match player {
136 None => "-".with_style(Style::fg(245)),
137 Some(Player::X) => "X".with_style(Style::BOLD + Style::fg(33)),
138 Some(Player::O) => "O".with_style(Style::BOLD + Style::fg(203)),
139 }
140}