Skip to content

Instantly share code, notes, and snippets.

@davcri
Last active July 18, 2020 11:20
Show Gist options
  • Save davcri/cab7c378460f1e7a2b47e1b7bc57bb95 to your computer and use it in GitHub Desktop.
Save davcri/cab7c378460f1e7a2b47e1b7bc57bb95 to your computer and use it in GitHub Desktop.
//
// Tic Tac Toe.
//
// # Run the game
//
// ```
// cargo run
// ```
//
// Make sure to have a Cargo.toml file with this content:
//
// ```
// [package]
// name = "tic-tac-toe"
// version = "0.1.0"
// edition = "2018"
//
// # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
//
// [dependencies]
// rand = "0.7.3"
// ```
use std::io;
use std::process;
use rand::Rng;
use std::{thread, time};
type Grid = [[char; 3]; 3];
static EMPTY_SYM: char = '.';
static GRID_SIZE: usize = 3;
enum GameState {
Playing,
PlayerWin,
OpponentWin,
Draw
}
fn main() {
// TODO: generate grid respecting grid_size
let mut grid: Grid = [
[EMPTY_SYM, EMPTY_SYM, EMPTY_SYM],
[EMPTY_SYM, EMPTY_SYM, EMPTY_SYM],
[EMPTY_SYM, EMPTY_SYM, EMPTY_SYM]
];
println!("Welcome to Tic Tac Toe!");
println!();
print_grid(grid);
println!("\nDo you want to use X or O?");
let player_sign = choose_player_symbol();
let opponent_sign = if player_sign == 'X' { 'O' } else { 'X' };
let mut is_player_turn = true;
loop {
match get_game_state(grid, player_sign) {
GameState::Playing => {
if is_player_turn {
println!("\nWhere do you want to place the {} mark? Eg: 0,1", player_sign);
// println!("Specify row and column separated by a comma. ");
let (row_idx, col_idx): (usize, usize) = get_mark_position();
if row_idx >= GRID_SIZE || col_idx >= GRID_SIZE {
println!("Wrong index");
continue;
}
if grid[row_idx][col_idx] != EMPTY_SYM {
println!("Position not empty!");
continue;
}
println!("You selected {}, {}\n", row_idx, col_idx);
grid[row_idx][col_idx] = player_sign;
} else {
println!("\nEnemy turn...\n");
thread::sleep(time::Duration::from_secs(2));
let (x, y) = get_random_free_position(grid);
grid[x][y] = opponent_sign;
}
is_player_turn = !is_player_turn;
print_grid(grid);
}
GameState::PlayerWin => {
println!("\nYou win!");
break;
}
GameState::OpponentWin => {
println!("\nYou lose!");
break;
}
GameState::Draw => {
println!("\nDraw!");
break;
}
}
}
println!("\nThanks for playing this boring game!");
println!("The next one will be better I promise!\n It will have xsuper fancy stuff like mushrooms, turtles and 'mamma mia!'");
}
fn get_random_free_position(grid: Grid) -> (usize, usize) {
let free_positions = get_empty_positions(grid);
let mut rng = rand::thread_rng();
let rnd_index = rng.gen_range(0, free_positions.len());
return free_positions[rnd_index];
}
fn get_empty_positions(grid: Grid) -> Vec<(usize, usize)> {
let mut free_positions = Vec::new();
for i in 0..grid.len() {
for j in 0..grid[0].len() {
let el = grid[i][j];
if el == EMPTY_SYM {
free_positions.push((i, j));
}
}
}
return free_positions;
}
fn get_game_state(grid: Grid, player_sign: char) -> GameState {
let directions_to_check = [
(-1, 0), // left
(1, 0), // right
(0, 1), // top
(0, -1), // bottom
(-1, -1), // top left
(1, -1), // top right
(-1, 1), // bottom left
(1, 1), // bottom right
];
// check if any player won the game
for i in 0..grid.len() {
for j in 0..grid[0].len() {
let current_elem = grid[i][j];
if current_elem == EMPTY_SYM {
continue;
}
// check all 8 directions
for (dx, dy) in directions_to_check.iter() {
// check position
let cpos = (i as i32 + dx, j as i32 + dy); // https://stackoverflow.com/questions/28273169/how-do-i-convert-between-numeric-types-safely-and-idiomatically
if is_position_inside_grid(cpos.0, cpos.1) {
// println!("Checking... {} {}", i as i32 + x, j as i32 + y);
let el = grid[cpos.0 as usize][cpos.1 as usize];
if el == EMPTY_SYM {
continue;
}
// if it's a match
if el == current_elem {
// take another step
let third_position = (cpos.0 + dx, cpos.1 + dy);
if is_position_inside_grid(third_position.0, third_position.1) {
let third_elem = grid[third_position.0 as usize][third_position.1 as usize];
if third_elem == current_elem {
if current_elem == player_sign {
return GameState::PlayerWin;
} else {
return GameState::OpponentWin;
}
}
}
}
}
}
}
}
// is the grid full?
let empty_positions = get_empty_positions(grid);
if empty_positions.len() == 0 {
return GameState::Draw;
} else {
return GameState::Playing;
}
}
fn is_position_inside_grid(i: i32, j: i32) -> bool {
return i >= 0 && j >= 0 && i < GRID_SIZE as i32 && j < GRID_SIZE as i32;
}
fn print_grid(grid: Grid) {
let rows = grid.len();
let columns = grid[0].len();
for i in 0..rows {
if i > 0 {
println!(" ---------------");
}
for j in 0..columns {
if j < 2 {
print!(" {} |", grid[i][j]);
} else {
print!(" {} ", grid[i][j]);
}
if j == 2 {
println!()
}
}
}
}
fn choose_player_symbol() -> char {
let mut player_sign = ' ';
// until player selects X or O
while player_sign != 'X' && player_sign != 'O' {
let mut input = String::new();
match io::stdin().read_line(&mut input) {
Ok(_n) => {
input = String::from(input.trim());
}
Err(error) => {
println!("error: {}", error);
process::exit(-1);
}
}
if input == "x" || input == "X" {
player_sign = 'X';
} else if input == "o" || input == "O" {
player_sign = 'O';
} else {
println!("Please, choose between 'X' or 'O'")
}
}
return player_sign;
}
fn get_mark_position() -> (usize, usize) {
let mut mark_obtained = false;
let mut pair = (0, 0);
// until player selects X or O
while !mark_obtained {
let mut input = String::new();
match io::stdin().read_line(&mut input) {
Ok(_n) => {
}
Err(error) => {
println!("error: {}", error);
process::exit(-1);
}
}
// remove whitespaces from input
input.retain(|c| !c.is_whitespace());
if input.len() > 3 {
println!("Input is too long");
continue;
}
let values: Vec<&str> = input.split(',').collect();
if values.len() == 2 {
pair = (values[0].parse().unwrap(), values[1].parse().unwrap());
}
mark_obtained = true;
}
return pair;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment