Spaces:
Running
Running
File size: 9,435 Bytes
463f868 9bd4ce5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | /// final_diagnostic.rs โ Comprehensive Turn Sequencer Performance Test
///
/// Run with: cargo run --bin final_diagnostic --release
///
/// Shows complete diagnostics for training the no-abilities variant with DFS
use std::fs;
use std::time::Instant;
use engine_rust::core::enums::Phase;
use engine_rust::core::logic::turn_sequencer::TurnSequencer;
use engine_rust::core::logic::{GameState, CardDatabase, ACTION_BASE_PASS};
use rand::SeedableRng;
use rand::seq::IndexedRandom;
fn load_vanilla_db() -> CardDatabase {
let candidates = [
"data/cards_vanilla.json",
"../data/cards_vanilla.json",
"../../data/cards_vanilla.json",
];
for path in &candidates {
if !std::path::Path::new(path).exists() {
continue;
}
let abs = std::fs::canonicalize(path)
.unwrap_or_else(|_| std::path::PathBuf::from(path));
println!("[DB] Loading from: {:?}\n", abs);
let json = fs::read_to_string(path).expect("Failed to read vanilla DB");
let mut db = CardDatabase::from_json(&json).expect("Failed to parse vanilla DB");
db.is_vanilla = true;
return db;
}
panic!("Could not find cards_vanilla.json");
}
fn fallback_deck(db: &CardDatabase) -> (Vec<i32>, Vec<i32>) {
let members: Vec<i32> = db.members.keys().take(48).cloned().collect();
let lives: Vec<i32> = db.lives.keys().take(12).cloned().collect();
(members, lives)
}
#[derive(Debug, Clone)]
struct TurnStats {
turn_num: u16,
hand_size: usize,
legal_actions: usize,
dfs_nodes: usize,
search_time_us: u128,
board_score: f32,
live_ev: f32,
total_score: f32,
}
fn run_diagnostic_turn(
state: &GameState,
db: &CardDatabase,
_rng: &mut impl rand::RngCore,
) -> TurnStats {
let p_idx = state.current_player as usize;
let hand_size = state.players[p_idx].hand.len();
let legal_actions = state.get_legal_action_ids(db).len();
let start = Instant::now();
let (_best_seq, _best_val, breakdown, total_nodes) = TurnSequencer::plan_full_turn(state, db);
let elapsed_us = start.elapsed().as_micros();
TurnStats {
turn_num: state.turn,
hand_size,
legal_actions,
dfs_nodes: total_nodes,
search_time_us: elapsed_us,
board_score: breakdown.0,
live_ev: breakdown.1,
total_score: breakdown.0 + breakdown.1,
}
}
fn main() {
println!("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
println!("โ FINAL TURN SEQUENCER DIAGNOSTIC โ");
println!("โ No-Abilities Variant for Training โ");
println!("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n");
let db = load_vanilla_db();
let (_members, _lives) = fallback_deck(&db);
let members = _members.clone();
let lives = _lives.clone();
let energy: Vec<i32> = db.energy_db.keys().take(12).cloned().collect();
println!("DB STATS:");
println!(" Members: {}", db.members.len());
println!(" Lives: {}", db.lives.len());
println!(" Energy: {}", db.energy_db.len());
println!(" Deck Size: 48 members + 12 lives\n");
let mut rng = rand::rngs::SmallRng::from_os_rng();
let mut all_stats = Vec::new();
// Run a few sample games
for game_num in 0..3 {
println!("โญโ GAME {} โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ", game_num + 1);
let mut state = GameState::default();
state.initialize_game(
members.clone(),
members.clone(),
energy.clone(),
energy.clone(),
lives.clone(),
lives.clone(),
);
state.ui.silent = true;
// Reach Main phase
let mut step = 0;
while !state.is_terminal() && state.phase != Phase::Main && step < 50 {
match state.phase {
Phase::Rps | Phase::MulliganP1 | Phase::MulliganP2 | Phase::TurnChoice | Phase::Response => {
let legal = state.get_legal_action_ids(&db);
if !legal.is_empty() {
if let Some(&action) = legal.choose(&mut rng) {
let _ = state.step(&db, action as i32);
} else {
break;
}
}
}
_ => {
state.auto_step(&db);
}
}
step += 1;
}
if state.phase != Phase::Main {
println!("โ โ ๏ธ Could not reach Main phase (stuck at {:?})", state.phase);
println!("โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ\n");
continue;
}
// Run up to 3 Main phases
let mut turn_count = 0;
let max_turns = 3;
while !state.is_terminal() && turn_count < max_turns && state.turn <= 10 {
if state.phase == Phase::Main {
let stats = run_diagnostic_turn(&state, &db, &mut rng);
all_stats.push(stats.clone());
println!("โ Turn {}.{} | Hand: {} | Legal: {} | DFS Nodes: {}",
stats.turn_num, state.current_player, stats.hand_size, stats.legal_actions, stats.dfs_nodes);
println!("โ Search: {}ฮผs | Board: {:.2} | LiveEV: {:.2} | Total: {:.2}",
stats.search_time_us, stats.board_score, stats.live_ev, stats.total_score);
turn_count += 1;
let _ = state.step(&db, ACTION_BASE_PASS);
} else {
// Auto-handle non-Main phases
match state.phase {
Phase::Rps | Phase::MulliganP1 | Phase::MulliganP2 | Phase::TurnChoice | Phase::Response => {
let legal = state.get_legal_action_ids(&db);
if !legal.is_empty() {
if let Some(&action) = legal.choose(&mut rng) {
let _ = state.step(&db, action as i32);
}
}
}
_ => {
state.auto_step(&db);
}
}
}
}
println!("โ Final Score: P0={} P1={}", state.players[0].score, state.players[1].score);
println!("โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ\n");
}
// Summary
if !all_stats.is_empty() {
println!("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
println!("โ SUMMARY STATISTICS โ");
println!("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n");
let avg_nodes: f32 = all_stats.iter().map(|s| s.dfs_nodes as f32).sum::<f32>() / all_stats.len() as f32;
let avg_time: f32 = all_stats.iter().map(|s| s.search_time_us as f32).sum::<f32>() / all_stats.len() as f32;
let avg_board: f32 = all_stats.iter().map(|s| s.board_score).sum::<f32>() / all_stats.len() as f32;
let avg_live: f32 = all_stats.iter().map(|s| s.live_ev).sum::<f32>() / all_stats.len() as f32;
println!("Turns Analyzed: {}", all_stats.len());
println!("Avg Hand Size: {:.1}", all_stats.iter().map(|s| s.hand_size as f32).sum::<f32>() / all_stats.len() as f32);
println!("Avg Legal Actions: {:.1}", all_stats.iter().map(|s| s.legal_actions as f32).sum::<f32>() / all_stats.len() as f32);
println!("\nDFS Performance:");
println!(" Avg Nodes: {:.0}", avg_nodes);
println!(" Avg Time: {:.1}ฮผs ({:.3}ms)", avg_time, avg_time / 1000.0);
println!(" Throughput: {:.0} turns/sec", 1_000_000.0 / avg_time);
println!("\nScore Breakdown:");
println!(" Avg Board Score: {:.2}", avg_board);
println!(" Avg Live EV: {:.2}", avg_live);
println!(" Avg Total: {:.2}", avg_board + avg_live);
println!("\nโ The DFS turn sequencer is suitable for fast training!");
println!(" - Exhaustive search explores all feasible combinations");
println!(" - Outputs both board state and live evaluation scores");
println!(" - No abilities โ solitaire mode โ deterministic scoring");
}
} |