implement a compact CardSet to replace HashSet<Card>
authorGeoffrey Allott <geoffrey@allott.email>
Sun, 4 Jun 2023 23:58:37 +0000 (00:58 +0100)
committerGeoffrey Allott <geoffrey@allott.email>
Sun, 4 Jun 2023 23:58:37 +0000 (00:58 +0100)
src/card.rs
src/game/cribbage/mod.rs
src/game/poker/holdem.rs
src/game/whist.rs

index 9a26e8c656f080c29ce310885ec0a7935e6289ad..673c8642e30fd2bc7c78dc2ea7a3de4455512db4 100644 (file)
@@ -85,12 +85,136 @@ pub struct Card {
     pub suit: Suit,
 }
 
+impl Card {
+    fn from_trailing_zeros(i: u32) -> Option<Self> {
+        Some(Card {
+            rank: match i / 4 + 2 {
+                2 => Rank::Two,
+                3 => Rank::Three,
+                4 => Rank::Four,
+                5 => Rank::Five,
+                6 => Rank::Six,
+                7 => Rank::Seven,
+                8 => Rank::Eight,
+                9 => Rank::Nine,
+                10 => Rank::Ten,
+                11 => Rank::Jack,
+                12 => Rank::Queen,
+                13 => Rank::King,
+                14 => Rank::Ace,
+                _ => return None,
+            },
+            suit: match i % 4 {
+                0 => Suit::Clubs,
+                1 => Suit::Diamonds,
+                2 => Suit::Hearts,
+                3 => Suit::Spades,
+                _ => return None,
+            },
+        })
+    }
+
+    fn repr(&self) -> u64 {
+        1 << (self.suit as u64 + 4 * (self.rank as u64 - 2))
+    }
+}
+
 impl Display for Card {
     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
         write!(f, "{}{}", self.rank, self.suit)
     }
 }
 
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
+pub struct CardSet {
+    repr: u64,
+}
+
+impl CardSet {
+    pub fn new() -> CardSet {
+        CardSet { repr: 0 }
+    }
+
+    pub fn complete_deck() -> CardSet {
+        CardSet { repr: 0b1111111111111_1111111111111_1111111111111_1111111111111 }
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.repr == 0
+    }
+
+    pub fn len(&self) -> usize {
+        self.repr.count_ones() as usize
+    }
+
+    pub fn clear(&mut self) {
+        self.repr = 0;
+    }
+
+    pub fn insert(&mut self, card: Card) -> bool {
+        let card_repr = card.repr();
+        if self.repr & card_repr != 0 {
+            false
+        } else {
+            self.repr |= card_repr;
+            true
+        }
+    }
+
+    pub fn contains(&self, card: &Card) -> bool {
+        self.repr & card.repr() != 0
+    }
+
+    pub fn remove(&mut self, card: &Card) -> bool {
+        let card_repr = card.repr();
+        if self.repr & card_repr == 0 {
+            false
+        } else {
+            self.repr &= !card_repr;
+            true
+        }
+    }
+}
+
+pub struct CardSetIntoIter {
+    inner: CardSet,
+}
+
+impl IntoIterator for CardSet {
+    type IntoIter = CardSetIntoIter;
+    type Item = Card;
+
+    fn into_iter(self) -> Self::IntoIter {
+        CardSetIntoIter { inner: self }
+    }
+}
+
+impl Iterator for CardSetIntoIter {
+    type Item = Card;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.inner.repr == 0 {
+            None
+        } else {
+            let i = self.inner.repr.trailing_zeros();
+            self.inner.repr &= !(1 << i);
+            Card::from_trailing_zeros(i)
+        }
+    }
+}
+
+impl FromIterator<Card> for CardSet {
+    fn from_iter<T>(iter: T) -> Self
+        where T: IntoIterator<Item=Card>
+    {
+        let mut set = CardSet::new();
+        for card in iter {
+            set.insert(card);
+        }
+        set
+    }
+}
+
 pub const TWO_OF_CLUBS: Card = Card { rank: Two, suit: Clubs };
 pub const THREE_OF_CLUBS: Card = Card { rank: Three, suit: Clubs };
 pub const FOUR_OF_CLUBS: Card = Card { rank: Four, suit: Clubs };
