1use constants::*;
2use itertools::*;
3use serde::{Deserialize, Serialize};
4
5use super::constants::*;
6use crate::{
7 display::format_vec,
8 domain::{
9 Card, Crib, GoStatus, Hand, Play, PlayState, Points, ScoreItem, ScoreKind, StarterCut,
10 Value,
11 },
12};
13
14#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
20pub struct ScoreSheet(Vec<ScoreItem>);
21
22impl ScoreSheet {
23 #[must_use]
30 pub fn add_event(mut self, kind: ScoreKind, cards: &[Card], points: Points) -> Self {
31 let event = ScoreItem::new(kind, Vec::from(cards), points);
32 self.0.push(event);
33 self
34 }
35
36 #[must_use]
44 pub fn add_event_if(
45 mut self,
46 condition: bool,
47 kind: ScoreKind,
48 cards: &[Card],
49 points: Points,
50 ) -> Self {
51 if condition {
52 let event = ScoreItem::new(kind, Vec::from(cards), points);
53 self.0.push(event);
54 }
55 self
56 }
57
58 #[must_use]
60 pub fn points(&self) -> Points {
61 self.0.iter().map(ScoreItem::points).sum()
62 }
63
64 #[must_use]
66 pub fn items(&self) -> &Vec<ScoreItem> {
67 &self.0
68 }
69
70 #[must_use]
76 pub fn his_heels(cut: Card) -> Self {
77 Self::default().add_event_if(
78 cut.is_jack(),
79 ScoreKind::HisHeels,
80 &[cut],
81 Points::from(SCORE_HIS_HEELS),
82 )
83 }
84
85 #[must_use]
91 pub fn play_card(play_state: &PlayState) -> Self {
92 Self::default()
93 .play_card_fifteens(play_state)
94 .play_card_pairs(play_state)
95 .play_card_runs(play_state)
96 .play_card_31(play_state)
97 .play_last_card(play_state)
98 }
99
100 fn play_card_fifteens(self, play_state: &PlayState) -> Self {
101 let cards = play_state
102 .current_plays()
103 .iter()
104 .map(|p| p.card())
105 .collect::<Vec<_>>();
106
107 self.add_event_if(
108 play_state.running_total() == Value::from(15),
109 ScoreKind::Fifteen,
110 cards.as_slice(),
111 Points::from(SCORE_FIFTEEN),
112 )
113 }
114
115 fn play_card_pairs(self, play_state: &PlayState) -> Self {
116 let cards = play_state
117 .current_plays()
118 .iter()
119 .rev()
120 .map(Play::card)
121 .collect::<Vec<_>>();
122
123 let pair_info = cards.split_first().and_then(|(first, rest)| {
124 let same_face = |card: &&Card| card.face() == first.face();
125 let count = 1 + rest.iter().take_while(same_face).count();
126 match count {
127 2 => Some((ScoreKind::Pair, SCORE_PAIR)),
128 3 => Some((ScoreKind::Triplet, SCORE_ROYAL_PAIR)),
129 4 => Some((ScoreKind::Quadruplet, SCORE_DOUBLE_ROYAL_PAIR)),
130 _ => None,
131 }
132 .map(|(kind, pts)| (kind, pts, count))
133 });
134
135 match pair_info {
136 Some((kind, points, count)) => self.add_event(kind, &cards[..count], points.into()),
137 None => self,
138 }
139 }
140
141 fn play_card_runs(self, play_state: &PlayState) -> Self {
142 let cards = play_state
143 .current_plays()
144 .iter()
145 .rev()
146 .map(Play::card)
147 .collect::<Vec<_>>();
148
149 let longest_run = (MINIMUM_RUN_LENGTH..=cards.len())
150 .rev()
151 .find(|&len| {
152 let slice = &cards[..len];
153 let mut ranks: Vec<_> = slice.iter().map(|c| c.rank()).collect();
154 ranks.sort_unstable();
155 ranks.windows(2).all(|w| w[1] == w[0] + 1)
156 })
157 .map(|len| {
158 let mut run = cards[..len].to_vec();
159 run.sort_by_key(|c| c.rank());
160 (run, Points::from(len))
161 });
162
163 if let Some((cards, points)) = longest_run {
164 self.add_event(ScoreKind::Run, cards.as_slice(), points)
165 } else {
166 self
167 }
168 }
169
170 fn play_card_31(self, play_state: &PlayState) -> Self {
171 let is_31 = play_state.running_total() == PLAY_TARGET.into();
172
173 if is_31 {
174 let cards = play_state
175 .current_plays()
176 .iter()
177 .map(|p| p.card())
178 .collect::<Vec<_>>();
179 self.add_event(ScoreKind::ThirtyOne, &cards, SCORE_THIRTY_ONE.into())
180 } else {
181 self
182 }
183 }
184
185 fn play_last_card(self, play_state: &PlayState) -> Self {
186 let is_finished = play_state.is_finished();
187 let is_31 = play_state.running_total() == PLAY_TARGET.into();
188
189 if is_finished && !is_31 {
190 let last_card = play_state.current_plays().last().map(|p| p.card());
193 self.add_event(
194 ScoreKind::LastCard,
195 &last_card.into_iter().collect::<Vec<_>>(),
196 SCORE_GO.into(),
197 )
198 } else {
199 self
200 }
201 }
202
203 #[must_use]
209 pub fn go(play_state: &PlayState) -> Self {
210 Self::default().go_last_card(play_state)
211 }
212
213 fn go_last_card(self, play_state: &PlayState) -> Self {
214 let last_card = play_state.current_plays().last().map(|p| p.card());
217
218 self.add_event_if(
219 play_state.go_status() != &GoStatus::NotCalled,
220 ScoreKind::LastCard,
221 &last_card.into_iter().collect::<Vec<_>>(),
222 Points::from(SCORE_GO),
223 )
224 }
225
226 #[must_use]
232 pub fn hand(hand: &Hand, cut: StarterCut) -> Self {
233 let mut all = hand.clone();
234 all.add(cut);
235
236 Self::default()
237 .fifteens(all.as_ref())
238 .pairs(all.as_ref())
239 .runs(all.as_ref())
240 .flush(hand.as_ref(), cut, 4)
241 .nobs(hand.as_ref(), cut)
242 }
243
244 #[must_use]
250 pub fn crib(crib: &Crib, cut: StarterCut) -> Self {
251 let mut all = crib.clone();
252 all.add(cut);
253
254 Self::default()
255 .fifteens(all.as_ref())
256 .pairs(all.as_ref())
257 .runs(all.as_ref())
258 .flush(crib.as_ref(), cut, 5)
259 .nobs(crib.as_ref(), cut)
260 }
261
262 fn fifteens(self, cards: &[Card]) -> Self {
263 (2..=cards.len())
264 .flat_map(|n| cards.iter().combinations(n))
265 .filter(|combo| combo.iter().map(|c| c.value()).sum::<Value>() == 15.into())
266 .fold(self, |acc, combo| {
267 let combo_cards: Vec<Card> = combo.iter().copied().copied().collect();
268 acc.add_event(ScoreKind::Fifteen, &combo_cards, SCORE_FIFTEEN.into())
269 })
270 }
271
272 fn pairs(self, cards: &[Card]) -> Self {
273 cards
274 .iter()
275 .copied()
276 .combinations(2)
277 .filter(|pair| pair[0].face() == pair[1].face())
278 .fold(self, |acc, pair| {
279 acc.add_event(ScoreKind::Pair, &pair, SCORE_PAIR.into())
280 })
281 }
282
283 fn runs(self, cards: &[Card]) -> Self {
284 let mut scores = Vec::default();
285
286 let mut cards = Vec::from(cards);
287 cards.sort_by_key(|c| c.rank());
288
289 for len in (MINIMUM_RUN_LENGTH..=cards.len()).rev() {
290 let mut points = Points::default();
291
292 for combination in cards.iter().combinations(len) {
293 let differences = combination
294 .windows(2)
295 .map(|cs| cs[1].rank() - cs[0].rank())
296 .collect::<Vec<_>>();
297
298 let sequential = differences.iter().all(|d| *d == 1);
299 if sequential {
300 let combination = combination.into_iter().cloned().collect::<Vec<_>>();
301 points = Points::from(combination.len());
302 scores.push((combination, points))
303 }
304 }
305
306 if points != Points::default() {
307 break;
308 }
309 }
310
311 scores
312 .into_iter()
313 .fold(self, |acc, e| acc.add_event(ScoreKind::Run, &e.0, e.1))
314 }
315
316 fn flush(self, cards: &[Card], cut: StarterCut, constraint: usize) -> Self {
317 let mut all = Vec::from(cards);
318 all.push(cut);
319
320 let all_same_suit = |cs: &[Card]| cs.iter().map(|c| c.suit()).all_equal();
321
322 let points_if_flush = |cs: &[Card]| {
323 (cs.len() >= constraint)
324 .then_some(all_same_suit(cs).then(|| Points::from(cs.len())))
325 .flatten()
326 };
327
328 if let Some(points) = points_if_flush(&all) {
329 self.add_event(ScoreKind::Flush, &all, points)
330 } else if let Some(points) = points_if_flush(cards) {
331 self.add_event(ScoreKind::Flush, cards, points)
332 } else {
333 self
334 }
335 }
336
337 fn nobs(self, cards: &[Card], cut: StarterCut) -> Self {
338 let mut matched = cards
339 .iter()
340 .filter(|c| c.is_jack() && c.suit() == cut.suit());
341
342 if let Some(card) = matched.next() {
343 self.add_event(ScoreKind::Nobs, &[*card], SCORE_NOBS.into())
344 } else {
345 self
346 }
347 }
348}
349
350impl std::fmt::Display for ScoreSheet {
351 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352 let events = format_vec(self.0.as_slice());
353 events.fmt(f)
354 }
355}
356
357#[cfg(test)]
358mod test {
359 use std::str::FromStr;
360
361 use super::*;
362 use crate::domain::{
363 Card, Hand, PLAYER0, PLAYER1,
364 test::domain_macros::{card, hand},
365 };
366
367 #[test]
368 fn impossible_pairs_will_return_0() {
369 let hand1 = hand!("AHADAH2C");
370 let hand2 = hand!("ACAS2H2D");
371
372 let mut play_state = PlayState::new(PLAYER0)
373 .with_pending_plays(PLAYER0, hand1.as_ref())
374 .with_pending_plays(PLAYER1, hand2.as_ref());
375 let _ = play_state.play(card!("AH"));
376 let _ = play_state.play(card!("AC"));
377 let _ = play_state.play(card!("AD"));
378 let _ = play_state.play(card!("AS"));
379 let _ = play_state.play(card!("AH"));
380
381 assert_eq!(
382 ScoreSheet::play_card(&play_state).points(),
383 Points::default()
384 );
385 }
386}