diff --git a/crates/game-solver/src/lib.rs b/crates/game-solver/src/lib.rs index d8707f2..7c6cae5 100644 --- a/crates/game-solver/src/lib.rs +++ b/crates/game-solver/src/lib.rs @@ -121,7 +121,7 @@ pub fn solve + Clone + Eq + Hash>( // we're trying to guess the score of the board via null windows while alpha < beta { let med = alpha + (beta - alpha) / 2; - + // do a null window search let evaluation = negamax(game, transposition_table, med, med + 1); diff --git a/crates/game-solver/src/reinforcement/agent.rs b/crates/game-solver/src/reinforcement/agent.rs index 3af3fa6..9c55312 100644 --- a/crates/game-solver/src/reinforcement/agent.rs +++ b/crates/game-solver/src/reinforcement/agent.rs @@ -1,4 +1,3 @@ - use super::state::State; /// An `Agent` is something which hold a certain state, and is able to take actions from that @@ -13,7 +12,7 @@ pub trait Agent { /// determine the action to be taken. fn pick_random_action(&mut self) -> S::A { let action = self.current_state().random_action(); - + self.take_action(&action); action diff --git a/crates/game-solver/src/reinforcement/mod.rs b/crates/game-solver/src/reinforcement/mod.rs index 3aacf8d..773b4da 100644 --- a/crates/game-solver/src/reinforcement/mod.rs +++ b/crates/game-solver/src/reinforcement/mod.rs @@ -3,14 +3,14 @@ use dfdx::prelude::*; #[cfg(feature = "save")] use dfdx::safetensors::SafeTensorError; -use self::state::State; use self::agent::Agent; +use self::state::State; use self::strategy::explore::ExplorationStrategy; use self::strategy::terminate::TerminationStrategy; -pub mod state; pub mod agent; +pub mod state; pub mod strategy; const BATCH: usize = 64; @@ -22,7 +22,7 @@ struct QNetWorkConfig, act2: ReLU, - linear3: LinearConstConfig + linear3: LinearConstConfig, } /// An `DQNAgentTrainer` can be trained for using a certain [Agent](mdp/trait.Agent.html). After @@ -115,17 +115,24 @@ where } /// Returns a clone of the entire learned state to be saved or used elsewhere. - pub fn export_learned_values(&self) -> QNetwork { + pub fn export_learned_values( + &self, + ) -> QNetwork { self.learned_values().clone() } // Returns a reference to the learned state. - pub fn learned_values(&self) -> &QNetwork { + pub fn learned_values( + &self, + ) -> &QNetwork { &self.q_network } /// Imports a model, completely replacing any learned progress - pub fn import_model(&mut self, model: QNetwork) { + pub fn import_model( + &mut self, + model: QNetwork, + ) { self.q_network.clone_from(&model); self.target_q_net.clone_from(&self.q_network); } diff --git a/crates/game-solver/src/reinforcement/state.rs b/crates/game-solver/src/reinforcement/state.rs index 14167a0..767c707 100644 --- a/crates/game-solver/src/reinforcement/state.rs +++ b/crates/game-solver/src/reinforcement/state.rs @@ -9,7 +9,7 @@ pub trait State: Eq + Hash + Clone { type A: Eq + Hash + Clone; /// The reward for when an `Agent` arrives at this `State`. - /// + /// /// Rewards are relative to each other, and are traditionally smaller integers. fn reward(&self) -> f64; /// The set of actions that can be taken from this `State`, to arrive in another `State`. @@ -19,6 +19,9 @@ pub trait State: Eq + Hash + Clone { /// to improve the performance by only generating the necessary action. fn random_action(&self) -> Self::A { let actions = self.actions(); - actions.choose(&mut rand::thread_rng()).cloned().expect("No actions available; perhaps use the SinkStates termination strategy?") + actions + .choose(&mut rand::thread_rng()) + .cloned() + .expect("No actions available; perhaps use the SinkStates termination strategy?") } -} \ No newline at end of file +} diff --git a/crates/game-solver/src/reinforcement/strategy/explore.rs b/crates/game-solver/src/reinforcement/strategy/explore.rs index 4535661..4f95d4c 100644 --- a/crates/game-solver/src/reinforcement/strategy/explore.rs +++ b/crates/game-solver/src/reinforcement/strategy/explore.rs @@ -7,7 +7,6 @@ pub trait ExplorationStrategy { fn pick_action(&self, agent: &mut dyn Agent) -> S::A; } - /// The random exploration strategy. /// This strategy always takes a random action, as defined for the /// Agent by diff --git a/crates/game-solver/src/reinforcement/strategy/mod.rs b/crates/game-solver/src/reinforcement/strategy/mod.rs index b5b7974..05498d0 100644 --- a/crates/game-solver/src/reinforcement/strategy/mod.rs +++ b/crates/game-solver/src/reinforcement/strategy/mod.rs @@ -1,2 +1,2 @@ pub mod explore; -pub mod terminate; \ No newline at end of file +pub mod terminate; diff --git a/crates/games-cli/src/main.rs b/crates/games-cli/src/main.rs index d6b9a63..6b9c6d3 100644 --- a/crates/games-cli/src/main.rs +++ b/crates/games-cli/src/main.rs @@ -1,22 +1,39 @@ use clap::{Parser, Subcommand}; -use games::{reversi::cli::main as reversi_main, reversi::cli::ReversiArgs}; +use games::{ + chomp::cli::{main as chomp_main, ChompArgs}, + domineering::cli::{main as domineering_main, DomineeringArgs}, + nim::cli::{main as nim_main, NimArgs}, + order_and_chaos::cli::{main as order_and_chaos_main, OrderAndChaosArgs}, + reversi::cli::{main as reversi_main, ReversiArgs}, + tic_tac_toe::cli::{main as tic_tac_toe_main, TicTacToeArgs}, +}; #[derive(Parser)] #[command(version, about, long_about = None)] struct Cli { #[command(subcommand)] - command: Commands + command: Commands, } #[derive(Subcommand)] enum Commands { - Reversi(ReversiArgs) + Reversi(ReversiArgs), + TicTacToe(TicTacToeArgs), + OrderAndChaos(OrderAndChaosArgs), + Nim(NimArgs), + Domineering(DomineeringArgs), + Chomp(ChompArgs), } fn main() { let cli = Cli::parse(); match cli.command { - Commands::Reversi(args) => reversi_main(args) + Commands::Reversi(args) => reversi_main(args), + Commands::TicTacToe(args) => tic_tac_toe_main(args), + Commands::OrderAndChaos(args) => order_and_chaos_main(args), + Commands::Nim(args) => nim_main(args), + Commands::Domineering(args) => domineering_main(args), + Commands::Chomp(args) => chomp_main(args), } } diff --git a/crates/games/src/chomp/cli.rs b/crates/games/src/chomp/cli.rs index c7d0ed6..dfc0648 100644 --- a/crates/games/src/chomp/cli.rs +++ b/crates/games/src/chomp/cli.rs @@ -9,14 +9,15 @@ 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 + #[arg(default_value_t = 6)] width: usize, + /// The height of the game + #[arg(default_value_t = 4)] + height: usize, /// Chomp moves, ordered as x1-y1 x2-y2 ... #[arg(value_parser = clap::value_parser!(ChompMove))] - moves: Vec + moves: Vec, } pub fn main(args: ChompArgs) { diff --git a/crates/games/src/domineering/cli.rs b/crates/games/src/domineering/cli.rs index c168399..f7ec2ff 100644 --- a/crates/games/src/domineering/cli.rs +++ b/crates/games/src/domineering/cli.rs @@ -1,5 +1,9 @@ -use std::{collections::HashMap, env::args, fmt::{Display, Formatter}}; +use std::{ + collections::HashMap, + fmt::{Display, Formatter}, +}; +use clap::Args; use game_solver::{game::Game, move_scores}; use crate::domineering::DomineeringGame; @@ -22,11 +26,16 @@ impl Display for Domineering, +} + +pub fn main(args: DomineeringArgs) { let mut game = DomineeringGame::new(); // parse every move in args, e.g. 0-0 1-1 in args - args().skip(1).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/nim/cli.rs b/crates/games/src/nim/cli.rs index 039fd79..a8f1a9b 100644 --- a/crates/games/src/nim/cli.rs +++ b/crates/games/src/nim/cli.rs @@ -12,7 +12,7 @@ impl Display for Nim { for (i, &heap) in self.heaps.iter().enumerate() { writeln!(f, "Heap {}: {}", i, heap)?; } - + Ok(()) } } @@ -25,13 +25,14 @@ pub struct NimArgs { configuration: String, /// Nim moves, ordered as x1-y1 x2-y2 ... #[arg(value_parser = clap::value_parser!(NimMove))] - moves: Vec + 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.configuration + let config = args + .configuration .split(',') .map(|num| num.parse::().expect("Not a number!")) .collect::>(); diff --git a/crates/games/src/order_and_chaos/cli.rs b/crates/games/src/order_and_chaos/cli.rs index e2185af..830b8cc 100644 --- a/crates/games/src/order_and_chaos/cli.rs +++ b/crates/games/src/order_and_chaos/cli.rs @@ -1,15 +1,19 @@ -use std::env::args; - +use clap::Args; use game_solver::{game::Game, par_move_scores}; use crate::order_and_chaos::{CellType, OrderAndChaos}; -pub fn main() { +#[derive(Args)] +pub struct OrderAndChaosArgs { + moves: Vec, +} + +pub fn main(args: OrderAndChaosArgs) { // create a new game of Nim with the given configuration let mut game = OrderAndChaos::new(); // parse every move in args, e.g. 0-0-x 1-1-o in args - args().skip(1).for_each(|arg| { + args.moves.iter().for_each(|arg| { let args: Vec<&str> = arg.split('-').collect(); let numbers = args[0..2] diff --git a/crates/games/src/reversi/cli.rs b/crates/games/src/reversi/cli.rs index 6630ad3..85217b5 100644 --- a/crates/games/src/reversi/cli.rs +++ b/crates/games/src/reversi/cli.rs @@ -1,8 +1,14 @@ use std::fmt; +use crate::{ + reversi::{Reversi, ReversiMove}, + util::move_natural::NaturalMove, +}; use clap::Args; -use crate::{reversi::{Reversi, ReversiMove}, util::move_natural::NaturalMove}; -use game_solver::{game::{Game, ZeroSumPlayer}, par_move_scores}; +use game_solver::{ + game::{Game, ZeroSumPlayer}, + par_move_scores, +}; use super::{HEIGHT, WIDTH}; @@ -10,7 +16,7 @@ use super::{HEIGHT, WIDTH}; pub struct ReversiArgs { /// Reversi moves, ordered as x1-y1 x2-y2 ... #[arg(value_parser = clap::value_parser!(ReversiMove))] - moves: Vec + moves: Vec, } fn player_to_char(player: Option) -> char { diff --git a/crates/games/src/reversi/mod.rs b/crates/games/src/reversi/mod.rs index 660d698..5df39cd 100644 --- a/crates/games/src/reversi/mod.rs +++ b/crates/games/src/reversi/mod.rs @@ -170,7 +170,9 @@ impl Game for Reversi { self.board.set(m.0[0], m.0[1], Some(self.player())).unwrap(); for idx in move_set { - self.board.set(idx.0[0], idx.0[1], Some(self.player())).unwrap(); + self.board + .set(idx.0[0], idx.0[1], Some(self.player())) + .unwrap(); } self.move_count += 1; diff --git a/crates/games/src/tic_tac_toe/cli.rs b/crates/games/src/tic_tac_toe/cli.rs index 7d2b391..17422d1 100644 --- a/crates/games/src/tic_tac_toe/cli.rs +++ b/crates/games/src/tic_tac_toe/cli.rs @@ -10,7 +10,7 @@ pub struct TicTacToeArgs { dimensions: usize, /// The size of the board - i.e. with two dimensions /// and a size of three, the board would look like - /// + /// /// ```txt /// * * * /// * * * @@ -18,7 +18,7 @@ pub struct TicTacToeArgs { /// ``` size: usize, /// The moves to make in the game, by dimension and index in that dimension. - moves: Vec + moves: Vec, } pub fn main(args: TicTacToeArgs) { diff --git a/crates/games/src/util/move_natural.rs b/crates/games/src/util/move_natural.rs index 79db4ff..3555a60 100644 --- a/crates/games/src/util/move_natural.rs +++ b/crates/games/src/util/move_natural.rs @@ -1,5 +1,3 @@ -// TODO: make generic as some n-tuple move (macro generation / dynamic type?) - use std::{fmt::Display, iter, str::FromStr}; use itertools::Itertools; @@ -16,26 +14,29 @@ impl FromStr for NaturalMove { let numbers = s.split("-").collect::>(); if numbers.len() != LENGTH { - return Err( - format!( - "Must be {} numbers separated by a hyphen ({})", - LENGTH, - iter::repeat("x").take(LENGTH).join("-") - ) - ); + return Err(format!( + "Must be {} numbers separated by a hyphen ({})", + LENGTH, + iter::repeat("x").take(LENGTH).join("-") + )); } - let numbers = numbers.iter() + let numbers = numbers + .iter() .map(|num| num.parse::()) .collect::>(); if let Some((position, _)) = numbers.iter().find_position(|x| x.is_err()) { let ordinal = ordinal::Ordinal(position + 1).to_string(); - + return Err(format!("The {} number is not a number.", ordinal)); } - - numbers.iter().map(|x| x.clone().unwrap()).collect::>().try_into() + + numbers + .iter() + .map(|x| x.clone().unwrap()) + .collect::>() + .try_into() .map_err(|_| "Could not convert Vec to fixed array; this is a bug.".to_string()) .map(|x| NaturalMove(x)) }