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}