Spaces:
Sleeping
Sleeping
| use engine_rust::core::logic::{CardDatabase, GameState}; | |
| use engine_rust::core::enums::Phase; | |
| use serde_json::json; | |
| use smallvec::SmallVec; | |
| use engine_rust::core::{ACTION_BASE_PASS}; | |
| use std::fs; | |
| fn main() { | |
| // Load vanilla database | |
| let db = load_vanilla_db(); | |
| let mut state = GameState::default(); | |
| // Build decks from actual cards in database | |
| let mut dummy_deck = Vec::new(); | |
| let mut dummy_lives = Vec::new(); | |
| // Collect real member cards | |
| for &id in db.members.keys() { | |
| dummy_deck.push(id); | |
| if dummy_deck.len() >= 48 { | |
| break; | |
| } | |
| } | |
| // Pad if needed | |
| while dummy_deck.len() < 48 { | |
| if let Some(&id) = db.members.keys().next() { | |
| dummy_deck.push(id); | |
| } else { | |
| break; | |
| } | |
| } | |
| // Collect real live cards | |
| for &id in db.lives.keys() { | |
| dummy_lives.push(id); | |
| if dummy_lives.len() >= 12 { | |
| break; | |
| } | |
| } | |
| // Pad if needed | |
| while dummy_lives.len() < 12 { | |
| if let Some(&id) = db.lives.keys().next() { | |
| dummy_lives.push(id); | |
| } else { | |
| break; | |
| } | |
| } | |
| // Collect energy cards | |
| let dummy_energy: Vec<i32> = db.energy_db.keys().take(12).cloned().collect(); | |
| state.initialize_game( | |
| dummy_deck.clone(), | |
| dummy_deck.clone(), | |
| dummy_energy.clone(), | |
| dummy_energy.clone(), | |
| dummy_lives.clone(), | |
| dummy_lives.clone(), | |
| ); | |
| // Step through mulligan etc to reach Main phase | |
| while state.phase != Phase::Main { | |
| let legal = state.get_legal_action_ids(&db); | |
| if !legal.is_empty() { | |
| let action = legal[0]; | |
| if state.step(&db, action).is_err() { | |
| break; | |
| } | |
| } else { | |
| break; | |
| } | |
| } | |
| // Get initial player to search from | |
| let root_player = state.current_player as usize; | |
| println!("Initial state - Current player: {}", root_player); | |
| let mut test_actions = SmallVec::<[i32; 64]>::new(); | |
| state.generate_legal_actions(&db, state.current_player as usize, &mut test_actions); | |
| println!("Legal actions at start: {} actions", test_actions.len()); | |
| println!("\nEnumerating ALL legal sequences from root state..."); | |
| // Single DFS pass: count all sequences at each depth | |
| let mut stats = SequenceStats::new(); | |
| enumerate_sequences(&state, &db, root_player, 0, &mut stats); | |
| // Output results | |
| println!("\n=== SEQUENCE ENUMERATION RESULTS ==="); | |
| println!("Total sequences explored (leaf nodes): {}", stats.total_sequences); | |
| println!("\nSequences by depth:"); | |
| for (depth, count) in &stats.by_depth { | |
| let ratio = if *depth == 0 { | |
| 1.0 | |
| } else { | |
| *count as f32 / stats.by_depth.get(&(depth - 1)).copied().unwrap_or(1) as f32 | |
| }; | |
| println!(" Depth {}: {} sequences (branching ratio from prev: {:.2}x)", depth, count, ratio); | |
| } | |
| // Compute average branching factor | |
| if stats.by_depth.len() >= 2 { | |
| let mut total_ratio = 0.0; | |
| let mut count = 0; | |
| for (depth, seq_count) in &stats.by_depth { | |
| if *depth > 0 { | |
| let prev_count = stats.by_depth.get(&(depth - 1)).copied().unwrap_or(1); | |
| if prev_count > 0 { | |
| total_ratio += *seq_count as f32 / prev_count as f32; | |
| count += 1; | |
| } | |
| } | |
| } | |
| if count > 0 { | |
| let avg_branching = total_ratio / count as f32; | |
| println!("\nAverage branching factor (seq_ratio): {:.2}x", avg_branching); | |
| } | |
| } | |
| // JSON output | |
| let json_stats = json!({ | |
| "total_sequences": stats.total_sequences, | |
| "max_depth_explored": stats.by_depth.keys().max().copied().unwrap_or(0), | |
| "by_depth": stats.by_depth, | |
| }); | |
| println!("\nJSON output:"); | |
| println!("{}", serde_json::to_string_pretty(&json_stats).unwrap()); | |
| } | |
| struct SequenceStats { | |
| total_sequences: usize, | |
| by_depth: std::collections::BTreeMap<usize, usize>, | |
| } | |
| impl SequenceStats { | |
| fn new() -> Self { | |
| Self::default() | |
| } | |
| fn record_sequence(&mut self, depth: usize) { | |
| self.total_sequences += 1; | |
| *self.by_depth.entry(depth).or_insert(0) += 1; | |
| } | |
| } | |
| /// Pure DFS: count all legal sequences without evaluation or pruning | |
| fn enumerate_sequences( | |
| state: &GameState, | |
| db: &CardDatabase, | |
| root_player: usize, | |
| depth: usize, | |
| stats: &mut SequenceStats, | |
| ) { | |
| if depth == 0 { | |
| println!("DEBUG: Entering with depth=0"); | |
| } | |
| // Base case: hard depth limit | |
| if depth > 15 { | |
| stats.record_sequence(depth); | |
| return; | |
| } | |
| // Check if current player HAS legal Main actions (excluding Pass means no real moves) | |
| let mut actions = SmallVec::<[i32; 64]>::new(); | |
| state.generate_legal_actions(db, state.current_player as usize, &mut actions); | |
| if depth == 0 { | |
| println!("DEBUG: Total actions = {}", actions.len()); | |
| } | |
| // Filter out Pass - if only Pass is legal, turn ends | |
| let main_actions: Vec<i32> = actions.into_iter() | |
| .filter(|&a| a != ACTION_BASE_PASS) | |
| .collect(); | |
| if depth == 0 { | |
| println!("DEBUG: Non-Pass actions = {}", main_actions.len()); | |
| } | |
| // If no real Main phase actions, sequence ends here | |
| if main_actions.is_empty() || state.phase != Phase::Main { | |
| if depth == 0 { | |
| println!("DEBUG: Hitting base case - main_actions.is_empty()={}, phase check would be needed", main_actions.is_empty()); | |
| } | |
| stats.record_sequence(depth); | |
| return; | |
| } | |
| if depth == 0 { | |
| println!("DEBUG: Will recurse into {} actions", main_actions.len()); | |
| } | |
| // Recursive case: try all non-Pass legal actions | |
| for action in main_actions { | |
| let mut next_state = state.clone(); | |
| if next_state.step(db, action).is_ok() { | |
| // Continue exploring | |
| enumerate_sequences(&next_state, db, root_player, depth + 1, stats); | |
| } | |
| } | |
| } | |
| 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"); | |
| } |