openpql_prelude/card/
isomorphic.rs

1use super::{Board, Card, CardCount, Flop, HandN, Suit, SuitMapping};
2
3impl<const N: usize> HandN<N> {
4    /// Creates a suit-isomorphic hand using the provided suit mapping.
5    pub fn new_iso_with_mapping(
6        cards: &[Card],
7        mapping: &mut SuitMapping,
8    ) -> Self {
9        Self::new(create_iso_array(cards, mapping))
10    }
11
12    /// Creates a suit-isomorphic hand and returns the suit mapping used.
13    pub fn new_iso(cards: &[Card]) -> (Self, SuitMapping) {
14        let mut mapping = SuitMapping::default();
15        let iso = Self::new_iso_with_mapping(cards, &mut mapping);
16        (iso, mapping)
17    }
18}
19
20impl Board {
21    /// Creates a suit-isomorphic board using the provided suit mapping.
22    pub fn new_iso_with_mapping(
23        cards: &[Card],
24        mapping: &mut SuitMapping,
25    ) -> Self {
26        create_iso_board(cards, mapping)
27    }
28
29    /// Creates a suit-isomorphic board and returns the suit mapping used.
30    pub fn new_iso(cards: &[Card]) -> (Self, SuitMapping) {
31        let mut mapping = SuitMapping::default();
32        let iso = Self::new_iso_with_mapping(cards, &mut mapping);
33        (iso, mapping)
34    }
35}
36
37type SuitCount = [CardCount; Suit::N_SUITS as usize];
38
39fn count_suits(cards: &[Card]) -> SuitCount {
40    let mut res = SuitCount::default();
41
42    for card in cards {
43        res[card.suit as usize] += 1;
44    }
45
46    res
47}
48
49/// # Panics
50/// input must be valid distict cards
51fn create_iso_array<const N: usize>(
52    slice: &[Card],
53    mapping: &mut SuitMapping,
54) -> [Card; N] {
55    let mut cards: [_; N] = slice[..N].try_into().unwrap();
56    let suit_count = count_suits(cards.as_slice());
57
58    cards.sort_unstable_by_key(|&card| {
59        (Suit::N_SUITS - suit_count[card.suit as usize], card)
60    });
61
62    for card in &mut cards {
63        card.suit = mapping.map_suit(card.suit);
64    }
65
66    cards.sort_unstable();
67
68    cards
69}
70
71fn create_iso_board(board_cards: &[Card], mapping: &mut SuitMapping) -> Board {
72    #[inline]
73    const fn map_card(card: Card, mapping: &mut SuitMapping) -> Card {
74        Card::new(card.rank, mapping.map_suit(card.suit))
75    }
76
77    let n = board_cards.len();
78    let mut board = Board::default();
79
80    if n >= Board::N_FLOP {
81        board.flop = Some(Flop::new_iso_with_mapping(board_cards, mapping));
82    }
83
84    if n >= Board::N_TURN {
85        board.turn = Some(map_card(board_cards[Board::IDX_TURN], mapping));
86    }
87
88    if n >= Board::N_RIVER {
89        board.river = Some(map_card(board_cards[Board::IDX_RIVER], mapping));
90    }
91
92    board
93}
94
95#[cfg(test)]
96#[cfg_attr(coverage_nightly, coverage(off))]
97mod tests {
98    use super::*;
99    use crate::*;
100
101    #[test]
102    fn test_iso() {
103        assert_eq!(
104            HandN::<5>::new_iso(&cards!("6s8h9dQdQc")).0,
105            HandN::<5>::new_iso(&cards!("6s8h9dQcQd")).0
106        );
107    }
108
109    #[test]
110    fn test_iso_flop() {
111        let mut res = FxHashMap::default();
112        let mut iso_set = FxHashSet::default();
113
114        for hand in Flop::iter_all::<true>() {
115            let (iso, _) = HandN::<3>::new_iso(&hand.0);
116            res.insert(hand, iso);
117            iso_set.insert(iso);
118        }
119
120        assert_eq!(res.len(), 7140);
121        assert_eq!(iso_set.len(), 573);
122    }
123
124    #[test]
125    fn test_iso_board() {
126        fn assert_iso_eq(lhs: &str, rhs: &str) {
127            assert_eq!(
128                Board::new_iso(&cards!(lhs)).0,
129                Board::new_iso(&cards!(rhs)).0
130            );
131        }
132
133        assert_iso_eq("", "");
134        assert_iso_eq("AsKhQd", "AhKsQc");
135        assert_iso_eq("AsKhQdJdTd", "AhKsQcJcTc"); // TODO: maybe ignore turn/river order?
136    }
137}