Skip to main content

rbp_gameplay/
perfect.rs

1//! Complete-information game history for **training time**.
2//!
3//! # Information Boundary
4//!
5//! | Type | Perspective | Context |
6//! |------|-------------|---------|
7//! | `Partial` | Hero only | Inference (strategy lookup) |
8//! | `Perfect` | Both hands | Training (CFR traversal) |
9//!
10//! During CFR training, we traverse the game tree knowing both players' cards
11//! (god's view), but strategies are indexed only by `NlheInfo` (public edges +
12//! private bucket). `Perfect` stores the complete root state needed for reach
13//! probability computation and counterfactual value calculation.
14//!
15//! # Conversions
16//!
17//! ```text
18//! Perfect::from((partial, hole))  ────►  Perfect     (add opponent info)
19//!                                 ◄────
20//! perfect.partial(hero)                              (erase opponent info)
21//!
22//! partial.histories() ─────►  Vec<(Obs, Perfect)>  (iterate all opponents)
23//! ```
24//!
25//! # Blind Handling
26//!
27//! Like `Partial`, blinds are constant and NOT stored in `actions`.
28//! The `root` field stores a POST-blind game state.
29use super::*;
30use rbp_cards::*;
31
32/// Complete game history with both players' cards known.
33///
34/// Stores root game state (POST-blind, with all cards set) and action sequence
35/// (excluding blinds). Game states are derived by applying actions to root.
36#[derive(Debug, Clone)]
37pub struct Perfect {
38    root: Game,
39    actions: Vec<Action>,
40}
41
42impl From<(&Partial, Hole)> for Perfect {
43    /// Creates history from partial with assumed opponent hole.
44    ///
45    /// Hero is derived from `partial.turn()`. The root game has:
46    /// - Hero's cards from `partial.seen()`
47    /// - Opponent's cards from `hole` parameter
48    /// - Blinds already posted (POST-blind state)
49    fn from((partial, hole): (&Partial, Hole)) -> Self {
50        debug_assert!(partial.base().n() == 2);
51        let preblind = partial.base().assume(partial.turn(), hole);
52        let root = Game::blinds()
53            .into_iter()
54            .fold(preblind, |mut g, a| g.consume(a));
55        Self {
56            root,
57            actions: partial.actions().to_vec(),
58        }
59    }
60}
61
62impl Recall for Perfect {
63    fn root(&self) -> Game {
64        self.root
65    }
66    fn actions(&self) -> &[Action] {
67        &self.actions
68    }
69}
70
71#[allow(dead_code)]
72impl Perfect {
73    /// Erases opponent information, returning hero's perspective.
74    ///
75    /// Extracts hero's hole cards and board from root, discarding
76    /// opponent's cards. Inverse of construction from `(&Partial, Hole)`.
77    fn erase(&self, hero: Turn) -> Partial {
78        let hole = self.root.seats()[hero.position()].cards();
79        let board = Hand::from(self.root.board());
80        let observation = Observation::from((Hand::from(hole), board));
81        let actions = self
82            .actions
83            .iter()
84            .filter(|a| a.is_choice())
85            .cloned()
86            .collect();
87        Partial::from((hero, observation, actions))
88    }
89}