trioskosmos's picture
Upload folder using huggingface_hub
88d4171 verified
raw
history blame
4.86 kB
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 }
}
}