File size: 4,858 Bytes
88d4171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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 }
    }
}