Skip to content

Instantly share code, notes, and snippets.

@cfsamson
Created April 26, 2020 14:33
Show Gist options
  • Save cfsamson/c0947ae05d681d1341d27328d269c275 to your computer and use it in GitHub Desktop.
Save cfsamson/c0947ae05d681d1341d27328d269c275 to your computer and use it in GitHub Desktop.
Tic-tac-toe Rust
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