use crate::core::logic::{GameState, CardDatabase}; pub trait Heuristic: Send + Sync { fn evaluate(&self, state: &GameState, db: &CardDatabase, p0_baseline: u32, p1_baseline: u32) -> f32; } pub struct OriginalHeuristic; impl Heuristic for OriginalHeuristic { fn evaluate(&self, state: &GameState, db: &CardDatabase, p0_baseline: u32, p1_baseline: u32) -> f32 { let score0 = self.evaluate_player(state, db, 0, p0_baseline); let score1 = self.evaluate_player(state, db, 1, p1_baseline); // Zero-sum mapping from difference to [0.0, 1.0] ((score0 - score1) + 1.0) / 2.0 } } impl OriginalHeuristic { fn evaluate_player(&self, state: &GameState, db: &CardDatabase, p_idx: usize, baseline_score: u32) -> f32 { let p = &state.players[p_idx]; let mut score = 0.0; // 1. Success Lives (Goal) - 0.5 per live score += p.success_lives.len() as f32 * 0.5; // 2. Bonus for clearing a live this turn - Significant reward for immediate progress if p.success_lives.len() > baseline_score as usize { score += 0.4; // Increased from 0.3 } // 3. Power on Board (Capabilities) - 0.02 per blade let mut my_power = 0; for i in 0..3 { my_power += state.get_effective_blades(p_idx, i, db); } score += my_power as f32 * 0.02; // Increased from 0.01 // 4. Energy Zone (Fuel) - 0.05 per energy // This was previously missing entirely. score += p.energy_zone.len() as f32 * 0.05; // 5. Board Hearts / Proximity let mut stage_hearts = [0u32; 7]; for i in 0..3 { let h = state.get_effective_hearts(p_idx, i, db); for color in 0..7 { stage_hearts[color] += h[color] as u32; } } // Conservative Yell + Volume Icons estimation let mut expected_hearts = stage_hearts; expected_hearts[6] += (my_power as f32 * 0.1) as u32; let mut num_lives = 0; let mut zone_reqs = [0u32; 7]; for &cid in &p.live_zone { if cid >= 0 { if let Some(l) = db.get_live(cid as u16) { num_lives += 1; for h in 0..7 { zone_reqs[h] += l.required_hearts[h] as u32; } } } } if num_lives > 0 { let p_val_yell = self.calculate_proximity_u32(&expected_hearts, &zone_reqs); let p_val_board = self.calculate_proximity_u32(&stage_hearts, &zone_reqs); if p_val_board >= 0.999 { // Guaranteed. Penalize redundancy (encourage playing lives if ready). score += 0.4 - (num_lives as f32 - 1.0) * 0.1; } else if p_val_yell >= 0.999 { // Soft guarantee via Yells. score += 0.15; } else { // Reward based on proximity (squared to favor being closer). score += (p_val_yell * p_val_yell) * 0.3; } } // 6. Hand Value (Long term potential) let is_mulligan = matches!(state.phase, crate::core::logic::Phase::MulliganP1 | crate::core::logic::Phase::MulliganP2); for (i, &cid) in p.hand.iter().enumerate() { if is_mulligan && ((p.mulligan_selection >> i) & 1 == 1) { // If selected for mulligan, value slightly less than average draw score += 0.04; } else if let Some(l) = db.get_live(cid) { // Live cards in hand are valuable if we can clear them score += (self.calculate_proximity_u32(&stage_hearts, &l.required_hearts.map(|v| v as u32)) * 0.1).max(0.04); } else { // Member cards in hand are generally good (increased from 0.03) score += 0.06; } } score } fn calculate_proximity_u32(&self, pool: &[u32; 7], req: &[u32; 7]) -> f32 { let mut pool_clone = *pool; let (sat, tot) = crate::core::hearts::process_hearts(&mut pool_clone, req); if tot == 0 { return 1.0; } (sat as f32 / tot as f32).clamp(0.0, 1.0) } } pub struct SimpleHeuristic; impl Heuristic for SimpleHeuristic { fn evaluate(&self, state: &GameState, _db: &CardDatabase, _p0_baseline: u32, _p1_baseline: u32) -> f32 { let p0 = &state.players[0]; let p1 = &state.players[1]; let score0 = p0.success_lives.len() as f32 * 10.0 + p0.energy_zone.len() as f32 * 0.5 + p0.hand.len() as f32 * 0.1; let score1 = p1.success_lives.len() as f32 * 10.0 + p1.energy_zone.len() as f32 * 0.5 + p1.hand.len() as f32 * 0.1; if score0 > score1 { 0.6 } else if score1 > score0 { 0.4 } else { 0.5 } } }