Skip to main content

rbp_gameplay/
recall.rs

1//! Trait for game history types that can replay actions.
2//!
3//! Both [`Perfect`] and [`Partial`] represent game histories from different
4//! perspectives (complete vs hero-only information). This trait captures
5//! their shared interface for action replay, state reconstruction, and
6//! edge conversion.
7//!
8//! # Blind Handling
9//!
10//! Blinds are constant and deterministic, so they are NOT stored in `actions()`.
11//! The `root()` method returns a POST-blind game state. Use `all_actions()`
12//! when you need the complete action sequence including blinds (e.g., for display).
13use super::*;
14
15/// A game history that can be replayed from a root state.
16///
17/// Provides default implementations for derived computations:
18/// - `head()` — Current game state
19/// - `states()` — Full sequence of game states
20/// - `history()` — Full edge history (all streets)
21/// - `subgame()` — Current street edges only
22/// - `choices()` — Available actions at current state
23/// - `aggression()` — Trailing aggressive action count
24/// - `complete()` — Complete action sequence including blinds (for display)
25pub trait Recall {
26    /// The starting game state for replaying actions (POST-blind).
27    fn root(&self) -> Game;
28
29    /// The action sequence from root to current state (excludes blinds).
30    fn actions(&self) -> &[Action];
31
32    /// Complete action sequence including blinds (for client display).
33    fn complete(&self) -> Vec<Action> {
34        Game::blinds()
35            .into_iter()
36            .chain(self.actions().iter().copied())
37            .collect()
38    }
39
40    /// Current game state (replay actions from root).
41    fn head(&self) -> Game {
42        self.actions()
43            .iter()
44            .copied()
45            .fold(self.root(), |mut g, a| g.consume(a))
46    }
47
48    /// Sequence of game states from root to head.
49    fn states(&self) -> Vec<Game> {
50        let root = self.root();
51        let acts = self
52            .actions()
53            .iter()
54            .copied()
55            .scan(root, |g, a| Some(g.consume(a)))
56            .collect::<Vec<Game>>();
57        std::iter::once(root).chain(acts).collect()
58    }
59
60    /// Current aggression (trailing aggressive actions on current street).
61    fn aggression(&self) -> usize {
62        self.actions()
63            .iter()
64            .rev()
65            .take_while(|a| a.is_choice())
66            .filter(|a| a.is_aggro())
67            .count()
68    }
69
70    /// Full edge history (all streets).
71    fn history(&self) -> Vec<Edge> {
72        self.states()
73            .into_iter()
74            .zip(self.actions().iter())
75            .scan(Path::default(), |past, (game, action)| {
76                let edge = game.edgify(*action, past.aggression());
77                *past = past
78                    .clone()
79                    .into_iter()
80                    .chain(std::iter::once(edge))
81                    .collect();
82                Some(edge)
83            })
84            .collect()
85    }
86
87    /// Current street edges only (trailing choice edges before any Draw).
88    fn subgame(&self) -> Path {
89        self.history()
90            .into_iter()
91            .rev()
92            .take_while(|e| e.is_choice())
93            .collect::<Vec<_>>()
94            .into_iter()
95            .rev()
96            .collect()
97    }
98
99    /// Available actions at current state.
100    fn choices(&self) -> Path {
101        self.head().choices(self.aggression())
102    }
103}