open_pql/base/
board.rs

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