Skip to content

Instantly share code, notes, and snippets.

@LucasWolschick
Created October 11, 2020 18:43
Show Gist options
  • Save LucasWolschick/d99b25e9ecfe217a43b75095eed200f9 to your computer and use it in GitHub Desktop.
Save LucasWolschick/d99b25e9ecfe217a43b75095eed200f9 to your computer and use it in GitHub Desktop.
Very ugly implementation of a tic tac toe game in Rust
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