Created
October 11, 2020 18:43
-
-
Save LucasWolschick/d99b25e9ecfe217a43b75095eed200f9 to your computer and use it in GitHub Desktop.
Very ugly implementation of a tic tac toe game in Rust
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::io::Write; | |
#[derive(Debug, Copy, Clone, Eq, PartialEq)] | |
enum Tile { | |
X, | |
O, | |
Empty, | |
} | |
impl Tile { | |
fn to_char(&self) -> char { | |
match self { | |
Tile::X => 'X', | |
Tile::O => 'O', | |
Tile::Empty => ' ', | |
} | |
} | |
} | |
struct Board { | |
tiles: Vec<Tile>, | |
} | |
impl Board { | |
fn new() -> Board { | |
Board { | |
tiles: vec![Tile::Empty; 9], | |
} | |
} | |
fn at(&self, x: i32, y: i32) -> Tile { | |
assert!(x < 3 && y < 3, "Supplied coordinates are out of bounds"); | |
let idx = (x + y * 3) as usize; | |
self.tiles[idx] | |
} | |
fn set(&mut self, x: i32, y: i32, value: Tile) { | |
assert!(x < 3 && y < 3, "Supplied coordinates are out of bounds"); | |
let idx = (x + y * 3) as usize; | |
self.tiles[idx] = value; | |
} | |
fn is_full(&self) -> bool { | |
!self.tiles.iter().any(|x| x == &Tile::Empty) | |
} | |
fn has_winner(&self) -> Option<Tile> { | |
// verify verticals | |
for x in 0..3 { | |
if self.at(x, 0) == self.at(x, 1) | |
&& self.at(x, 0) == self.at(x, 2) | |
&& self.at(x, 0) != Tile::Empty | |
{ | |
return Some(self.at(x, 0)); | |
} | |
} | |
// verify horizontals | |
for y in 0..3 { | |
if self.at(0, y) == self.at(1, y) | |
&& self.at(0, y) == self.at(2, y) | |
&& self.at(0, y) != Tile::Empty | |
{ | |
return Some(self.at(0, y)); | |
} | |
} | |
// verify diagonals | |
if self.at(0, 0) == self.at(1, 1) | |
&& self.at(0, 0) == self.at(2, 2) | |
&& self.at(0, 0) != Tile::Empty | |
{ | |
return Some(self.at(0, 0)); | |
} | |
if self.at(2, 0) == self.at(1, 1) | |
&& self.at(2, 0) == self.at(0, 2) | |
&& self.at(2, 0) != Tile::Empty | |
{ | |
return Some(self.at(2, 0)); | |
} | |
None | |
} | |
fn to_string(&self) -> String { | |
let mut s = String::new(); | |
const LETTERS: [&str; 3] = ["A", "B", "C"]; | |
s += " 1 2 3 \n"; | |
for y in 0..3 { | |
// add the row letter | |
let letter = LETTERS[y as usize]; | |
s.push_str(letter); | |
s.push_str(" "); | |
// print the tiles | |
let tiles = format!( | |
" {} | {} | {} ", | |
self.at(0, y).to_char(), | |
self.at(1, y).to_char(), | |
self.at(2, y).to_char() | |
); | |
s.push_str(&tiles); | |
s.push_str("\n"); | |
// print the bar | |
if y != 2 { | |
let bar = format!(" {}\n", "-".repeat(11)); | |
s.push_str(&bar); | |
} | |
} | |
s | |
} | |
} | |
fn take_binary_input( | |
prompt: &str, | |
answer_true: &str, | |
answer_false: &str, | |
strict_answer: bool, | |
) -> bool { | |
loop { | |
print!("{} ({}/{}) ", prompt, answer_true, answer_false); | |
std::io::stdout().flush().unwrap(); | |
let mut answer = String::new(); | |
std::io::stdin() | |
.read_line(&mut answer) | |
.expect("Error handling input"); | |
if strict_answer { | |
if answer.trim().to_lowercase() == answer_true.to_lowercase() { | |
return true; | |
} else if answer.trim().to_lowercase() == answer_false.to_lowercase() { | |
return false; | |
} else { | |
println!("Please answer {} or {}", answer_true, answer_false); | |
} | |
} else { | |
return answer.trim().to_lowercase() == answer_true.to_lowercase(); | |
} | |
} | |
} | |
fn main() { | |
println!("TIC TAC TOE\n"); | |
loop { | |
let p1_symbol = if take_binary_input("Player 1, which symbol do you want?", "x", "o", true) | |
{ | |
Tile::X | |
} else { | |
Tile::O | |
}; | |
let p2_symbol = if p1_symbol == Tile::X { | |
Tile::O | |
} else { | |
Tile::X | |
}; | |
println!("Player 2, your symbol is {}.", p2_symbol.to_char()); | |
let mut player = if p1_symbol == Tile::X { 1 } else { 2 }; | |
let mut board = Board::new(); | |
while !(board.has_winner().is_some() || board.is_full()) { | |
println!("{}", board.to_string()); | |
println!("Player {}'s turn", player); | |
loop { | |
let choice: (usize, usize) = loop { | |
print!("Type a letter and a number (eg, a1): "); | |
std::io::stdout().flush().unwrap(); | |
let mut input = String::new(); | |
std::io::stdin() | |
.read_line(&mut input) | |
.expect("Error handling input."); | |
let input = input.trim().to_lowercase(); | |
if input.len() != 2 { | |
println!("Please type a single letter and a single number, together"); | |
continue; | |
} | |
let first = input.chars().next().unwrap(); | |
let last = input.chars().last().unwrap(); | |
const ALLOWED_LETTERS: [char; 3] = ['a', 'b', 'c']; | |
const ALLOWED_NUMBERS: [char; 3] = ['1', '2', '3']; | |
if ALLOWED_LETTERS.contains(&first) && ALLOWED_NUMBERS.contains(&last) { | |
break ( | |
ALLOWED_LETTERS.iter().position(|x| x == &first).unwrap(), | |
ALLOWED_NUMBERS.iter().position(|x| x == &last).unwrap(), | |
); | |
} else if ALLOWED_LETTERS.contains(&last) && ALLOWED_NUMBERS.contains(&first) { | |
break ( | |
ALLOWED_LETTERS.iter().position(|x| x == &last).unwrap(), | |
ALLOWED_NUMBERS.iter().position(|x| x == &first).unwrap(), | |
); | |
} else { | |
println!("Please type a valid letter and number, together"); | |
continue; | |
}; | |
}; | |
if board.at(choice.1 as i32, choice.0 as i32) == Tile::Empty { | |
board.set( | |
choice.1 as i32, | |
choice.0 as i32, | |
if player == 1 { p1_symbol } else { p2_symbol }, | |
); | |
break; | |
} else { | |
println!("This position is already occupied."); | |
} | |
} | |
player = player % 2 + 1; | |
} | |
println!("{}", board.to_string()); | |
if let Some(winner) = board.has_winner() { | |
let player = if winner == p1_symbol { 1 } else { 2 }; | |
println!("Player {} wins!", player); | |
} else { | |
println!("It's a tie!"); | |
} | |
if !take_binary_input("Do you wish to play again?", "y", "n", false) { | |
break; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment