more logic for cribbage
authorGeoffrey Allott <geoffrey@allott.email>
Sun, 8 Aug 2021 16:28:37 +0000 (17:28 +0100)
committerGeoffrey Allott <geoffrey@allott.email>
Sun, 8 Aug 2021 16:28:37 +0000 (17:28 +0100)
src/game/action.rs
src/game/cribbage/mod.rs

index 9e17c3500edcfeede46c612c3af7e333943847d6..2ce6015e3f764f79f367afcb4f98ce987f17796b 100644 (file)
@@ -46,6 +46,8 @@ pub enum Action {
     RevealCard { card: Card },
     CutCard { card: Card },
     PlayCard { card: Card },
+    PutInBox { card: Option<Card> },
+    Pass,
     ChooseTrumps { suit: Suit },
     Fold,
     TimeoutFold,
@@ -55,6 +57,7 @@ pub enum Action {
     WinTrick,
     WinCall,
     WinHand { chips: u64, hand: Option<String> },
+    Score { points: u64, reason: String },
     WinGame,
     Message { message: String },
     Leave,
@@ -85,6 +88,7 @@ pub enum ActionError {
     StartingStackTooSmall,
     StartingStackTooLarge,
     CannotFold,
+    CannotPass,
     NotEnoughChips,
     BetSizeTooSmall,
     BetSizeTooLarge,
@@ -110,6 +114,7 @@ impl Display for ActionError {
             ActionError::StartingStackTooSmall => f.write_str("Starting stack provided is too small"),
             ActionError::StartingStackTooLarge => f.write_str("Starting stack provided is too large"),
             ActionError::CannotFold => f.write_str("Cannot fold when able to check"),
+            ActionError::CannotPass => f.write_str("Cannot pass when a card is playable"),
             ActionError::NotEnoughChips => f.write_str("Not enough chips to make bet"),
             ActionError::BetSizeTooSmall => f.write_str("Bet size is too small"),
             ActionError::BetSizeTooLarge => f.write_str("Bet size is too large"),
index 0d47f79c9d0c30dc9caedb3c5f7721cee38822cb..279640e9d4949b6d2fa228fb0a7634926e99a1ee 100644 (file)
@@ -1 +1,306 @@
 mod score;
+
+/*
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum State {
+    NotStarted,
+    Dealing,
+    Choosing,
+    TurnUp,
+    Pegging,
+    Scoring,
+    Completed,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct CribbageSettings {
+    title: String,
+    max_players: u32,
+    target_score: u32,
+    #[serde(default)]
+    start_time: StartCondition,
+}
+
+impl CribbageSettings {
+    pub fn title(&self) -> &str {
+        &self.title
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct Cribbage {
+    id: i64,
+    settings: CribbageSettings,
+    rng: WaveRng,
+    actions_len: usize,
+    state: State,
+    seats: Seats,
+    dealer: Option<Username>,
+    receiver: Option<Username>,
+    deck: HashSet<Card>,
+    hands: HashMap<Username, HashSet<Card>>,
+    turn_up: Option<Card>,
+    box_cards: HashSet<Card>,
+    pegging_cards: Vec<(Username, Card)>,
+    used_pegging_cards: Vec<(Username, Card)>,
+    players_still_in: HashSet<Username>,
+    active: Option<Username>,
+    points: HashMap<Username, u32>,
+}
+
+impl Cribbage {
+    pub fn new(id: i64, settings: CribbageSettings, seed: Seed) -> Self {
+        Self {
+            id,
+            settings,
+            rng: seed.into_rng(),
+            actions_len: 0,
+            state: State::NotStarted,
+            seats: Seats::new(),
+            dealer: None,
+            receiver: None,
+            deck: FIFTY_TWO_CARD_DECK.iter().cloned().collect(),
+            hands: HashMap::new(),
+            turn_up: None,
+            box_cards: HashSet::new(),
+            pegging_cards: Vec::new(),
+            used_pegging_cards: Vec::new(),
+            players_still_in: HashSet::new(),
+            active: None,
+            points: HashMap::new(),
+        }
+    }
+}
+
+impl Game for Cribbage {
+    fn id(&self) -> i64 {
+        self.id
+    }
+
+    fn players(&self) -> HashSet<Username> {
+        self.seats.player_set()
+    }
+
+    fn actions_len(&self) -> usize {
+        self.actions_len
+    }
+
+    fn validate_action(&self, UserAction { timestamp, username, action }: UserAction) -> Result<ValidatedUserAction, ActionError> {
+        match (self.state, action) {
+            (_, Action::AddOn { .. }) | (_, Action::RevealCard { .. }) | (_, Action::Fold) | (_, Action::TimeoutFold) | (_, Action::Bet { .. }) => {
+                Err(ActionError::InvalidActionForGameType)
+            }
+            (_, Action::Message { message }) => Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::Message { message } })),
+            (State::NotStarted, Action::Join { seat, .. }) => {
+                if self.seats.contains_player(username) {
+                    Err(ActionError::AlreadyJoined)
+                } else if self.seats.players_len() > self.settings.max_players as usize {
+                    Err(ActionError::NoSeatAvailable)
+                } else if !self.seats.seat_is_available(seat) {
+                    Err(ActionError::SeatNotAvailable)
+                } else {
+                    Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::Join { seat, chips: 0 } }))
+                }
+            }
+            (State::Completed, Action::Join { .. }) => Err(ActionError::GameHasEnded),
+            (_, Action::Join { .. }) => Err(ActionError::GameHasStarted),
+            (_, _) if !self.seats.contains_player(username) => Err(ActionError::NotAuthorised),
+            (State::NotStarted, Action::Leave) => Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::Leave })),
+            (State::Completed, Action::Leave) => Err(ActionError::GameHasEnded),
+            (_, Action::Leave) => Err(ActionError::GameHasStarted),
+            (State::Dealing, _) => Err(ActionError::Dealing),
+            (State::Choosing, Action::PutInBox { card: Some(card) }) => {
+                if self.hand_contains_card(username, card) {
+                    if self.hand_size(username) > 4 {
+                        Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::PutInBox { card: Some(card) } }))
+                    } else {
+                        Err(ActionError::CardNotPlayable)
+                    }
+                } else {
+                    Err(ActionError::CardNotAvailable)
+                }
+            }
+            (State::Pegging, Action::PlayCard { card }) => {
+                if Some(username) != self.active {
+                    Err(ActionError::OutOfTurn)
+                } else if !self.hand_contains_card(username, card) {
+                    Err(ActionError::CardNotAvailable)
+                } else if self.current_total() + value(card.rank) > 31 {
+                    Err(ActionError::CardNotPlayable)
+                } else {
+                    Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::PlayCard { card } }))
+                }
+            }
+            (State::Pegging, Action::Pass) => {
+                if Some(username) != self.active {
+                    Err(ActionError::OutOfTurn)
+                } else if self.hand_has_playable_card(username) {
+                    Err(ActionError::CannotPass)
+                } else {
+                    Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::Pass }))
+                }
+            }
+            (State::TurnUp, _) => Err(ActionError::Dealing),
+            (State::Scoring, _) => Err(ActionError::Dealing),
+            (State::Completed, _) => Err(ActionError::GameHasEnded),
+            (_, _) => Err(ActionError::InvalidActionForGameType),
+        }
+    }
+
+    fn take_action(&mut self, ValidatedUserAction(UserAction { username, action, .. }): ValidatedUserAction) -> Result<(), ActionError> {
+        self.actions_len += 1;
+        if matches!(action, Action::Message { .. }) {
+            return Ok(());
+        }
+        self.rng.advance();
+        match (self.state, action) {
+            (_, Action::AddOn { .. }) | (_, Action::Fold) | (_, Action::TimeoutFold) | (_, Action::Bet { .. }) => Err(ActionError::InvalidActionForGameType),
+            (State::NotStarted, Action::Join { seat, .. }) => self.seats.add_player(seat, username),
+            (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.turn_up = None;
+                self.hands.clear();
+                self.receiver = self.seats.player_after(username);
+                self.box_cards.clear();
+                self.pegging_cards.clear();
+                self.players_still_in.clear();
+                self.active = None;
+                self.state = State::Dealing;
+                Ok(())
+            }
+            (State::Dealing, Action::ReceiveCard { card: Some(card) }) => {
+                self.deck.remove(&card);
+                self.hands.entry(username).or_default().insert(card);
+                if self.all_hands_dealt() {
+                    self.receiver = None;
+                } else {
+                    self.receiver = self.receiver.and_then(|player| self.seats.player_after(player));
+                }
+                Ok(())
+            }
+            (State::Dealing, Action::EndDeal) => {
+                self.state = State::Choosing;
+                Ok(())
+            }
+            (State::Choosing, Action::PutInBox { card: Some(Card) }) => {
+                if let Some(hand) = self.hands.get_mut(&username) {
+                    hand.remove(&card);
+                }
+                self.box_cards.insert(card);
+                if self.box_cards.len() == 4 {
+                    self.state = State::TurnUp;
+                }
+                Ok(())
+            }
+            (State::TurnUp, Action::CommunityCard { card }) => {
+                self.turn_up = Some(card);
+                self.state = State::Pegging;
+                self.active = self.dealer.and_then(|dealer| self.seats.player_after(dealer));
+                self.players_still_in = self.players();
+                Ok(())
+            }
+            (State::Pegging, Action::PlayCard { card }) => {
+                if let Some(hand) = self.hands.get_mut(&username) {
+                    hand.remove(&card);
+                    if hand.is_empty() {
+                        self.players_still_in.remove(&username);
+                    }
+                }
+                self.pegging_cards.push((username, card));
+                Ok(())
+            }
+            (State::Pegging, Action::Pass) => {
+                self.players_still_in.remove(&username);
+                match self.active = self.active.and_then(|player| self.seats.player_after_where(player, |player| self.players_still_in.contains(&player))) {
+                    None => {
+                        self.used_pegging_cards.extend(self.pegging_cards.drain(..));
+                        self.players_still_in = self.hands.iter().filter(|(_, cards)| !cards.is_empty()).map(|(&username, _)| username).collect();
+                        self.active = self.active.and_then(|player| self.seats.player_after_where(player, |player| self.players_still_in.contains(&player)));
+                    }
+                    Some(active) => self.active = active,
+                }
+                if self.all_hands_are_empty() {
+                    for (username, card) in self.used_pegging_cards.drain(..) {
+                        self.hands.entry(username).or_default().insert(card);
+                    }
+                    self.state = State::Scoring;
+                }
+                Ok(())
+            }
+            (State::Pegging, Action::Score { points, .. }) => {
+                *self.points.entry(username).or_default() += points;
+                match self.active.and_then(|player| self.seats.player_after_where(player, |player| self.players_still_in.contains(&player))) {
+                    None => {
+                        self.used_pegging_cards.extend(self.pegging_cards.drain(..));
+                        self.players_still_in = self.hands.iter().filter(|(_, cards)| !cards.is_empty()).map(|(&username, _)| username).collect();
+                        self.active = self.active.and_then(|player| self.seats.player_after_where(player, |player| self.players_still_in.contains(&player)));
+                    }
+                    Some(active) => self.active = active,
+                }
+                if self.all_hands_are_empty() {
+                    for (username, card) in self.used_pegging_cards.drain(..) {
+                        self.hands.entry(username).or_default().insert(card);
+                    }
+                    self.state = State::Scoring;
+                }
+            }
+            (State::Scoring, Action::Score { points, .. }) => {
+                *self.points.entry(username).or_default() += points;
+            }
+            (State::Completed, _) => Err(ActionError::GameHasEnded),
+            (_, _) => Err(ActionError::InvalidActionForGameType),
+        }
+    }
+
+    fn next_dealer_action(&self, timestamp: Timestamp) -> DealerAction {
+        let mut rng = self.rng.clone();
+        match self.state {
+            State::NotStarted => match self.settings.start_time {
+                StartCondition::WhenFull => {
+                    if self.seats.players_len() == self.settings.max_players as usize {
+                        if let Some(username) = rng.choose_from(self.seats.player_set()) {
+                            return DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::NextToDeal }));
+                        }
+                    }
+                    DealerAction::WaitForPlayer
+                }
+                StartCondition::AtTime(start_time) => {
+                    if timestamp >= start_time {
+                        if self.seats.players_len() < 2 {
+                            return DealerAction::WaitForPlayer;
+                        }
+                        if let Some(username) = rng.choose_from(self.seats.player_set()) {
+                            return DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::NextToDeal }));
+                        }
+                    }
+                    DealerAction::WaitUntil(start_time)
+                }
+            },
+            State::Dealing => {
+                if let Some(username) = self.receiver {
+                    let card = rng.choose_from(&self.deck).cloned();
+                    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) {
+                            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");
+                            DealerAction::Leave
+                        }
+                    } else {
+                        DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::EndDeal }))
+                    }
+                } else {
+                    error!("Expected to deal a card but there was no dealer");
+                    DealerAction::Leave
+                }
+            }
+        }
+    }
+}
+
+*/