Spaces:
Running
Running
| 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 } | |
| } | |
| } | |