Spaces:
Sleeping
Sleeping
| use std::fs; | |
| use std::time::Instant; | |
| use engine_rust::core::enums::Phase; | |
| use engine_rust::core::logic::{GameState, CardDatabase, ACTION_BASE_PASS}; | |
| use rand::seq::IndexedRandom; | |
| use rand::SeedableRng; | |
| use rand::prelude::StdRng; | |
| 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 json = fs::read_to_string(path).expect("Failed to read DB"); | |
| let mut db = CardDatabase::from_json(&json).expect("Failed to parse DB"); | |
| db.is_vanilla = true; | |
| return db; | |
| } | |
| panic!("cards_vanilla.json not found"); | |
| } | |
| fn load_deck(path: &str, db: &CardDatabase) -> (Vec<i32>, Vec<i32>) { | |
| let candidates = [path, &format!("../{}", path), &format!("../../{}", path)]; | |
| for candidate in &candidates { | |
| if let Ok(content) = fs::read_to_string(candidate) { | |
| let mut members = Vec::new(); | |
| let mut lives = Vec::new(); | |
| for line in content.lines() { | |
| let line = line.trim(); | |
| if line.is_empty() || line.starts_with('#') { | |
| continue; | |
| } | |
| let parts: Vec<&str> = line.split_whitespace().collect(); | |
| if parts.is_empty() { | |
| continue; | |
| } | |
| let card_no = parts[0]; | |
| let count: usize = if parts.len() >= 3 && parts[1] == "x" { | |
| parts[2].parse().unwrap_or(1) | |
| } else { | |
| 1 | |
| }; | |
| if let Some(id) = db.id_by_no(card_no) { | |
| for _ in 0..count { | |
| if db.lives.contains_key(&id) { | |
| lives.push(id); | |
| } else { | |
| members.push(id); | |
| } | |
| } | |
| } | |
| } | |
| while members.len() < 48 { | |
| if let Some(&id) = db.members.keys().next() { | |
| members.push(id); | |
| } else { | |
| break; | |
| } | |
| } | |
| while lives.len() < 12 { | |
| if let Some(&id) = db.lives.keys().next() { | |
| lives.push(id); | |
| } else { | |
| break; | |
| } | |
| } | |
| members.truncate(48); | |
| lives.truncate(12); | |
| return (members, lives); | |
| } | |
| } | |
| panic!("Could not load deck"); | |
| } | |
| fn main() { | |
| println!("\n[PROFILE] Profiling state.step() calls in random games\n"); | |
| let db = load_vanilla_db(); | |
| let (p0_members, p0_lives) = load_deck("../ai/decks/muse_cup.txt", &db); | |
| let (p1_members, p1_lives) = load_deck("../ai/decks/muse_cup.txt", &db); | |
| let energy: Vec<i32> = db.energy_db.keys().take(12).cloned().collect(); | |
| let mut state = GameState::default(); | |
| state.initialize_game( | |
| p0_members, | |
| p1_members, | |
| energy.clone(), | |
| energy, | |
| p0_lives, | |
| p1_lives, | |
| ); | |
| let mut rng = StdRng::seed_from_u64(42u64); | |
| // Suppress debug output | |
| state.ui.silent = true; | |
| println!("Advancing to first Main phase..."); | |
| while state.phase != Phase::Main && !state.is_terminal() { | |
| 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() { | |
| let &action = legal.choose(&mut rng).unwrap_or(&ACTION_BASE_PASS); | |
| let _ = state.step(&db, action); | |
| } else { | |
| let _ = state.step(&db, ACTION_BASE_PASS); | |
| } | |
| } | |
| _ => { | |
| state.auto_step(&db); | |
| } | |
| } | |
| } | |
| // Profile Main phase moves | |
| println!("TURN 1 - First Main phase profiling:\n"); | |
| let player = state.current_player; | |
| println!("Player: P{}", player); | |
| // Profile each move | |
| let mut move_count = 0; | |
| let mut total_moves_time = 0.0; | |
| while state.phase == Phase::Main { | |
| let legal = state.get_legal_action_ids(&db); | |
| if legal.is_empty() { | |
| let move_start = Instant::now(); | |
| let _ = state.step(&db, ACTION_BASE_PASS); | |
| let move_time = move_start.elapsed().as_micros() as f64 / 1000.0; | |
| println!(" Move {}: [PASS] - {:.3}ms", move_count + 1, move_time); | |
| total_moves_time += move_time; | |
| move_count += 1; | |
| break; | |
| } | |
| let &action = legal.choose(&mut rng).unwrap_or(&ACTION_BASE_PASS); | |
| let move_start = Instant::now(); | |
| if state.step(&db, action).is_err() { | |
| break; | |
| } | |
| let move_time = move_start.elapsed().as_micros() as f64 / 1000.0; | |
| move_count += 1; | |
| println!(" Move {}: action={} - {:.3}ms", move_count, action, move_time); | |
| total_moves_time += move_time; | |
| if action == ACTION_BASE_PASS { | |
| break; | |
| } | |
| } | |
| println!("\nTurn 1 Main phase: {} moves in {:.3}ms", move_count, total_moves_time); | |
| println!("Average per move: {:.3}ms", total_moves_time / move_count as f64); | |
| // Check if abilities are actually present | |
| println!("\n[CHECK] Verifying card abilities in vanilla DB:"); | |
| let mut total_abilities = 0usize; | |
| let mut cards_with_abilities = 0usize; | |
| for (_, member) in &db.members { | |
| if !member.abilities.is_empty() { | |
| total_abilities += member.abilities.len(); | |
| cards_with_abilities += 1; | |
| } | |
| } | |
| for (_, live) in &db.lives { | |
| if !live.abilities.is_empty() { | |
| total_abilities += live.abilities.len(); | |
| cards_with_abilities += 1; | |
| } | |
| } | |
| println!(" Total cards: {} members, {} lives", db.members.len(), db.lives.len()); | |
| println!(" Cards with abilities: {}", cards_with_abilities); | |
| println!(" Total abilities: {}", total_abilities); | |
| if total_abilities == 0 { | |
| println!(" ✓ Vanilla game has NO abilities (as expected)"); | |
| } else { | |
| println!(" ⚠ WARNING: Vanilla game has {} abilities!", total_abilities); | |
| } | |
| // Now profile a full turn (Main + LiveSet phases) | |
| println!("\n[FULL TURN] Now playing full turn including LiveSet...\n"); | |
| let turn_start = Instant::now(); | |
| // Skip to next Main phase | |
| while !state.is_terminal() && state.phase != Phase::Main { | |
| state.auto_step(&db); | |
| } | |
| let player2 = state.current_player; | |
| move_count = 0; | |
| total_moves_time = 0.0; | |
| while state.phase == Phase::Main { | |
| let legal = state.get_legal_action_ids(&db); | |
| if legal.is_empty() { | |
| let move_start = Instant::now(); | |
| let _ = state.step(&db, ACTION_BASE_PASS); | |
| let move_time = move_start.elapsed().as_micros() as f64 / 1000.0; | |
| total_moves_time += move_time; | |
| move_count += 1; | |
| break; | |
| } | |
| let &action = legal.choose(&mut rng).unwrap_or(&ACTION_BASE_PASS); | |
| let move_start = Instant::now(); | |
| if state.step(&db, action).is_err() { | |
| break; | |
| } | |
| let move_time = move_start.elapsed().as_micros() as f64 / 1000.0; | |
| total_moves_time += move_time; | |
| move_count += 1; | |
| if action == ACTION_BASE_PASS { | |
| break; | |
| } | |
| } | |
| // LiveSet phases | |
| let mut liveset_count = 0usize; | |
| let liveset_start = Instant::now(); | |
| while state.phase == Phase::LiveSet { | |
| let legal = state.get_legal_action_ids(&db); | |
| if !legal.is_empty() { | |
| let &action = legal.choose(&mut rng).unwrap_or(&ACTION_BASE_PASS); | |
| let _ = state.step(&db, action); | |
| liveset_count += 1; | |
| } else { | |
| break; | |
| } | |
| } | |
| let liveset_time = liveset_start.elapsed().as_millis(); | |
| // Auto-step | |
| let auto_start = Instant::now(); | |
| while !state.is_terminal() && state.phase != Phase::Main { | |
| state.auto_step(&db); | |
| } | |
| let auto_time = auto_start.elapsed().as_millis(); | |
| let turn_total = turn_start.elapsed().as_millis(); | |
| println!("TURN 2 - Full turn breakdown:"); | |
| println!(" Player: P{}", player2); | |
| println!(" Main phase: {} moves in {:.3}ms", move_count, total_moves_time); | |
| println!(" LiveSet phase: {} selections in {}ms", liveset_count, liveset_time); | |
| println!(" Auto-step phases: {}ms", auto_time); | |
| println!(" TOTAL: {}ms", turn_total); | |
| } |