Created
April 26, 2020 14:33
-
-
Save cfsamson/c0947ae05d681d1341d27328d269c275 to your computer and use it in GitHub Desktop.
Tic-tac-toe 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 rand; | |
use std::{ | |
fmt, | |
io::{stdin, Read}, | |
}; | |
fn main() { | |
run(); | |
} | |
fn run() { | |
let mut board = match initialize() { | |
Ok(board) => board, | |
Err(e) => panic!("{}", e), | |
}; | |
println!("{}", board); | |
loop { | |
println!("What's your pick (row, col)?"); | |
let pick = stdin_line(); | |
let (row, col) = match validate_pick(pick) { | |
Ok(pick) => pick, | |
Err(e) => { | |
println!("{}", e); | |
continue; | |
} | |
}; | |
match board.take(row, col) { | |
Ok(winner) => { | |
if winner { | |
board.print_winner(); | |
break; | |
} | |
} | |
Err(e) => { | |
println!("{}", e); | |
continue; | |
} | |
} | |
println!("{}", board); | |
} | |
} | |
fn initialize() -> Result<Board, String> { | |
println!("Welcome to tic-tac-toe!"); | |
println!("Please write the size of the board:"); | |
let size = stdin_line(); | |
println!("Player A name:"); | |
let player_a = stdin_line(); | |
println!("Player B name:"); | |
let player_b = stdin_line(); | |
Board::new(size, player_a, player_b) | |
} | |
fn stdin_line() -> String { | |
let mut buffer = String::new(); | |
while buffer.is_empty() { | |
stdin().read_line(&mut buffer).unwrap(); | |
} | |
let buffer = buffer.lines().nth(0).unwrap(); | |
buffer.to_string() | |
} | |
fn validate_pick(s: String) -> Result<(usize, usize), String> { | |
let picks: Vec<&str> = s.split(",").map(|n| n.trim()).collect(); | |
let row = picks | |
.get(0) | |
.ok_or(format!( | |
r#""{}" is not a valid pick. The format is: 1, 1"#, | |
s | |
)) | |
.map(|s| { | |
s.parse::<usize>() | |
.map_err(|_| format!(r#""{}" is not a valid position."#, s)) | |
})??; | |
let col = picks | |
.get(1) | |
.ok_or(format!( | |
r#""{}" is not a valid pick. The format is: 1, 1"#, | |
s | |
)) | |
.map(|s| { | |
s.parse::<usize>() | |
.map_err(|_| format!(r#""{}" is not a valid position."#, s)) | |
})??; | |
Ok((row, col)) | |
} | |
#[derive(Debug, Clone, Copy)] | |
enum Turn { | |
PlayerA, | |
PlayerB, | |
} | |
#[derive(Debug)] | |
struct Board { | |
board: Vec<Vec<CellState>>, | |
size: usize, | |
player_a: String, | |
player_b: String, | |
turn: Turn, | |
} | |
#[derive(Debug, PartialEq, Eq)] | |
enum CellState { | |
Available, | |
PlayerA, | |
PlayerB, | |
} | |
impl fmt::Display for CellState { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
use CellState::*; | |
match self { | |
Available => write!(f, "-"), | |
PlayerA => write!(f, "O"), | |
PlayerB => write!(f, "X"), | |
} | |
} | |
} | |
impl From<Turn> for CellState { | |
fn from(t: Turn) -> Self { | |
match t { | |
Turn::PlayerA => CellState::PlayerA, | |
Turn::PlayerB => CellState::PlayerB, | |
} | |
} | |
} | |
impl Board { | |
fn new(size: String, player_a: String, player_b: String) -> Result<Self, String> { | |
let size: usize = size | |
.parse() | |
.map_err(|_| format!(r#""{}" is not a valid size."#, size))?; | |
let mut board = vec![]; | |
for _ in 0..size { | |
let cols: Vec<_> = (0..size).map(|_| CellState::Available).collect(); | |
board.push(cols); | |
} | |
let coin_toss = if rand::random::<bool>() { | |
Turn::PlayerA | |
} else { | |
Turn::PlayerB | |
}; | |
Ok(Board { | |
board, | |
size, | |
player_a, | |
player_b, | |
turn: coin_toss, | |
}) | |
} | |
fn take(&mut self, row: usize, col: usize) -> Result<bool, String> { | |
if row > self.size - 1 || col > self.size - 1 { | |
return Err(format!("({}, {}) is outside the board", row, col)); | |
} | |
let val = &self.board[row][col]; | |
if *val == CellState::PlayerA || *val == CellState::PlayerB { | |
return Err("Too late, it's already taken!".to_string()); | |
} | |
if row > self.size || col > self.size { | |
return Err("Outside the board, remember rows and cols start at 0".to_string()); | |
} | |
self.board[row][col] = CellState::from(self.turn); | |
if self.is_winner() { | |
return Ok(true); | |
} | |
match self.turn { | |
Turn::PlayerA => self.turn = Turn::PlayerB, | |
Turn::PlayerB => self.turn = Turn::PlayerA, | |
}; | |
Ok(false) | |
} | |
fn is_winner(&self) -> bool { | |
let winning_state = CellState::from(self.turn); | |
let max_index = self.size - 1; | |
let mut consecutive_rows = 0; | |
for i in 0..max_index { | |
for row in &self.board { | |
if row[i] == winning_state { | |
consecutive_rows += 1; | |
} | |
} | |
if consecutive_rows == self.size { | |
return true; | |
} else { | |
consecutive_rows = 0; | |
} | |
} | |
let mut consecutive_cols = 0; | |
for row in &self.board { | |
for col in row { | |
if *col == winning_state { | |
consecutive_cols += 1; | |
} | |
} | |
if consecutive_cols == self.size { | |
return true; | |
} else { | |
consecutive_cols = 0; | |
} | |
} | |
let mut consecutive_vert_1 = 0; | |
let mut consecutive_vert_2 = 0; | |
for (row_i, row) in self.board.iter().enumerate() { | |
if row[row_i] == winning_state { | |
consecutive_vert_1 += 1; | |
} | |
if row[max_index - row_i] == winning_state { | |
consecutive_vert_2 += 1; | |
} | |
} | |
if consecutive_vert_1 == self.size || consecutive_vert_2 == self.size { | |
return true; | |
} | |
false | |
} | |
fn print_winner(&self) { | |
let winner = match self.turn { | |
Turn::PlayerA => &self.player_a, | |
Turn::PlayerB => &self.player_b, | |
}; | |
println!("{}", self); | |
println!("Congratulations {}. You've won! Good job!", winner); | |
} | |
} | |
impl fmt::Display for Board { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
writeln!(f)?; | |
for row in &self.board { | |
for col in row { | |
write!(f, "{}\t", col)?; | |
} | |
println!("\n"); | |
} | |
match &self.turn { | |
Turn::PlayerA => write!(f, "It's {}'s turn!", self.player_a)?, | |
Turn::PlayerB => write!(f, "It's {}'s turn!", self.player_b)?, | |
} | |
Ok(()) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment