rabukasim / engine_rust_src /src /bin /final_diagnostic.rs
trioskosmos's picture
Upload folder using huggingface_hub
463f868 verified
/// 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");
}
}