Skip to main content

rbp_gameplay/
turn.rs

1/// Whose turn it is to act in the game tree.
2///
3/// Distinguishes between player decision nodes, chance nodes (card deals),
4/// and terminal nodes (hand complete).
5///
6/// # Variants
7///
8/// - `Choice(usize)` — Player `usize` must make a decision
9/// - `Chance` — Dealer reveals cards (no player decision)
10/// - `Terminal` — Hand is over, compute payoffs
11#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
12pub enum Turn {
13    Terminal,
14    Chance,
15    Choice(usize),
16}
17
18impl Turn {
19    /// Extracts the player index. Panics if not a Choice.
20    pub fn position(&self) -> usize {
21        match self {
22            Self::Choice(c) => *c,
23            _ => panic!("don't ask"),
24        }
25    }
26    /// True if this is a player decision node.
27    pub fn is_choice(&self) -> bool {
28        matches!(self, Self::Choice(_))
29    }
30    /// True if this is a card deal node.
31    pub fn is_chance(&self) -> bool {
32        matches!(self, Self::Chance)
33    }
34    /// True if the hand is complete.
35    pub fn is_terminal(&self) -> bool {
36        matches!(self, Self::Terminal)
37    }
38    /// 1-indexed player number for display.
39    pub fn display(&self) -> usize {
40        match self {
41            Self::Choice(c) => *c + 1,
42            _ => panic!("don't ask"),
43        }
44    }
45    /// Display label (e.g., "P1", "P2").
46    pub fn label(&self) -> String {
47        format!("P{}", self.display())
48    }
49}
50
51impl rbp_core::Arbitrary for Turn {
52    fn random() -> Self {
53        Self::Choice(rand::random_range(0..rbp_core::N))
54    }
55}
56
57impl From<usize> for Turn {
58    fn from(player: usize) -> Self {
59        Self::Choice(player)
60    }
61}
62
63
64impl TryFrom<&str> for Turn {
65    type Error = anyhow::Error;
66    fn try_from(s: &str) -> Result<Self, Self::Error> {
67        match s {
68            "XX" => Ok(Self::Terminal),
69            "??" => Ok(Self::Chance),
70            turn => turn[1..]
71                .parse::<usize>()
72                .map(Self::Choice)
73                .map_err(|_| anyhow::anyhow!("invalid player turn")),
74        }
75    }
76}
77
78impl std::fmt::Display for Turn {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        match self {
81            Self::Choice(c) => write!(f, "P{}", c),
82            Self::Terminal => write!(f, "-"),
83            Self::Chance => write!(f, "?"),
84        }
85    }
86}
87
88/// Named position at a poker table relative to the dealer button.
89///
90/// Position names vary by table size:
91/// - Heads-up (2): BTN (=SB), BB
92/// - 6-max: BTN, SB, BB, UTG, HJ, CO
93/// - 9/10-max: BTN, SB, BB, UTG(0..n), MP(0..n), HJ, CO
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
95pub enum PositionName {
96    BTN,
97    SB,
98    BB,
99    UTG(usize),
100    MP(usize),
101    HJ,
102    CO,
103}
104
105impl PositionName {
106    /// Computes the position name for a seat relative to the dealer.
107    pub fn from_seat(seat: usize, dealer: usize, table: usize) -> Self {
108        let offset = (seat + table - dealer) % table;
109        match table {
110            2 => match offset {
111                0 => Self::BTN,
112                _ => Self::BB,
113            },
114            6 => match offset {
115                0 => Self::BTN,
116                1 => Self::SB,
117                2 => Self::BB,
118                3 => Self::UTG(0),
119                4 => Self::HJ,
120                _ => Self::CO,
121            },
122            _ => match offset {
123                0 => Self::BTN,
124                1 => Self::SB,
125                2 => Self::BB,
126                3 => Self::UTG(0),
127                4 => Self::UTG(1),
128                5 => Self::MP(0),
129                6 => Self::MP(1),
130                7 => Self::HJ,
131                _ => Self::CO,
132            },
133        }
134    }
135}
136
137impl std::fmt::Display for PositionName {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        match self {
140            Self::BTN => write!(f, "BTN"),
141            Self::SB => write!(f, "SB"),
142            Self::BB => write!(f, "BB"),
143            Self::UTG(0) => write!(f, "UTG"),
144            Self::UTG(n) => write!(f, "UTG+{}", n),
145            Self::MP(0) => write!(f, "MP"),
146            Self::MP(n) => write!(f, "MP+{}", n),
147            Self::HJ => write!(f, "HJ"),
148            Self::CO => write!(f, "CO"),
149        }
150    }
151}