diff --git a/balance-tool/Cargo.toml b/balance-tool/Cargo.toml new file mode 100644 index 00000000..b86831cc --- /dev/null +++ b/balance-tool/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "balance-tool" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "balance-tool" +path = "src/main.rs" + +[dependencies] +mc-ai = { path = "../crates/mc-ai" } +mc-balance = { path = "../crates/mc-balance" } +mc-sim = { path = "../crates/mc-sim" } +serde_json.workspace = true diff --git a/balance-tool/src/main.rs b/balance-tool/src/main.rs new file mode 100644 index 00000000..12690463 --- /dev/null +++ b/balance-tool/src/main.rs @@ -0,0 +1,53 @@ +//! Balance discovery tool — enumerates the strategy space and reports dominant strategies. +//! +//! Usage: balance-tool [--races dwarf,human,orc,high_elf] [--steps 1,3,5] [--threshold 0.70] +//! +use mc_balance::{ + report::DominanceReport, + strategy::StrategyGrid, + tournament::Tournament, +}; +use mc_sim::GameRunner; + +fn main() { + // TODO: parse CLI args (clap). Hardcoded defaults for now. + let races = vec![ + "dwarf".to_string(), + "human".to_string(), + "orc".to_string(), + "high_elf".to_string(), + ]; + let axis_steps = vec![1u8, 3, 5]; + let dominance_threshold = 0.70_f32; + let games_per_pair = 4_u32; + let max_turns = 100_u32; + + let grid = StrategyGrid::new(races).with_axis_steps(axis_steps); + let total = grid.size(); + + eprintln!("Generating {total} strategy configurations..."); + let strategies = grid.generate(); + + // Pairs = n*(n-1)/2. With 4 races × 243 configs = 972 strategies → ~472k pairs. + // Each pair plays games_per_pair games → ~1.9M games total. + // With stub runner this is instant; with real mc-sim expect ~hours on CPU. + let pairs = total * (total - 1) / 2; + eprintln!( + "Running {pairs} pairs × {games_per_pair} games = {} total games...", + pairs * games_per_pair as usize, + ); + + let runner = GameRunner::default(); + let tournament = Tournament::new(games_per_pair, max_turns); + let records = tournament.run(&strategies, &runner); + + let report = DominanceReport::from_records(&records, dominance_threshold); + print!("{}", report.summary()); + + // JSON dump for further analysis. + if let Ok(json) = serde_json::to_string_pretty(&report) { + std::fs::write("balance_report.json", json) + .expect("failed to write balance_report.json"); + eprintln!("Full report written to balance_report.json"); + } +}