Last active
July 18, 2020 11:20
-
-
Save davcri/cab7c378460f1e7a2b47e1b7bc57bb95 to your computer and use it in GitHub Desktop.
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
| // | |
| // 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