cribbage_core/hand/
mod.rs

1mod crib_cards;
2mod crib_part;
3mod dealt_cards;
4mod kept_cards;
5
6use crate::card::{Card, Rank};
7use crate::deck::Deck;
8use crate::CribbageCoreError;
9
10pub use self::crib_cards::CribCards;
11pub use self::crib_part::FourPlayerCribPart;
12pub use self::crib_part::ThreePlayerCribPart;
13pub use self::crib_part::TwoPlayerCribPart;
14pub use self::dealt_cards::FourPlayerDeal;
15pub use self::dealt_cards::ThreePlayerDeal;
16pub use self::dealt_cards::TwoPlayerDeal;
17pub use self::kept_cards::KeptCards;
18
19pub fn deal_four_player_hand(deck: &mut Deck) -> Result<FourPlayerDeal, CribbageCoreError> {
20    let cards = deck.draw_n(5)?;
21    Ok(FourPlayerDeal::new([
22        cards[0], cards[1], cards[2], cards[3], cards[4],
23    ]))
24}
25
26pub fn deal_three_player_hand(deck: &mut Deck) -> Result<ThreePlayerDeal, CribbageCoreError> {
27    let cards = deck.draw_n(5)?;
28    Ok(ThreePlayerDeal::new([
29        cards[0], cards[1], cards[2], cards[3], cards[4],
30    ]))
31}
32
33pub fn deal_two_player_hand(deck: &mut Deck) -> Result<TwoPlayerDeal, CribbageCoreError> {
34    let cards = deck.draw_n(6)?;
35    Ok(TwoPlayerDeal::new([
36        cards[0], cards[1], cards[2], cards[3], cards[4], cards[5],
37    ]))
38}
39
40#[derive(Debug)]
41pub struct Hand {
42    cards: [Card; 4],
43    cut: Card,
44    is_crib: bool,
45    score: Option<u8>,
46}
47
48impl Hand {
49    pub fn new(cards: [Card; 4], cut: Card, is_crib: bool) -> Hand {
50        Hand {
51            cards,
52            cut,
53            is_crib,
54            score: None,
55        }
56    }
57
58    pub fn cards(&self) -> &[Card] {
59        &self.cards
60    }
61
62    pub fn score(&mut self) -> u8 {
63        if let Some(score) = self.score {
64            return score;
65        }
66
67        let mut score = 0u8;
68        score += self.score_fifteens();
69        score += self.score_pairs();
70        score += self.score_runs();
71        score += self.score_flush();
72        score += self.score_nobs();
73        self.score = Some(score);
74        score
75    }
76
77    fn score_fifteens(&self) -> u8 {
78        let mut points = 0u8;
79        let subsets = vec![
80            self.sets_of_two(),
81            self.sets_of_three(),
82            self.sets_of_four(),
83            self.sets_of_five(),
84        ];
85
86        for subset in subsets {
87            for set in subset {
88                let mut sum = 0u8;
89                for card in &set {
90                    sum += card.rank().value();
91                }
92
93                if sum == 15 {
94                    points += 2;
95                }
96            }
97        }
98
99        points
100    }
101
102    fn score_flush(&self) -> u8 {
103        let mut points = 0;
104        let suit = self.cards[0].suit();
105        if self.cards[1..4].iter().all(|c| c.suit() == suit) {
106            points += 4;
107
108            if self.cut.suit() == suit {
109                points += 1;
110            } else if self.is_crib {
111                points = 0;
112            }
113        }
114
115        points
116    }
117
118    fn score_nobs(&self) -> u8 {
119        for card in &self.cards {
120            if card.rank() == Rank::Jack && card.suit() == self.cut.suit() {
121                return 1;
122            }
123        }
124
125        0
126    }
127
128    fn score_pairs(&self) -> u8 {
129        let mut points = 0u8;
130        let sets_of_two = self.sets_of_two();
131        for set in sets_of_two {
132            if set[0].rank() == set[1].rank() {
133                points += 2u8;
134            }
135        }
136
137        points
138    }
139
140    fn score_runs(&self) -> u8 {
141        let mut points = 0u8;
142        let mut temp = 0u8;
143
144        for mut set in self.sets_of_five() {
145            set.sort();
146            temp += Hand::score_run(&set);
147        }
148
149        points += temp;
150        if points != 0u8 {
151            return points;
152        }
153
154        for mut set in self.sets_of_four() {
155            set.sort();
156            temp += Hand::score_run(&set);
157        }
158
159        points += temp;
160        if points != 0u8 {
161            return points;
162        }
163
164        for mut set in self.sets_of_three() {
165            set.sort();
166            temp += Hand::score_run(&set);
167        }
168
169        points += temp;
170        if points != 0u8 {
171            return points;
172        }
173
174        points
175    }
176
177    fn score_run(cards: &[Card]) -> u8 {
178        if cards.len() < 3usize {
179            return 0u8;
180        }
181
182        let mut points = 1u8;
183        for i in 1..cards.len() {
184            if cards[i].rank().ordinal() == (cards[i - 1].rank().ordinal() + 1) {
185                points += 1u8;
186            } else {
187                points = 0u8;
188                break;
189            }
190        }
191
192        points
193    }
194
195    fn sets_of_two(&self) -> Vec<Vec<Card>> {
196        vec![
197            vec![self.cards[0], self.cards[1]],
198            vec![self.cards[0], self.cards[2]],
199            vec![self.cards[0], self.cards[3]],
200            vec![self.cards[0], self.cut],
201            vec![self.cards[1], self.cards[2]],
202            vec![self.cards[1], self.cards[3]],
203            vec![self.cards[1], self.cut],
204            vec![self.cards[2], self.cards[3]],
205            vec![self.cards[2], self.cut],
206            vec![self.cards[3], self.cut],
207        ]
208    }
209
210    fn sets_of_three(&self) -> Vec<Vec<Card>> {
211        vec![
212            vec![self.cards[0], self.cards[1], self.cards[2]],
213            vec![self.cards[0], self.cards[1], self.cards[3]],
214            vec![self.cards[0], self.cards[2], self.cards[3]],
215            vec![self.cards[1], self.cards[2], self.cards[3]],
216            vec![self.cards[0], self.cards[1], self.cut],
217            vec![self.cards[0], self.cards[2], self.cut],
218            vec![self.cards[1], self.cards[2], self.cut],
219            vec![self.cards[0], self.cards[3], self.cut],
220            vec![self.cards[1], self.cards[3], self.cut],
221            vec![self.cards[2], self.cards[3], self.cut],
222        ]
223    }
224
225    fn sets_of_four(&self) -> Vec<Vec<Card>> {
226        vec![
227            vec![self.cards[0], self.cards[1], self.cards[2], self.cards[3]],
228            vec![self.cards[0], self.cards[1], self.cards[2], self.cut],
229            vec![self.cards[0], self.cards[1], self.cards[3], self.cut],
230            vec![self.cards[0], self.cards[2], self.cards[3], self.cut],
231            vec![self.cards[1], self.cards[2], self.cards[3], self.cut],
232        ]
233    }
234
235    fn sets_of_five(&self) -> Vec<Vec<Card>> {
236        vec![vec![
237            self.cards[0],
238            self.cards[1],
239            self.cards[2],
240            self.cards[3],
241            self.cut,
242        ]]
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use crate::card::Card;
249    use crate::hand::Hand;
250    use std::str::FromStr;
251
252    #[allow(dead_code)]
253    #[cfg_attr(feature = "extensive-tests", test)]
254    fn test_hands() {
255        use std::env;
256        use std::fs::File;
257        use std::io::{BufRead, BufReader};
258        use std::path::PathBuf;
259
260        let key = "CRIBBAGE_CORE_RESOURCES_DIRECTORY";
261        let resources = match env::var(key) {
262            Ok(val) => val,
263            Err(_) => panic!("Environment variable {} not set", key),
264        };
265
266        let mut path = PathBuf::new();
267        path.push(resources);
268        path.push("cribbage_hand_scores");
269
270        let filenames = [
271            "A.txt", "2.txt", "3.txt", "4.txt", "5.txt", "6.txt", "7.txt", "8.txt", "9.txt",
272            "T.txt", "J.txt", "Q.txt", "K.txt",
273        ];
274
275        for filename in &filenames {
276            path.push(filename);
277            println!("Processing: {:?}", path);
278            let reader = BufReader::new(File::open(&path).unwrap());
279            for (line_number, line) in reader.lines().enumerate() {
280                let line = line.unwrap().to_string();
281                let tokens: Vec<&str> = line.split(' ').collect();
282                if tokens.len() != 7 {
283                    panic!("Line {} in {:?} is malformed", line_number + 1, path);
284                }
285
286                let cut = Card::from_str(tokens[0]).unwrap();
287                let card1 = Card::from_str(tokens[1]).unwrap();
288                let card2 = Card::from_str(tokens[2]).unwrap();
289                let card3 = Card::from_str(tokens[3]).unwrap();
290                let card4 = Card::from_str(tokens[4]).unwrap();
291                let expected_non_crib_hand_score: u8 = tokens[5].parse().unwrap();
292                let expected_crib_hand_score: u8 = tokens[6].parse().unwrap();
293
294                let mut hand = Hand::new([card1, card2, card3, card4], cut, false);
295                let non_crib_hand_score = hand.score();
296                if non_crib_hand_score != expected_non_crib_hand_score {
297                    panic!(
298                        "Line {} in {:?}: {:?} scored {} points as a non-crib hand,\
299                         but should have scored {} points",
300                        line_number + 1,
301                        path,
302                        hand,
303                        non_crib_hand_score,
304                        expected_non_crib_hand_score
305                    );
306                }
307
308                let mut crib_hand = Hand::new([card1, card2, card3, card4], cut, true);
309                let crib_hand_score = crib_hand.score();
310                if crib_hand_score != expected_crib_hand_score {
311                    panic!(
312                        "Line {} in {:?}: {:?} scored {} points as a crib hand,\
313                         but should have scored {} points",
314                        line_number + 1,
315                        path,
316                        hand,
317                        non_crib_hand_score,
318                        expected_non_crib_hand_score
319                    );
320                }
321            }
322
323            path.pop();
324        }
325    }
326}