openpql_prelude/card/
board.rs1use super::{Card, Card64, Flop, Hash, fmt};
2
3#[macro_export]
4macro_rules! board {
5 ($s:expr) => {
6 $crate::Board::from(cards!($s).as_slice())
7 };
8}
9
10#[derive(Copy, Clone, derive_more::Debug, PartialEq, Eq, Hash, Default)]
14#[debug("Board<{}>", self)]
15pub struct Board {
16 pub flop: Option<Flop>,
17 pub turn: Option<Card>,
18 pub river: Option<Card>,
19}
20
21impl Board {
22 pub const IDX_TURN: usize = 3;
24 pub const IDX_RIVER: usize = 4;
26 pub const N_PREFLOP: usize = 0;
28 pub const N_FLOP: usize = 3;
30 pub const N_TURN: usize = 4;
32 pub const N_RIVER: usize = 5;
34
35 pub fn from_slice(cards: &[Card]) -> Self {
40 let flop = if cards.len() >= Self::N_FLOP {
41 Some(Flop::from_slice(&cards[0..Self::N_FLOP]))
42 } else {
43 None
44 };
45 let turn = cards.get(Self::IDX_TURN).copied();
46 let river = cards.get(Self::IDX_RIVER).copied();
47
48 Self { flop, turn, river }
49 }
50
51 #[must_use]
53 #[inline]
54 pub const fn is_empty(&self) -> bool {
55 self.flop.is_none()
56 }
57
58 pub fn len(&self) -> usize {
60 match self.flop {
61 Some(_) => {
62 Self::N_FLOP
63 + self.turn.iter().count()
64 + self.river.iter().count()
65 }
66 None => 0,
67 }
68 }
69
70 pub fn iter(&self) -> impl Iterator<Item = Card> + '_ {
72 self.flop
73 .iter()
74 .flat_map(|flop| flop.iter().copied())
75 .chain(self.turn)
76 .chain(self.river)
77 }
78
79 pub const fn contains_card(&self, card: Card) -> bool {
81 #[inline]
82 const fn inner_eq(op: Option<Card>, rhs: Card) -> bool {
83 match op {
84 Some(lhs) => lhs.eq(rhs),
85 None => false,
86 }
87 }
88
89 if let Some(flop) = self.flop
90 && flop.contains_card(card)
91 {
92 return true;
93 }
94
95 inner_eq(self.turn, card) || inner_eq(self.river, card)
96 }
97
98 pub(crate) const fn to_c64_flop(self) -> Card64 {
99 match self.flop {
100 Some(flop) => flop.to_c64(),
101 None => Card64::EMPTY,
102 }
103 }
104
105 pub(crate) const fn to_c64_turn(self) -> Card64 {
106 match self.turn {
107 Some(turn) => turn.to_c64(),
108 None => Card64::EMPTY,
109 }
110 }
111
112 pub(crate) const fn to_c64_river(self) -> Card64 {
113 match self.river {
114 Some(river) => river.to_c64(),
115 None => Card64::EMPTY,
116 }
117 }
118}
119
120impl From<&[Card]> for Board {
121 fn from(xs: &[Card]) -> Self {
122 Self::from_slice(xs)
123 }
124}
125
126impl fmt::Display for Board {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 self.iter().try_for_each(|card| write!(f, "{card}"))
129 }
130}
131
132impl From<Board> for Card64 {
133 fn from(board: Board) -> Self {
134 board.iter().collect()
135 }
136}
137
138#[cfg(any(test, feature = "quickcheck"))]
139impl quickcheck::Arbitrary for Board {
140 fn arbitrary(g: &mut quickcheck::Gen) -> Self {
141 let cards = crate::CardN::<{ Self::N_RIVER }>::arbitrary(g);
142
143 Self::from_slice(cards.as_ref())
144 }
145}
146
147#[cfg(test)]
148#[cfg_attr(coverage_nightly, coverage(off))]
149mod tests {
150 use super::*;
151 use crate::*;
152
153 #[quickcheck]
154 fn test_from_slice(cs: CardN<5>) {
155 assert_eq!(
156 Board::from([cs[0], cs[1], cs[2], cs[3], cs[4]].as_slice()),
157 Board::from([cs[2], cs[1], cs[0], cs[3], cs[4]].as_slice()),
158 );
159 assert_ne!(
160 Board::from([cs[0], cs[1], cs[2], cs[3], cs[4]].as_slice()),
161 Board::from([cs[0], cs[1], cs[2], cs[4], cs[3]].as_slice()),
162 );
163 }
164
165 #[test]
166 fn test_empty() {
167 assert!(Board::default().is_empty());
168 }
169
170 #[test]
171 fn test_board_iter_and_len() {
172 let board = board!("Qd As Kh Jc");
173
174 assert_eq!(board.iter().collect::<Vec<_>>(), cards!("Qd Kh As Jc"));
175 assert_eq!(board.len(), 4);
176 assert_eq!(Board::default().len(), 0);
177 }
178
179 #[quickcheck]
180 fn test_board_contains_card(board: Board, card: Card) {
181 assert_eq!(board.iter().any(|x| x == card), board.contains_card(card));
182 assert!(!Board::default().contains_card(card));
183 }
184
185 #[test]
186 fn test_display() {
187 let cards = cards!["AsKhQd3s2s"];
188 let board = Board::from_slice(&cards);
189
190 assert_eq!(format!("{board}"), "QdKhAs3s2s");
191 assert_eq!(format!("{board:?}"), "Board<QdKhAs3s2s>");
192 }
193
194 #[test]
195 fn test_to_c64() {
196 let cards = cards!("AsKhQdJcTs");
197
198 for i in 3..=5 {
199 let board = Board::from_slice(&cards[0..i]);
200 let card64 = Card64::from(board);
201
202 for j in 0..i {
203 assert!(card64.contains_card(cards[j]));
204 }
205
206 assert_eq!(card64.count() as usize, i);
207 }
208 }
209}