index 6c0eb5453738a2b187e400f2ed333476c51b1161..7d041ccae20b8b2b72cb883b0dca77e6ee8a6c66 100644 (file)
@@ -1,6 +1,6 @@
 use std::collections::{HashMap, HashSet};
 
-use crate::card::{Card, FIFTY_TWO_CARD_DECK, Rank};
+use crate::card::{Card, CardSet, Rank};
 use crate::rng::{Seed, WaveRng};
 use crate::seats::Seats;
 use crate::username::Username;
@@ -51,11 +51,11 @@ pub struct Cribbage {
     seats: Seats,
     dealer: Option<Username>,
     receiver: Option<Username>,
-    deck: HashSet<Card>,
-    hands: HashMap<Username, HashSet<Card>>,
+    deck: CardSet,
+    hands: HashMap<Username, CardSet>,
     turn_up: Option<Card>,
-    box_cards: HashSet<Card>,
-    revealed: HashSet<Card>,
+    box_cards: CardSet,
+    revealed: CardSet,
     pegging_cards: Vec<(Username, Card)>,
     used_pegging_cards: Vec<(Username, Card)>,
     players_still_in: HashSet<Username>,
@@ -74,11 +74,11 @@ impl Cribbage {
             seats: Seats::new(),
             dealer: None,
             receiver: None,
-            deck: FIFTY_TWO_CARD_DECK.iter().cloned().collect(),
+            deck: CardSet::complete_deck(),
             hands: HashMap::new(),
             turn_up: None,
-            box_cards: HashSet::new(),
-            revealed: HashSet::new(),
+            box_cards: CardSet::new(),
+            revealed: CardSet::new(),
             pegging_cards: Vec::new(),
             used_pegging_cards: Vec::new(),
             players_still_in: HashSet::new(),
@@ -92,15 +92,15 @@ impl Cribbage {
     }
 
     fn hand_size(&self, username: Username) -> usize {
-        self.hands.get(&username).map_or(0, HashSet::len)
+        self.hands.get(&username).map_or(0, CardSet::len)
     }
 
     fn all_hands_are_empty(&self) -> bool {
-        self.hands.values().all(HashSet::is_empty)
+        self.hands.values().all(CardSet::is_empty)
     }
 
     fn all_hands_dealt(&self) -> bool {
-        self.hands.values().map(HashSet::len).all(|len| len == 6)
+        self.hands.values().map(CardSet::len).all(|len| len == 6)
     }
 
     fn pegging_total(&self) -> u32 {
@@ -109,7 +109,7 @@ impl Cribbage {
 
     fn hand_has_playable_pegging_card(&self, username: Username) -> bool {
         let total = self.pegging_total();
-        self.hands.get(&username).map_or(false, |hand| hand.iter().any(|card| total + value(card.rank) <= 31))
+        self.hands.get(&username).map_or(false, |hand| hand.into_iter().any(|card| total + value(card.rank) <= 31))
     }
 
     fn next_player_still_in(&self) -> Option<Username> {
@@ -133,14 +133,14 @@ impl Cribbage {
     }
 
     fn unrevealed_card(&self, username: Username) -> Option<Card> {
-        self.hands.get(&username).and_then(|hand| hand.iter().filter(|card| !self.revealed.contains(*card)).min().copied())
+        self.hands.get(&username).and_then(|hand| hand.into_iter().filter(|card| !self.revealed.contains(card)).min())
     }
 
     fn four_card_hand(&self, username: Username) -> Option<[Card; 4]> {
         self.hands.get(&username).and_then(|hand| {
             let hand: Vec<_> = hand.into_iter().collect();
             if let [a, b, c, d] = hand[..] {
-                Some([*a, *b, *c, *d])
+                Some([a, b, c, d])
             } else {
                 None
             }
@@ -148,13 +148,13 @@ impl Cribbage {
     }
 
     fn unrevealed_box_card(&self) -> Option<Card> {
-        self.box_cards.iter().filter(|card| !self.revealed.contains(*card)).min().copied()
+        self.box_cards.into_iter().filter(|card| !self.revealed.contains(card)).min()
     }
 
     fn four_card_box(&self) -> Option<[Card; 4]> {
-        let hand: Vec<_> = self.box_cards.iter().collect();
+        let hand: Vec<_> = self.box_cards.into_iter().collect();
         if let [a, b, c, d] = hand[..] {
-            Some([*a, *b, *c, *d])
+            Some([a, b, c, d])
         } else {
             None
         }
@@ -165,7 +165,7 @@ impl Cribbage {
     }
 
     fn winner(&self) -> Option<Username> {
-        self.points.iter().filter(|&(_, points)| *points >= self.settings.target_score).next().map(|(username, _)| *username)
+        self.points.iter().find(|&(_, points)| *points >= self.settings.target_score).map(|(username, _)| *username)
     }
 }
 
@@ -258,7 +258,7 @@ impl Game for Cribbage {
             (State::NotStarted, Action::Leave) => self.seats.remove_player(username),
             (_, Action::NextToDeal) => {
                 self.dealer = Some(username);
-                self.deck = FIFTY_TWO_CARD_DECK.iter().cloned().collect();
+                self.deck = CardSet::complete_deck();
                 self.turn_up = None;
                 self.hands.clear();
                 self.receiver = self.seats.player_after(username);
@@ -338,7 +338,7 @@ impl Game for Cribbage {
                 match self.next_player_still_in() {
                     None => {
                         self.state = State::ScoringPegging;
-                        self.used_pegging_cards.extend(self.pegging_cards.drain(..));
+                        self.used_pegging_cards.append(&mut self.pegging_cards);
                         self.players_still_in = self.hands.iter().filter(|(_, cards)| !cards.is_empty()).map(|(&username, _)| username).collect();
                     },
                     active => self.active = active,
@@ -348,7 +348,7 @@ impl Game for Cribbage {
             (State::ScoringPegging, Action::Score { points, .. }) => {
                 *self.points.entry(username).or_default() += points as u32;
                 if self.next_player_still_in().is_none() || self.pegging_total() == 31 {
-                    self.used_pegging_cards.extend(self.pegging_cards.drain(..));
+                    self.used_pegging_cards.append(&mut self.pegging_cards);
                     self.players_still_in = self.hands.iter().filter(|(_, cards)| !cards.is_empty()).map(|(&username, _)| username).collect();
                 }
                 if self.player_has_won(username) {
@@ -418,11 +418,11 @@ impl Game for Cribbage {
             },
             State::Dealing => {
                 if let Some(username) = self.receiver {
-                    let card = rng.choose_from(&self.deck).cloned();
+                    let card = rng.choose_from(self.deck);
                     DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::ReceiveCard { card } }))
                 } else if let Some(username) = self.dealer {
                     if self.seats.players_len() == 3 && self.box_cards.is_empty() {
-                        if let Some(&card) = rng.choose_from(&self.deck) {
+                        if let Some(card) = rng.choose_from(self.deck) {
                             DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::PutInBox { card: Some(card) } }))
                         } else {
                             error!("Expected to deal a card but none were left in deck");
@@ -439,7 +439,7 @@ impl Game for Cribbage {
             State::Choosing => DealerAction::WaitForPlayer,
             State::TurnUp => {
                 if let Some(username) = self.dealer {
-                    if let Some(&card) = rng.choose_from(&self.deck) {
+                    if let Some(card) = rng.choose_from(self.deck) {
                         DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::CommunityCard { card } }))
                     } else {
                         error!("Expected to deal a card but none were left in deck");
index f0b8c47354a03f78b3d4e362e4e42fdd8aaf40e8..a55a9bafdb459ae670a78484c64e658ce37a5cde 100644 (file)
@@ -4,7 +4,7 @@ use std::convert::TryInto;
 
 use itertools::Itertools;
 
-use crate::card::{Card, FIFTY_TWO_CARD_DECK};
+use crate::card::{Card, CardSet};
 use crate::rng::{Seed, WaveRng};
 use crate::seats::Seats;
 use crate::username::Username;
@@ -102,9 +102,9 @@ pub struct TexasHoldEm {
     state: State,
     seats: Seats,
     stacks: HashMap<Username, u64>,
-    deck: HashSet<Card>,
-    hands: HashMap<Username, HashSet<Card>>,
-    community: HashSet<Card>,
+    deck: CardSet,
+    hands: HashMap<Username, CardSet>,
+    community: CardSet,
     dealer: Option<Username>,
     receiver: Option<Username>,
     active: Option<Username>,
@@ -114,7 +114,7 @@ pub struct TexasHoldEm {
     pot: u64,
     level: Level,
     ghosts: HashMap<Username, u8>,
-    revealed: HashSet<(Username, Card)>,
+    revealed: CardSet,
 }
 
 impl TexasHoldEm {
@@ -130,9 +130,9 @@ impl TexasHoldEm {
             state: State::NotStarted,
             seats: Seats::new(),
             stacks: HashMap::new(),
-            deck: FIFTY_TWO_CARD_DECK.iter().cloned().collect(),
+            deck: CardSet::complete_deck(),
             hands: HashMap::new(),
-            community: HashSet::new(),
+            community: CardSet::new(),
             dealer: None,
             receiver: None,
             active: None,
@@ -142,7 +142,7 @@ impl TexasHoldEm {
             pot: 0,
             level,
             ghosts: HashMap::new(),
-            revealed: HashSet::new(),
+            revealed: CardSet::new(),
         }
     }
 
@@ -260,8 +260,8 @@ impl TexasHoldEm {
         if !self.settings.hide_cards && (self.players_able_to_bet() <= 1 || matches!(self.state, State::Showdown)) {
             for &username in self.in_hand.iter().sorted() {
                 if let Some(hand) = self.hands.get(&username) {
-                    for &card in hand.iter().sorted() {
-                        if !self.revealed.contains(&(username, card)) {
+                    for card in hand.into_iter().sorted() {
+                        if !self.revealed.contains(&card) {
                             return Some((username, card));
                         }
                     }
@@ -378,7 +378,7 @@ impl Game for TexasHoldEm {
                 }
                 self.remove_ghosts()?;
                 self.dealer = Some(username);
-                self.deck = FIFTY_TWO_CARD_DECK.iter().cloned().collect();
+                self.deck = CardSet::complete_deck();
                 self.hands.clear();
                 self.community.clear();
                 self.active = None;
@@ -405,7 +405,7 @@ impl Game for TexasHoldEm {
                 Ok(())
             }
             (_, Action::RevealCard { card }) => {
-                self.revealed.insert((username, card));
+                self.revealed.insert(card);
                 Ok(())
             }
             (State::Dealing, Action::EndDeal) => {
@@ -536,7 +536,7 @@ impl Game for TexasHoldEm {
             },
             State::Dealing => {
                 if let Some(username) = self.receiver {
-                    let card = rng.choose_from(&self.deck).cloned();
+                    let card = rng.choose_from(self.deck);
                     DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::ReceiveCard { card } }))
                 } else if let Some(username) = self.dealer {
                     DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::EndDeal }))
@@ -630,7 +630,7 @@ impl Game for TexasHoldEm {
                 if let Some((username, card)) = self.next_card_to_reveal() {
                     DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::RevealCard { card } }))
                 } else if let Some(username) = self.dealer {
-                    if let Some(&card) = rng.choose_from(&self.deck) {
+                    if let Some(card) = rng.choose_from(self.deck) {
                         DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::CommunityCard { card } }))
                     } else {
                         error!("Dealing community card but there are no cards left in deck: {:#?}", self);
@@ -670,7 +670,7 @@ impl Game for TexasHoldEm {
                         .hands
                         .iter()
                         .sorted_by_key(|&(username, _)| username)
-                        .map(|(&username, hand)| (username, hand.iter().chain(self.community.iter()).cloned().collect::<Vec<_>>()))
+                        .map(|(&username, hand)| (username, hand.into_iter().chain(self.community.into_iter()).collect::<Vec<_>>()))
                         .filter_map(|(username, cards)| cards.try_into().ok().map(rank_7_card_hand).map(|hand| (username, hand)))
                         .max_items_by_key(|(_, hand)| *hand);
                     info!("Showdown: community: {:?}", self.community);
index 79a101e2d1a6715f2e12a9eac639548b19d2c875..38d77b9e96b4b444b1e161b3825623aacdbdcce7 100644 (file)
@@ -1,6 +1,6 @@
 use std::collections::{HashMap, HashSet};
 
-use crate::card::{Card, Suit, FIFTY_TWO_CARD_DECK};
+use crate::card::{Card, CardSet, Suit};
 use crate::rng::{Seed, WaveRng};
 use crate::seats::Seats;
 use crate::username::Username;
@@ -44,8 +44,8 @@ pub struct KnockOutWhist {
     dealer: Option<Username>,
     receiver: Option<Username>,
     call: Option<Username>,
-    deck: HashSet<Card>,
-    hands: HashMap<Username, HashSet<Card>>,
+    deck: CardSet,
+    hands: HashMap<Username, CardSet>,
     trick: HashMap<Username, Card>,
     active: Option<Username>,
     tricks_won: HashMap<Username, u32>,
@@ -68,7 +68,7 @@ impl KnockOutWhist {
             dealer: None,
             receiver: None,
             call: None,
-            deck: FIFTY_TWO_CARD_DECK.iter().cloned().collect(),
+            deck: CardSet::complete_deck(),
             hands: HashMap::new(),
             trick: HashMap::new(),
             active: None,
@@ -86,7 +86,7 @@ impl KnockOutWhist {
     }
 
     fn hand_contains_suit(&self, username: Username, suit: Suit) -> bool {
-        self.hands.get(&username).map_or(false, |hand| hand.iter().any(|card| card.suit == suit))
+        self.hands.get(&username).map_or(false, |hand| hand.into_iter().any(|card| card.suit == suit))
     }
 
     fn trick_winner(&self) -> Option<Username> {
@@ -99,7 +99,7 @@ impl KnockOutWhist {
     }
 
     fn cards_by_player(&self) -> impl Iterator<Item = (Username, Card)> + '_ {
-        self.hands.iter().flat_map(|(username, hand)| hand.iter().map(move |card| (*username, *card)))
+        self.hands.iter().flat_map(|(username, hand)| hand.into_iter().map(move |card| (*username, card)))
     }
 
     fn all_hands_dealt(&self) -> bool {
@@ -182,7 +182,7 @@ impl Game for KnockOutWhist {
             (State::NotStarted, Action::Leave) => self.seats.remove_player(username),
             (_, Action::NextToDeal) => {
                 self.dealer = Some(username);
-                self.deck = FIFTY_TWO_CARD_DECK.iter().cloned().collect();
+                self.deck = CardSet::complete_deck();
                 self.hands.clear();
                 self.receiver = self.seats.player_after(username);
                 self.trump_card = None;
@@ -251,7 +251,7 @@ impl Game for KnockOutWhist {
                         self.call = self.winners.iter().next().cloned();
                     } else {
                         self.receiver = self.dealer.and_then(|dealer| self.seats.player_after_where(dealer, |username| self.winners.contains(&username)));
-                        self.deck = FIFTY_TWO_CARD_DECK.iter().cloned().collect();
+                        self.deck = CardSet::complete_deck();
                         self.hands.clear();
                         self.trump_card = None;
                         self.state = State::CutForCall;
@@ -277,7 +277,7 @@ impl Game for KnockOutWhist {
             (State::CutForCall, Action::CutCard { card }) => {
                 self.deck.remove(&card);
                 self.hands.entry(username).or_default().insert(card);
-                if self.hands.values().map(HashSet::len).sum::<usize>() == self.winners.len() {
+                if self.hands.values().map(CardSet::len).sum::<usize>() == self.winners.len() {
                     if let Some((username, max)) = self.cards_by_player().max_by_key(|(_, card)| card.rank) {
                         if self.cards_by_player().filter(|(_, card)| card.rank == max.rank).count() > 1 {
                             self.winners = self.cards_by_player().filter(|(_, card)| card.rank == max.rank).map(|(username, _)| username).collect();
@@ -333,12 +333,12 @@ impl Game for KnockOutWhist {
             },
             State::Dealing => {
                 if let Some(username) = self.receiver {
-                    let card = rng.choose_from(&self.deck).cloned();
+                    let card = rng.choose_from(self.deck);
                     DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::ReceiveCard { card } }))
                 } else if let Some(username) = self.dealer {
                     match (self.call, self.trump_card) {
                         (None, None) => {
-                            if let Some(&card) = rng.choose_from(&self.deck) {
+                            if let Some(card) = rng.choose_from(self.deck) {
                                 DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::CommunityCard { card } }))
                             } else {
                                 error!("Expected to deal a card but none were left in deck");
@@ -379,7 +379,7 @@ impl Game for KnockOutWhist {
             }
             State::CutForCall => {
                 if let Some(username) = self.receiver {
-                    if let Some(card) = rng.choose_from(&self.deck).cloned() {
+                    if let Some(card) = rng.choose_from(self.deck) {
                         DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::CutCard { card } }))
                     } else {
                         error!("Expected to cut for call but there were no cards left in the deck");