mod ai; mod board; mod console_helpers; use ai::get_best_move; use board::{Board, Coord, GameState, Tile, init_board, parse_coord}; use console_helpers::{clear_scrn, write_header}; use std::io; fn render_board(board: &Board) -> () { println!(" 1 2 3"); let mut y = 1; for row_i in 0..3 { let row: Vec = (0..3).map(|col_i| board.state[col_i][row_i]).collect(); print!("{y} "); for tile in row { print!(" {tile} ") } println!("\n"); y += 1; } } fn get_coord_input() -> Coord { loop { let mut entered = String::new(); match io::stdin().read_line(&mut entered) { Ok(_) => {} Err(_) => { println!("Couldn't read input!"); continue; } }; let coord = match parse_coord(&entered) { Ok(c) => c, Err(e) => { println!("Couldn't parse input '{entered}'! ({e})"); continue; } }; return coord; } } fn check_state(board: &Board) -> GameState { //check row let mut has_space = false; for row_i in 0..3 { let row: Vec = (0..3) .map(|col_i| { if !has_space && board.state[col_i][row_i] == Tile::Unowned { has_space = true } return board.state[col_i][row_i]; }) .collect(); if row[0] != Tile::Unowned && row[0] == row[1] && row[1] == row[2] { return match row[0] { Tile::PlayerOne => GameState::PlayerOneWin, Tile::PlayerTwo => GameState::PlayerTwoWin, Tile::Unowned => panic!("Impossible state"), }; } } //check draw if !has_space { return GameState::Draw; } //check col for col in board.state { if col[0] != Tile::Unowned && col[0] == col[1] && col[1] == col[2] { return match col[0] { Tile::PlayerOne => GameState::PlayerOneWin, Tile::PlayerTwo => GameState::PlayerTwoWin, Tile::Unowned => panic!("Impossible state"), }; } } //check diagonal if board.state[0][0] != Tile::Unowned && board.state[0][0] == board.state[1][1] && board.state[1][1] == board.state[2][2] { return match board.state[0][0] { Tile::PlayerOne => GameState::PlayerOneWin, Tile::PlayerTwo => GameState::PlayerTwoWin, Tile::Unowned => panic!("Impossible state"), }; } if board.state[2][0] != Tile::Unowned && board.state[2][0] == board.state[1][1] && board.state[1][1] == board.state[0][2] { return match board.state[2][0] { Tile::PlayerOne => GameState::PlayerOneWin, Tile::PlayerTwo => GameState::PlayerTwoWin, Tile::Unowned => panic!("Impossible state"), }; } //no wincon return GameState::InProgress; } fn main() { let mut board = init_board(); loop { // clear_scrn(); write_header("Current state"); render_board(&board); println!("Enter a move (e.g., 1,2) >"); let coord = get_coord_input(); board.set_at_coord(coord, Tile::PlayerOne); match check_state(&board) { GameState::InProgress => (), GameState::Draw => { println!("Draw!"); break; } GameState::PlayerOneWin => { println!("Player one wins!"); break; } GameState::PlayerTwoWin => { println!("Player two wins!"); break; } } clear_scrn(); //get AI move let ai_move = get_best_move(&board, Tile::PlayerTwo, true); board.set_at_coord(ai_move.coord, Tile::PlayerTwo); match check_state(&board) { GameState::InProgress => (), GameState::Draw => { println!("Draw!"); break; } GameState::PlayerOneWin => { println!("Player one wins!"); break; } GameState::PlayerTwoWin => { println!("Player two wins!"); break; } } } render_board(&board); println!("Game over!") }