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
+ }
+ }
+ }
+ }
+}
+
+*/