open_pql/base/
board.rs

1use super::{Card, Card64, HandN, Hash, N_FLOP, fmt};
2
3/// Creates a flop (3 community cards) from a string representation.
4///
5/// # Examples
6///
7/// ```
8/// use open_pql::flop;
9///
10/// let community_flop = flop!("As Kh Qd");
11/// ```
12#[cfg(any(test, feature = "benchmark"))]
13#[macro_export]
14macro_rules! flop {
15    ($s:expr) => {
16        $crate::Flop::from(
17            <[$crate::Card; 3]>::try_from($crate::Card::new_vec($s)).unwrap(),
18        )
19    };
20}
21
22/// Creates a board (community cards) from a string representation.
23///
24/// # Examples
25///
26/// ```
27/// use open_pql::board;
28///
29/// let community_board = board!("As Kh Qd Jc Ts");
30/// ```
31#[cfg(any(test, feature = "benchmark"))]
32#[macro_export]
33macro_rules! board {
34    ($s:expr) => {
35        $crate::Board::from(
36            $crate::Card::new_vec($s).as_ref() as &[$crate::Card]
37        )
38    };
39}
40
41pub type Flop = HandN<3>;
42
43/// Represents a poker board (flop, turn, river)
44///
45/// A poker board consists of community cards dealt during a game:
46/// - Flop: The first three community cards (optional)
47/// - Turn: The fourth community card (optional, requires flop)
48/// - River: The fifth community card (optional, requires turn)
49///
50/// # Examples
51///
52/// ```
53/// use open_pql::{Board, Card, Rank::*, Suit::*};
54///
55/// let cards = [Card::new(RA, S), Card::new(RK, H), Card::new(RQ, D)];
56/// let board = Board::from_slice(&cards);
57///
58/// assert_eq!(board.len(), 3);
59/// assert!(!board.is_empty());
60/// ```
61#[derive(Copy, Clone, PartialEq, Eq, Hash, Default)]
62pub struct Board {
63    pub flop: Option<Flop>,
64    pub turn: Option<Card>,
65    pub river: Option<Card>,
66}
67
68impl Board {
69    /// Creates a board from a slice of cards
70    pub fn from_slice(cards: &[Card]) -> Self {
71        let flop = if cards.len() >= N_FLOP {
72            Some(Flop::from_slice(&cards[0..3]))
73        } else {
74            None
75        };
76        let turn = cards.get(3).copied();
77        let river = cards.get(4).copied();
78
79        Self { flop, turn, river }
80    }
81
82    /// Checks if the board is empty (has no cards)
83    #[must_use]
84    #[inline]
85    pub const fn is_empty(&self) -> bool {
86        self.flop.is_none()
87    }
88
89    /// Returns the number of cards on the board
90    pub fn len(&self) -> usize {
91        match self.flop {
92            Some(_) => 3 + self.turn.iter().count() + self.river.iter().count(),
93            None => 0,
94        }
95    }
96
97    /// Returns an iterator over all cards on the board
98    pub fn iter(&self) -> impl Iterator<Item = Card> + '_ {
99        let flop_iter = self.flop.iter().flat_map(Flop::iter);
100        flop_iter.chain(self.turn).chain(self.river)
101    }
102
103    /// Returns all cards on the board as a vector
104    pub fn to_vec(&self) -> Vec<Card> {
105        self.iter().collect()
106    }
107
108    /// Clears the board
109    pub fn clear(&mut self) {
110        *self = Self::default();
111    }
112
113    pub fn contains_card(&self, card: Card) -> bool {
114        if let Some(flop) = self.flop
115            && flop.as_slice().contains(&card)
116        {
117            return true;
118        }
119
120        Some(card) == self.turn || Some(card) == self.river
121    }
122
123    #[must_use]
124    pub(crate) const fn swap_turn(&self, card: Card) -> Self {
125        Self {
126            flop: self.flop,
127            turn: Some(card),
128            river: self.river,
129        }
130    }
131
132    #[must_use]
133    pub(crate) const fn swap_river(&self, card: Card) -> Self {
134        Self {
135            flop: self.flop,
136            turn: self.turn,
137            river: Some(card),
138        }
139    }
140}
141
142impl From<&[Card]> for Board {
143    fn from(xs: &[Card]) -> Self {
144        Self::from_slice(xs)
145    }
146}
147
148impl fmt::Debug for Board {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        write!(f, "Board<")?;
151        for c in self.iter() {
152            write!(f, "{c}")?;
153        }
154        write!(f, ">")
155    }
156}
157
158impl From<Board> for Card64 {
159    fn from(board: Board) -> Self {
160        let mut result = Self::default();
161        if let Some(flop) = board.flop {
162            result |= Self::from(flop.as_slice());
163        }
164        if let Some(turn) = board.turn {
165            result |= Self::from(turn);
166        }
167        if let Some(river) = board.river {
168            result |= Self::from(river);
169        }
170        result
171    }
172}
173
174impl From<(Card, Card, Card, Card, Card)> for Board {
175    fn from(cs: (Card, Card, Card, Card, Card)) -> Self {
176        Self {
177            flop: Some(Flop::from_slice(&[cs.0, cs.1, cs.2])),
178            turn: Some(cs.3),
179            river: Some(cs.4),
180        }
181    }
182}
183
184impl From<Flop> for Board {
185    fn from(flop: Flop) -> Self {
186        Self {
187            flop: Some(flop),
188            ..Default::default()
189        }
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196    use crate::*;
197
198    impl Arbitrary for Board {
199        fn arbitrary(g: &mut quickcheck::Gen) -> Self {
200            let cards = CardN::<5>::arbitrary(g);
201
202            Self::from_slice(cards.as_ref())
203        }
204    }
205
206    #[quickcheck]
207    fn test_flop_eq(cards: CardN<3>) {
208        let flops: Vec<Flop> = vec![
209            Flop::from_slice(&[cards[0], cards[1], cards[2]]),
210            Flop::from_slice(&[cards[0], cards[2], cards[1]]),
211            Flop::from_slice(&[cards[1], cards[0], cards[2]]),
212            Flop::from_slice(&[cards[1], cards[2], cards[0]]),
213            Flop::from_slice(&[cards[2], cards[1], cards[0]]),
214            Flop::from_slice(&[cards[2], cards[0], cards[1]]),
215        ];
216
217        for i in 0..flops.len() {
218            for j in 0..flops.len() {
219                assert_eq!(flops[i], flops[j]);
220            }
221        }
222    }
223
224    #[quickcheck]
225    fn test_board_eq(cs: CardN<5>) {
226        assert_eq!(
227            Board::from([cs[0], cs[1], cs[2], cs[3], cs[4]].as_slice()),
228            Board::from([cs[2], cs[1], cs[0], cs[3], cs[4]].as_slice())
229        );
230    }
231
232    #[test]
233    fn test_board_creation() {
234        // Empty board
235        let empty_board = Board::default();
236        assert!(empty_board.is_empty());
237        assert_eq!(empty_board.len(), 0);
238
239        // No Flop
240        let flop_cards = cards!("QdKhAs");
241        for j in 0..=2 {
242            assert_eq!(Board::from_slice(&flop_cards[0..j]).len(), 0);
243        }
244
245        // Flop only
246        let flop_board = Board::from_slice(&flop_cards);
247        assert!(!flop_board.is_empty());
248        assert_eq!(flop_board.len(), 3);
249        assert_eq!(flop_board.flop, Some(flop!("QdKhAs")));
250        assert_eq!(flop_board.turn, None);
251        assert_eq!(flop_board.river, None);
252
253        // Flop + Turn
254        let turn_card = Card::new(Rank::RJ, Suit::C);
255        let mut flop_turn_cards = flop_cards;
256        flop_turn_cards.push(turn_card);
257        let flop_turn_board = Board::from_slice(&flop_turn_cards);
258        assert_eq!(flop_turn_board.len(), 4);
259        assert_eq!(flop_turn_board.turn, Some(turn_card));
260        assert_eq!(flop_turn_board.river, None);
261
262        // Full board (Flop + Turn + River)
263        let river_card = Card::new(Rank::RT, Suit::S);
264        let mut full_cards = flop_turn_cards;
265        full_cards.push(river_card);
266        let full_board = Board::from_slice(&full_cards);
267        assert_eq!(full_board.len(), 5);
268        assert_eq!(full_board.river, Some(river_card));
269    }
270
271    #[test]
272    fn test_board_clear() {
273        let cards = cards!("QdKhAsJcTs");
274        let mut board = Board::from_slice(&cards);
275
276        assert!(!board.is_empty());
277        board.clear();
278        assert!(board.is_empty());
279    }
280
281    #[test]
282    fn test_board_iteration() {
283        let cards = cards!("QdKhAsJcTs");
284        // Test with different board sizes
285        for i in N_FLOP..=5 {
286            let board = Board::from_slice(&cards[0..i]);
287            let collected: Vec<Card> = board.iter().collect();
288            assert_eq!(collected, cards[0..i].to_vec());
289            assert_eq!(board.to_vec(), cards[0..i].to_vec());
290            assert_eq!(board.len(), i);
291        }
292    }
293
294    #[test]
295    fn test_board_card64_conversion() {
296        let cards = cards!("AsKhQdJcTs");
297
298        // Test with different board sizes
299        for i in N_FLOP..=5 {
300            let board = Board::from_slice(&cards[0..i]);
301            let card64 = Card64::from(board);
302
303            // Verify each card is set in the Card64
304            for j in 0..i {
305                assert!(card64.contains_card(cards[j]));
306            }
307
308            assert_eq!(card64.count() as usize, i);
309        }
310    }
311
312    #[quickcheck]
313    fn test_board_contains_card(board: Board, card: Card) {
314        assert_eq!(board.to_vec().contains(&card), board.contains_card(card));
315    }
316
317    #[test]
318    fn test_board_debug_format() {
319        let cards = cards!["AsKhQd"];
320
321        let board = Board::from_slice(&cards);
322        let debug_str = format!("{board:?}");
323        assert!(debug_str.contains("Board<QdKhAs>"));
324    }
325}