
Task

Play a game of tic-tac-toe.

Ensure that legal moves are played and that a winning position is notified.


Tic-tac-toe   is also known as:

   naughts and crosses 
   tic tac toe 
   tick tack toe 
   three in a row 
   tres en rayo       and
   Xs  and  Os


See also
   MathWorld™, Tic-Tac-Toe game.
   Wikipedia tic-tac-toe.


 
use GameState::{ComputerWin, Draw, PlayerWin, Playing};
 
use rand::prelude::*;
 
#[derive(PartialEq, Debug)]
enum GameState {
    PlayerWin,
    ComputerWin,
    Draw,
    Playing,
}
 
type Board = [[char; 3]; 3];
 
fn main() {
    let mut rng = StdRng::from_entropy();
 
    let mut board: Board = [['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9']];
 
    draw_board(board);
    loop {
        player_turn(&mut board);
        if check_win(board) != Playing {
            break;
        }
        computer_turn(&mut rng, &mut board);
        if check_win(board) != Playing {
            break;
        }
        draw_board(board);
    }
 
    draw_board(board);
    let announcement = match check_win(board) {
        PlayerWin => "The Player has won!",
        ComputerWin => "The Computer has won!",
        Draw => "Draw!",
        Playing => unreachable!(),
    };
    println!("{}", announcement);
}
 
fn is_empty(cell: &char) -> bool {
    *cell != 'X' && *cell != 'O'
}
 
fn check_win(board: Board) -> GameState {
    // check for win
    for (i, row) in board.iter().enumerate() {
        if row[0] == row[1] && row[0] == row[2] {
            return which_win(row[0]);
        } else if board[0][i] == board[1][i] && board[0][i] == board[2][i] {
            return which_win(board[0][i]);
        }
    }
    if board[0][0] == board[1][1] && board[0][0] == board[2][2] {
        return which_win(board[0][0]);
    } else if board[0][2] == board[1][1] && board[0][2] == board[2][0] {
        return which_win(board[0][2]);
    }
 
    // check if it's not a draw
    let is_draw = board.iter().flatten().any(is_empty);
    if is_draw {
        Playing
    } else {
        Draw
    }
}
 
fn which_win(s: char) -> GameState {
    match s {
        'X' => PlayerWin,
        'O' => ComputerWin,
        _ => unreachable!(),
    }
}
 
fn player_turn(board: &mut Board) {
    use std::io;
 
    println!("Player, enter your field of choice!: ");
    let mut ln = String::new();
    io::stdin()
        .read_line(&mut ln)
        .expect("Failed to read stdin");
    let choice = ln.trim().parse::<usize>().expect("Failed to parse input");
    let row = (choice - 1) / 3;
    let col = (choice - 1) % 3;
 
    if board[row][col] == 'X' || board[row][col] == 'O' {
        println!("Someone already took this field!");
        player_turn(board);
    } else {
        board[row][col] = 'X';
    }
}
 
fn computer_turn<R: Rng>(rng: &mut R, board: &mut Board) {
    let possible_choices: Vec<_> = board
        .iter()
        .flatten()
        .enumerate()
        .filter(|&(_, c)| is_empty(c))
        .map(|(i, _)| i)
        .collect();
 
    let choice = possible_choices.choose(rng).unwrap();
    println!("Computer chose: {}", choice);
    let row = choice / 3;
    let col = choice % 3;
    board[row][col] = 'O';
}
 
fn draw_board(board: Board) {
    for row in &board {
        println!("{} {} {}", row[0], row[1], row[2]);
    }
}
 
 