use mnk_games::gravity::GravityGame;
use mnk_games::variants::{ConnectFour, Gomoku, TicTacToe};
use mnk_games::{GameStatus, MnkGame};
use std::env::args;
use std::fmt::Display;
use std::process::exit;
use std::{error, io};
#[derive(Debug)]
enum Error {
ArgumentCount { expected: String, actual: usize },
IllegalArgument { expected: String, actual: String },
Io(io::Error),
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ArgumentCount { expected, actual } => {
write!(f, "expected {expected} argument(s), not {actual}")
}
Self::IllegalArgument { expected, actual } => {
write!(f, "expected {expected}, not {actual}")
}
Self::Io(error) => write!(f, "{error}"),
}
}
}
impl error::Error for Error {}
fn main() {
if let Err(error) = try_main() {
eprintln!("{error}");
exit(1);
}
}
fn try_main() -> Result<(), Error> {
let args: Vec<String> = args().collect();
match args.len() {
2 => match args[1].as_str() {
"tic-tac-toe" => play(TicTacToe::new),
"gomoku" => play(Gomoku::new),
"connect-four" => play_gravity(ConnectFour::new),
argument => Err(Error::IllegalArgument {
expected: "tic-tac-toe, gomoku, or connect-four".to_string(),
actual: argument.to_string(),
}),
},
n => Err(Error::ArgumentCount {
expected: "1".to_string(),
actual: n - 1,
}),
}
}
fn play<const R: usize, const C: usize, const K: usize>(
game_builder: impl FnOnce() -> MnkGame<R, C, K>,
) -> Result<(), Error> {
let mut game = game_builder();
while let GameStatus::Ongoing { next: _ } = game.status() {
println!("{game}");
request_move(&mut game)?;
println!();
}
println!("{game}");
Ok(())
}
fn request_move<const R: usize, const C: usize, const K: usize>(
game: &mut MnkGame<R, C, K>,
) -> Result<(), Error> {
println!("Your move:");
loop {
let mut input = String::new();
if let Err(error) = io::stdin().read_line(&mut input) {
return Err(Error::Io(error));
}
let coords: Vec<Result<usize, _>> = input.trim().split(' ').map(str::parse).collect();
match coords[..] {
[Ok(row), Ok(column)] => {
if let Ok(()) = game.play_at(row, column) {
return Ok(());
}
println!("Illegal move");
}
[_, _] => println!("Must enter non-negative integers"),
_ => println!("Must enter 2 coordinates"),
}
}
}
fn play_gravity<const R: usize, const C: usize, const K: usize>(
game_builder: impl FnOnce() -> GravityGame<R, C, K>,
) -> Result<(), Error> {
let mut game = game_builder();
while let GameStatus::Ongoing { next: _ } = game.status() {
println!("{game}");
request_gravity_move(&mut game)?;
println!();
}
println!("{game}");
Ok(())
}
fn request_gravity_move<const R: usize, const C: usize, const K: usize>(
game: &mut GravityGame<R, C, K>,
) -> Result<(), Error> {
println!("Your move:");
loop {
let mut input = String::new();
if let Err(error) = io::stdin().read_line(&mut input) {
return Err(Error::Io(error));
}
let parsed_input: Result<usize, _> = input.trim().parse();
if let Ok(column) = parsed_input {
if let Ok(()) = game.play_in(column) {
return Ok(());
}
println!("Illegal move");
} else {
println!("Must enter a non-negative integer column");
}
}
}