Skip to content

Commit

Permalink
feat: base game args
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoDog896 committed Jun 1, 2024
1 parent fedcca0 commit da6dab3
Show file tree
Hide file tree
Showing 15 changed files with 100 additions and 51 deletions.
2 changes: 1 addition & 1 deletion crates/game-solver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ pub fn solve<T: Game<Player = ZeroSumPlayer> + 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);

Expand Down
3 changes: 1 addition & 2 deletions crates/game-solver/src/reinforcement/agent.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -13,7 +12,7 @@ pub trait Agent<S: State> {
/// 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
Expand Down
19 changes: 13 additions & 6 deletions crates/game-solver/src/reinforcement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use dfdx::prelude::*;
#[cfg(feature = "save")]

Check warning on line 3 in crates/game-solver/src/reinforcement/mod.rs

View workflow job for this annotation

GitHub Actions / deploy

unexpected `cfg` condition value: `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;
Expand All @@ -22,7 +22,7 @@ struct QNetWorkConfig<const STATE_SIZE: usize, const ACTION_SIZE: usize, const I
act1: ReLU,
linear2: LinearConstConfig<INNER_SIZE, INNER_SIZE>,
act2: ReLU,
linear3: LinearConstConfig<INNER_SIZE, ACTION_SIZE>
linear3: LinearConstConfig<INNER_SIZE, ACTION_SIZE>,
}

/// An `DQNAgentTrainer` can be trained for using a certain [Agent](mdp/trait.Agent.html). After
Expand Down Expand Up @@ -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<STATE_SIZE, ACTION_SIZE, INNER_SIZE, f32, AutoDevice> {
pub fn export_learned_values(
&self,
) -> QNetwork<STATE_SIZE, ACTION_SIZE, INNER_SIZE, f32, AutoDevice> {
self.learned_values().clone()
}

// Returns a reference to the learned state.
pub fn learned_values(&self) -> &QNetwork<STATE_SIZE, ACTION_SIZE, INNER_SIZE, f32, AutoDevice> {
pub fn learned_values(
&self,
) -> &QNetwork<STATE_SIZE, ACTION_SIZE, INNER_SIZE, f32, AutoDevice> {
&self.q_network
}

/// Imports a model, completely replacing any learned progress
pub fn import_model(&mut self, model: QNetwork<STATE_SIZE, ACTION_SIZE, INNER_SIZE, f32, AutoDevice>) {
pub fn import_model(
&mut self,
model: QNetwork<STATE_SIZE, ACTION_SIZE, INNER_SIZE, f32, AutoDevice>,
) {
self.q_network.clone_from(&model);
self.target_q_net.clone_from(&self.q_network);
}
Expand Down
9 changes: 6 additions & 3 deletions crates/game-solver/src/reinforcement/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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?")
}
}
}
1 change: 0 additions & 1 deletion crates/game-solver/src/reinforcement/strategy/explore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ pub trait ExplorationStrategy<S: State> {
fn pick_action(&self, agent: &mut dyn Agent<S>) -> S::A;
}


/// The random exploration strategy.
/// This strategy always takes a random action, as defined for the
/// Agent by
Expand Down
2 changes: 1 addition & 1 deletion crates/game-solver/src/reinforcement/strategy/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pub mod explore;
pub mod terminate;
pub mod terminate;
25 changes: 21 additions & 4 deletions crates/games-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -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),
}
}
9 changes: 5 additions & 4 deletions crates/games/src/chomp/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ChompMove>
moves: Vec<ChompMove>,
}

pub fn main(args: ChompArgs) {
Expand Down
15 changes: 12 additions & 3 deletions crates/games/src/domineering/cli.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -22,11 +26,16 @@ impl<const WIDTH: usize, const HEIGHT: usize> Display for Domineering<WIDTH, HEI
}
}

pub fn main() {
#[derive(Args)]
pub struct DomineeringArgs {
moves: Vec<String>,
}

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<usize> = arg
.split('-')
.map(|num| num.parse::<usize>().expect("Not a number!"))
Expand Down
7 changes: 4 additions & 3 deletions crates/games/src/nim/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ impl Display for Nim {
for (i, &heap) in self.heaps.iter().enumerate() {
writeln!(f, "Heap {}: {}", i, heap)?;
}

Ok(())
}
}
Expand All @@ -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<NimMove>
moves: Vec<NimMove>,
}

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::<usize>().expect("Not a number!"))
.collect::<Vec<_>>();
Expand Down
12 changes: 8 additions & 4 deletions crates/games/src/order_and_chaos/cli.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
}

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]
Expand Down
12 changes: 9 additions & 3 deletions crates/games/src/reversi/cli.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
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};

#[derive(Args)]
pub struct ReversiArgs {
/// Reversi moves, ordered as x1-y1 x2-y2 ...
#[arg(value_parser = clap::value_parser!(ReversiMove))]
moves: Vec<ReversiMove>
moves: Vec<ReversiMove>,
}

fn player_to_char(player: Option<ZeroSumPlayer>) -> char {
Expand Down
4 changes: 3 additions & 1 deletion crates/games/src/reversi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions crates/games/src/tic_tac_toe/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ 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
/// * * *
/// * * *
/// * * *
/// ```
size: usize,
/// The moves to make in the game, by dimension and index in that dimension.
moves: Vec<String>
moves: Vec<String>,
}

pub fn main(args: TicTacToeArgs) {
Expand Down
27 changes: 14 additions & 13 deletions crates/games/src/util/move_natural.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -16,26 +14,29 @@ impl<const LENGTH: usize> FromStr for NaturalMove<LENGTH> {
let numbers = s.split("-").collect::<Vec<_>>();

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::<usize>())
.collect::<Vec<_>>();

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::<Vec<_>>().try_into()

numbers
.iter()
.map(|x| x.clone().unwrap())
.collect::<Vec<_>>()
.try_into()
.map_err(|_| "Could not convert Vec to fixed array; this is a bug.".to_string())
.map(|x| NaturalMove(x))
}
Expand Down

0 comments on commit da6dab3

Please sign in to comment.