use std::collections::HashMap;
use std::collections::HashSet;
use std::io;
use std::io::Write;
use open_ttt_lib::{ai, board, game};
const INSTRUCTIONS: &str = r#"
Single Player Example Game
==========================
This example shows creating a console based single player Tic Tac Toe game. A
human playing as 'X' and an AI opponent playing as 'O' take turns placing their
marks.
This example includes showing how the AI opponent views the game. The following
characters are used for the game board's display:
X - Player 'X' owns the square.
O - Player 'O' owns the square.
w - The AI opponent will win if it places its mark at this location.
l - The AI opponent could lose if it places its mark at this location.
c - This location leads to a cat's game --- neither player wins.
? - The AI opponent could not determine the outcome of this location.
Type 'exit' or press Ctrl+C to exit the example.
"#;
fn main() {
let mut game = game::Game::new();
let opponent = ai::Opponent::new(ai::Difficulty::Medium);
println!("{}", INSTRUCTIONS);
let mut exit_game = false;
while !exit_game {
match game.state() {
game::State::PlayerXMove => {
println!("\nPlayer X's turn...\n");
display_board(&game.board(), None, None);
exit_game = !do_player_move(&mut game);
}
game::State::PlayerOMove => {
println!("\nPlayer O's turn...\n");
let ai_outcomes = opponent.evaluate_game(&game);
display_board(&game.board(), None, Some(&ai_outcomes));
game.do_move(ai::best_position(&ai_outcomes).unwrap())
.unwrap();
}
game::State::PlayerXWin(winning_positions) => {
println!("\nGame Over: Player X wins!\n");
display_board(&game.board(), Some(&winning_positions), None);
println!("\n\n=== Starting Next Game ===");
game.start_next_game();
}
game::State::PlayerOWin(winning_positions) => {
println!("\nGame Over: Player O wins!\n");
display_board(&game.board(), Some(&winning_positions), None);
println!("\n\n=== Starting Next Game ===");
game.start_next_game();
}
game::State::CatsGame => {
println!("\nGame Over: cat's game.\n");
display_board(&game.board(), None, None);
println!("\n\n=== Starting Next Game ===");
game.start_next_game();
}
};
}
}
fn do_player_move(game: &mut game::Game) -> bool {
print!("\nSelect a square: ");
let input = get_user_input();
if input.to_lowercase().trim() == "exit" {
return false;
}
if let Some(position) = parse_input(&input) {
if let Err(error) = game.do_move(position) {
println!("{}", error);
}
} else {
println!(
"Invalid position of '{}' entered. Select positions using the \
column letter and and row number. Examples: 'A1' or 'B3'",
input.trim()
);
}
true
}
fn display_board(
board: &board::Board,
winning_positions: Option<&HashSet<board::Position>>,
ai_outcomes: Option<&HashMap<game::Position, ai::Outcome>>,
) {
let empty_winning_positions = HashSet::new();
let empty_ai_outcomes = HashMap::new();
assert!(board.size().columns == 3);
println!(" A B C");
for row in 0..board.size().rows {
display_row_separator(&board);
display_row_content(
&board,
row,
&winning_positions
.or(Some(&empty_winning_positions))
.unwrap(),
&ai_outcomes.or(Some(&empty_ai_outcomes)).unwrap(),
);
}
display_row_separator(&board);
}
fn display_row_separator(board: &board::Board) {
print!(" ");
for _ in 0..board.size().columns {
print!("+---");
}
println!("+");
}
fn display_row_content(
board: &board::Board,
row: i32,
winning_positions: &HashSet<board::Position>,
ai_outcomes: &HashMap<game::Position, ai::Outcome>,
) {
print!(" {} ", row + 1);
for column in 0..board.size().columns {
let position = game::Position { row, column };
let mark = match board.get(position).unwrap() {
board::Owner::PlayerX => "X",
board::Owner::PlayerO => "O",
board::Owner::None => {
if ai_outcomes.contains_key(&position) {
match ai_outcomes.get(&position).unwrap() {
ai::Outcome::Win => "w",
ai::Outcome::CatsGame => "c",
ai::Outcome::Loss => "l",
ai::Outcome::Unknown => "?",
}
} else {
" "
}
}
};
let filler = if winning_positions.contains(&position) {
"*"
} else {
" "
};
print!("|{0}{1}{0}", filler, mark);
}
println!("|");
}
fn get_user_input() -> String {
io::stdout().flush().unwrap();
let mut value = String::new();
io::stdin()
.read_line(&mut value)
.expect("Failed to read line.");
value
}
fn parse_input(value: &str) -> Option<board::Position> {
let normalized_string = value.trim().to_uppercase();
if normalized_string.len() != 2 {
return None;
}
let column = if let Some(column_char) = normalized_string.chars().next() {
match column_char {
'A' => 0,
'B' => 1,
'C' => 2,
_ => return None,
}
} else {
return None;
};
let row = if let Some(row_char) = normalized_string.chars().last() {
match row_char {
'1' => 0,
'2' => 1,
'3' => 2,
_ => return None,
}
} else {
return None;
};
Some(board::Position { row, column })
}