+use std::collections::{HashMap, HashSet};
+
+use crate::card::{Card, FIFTY_TWO_CARD_DECK};
+use crate::rng::{Seed, WaveRng};
+use crate::seats::Seats;
+use crate::username::Username;
+use crate::util::timestamp::Timestamp;
+
+use super::{Action, ActionError, DealerAction, Game, StartCondition, UserAction, ValidatedUserAction};
+
mod score;
-/*
+use self::score::{PeggingScore, score_4_card_cribbage_hand, score_pegging, value};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum State {
Choosing,
TurnUp,
Pegging,
+ ScoringPegging,
Scoring,
+ ScoringBox,
Completed,
}
points: HashMap::new(),
}
}
+
+ fn hand_contains_card(&self, username: Username, card: Card) -> bool {
+ self.hands.get(&username).map_or(false, |hand| hand.contains(&card))
+ }
+
+ fn hand_size(&self, username: Username) -> usize {
+ self.hands.get(&username).map_or(0, HashSet::len)
+ }
+
+ fn all_hands_are_empty(&self) -> bool {
+ self.hands.values().all(HashSet::is_empty)
+ }
+
+ fn all_hands_dealt(&self) -> bool {
+ self.hands.values().map(HashSet::len).all(|len| len == 6)
+ }
+
+ fn pegging_total(&self) -> u32 {
+ self.pegging_cards.iter().map(|(_, card)| value(card.rank)).sum()
+ }
+
+ 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))
+ }
+
+ fn next_player_still_in(&self) -> Option<Username> {
+ self.active.and_then(|player| self.seats.player_after_where(player, |player| self.players_still_in.contains(&player)))
+ }
+
+ fn last_pegging_score(&self) -> Option<(Username, PeggingScore)> {
+ match self.pegging_cards.last() {
+ None => self.used_pegging_cards.last().map(|(username, _)| (*username, PeggingScore::one_for_a_go())),
+ Some((username, _)) => {
+ let cards: Vec<_> = self.pegging_cards.iter().map(|(_, card)| *card).collect();
+ let score = score_pegging(&cards, false);
+ if score.points() > 0 {
+ Some((*username, score))
+ } else {
+ None
+ }
+ }
+ }
+ }
+
+ 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])
+ } else {
+ None
+ }
+ })
+ }
+
+ fn player_has_won(&self, username: Username) -> bool {
+ self.points.get(&username).map_or(false, |points| *points >= 121)
+ }
+
+ fn winner(&self) -> Option<Username> {
+ self.points.iter().filter(|&(_, points)| *points >= 121).next().map(|(username, _)| *username)
+ }
}
impl Game for Cribbage {
Err(ActionError::OutOfTurn)
} else if !self.hand_contains_card(username, card) {
Err(ActionError::CardNotAvailable)
- } else if self.current_total() + value(card.rank) > 31 {
+ } else if self.pegging_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) {
+ } else if self.hand_has_playable_pegging_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::ScoringBox, _) => Err(ActionError::Dealing),
(State::Completed, _) => Err(ActionError::GameHasEnded),
(_, _) => Err(ActionError::InvalidActionForGameType),
}
self.state = State::Dealing;
Ok(())
}
+ (_, Action::WinGame) => {
+ self.state = State::Completed;
+ Ok(())
+ }
(State::Dealing, Action::ReceiveCard { card: Some(card) }) => {
self.deck.remove(&card);
self.hands.entry(username).or_default().insert(card);
self.state = State::Choosing;
Ok(())
}
- (State::Choosing, Action::PutInBox { card: Some(Card) }) => {
+ (State::Choosing, Action::PutInBox { card: Some(card) }) => {
if let Some(hand) = self.hands.get_mut(&username) {
hand.remove(&card);
}
}
}
self.pegging_cards.push((username, card));
+ if self.last_pegging_score().is_some() {
+ self.state = State::ScoringPegging;
+ }
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))) {
+ match self.next_player_still_in() {
None => {
+ self.state = State::ScoringPegging;
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,
+ self.active = self.next_player_still_in();
+ },
+ 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.active = self.dealer.and_then(|dealer| self.seats.player_after(dealer));
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))) {
+ (State::ScoringPegging, Action::Score { points, .. }) => {
+ *self.points.entry(username).or_default() += points as u32;
+ match self.next_player_still_in() {
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)));
+ self.active = self.next_player_still_in();
}
- Some(active) => self.active = active,
+ active => self.active = active,
}
- if self.all_hands_are_empty() {
+ if self.player_has_won(username) {
+ self.state = State::ScoringPegging;
+ } else if self.all_hands_are_empty() {
for (username, card) in self.used_pegging_cards.drain(..) {
self.hands.entry(username).or_default().insert(card);
}
+ self.active = self.dealer.and_then(|dealer| self.seats.player_after(dealer));
self.state = State::Scoring;
+ } else {
+ self.state = State::Pegging;
}
+ Ok(())
}
(State::Scoring, Action::Score { points, .. }) => {
- *self.points.entry(username).or_default() += points;
+ *self.points.entry(username).or_default() += points as u32;
+ if self.dealer == Some(username) {
+ self.state = State::ScoringBox;
+ } else {
+ self.active = self.seats.player_after(username);
+ }
+ Ok(())
+ }
+ (State::ScoringBox, Action::Score { points, .. }) => {
+ *self.points.entry(username).or_default() += points as u32;
+ self.active = None;
+ Ok(())
}
(State::Completed, _) => Err(ActionError::GameHasEnded),
(_, _) => Err(ActionError::InvalidActionForGameType),
DealerAction::Leave
}
}
+ State::Choosing => DealerAction::WaitForPlayer,
+ State::TurnUp => {
+ if let Some(username) = self.dealer {
+ 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");
+ DealerAction::Leave
+ }
+ } else {
+ error!("Expected to deal a card but there was no dealer");
+ DealerAction::Leave
+ }
+ }
+ State::Pegging => DealerAction::WaitForPlayer,
+ State::ScoringPegging => {
+ if let Some(username) = self.winner() {
+ DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::WinGame }))
+ } else if let Some((username, score)) = self.last_pegging_score() {
+ DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::Score { points: score.points() as u64, reason: format!("{}", score) } }))
+ } else {
+ error!("Expected a pegging score");
+ DealerAction::Leave
+ }
+ }
+ State::Scoring | State::ScoringBox => {
+ if let Some(username) = self.winner() {
+ DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::WinGame }))
+ } else if let Some(username) = self.active {
+ if let (Some(hand), Some(turn_up)) = (self.four_card_hand(username), self.turn_up) {
+ let score = score_4_card_cribbage_hand(hand, turn_up, self.state == State::ScoringBox);
+ DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::Score { points: score.points() as u64, reason: format!("{}", score) } }))
+ } else {
+ error!("Found no 4-card hand for scoring user");
+ DealerAction::Leave
+ }
+ } else if let Some(username) = self.dealer.and_then(|dealer| self.seats.player_after(dealer)) {
+ DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::NextToDeal }))
+ } else {
+ error!("Could not find next dealer");
+ DealerAction::Leave
+ }
+ }
+ State::Completed => DealerAction::Leave,
}
}
}
-
-*/
thirty_one: u8,
pair: u8,
run: u8,
+ go: bool,
}
impl PeggingScore {
+ pub fn one_for_a_go() -> Self {
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 0, go: true }
+ }
+
pub fn points(&self) -> u8 {
- self.fifteen + self.thirty_one + self.pair + self.run
+ self.fifteen + self.thirty_one + self.pair + self.run + self.go as u8
}
}
impl Display for PeggingScore {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
- PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 0 } => Ok(()),
- PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 0 } => f.write_str("Fifteen for two"),
- PeggingScore { fifteen: 2, thirty_one: 0, pair: 2, run: 0 } => f.write_str("Fifteen two and two is four"),
- PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 3 } => f.write_str("Fifteen two and three is five"),
- PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 4 } => f.write_str("Fifteen two and four is six"),
- PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 5 } => f.write_str("Fifteen two and five is seven"),
- PeggingScore { fifteen: 2, thirty_one: 0, pair: 6, run: 0 } => f.write_str("Fifteen two and six is eight"),
- PeggingScore { fifteen: 2, thirty_one: 0, pair: 12, run: 0 } => f.write_str("Fifteen two and twelve is fourteen"),
- PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 0 } => f.write_str("Thirty-one for two"),
- PeggingScore { fifteen: 0, thirty_one: 2, pair: 2, run: 0 } => f.write_str("Thirty-one for two and two is four"),
- PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 3 } => f.write_str("Thirty-one for two and three is five"),
- PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 4 } => f.write_str("Thirty-one for two and four is six"),
- PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 5 } => f.write_str("Thirty-one for two and five is seven"),
- PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 6 } => f.write_str("Thirty-one for two and six is eight"),
- PeggingScore { fifteen: 0, thirty_one: 2, pair: 6, run: 0 } => f.write_str("Thirty-one for two and six is eight"),
- PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 7 } => f.write_str("Thirty-one for two and seven is nine"),
- PeggingScore { fifteen: 0, thirty_one: 2, pair: 12, run: 0 } => f.write_str("Thirty-one for two and twelve is fourteen"),
- PeggingScore { fifteen: 0, thirty_one: 0, pair: 2, run: 0 } => f.write_str("Two"),
- PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 3 } => f.write_str("Three"),
- PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 4 } => f.write_str("Four"),
- PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 5 } => f.write_str("Five"),
- PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 6 } => f.write_str("Six"),
- PeggingScore { fifteen: 0, thirty_one: 0, pair: 6, run: 0 } => f.write_str("Six"),
- PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 7 } => f.write_str("Seven"),
- PeggingScore { fifteen: 0, thirty_one: 0, pair: 12, run: 0 } => f.write_str("Twelve"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 0, go: false } => Ok(()),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 0, go: true } => f.write_str("One for a go"),
+ PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 0, go: false } => f.write_str("Fifteen for two"),
+ PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 0, go: true } => f.write_str("Fifteen for two and a go is three"),
+ PeggingScore { fifteen: 2, thirty_one: 0, pair: 2, run: 0, go: false } => f.write_str("Fifteen two and two is four"),
+ PeggingScore { fifteen: 2, thirty_one: 0, pair: 2, run: 0, go: true } => f.write_str("Fifteen two and two and a go is five"),
+ PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 3, go: false } => f.write_str("Fifteen two and three is five"),
+ PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 3, go: true } => f.write_str("Fifteen two and three and a go is six"),
+ PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 4, go: false } => f.write_str("Fifteen two and four is six"),
+ PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 4, go: true } => f.write_str("Fifteen two and four and a go is seven"),
+ PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 5, go: false } => f.write_str("Fifteen two and five is seven"),
+ PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 5, go: true } => f.write_str("Fifteen two and five and a go is eight"),
+ PeggingScore { fifteen: 2, thirty_one: 0, pair: 6, run: 0, go: false } => f.write_str("Fifteen two and six is eight"),
+ PeggingScore { fifteen: 2, thirty_one: 0, pair: 6, run: 0, go: true } => f.write_str("Fifteen two and six and a go is nine"),
+ PeggingScore { fifteen: 2, thirty_one: 0, pair: 12, run: 0, go: false } => f.write_str("Fifteen two and twelve is fourteen"),
+ PeggingScore { fifteen: 2, thirty_one: 0, pair: 12, run: 0, go: true } => f.write_str("Fifteen two and twelve and a go is fifteen"),
+ PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 0, go: false } => f.write_str("Thirty-one for two"),
+ PeggingScore { fifteen: 0, thirty_one: 2, pair: 2, run: 0, go: false } => f.write_str("Thirty-one for two and two is four"),
+ PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 3, go: false } => f.write_str("Thirty-one for two and three is five"),
+ PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 4, go: false } => f.write_str("Thirty-one for two and four is six"),
+ PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 5, go: false } => f.write_str("Thirty-one for two and five is seven"),
+ PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 6, go: false } => f.write_str("Thirty-one for two and six is eight"),
+ PeggingScore { fifteen: 0, thirty_one: 2, pair: 6, run: 0, go: false } => f.write_str("Thirty-one for two and six is eight"),
+ PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 7, go: false } => f.write_str("Thirty-one for two and seven is nine"),
+ PeggingScore { fifteen: 0, thirty_one: 2, pair: 12, run: 0, go: false } => f.write_str("Thirty-one for two and twelve is fourteen"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 2, run: 0, go: false } => f.write_str("Two"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 2, run: 0, go: true } => f.write_str("Two and a go is three"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 3, go: false } => f.write_str("Three"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 3, go: true } => f.write_str("Three and a go is four"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 4, go: false } => f.write_str("Four"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 4, go: true } => f.write_str("Four and a go is five"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 5, go: false } => f.write_str("Five"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 5, go: true } => f.write_str("Five and a go is six"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 6, go: false } => f.write_str("Six"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 6, go: true } => f.write_str("Six and a go is seven"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 6, run: 0, go: false } => f.write_str("Six"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 6, run: 0, go: true } => f.write_str("Six and a go is seven"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 7, go: false } => f.write_str("Seven"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 7, go: true } => f.write_str("Seven and a go is eight"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 12, run: 0, go: false } => f.write_str("Twelve"),
+ PeggingScore { fifteen: 0, thirty_one: 0, pair: 12, run: 0, go: true } => f.write_str("Twelve and a go is thirteen"),
_ => write!(f, "[ERROR] {:?}", self),
}
}
}
-fn value(rank: Rank) -> u32 {
+pub fn value(rank: Rank) -> u32 {
match rank {
Ace => 1,
Two => 2,
}
}
-fn sum_value(cards: &[Card]) -> u32 {
+pub fn sum_value(cards: &[Card]) -> u32 {
cards.iter().map(|card| card.rank).map(value).sum()
}
cards.iter().map(|card| card.rank.ace_low_rank()).sorted().tuple_windows().all(|(rank1, rank2)| rank2 == rank1 + 1)
}
-pub fn score_pegging(cards: &[Card]) -> PeggingScore {
- let mut score = PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 0 };
+pub fn score_pegging(cards: &[Card], go: bool) -> PeggingScore {
+ let mut score = PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 0, go };
if sum_value(cards) == 15 {
score.fifteen += 2;
#[test]
fn pegging_scores() {
- let score = score_pegging(&[ACE_OF_SPADES, TWO_OF_SPADES, THREE_OF_SPADES, FOUR_OF_SPADES, FIVE_OF_SPADES]);
+ let score = score_pegging(&[ACE_OF_SPADES, TWO_OF_SPADES, THREE_OF_SPADES, FOUR_OF_SPADES, FIVE_OF_SPADES], false);
assert_eq!(7, score.points());
assert_eq!("Fifteen two and five is seven", format!("{}", score));
- let score = score_pegging(&[TEN_OF_HEARTS, FIVE_OF_CLUBS]);
+ let score = score_pegging(&[TEN_OF_HEARTS, FIVE_OF_CLUBS], false);
assert_eq!(2, score.points());
assert_eq!("Fifteen for two", format!("{}", score));
- let score = score_pegging(&[TEN_OF_HEARTS, FIVE_OF_CLUBS, TEN_OF_CLUBS]);
+ let score = score_pegging(&[TEN_OF_HEARTS, FIVE_OF_CLUBS, TEN_OF_CLUBS], false);
assert_eq!(0, score.points());
assert_eq!("", format!("{}", score));
- let score = score_pegging(&[TEN_OF_HEARTS, FIVE_OF_CLUBS, TEN_OF_CLUBS, SIX_OF_DIAMONDS]);
+ let score = score_pegging(&[TEN_OF_HEARTS, FIVE_OF_CLUBS, TEN_OF_CLUBS, SIX_OF_DIAMONDS], false);
assert_eq!(2, score.points());
assert_eq!("Thirty-one for two", format!("{}", score));
}