From fedcca05a4ab5038b6906bbb898c89f23264d098 Mon Sep 17 00:00:00 2001 From: Tristan F <26509014+LeoDog896@users.noreply.github.com> Date: Mon, 27 May 2024 21:34:52 -0400 Subject: [PATCH] refactor: make most games use natural move --- crates/games/src/chomp/cli.rs | 32 +++++++++----- crates/games/src/chomp/mod.rs | 62 ++++++++++++++------------- crates/games/src/domineering/cli.rs | 20 ++++++++- crates/games/src/domineering/mod.rs | 21 +-------- crates/games/src/nim/cli.rs | 34 +++++++++------ crates/games/src/tic_tac_toe/cli.rs | 38 ++++++++-------- crates/games/src/util/move_natural.rs | 2 +- 7 files changed, 116 insertions(+), 93 deletions(-) diff --git a/crates/games/src/chomp/cli.rs b/crates/games/src/chomp/cli.rs index 85786e6..c7d0ed6 100644 --- a/crates/games/src/chomp/cli.rs +++ b/crates/games/src/chomp/cli.rs @@ -1,20 +1,30 @@ -use std::{collections::HashMap, env::args}; +use std::collections::HashMap; +use clap::Args; use game_solver::{game::Game, move_scores}; use crate::chomp::Chomp; -pub fn main() { - let mut game = Chomp::new(6, 4); +use super::ChompMove; + +#[derive(Args)] +pub struct ChompArgs { + // TODO: width default = 6, height default = 4 + /// The height of the game + height: usize, + /// The width of the game + width: usize, + /// Chomp moves, ordered as x1-y1 x2-y2 ... + #[arg(value_parser = clap::value_parser!(ChompMove))] + moves: Vec +} - // parse every move in args, e.g. 0-0 1-1 in args - args().skip(1).for_each(|arg| { - let numbers: Vec = arg - .split('-') - .map(|num| num.parse::().expect("Not a number!")) - .collect(); +pub fn main(args: ChompArgs) { + let mut game = Chomp::new(args.width, args.height); - game.make_move(&(numbers[0], numbers[1])); + // parse every move in args, e.g. 0-0 1-1 in args + args.moves.iter().for_each(|arg| { + game.make_move(arg); }); print!("{}", game); @@ -34,7 +44,7 @@ pub fn main() { println!("\n\nBest moves @ score {}:", score); current_move_score = Some(score); } - print!("({}, {}), ", game_move.0, game_move.1); + print!("({}), ", game_move); } println!(); } diff --git a/crates/games/src/chomp/mod.rs b/crates/games/src/chomp/mod.rs index fc15a22..2a2ccd3 100644 --- a/crates/games/src/chomp/mod.rs +++ b/crates/games/src/chomp/mod.rs @@ -2,7 +2,7 @@ //! The bottom right square is poisoned, and the players take turns eating squares. //! Every square they eat, every square to the right and above it is also eaten (inclusively) //! -//! This is a flipped version of the traiditional [Chomp](https://en.wikipedia.org/wiki/Chomp) game. +//! This is a flipped version of the traditional [Chomp](https://en.wikipedia.org/wiki/Chomp) game. //! //! This is not the best example for analysis via a combinatorial game, as not only is it //! impartial (making it analyzable via the Sprague-Grundy theorem), but it is also trivially @@ -21,6 +21,8 @@ use std::{ hash::Hash, }; +use crate::util::move_natural::NaturalMove; + #[derive(Clone, Hash, Eq, PartialEq)] struct Chomp { width: usize, @@ -44,8 +46,10 @@ impl Chomp { } } +pub type ChompMove = NaturalMove<2>; + impl Game for Chomp { - type Move = (usize, usize); + type Move = ChompMove; type Iter<'a> = std::vec::IntoIter; type Player = ZeroSumPlayer; @@ -66,9 +70,9 @@ impl Game for Chomp { } fn make_move(&mut self, m: &Self::Move) -> bool { - if *self.board.get(m.0, m.1).unwrap() { - for i in m.0..self.width { - for j in 0..=m.1 { + if *self.board.get(m.0[0], m.0[1]).unwrap() { + for i in m.0[0]..self.width { + for j in 0..=m.0[1] { self.board.set(i, j, false).unwrap(); } } @@ -84,7 +88,7 @@ impl Game for Chomp { for i in (0..self.height).rev() { for j in 0..self.width { if *self.board.get(j, i).unwrap() { - moves.push((j, i)); + moves.push(NaturalMove([j, i])); } } } @@ -137,29 +141,29 @@ mod tests { move_scores.sort(); let mut new_scores = vec![ - ((2, 2), 13), - ((5, 0), -12), - ((4, 0), -12), - ((3, 0), -12), - ((2, 0), -12), - ((0, 0), -12), - ((5, 1), -12), - ((4, 1), -12), - ((3, 1), -12), - ((2, 1), -12), - ((0, 1), -12), - ((5, 2), -12), - ((4, 2), -12), - ((3, 2), -12), - ((5, 3), -12), - ((1, 0), -16), - ((1, 1), -16), - ((1, 2), -16), - ((4, 3), -16), - ((3, 3), -16), - ((2, 3), -16), - ((0, 2), -22), - ((1, 3), -22), + (NaturalMove([2, 2]), 13), + (NaturalMove([5, 0]), -12), + (NaturalMove([4, 0]), -12), + (NaturalMove([3, 0]), -12), + (NaturalMove([2, 0]), -12), + (NaturalMove([0, 0]), -12), + (NaturalMove([5, 1]), -12), + (NaturalMove([4, 1]), -12), + (NaturalMove([3, 1]), -12), + (NaturalMove([2, 1]), -12), + (NaturalMove([0, 1]), -12), + (NaturalMove([5, 2]), -12), + (NaturalMove([4, 2]), -12), + (NaturalMove([3, 2]), -12), + (NaturalMove([5, 3]), -12), + (NaturalMove([1, 0]), -16), + (NaturalMove([1, 1]), -16), + (NaturalMove([1, 2]), -16), + (NaturalMove([4, 3]), -16), + (NaturalMove([3, 3]), -16), + (NaturalMove([2, 3]), -16), + (NaturalMove([0, 2]), -22), + (NaturalMove([1, 3]), -22), ]; new_scores.sort(); diff --git a/crates/games/src/domineering/cli.rs b/crates/games/src/domineering/cli.rs index 5157695..c168399 100644 --- a/crates/games/src/domineering/cli.rs +++ b/crates/games/src/domineering/cli.rs @@ -1,9 +1,27 @@ -use std::{collections::HashMap, env::args}; +use std::{collections::HashMap, env::args, fmt::{Display, Formatter}}; use game_solver::{game::Game, move_scores}; use crate::domineering::DomineeringGame; +use super::Domineering; + +impl Display for Domineering { + fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { + for i in 0..HEIGHT { + for j in 0..WIDTH { + if *self.board.get(j, i).unwrap() { + write!(f, "X")?; + } else { + write!(f, ".")?; + } + } + writeln!(f)?; + } + Ok(()) + } +} + pub fn main() { let mut game = DomineeringGame::new(); diff --git a/crates/games/src/domineering/mod.rs b/crates/games/src/domineering/mod.rs index 1ffff88..0211827 100644 --- a/crates/games/src/domineering/mod.rs +++ b/crates/games/src/domineering/mod.rs @@ -9,10 +9,7 @@ pub mod cli; use array2d::Array2D; use game_solver::game::{Game, ZeroSumPlayer}; -use std::{ - fmt::{Display, Formatter}, - hash::Hash, -}; +use std::hash::Hash; #[derive(Clone, Hash, Eq, PartialEq)] struct Domineering { @@ -111,22 +108,6 @@ impl Game for Domineering Display for Domineering { - fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { - for i in 0..HEIGHT { - for j in 0..WIDTH { - if *self.board.get(j, i).unwrap() { - write!(f, "X")?; - } else { - write!(f, ".")?; - } - } - writeln!(f)?; - } - Ok(()) - } -} - // n, m type DomineeringGame = Domineering<5, 5>; diff --git a/crates/games/src/nim/cli.rs b/crates/games/src/nim/cli.rs index 65bbff3..039fd79 100644 --- a/crates/games/src/nim/cli.rs +++ b/crates/games/src/nim/cli.rs @@ -1,24 +1,37 @@ -use std::{env::args, fmt::{Display, Formatter}}; +use std::fmt::{Display, Formatter}; +use clap::Args; use game_solver::{game::Game, par_move_scores}; -use crate::{nim::Nim, util::move_natural::NaturalMove}; +use crate::nim::Nim; + +use super::NimMove; impl Display for Nim { fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { for (i, &heap) in self.heaps.iter().enumerate() { writeln!(f, "Heap {}: {}", i, heap)?; } + Ok(()) } } -pub fn main() { +#[derive(Args)] +pub struct NimArgs { + /// The configuration of the game. For example, 3,5,7 + /// creates a Nimbers game that has three heaps, where each + /// heap has 3, 5, and 7 objects respectively + configuration: String, + /// Nim moves, ordered as x1-y1 x2-y2 ... + #[arg(value_parser = clap::value_parser!(NimMove))] + moves: Vec +} + +pub fn main(args: NimArgs) { // parse the original configuration of the game from args // e.g. 3,5,7 for 3 heaps with 3, 5, and 7 objects respectively - let config = args() - .nth(1) - .expect("Please provide a configuration of the game, e.g. 3,5,7") + let config = args.configuration .split(',') .map(|num| num.parse::().expect("Not a number!")) .collect::>(); @@ -27,13 +40,8 @@ pub fn main() { let mut game = Nim::new(config); // parse every move in args, e.g. 0-0 1-1 in args - args().skip(2).for_each(|arg| { - let numbers: Vec = arg - .split('-') - .map(|num| num.parse::().expect("Not a number!")) - .collect(); - - game.make_move(&NaturalMove([numbers[0], numbers[1]])); + args.moves.iter().for_each(|nim_move| { + game.make_move(&nim_move); }); print!("{}", game); diff --git a/crates/games/src/tic_tac_toe/cli.rs b/crates/games/src/tic_tac_toe/cli.rs index 0b70745..7d2b391 100644 --- a/crates/games/src/tic_tac_toe/cli.rs +++ b/crates/games/src/tic_tac_toe/cli.rs @@ -1,29 +1,31 @@ -use std::env::args; - +use clap::Args; use game_solver::{game::Game, par_move_scores}; use ndarray::IntoDimension; use crate::tic_tac_toe::{format_dim, TicTacToe}; -pub fn main() { - // get the amount of dimensions from the first argument - let dim = args() - .nth(1) - .expect("Please provide a dimension!") - .parse::() - .expect("Not a number!"); - - // get the size of the board from the second argument - let size = args() - .nth(2) - .expect("Please provide a game size") - .parse::() - .expect("Not a number!"); +#[derive(Args)] +pub struct TicTacToeArgs { + /// The amount of dimensions in the game. + dimensions: usize, + /// The size of the board - i.e. with two dimensions + /// and a size of three, the board would look like + /// + /// ```txt + /// * * * + /// * * * + /// * * * + /// ``` + size: usize, + /// The moves to make in the game, by dimension and index in that dimension. + moves: Vec +} - let mut game = TicTacToe::new(dim, size); +pub fn main(args: TicTacToeArgs) { + let mut game = TicTacToe::new(args.dimensions, args.size); // parse every move in args, e.g. 0-0 1-1 in args - args().skip(3).for_each(|arg| { + args.moves.iter().for_each(|arg| { let numbers: Vec = arg .split('-') .map(|num| num.parse::().expect("Not a number!")) diff --git a/crates/games/src/util/move_natural.rs b/crates/games/src/util/move_natural.rs index 38b4db5..79db4ff 100644 --- a/crates/games/src/util/move_natural.rs +++ b/crates/games/src/util/move_natural.rs @@ -4,7 +4,7 @@ use std::{fmt::Display, iter, str::FromStr}; use itertools::Itertools; -#[derive(Clone, Debug, Copy, PartialEq)] +#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct NaturalMove(pub [usize; LENGTH]); impl FromStr for NaturalMove {