twentyone/game.rs
1//! Game-related functions and structures, such as the dealer or hand value checking
2use crate::cards;
3
4/// Actions a player can perform
5pub enum PlayerAction {
6 Hit,
7 Stand,
8 DoubleDown,
9 Split,
10 /// Bet an amount of money
11 Bet(i32),
12 None,
13}
14
15/// Requests for the player from the dealer
16pub enum DealerRequest {
17 /// Request a bet from the player
18 Bet,
19 /// Request a player to play a hand
20 ///
21 /// # Arguments
22 ///
23 /// * `usize` - The index of the hand to play
24 Play(usize),
25 /// The dealer's up card
26 UpCard([char; 2]),
27 /// The dealer's hit card
28 HitCard([char; 2]),
29 /// The dealer's hand after they have finished playing
30 DealerHand(Vec<[char; 2]>),
31 /// The low card threshold was hit and a new shoe was created
32 LowCards,
33 /// An error with a returned PlayerAction
34 ///
35 /// # Arguments
36 ///
37 /// * `PlayerActionError` - More info on why the error occurred
38 Error(PlayerActionError),
39}
40
41/// Reason for a dealer being unable to perform an action
42pub enum PlayerActionError {
43 /// Not enough money for the requested action
44 ///
45 /// # Arguments
46 ///
47 /// * `usize` - The index of the affected hand, if applicable
48 /// * `PlayerAction` - The attempted action
49 NotEnoughMoney(usize, PlayerAction),
50 /// An unexpected action was returned
51 ///
52 /// # Arguments
53 ///
54 /// * `usize` - The index of the affected hand, if applicable
55 /// * `PlayerAction` - The unexpected action
56 UnexpectedAction(usize, PlayerAction),
57}
58
59/// Configure different aspects of the game
60pub struct GameConfig {
61 /// Whether the dealer should stand on soft 17 or hit
62 pub stand_soft_17: bool,
63 /// The multiplier for when a player gets a blackjack
64 pub blackjack_payout: f32,
65 /// Whether to allow splitting
66 pub splitting: bool,
67 /// Whether to allow doubling down
68 pub doubling_down: bool,
69 /// Whether to allow doubling down after splitting
70 pub double_after_split: bool,
71 /// The minimum player bet
72 pub min_bet: i32,
73 /// The maximum player bet
74 pub max_bet: i32,
75 /// How many decks to add to the new shoe if `auto_new_shoe` is enabled
76 pub shoe_deck_count: u8,
77 /// How many cards must be left in a deck before DealerRequest::LowCards is called.
78 /// If `auto_new_shoe` is enabled, the new shoe will be created when this number is reached.
79 pub low_cards_threshold: usize,
80}
81
82/// A default configuration for game settings.
83///
84/// Allows doubling down and splitting, stands on soft 17,
85/// pays out blackjacks 3 to 2, and allows doubling after splitting.
86///
87/// Creates a new 6-deck shoe when 52 or less cards are remaining.
88/// Minimum bet is 1 and maximum bet is `i32::MAX` (2,147,483,647)
89pub const DEFAULT_CONFIG: GameConfig = GameConfig {
90 stand_soft_17: true,
91 blackjack_payout: 1.5,
92 splitting: true,
93 doubling_down: true,
94 double_after_split: true,
95 min_bet: 1,
96 max_bet: i32::MAX,
97 shoe_deck_count: 6,
98 low_cards_threshold: 52,
99};
100
101/// Describes a blackjack dealer
102pub struct Dealer<'a> {
103 hand: Vec<[char; 2]>,
104 shoe: Vec<[char; 2]>,
105 players: Vec<Player>,
106 config: GameConfig,
107 callback: &'a dyn Fn(DealerRequest, Option<&Player>, &Dealer) -> PlayerAction,
108}
109
110/// Describes a blackjack player
111pub struct Player {
112 money: i32,
113 hands: Vec<Vec<[char; 2]>>,
114}
115
116impl Dealer<'_> {
117 /// Returns a new Dealer
118 ///
119 /// # Arguments
120 ///
121 /// * `shoe` - The shoe (or deck) to draw from
122 /// * `callback` - A function to handle player turns
123 ///
124 /// `callback` is passed a `DealerRequest` and an `Option<&Player>`.
125 /// The option will always have a player if it applies to the event (eg. betting),
126 /// but will not have a player for dealer updates (eg. up card, dealer hits).
127 ///
128 /// # Callback
129 ///
130 /// The callback function will always return a `PlayerAction`,
131 /// but it should return different things based on the `DealerRequest`:
132 ///
133 /// | `DealerRequest` | `PlayerAction` |
134 /// |---------------------------------------------|------------------------------------------------------------------------------------------------------|
135 /// | `DealerRequest::Bet` | `PlayerAction::Bet(i32)` |
136 /// | `DealerRequest::Play` | One of `PlayerAction::Hit`, `PlayerAction::Stand`, `PlayerAction::DoubleDown`, `PlayerAction::Split` |
137 /// | `DealerRequest::Error(PlayerActionError)` | `PlayerAction::None` and handle the returned error |
138 /// | `DealerRequest::UpCard([char; 2])` | `PlayerAction::None` |
139 /// | `DealerRequest::HitCard([char; 2])` | `PlayerAction::None` |
140 /// | `DealerRequest::DealerHand(Vec<[char; 2]>)` | `PlayerAction::None` |
141 ///
142 /// If an unexpected return value is given, the callback will be called
143 /// again with a request of `DealerAction::Error(PlayerActionError::UnexpectedAction)`
144 /// along with the index of the affected hand (if applicable)
145 /// and the request that was invalid.
146 ///
147 /// After an error is provided, the dealer will request the same action that
148 /// caused the error. If nothing changes, the dealer will infinitely loop.
149 ///
150 /// # Examples
151 ///
152 /// Example code is available in the [Quick Start](../index.html#quick-start) from the main page.
153 pub fn new<'a>(
154 shoe: Vec<[char; 2]>,
155 game_config: GameConfig,
156 callback: &'a dyn Fn(DealerRequest, Option<&Player>, &Dealer) -> PlayerAction,
157 ) -> Dealer {
158 Dealer {
159 hand: Vec::new(),
160 shoe: shoe,
161 players: Vec::new(),
162 config: game_config,
163 callback: callback,
164 }
165 }
166
167 /// Returns a reference to the dealer's hand
168 pub fn hand(&self) -> &Vec<[char; 2]> {
169 &self.hand
170 }
171
172 /// Returns a reference to the dealer's shoe
173 pub fn shoe(&self) -> &Vec<[char; 2]> {
174 &self.shoe
175 }
176
177 /// Returns a reference to the dealer's players
178 pub fn players(&self) -> &Vec<Player> {
179 &self.players
180 }
181 /// Returns a mutable reference to the dealer's hand
182 pub fn hand_mut(&mut self) -> &mut Vec<[char; 2]> {
183 &mut self.hand
184 }
185
186 /// Returns a mutable reference to the dealer's shoe
187 pub fn shoe_mut(&mut self) -> &mut Vec<[char; 2]> {
188 &mut self.shoe
189 }
190
191 /// Returns a mutable reference to the dealer's players
192 pub fn players_mut(&mut self) -> &mut Vec<Player> {
193 &mut self.players
194 }
195
196 /// Clear the dealer's and all players' hands
197 pub fn clear_table(&mut self) {
198 self.hand.clear();
199 for player in self.players.iter_mut() {
200 player.hands_mut().clear();
201 player.hands_mut().push(Vec::new());
202 }
203 }
204
205 /// Deal a hand to all players
206 pub fn deal_hands(&mut self) {
207 for _ in 0..2 {
208 cards::hit_card(&mut self.shoe, &mut self.hand);
209 for player in self.players.iter_mut() {
210 cards::hit_card(&mut self.shoe, &mut player.hands_mut()[0]);
211 }
212 }
213 }
214
215 /// Hit a card to a player
216 ///
217 /// # Arguments
218 ///
219 /// * `player` - The index of the player to hit
220 /// * `hand` - The index of the player's hand (used for split hands)
221 pub fn hit_card(&mut self, player: usize, hand: usize) {
222 cards::hit_card(&mut self.shoe, &mut self.players[player].hands[hand]);
223 }
224
225 /// Play a round of blackjack
226 ///
227 /// Calls `callback` to get player bets/actions.
228 ///
229 /// # Arguments
230 ///
231 /// * `clear_table` - Clear the table at the beginning of the round
232 pub fn play_round(&mut self, clear_table: bool) {
233 if clear_table {
234 self.clear_table();
235 }
236
237 let mut player_bets: Vec<i32> = Vec::new();
238
239 // Get bets
240 for i in 0..self.players.len() {
241 loop {
242 let bet = (self.callback)(DealerRequest::Bet, Some(&self.players[i]), &self);
243 if let PlayerAction::Bet(amount) = bet {
244 // Check if player can afford bet and if it is within limits
245 if self.players[i].money() >= &amount {
246 if self.config.min_bet <= amount && amount <= self.config.max_bet {
247 player_bets.push(amount);
248 *self.players[i].money_mut() -= amount;
249 break;
250 } else {
251 let error = PlayerActionError::UnexpectedAction(0, bet);
252 (self.callback)(
253 DealerRequest::Error(error),
254 Some(&self.players[i]),
255 &self,
256 );
257 }
258 } else {
259 let error = PlayerActionError::NotEnoughMoney(0, bet);
260 (self.callback)(DealerRequest::Error(error), Some(&self.players[i]), &self);
261 }
262 } else {
263 let error = PlayerActionError::UnexpectedAction(0, bet);
264 (self.callback)(DealerRequest::Error(error), Some(&self.players[i]), &self);
265 }
266 }
267 }
268
269 // Deal hands
270 self.deal_hands();
271
272 // Send dealer up card
273 (self.callback)(DealerRequest::UpCard(self.hand[1]), None, &self);
274
275 // Get player actions
276 for i in 0..self.players.len() {
277 let mut can_double: Vec<bool>;
278 let mut can_split: bool;
279 if self.config.doubling_down {
280 // Check if player has enough money to double down
281 can_double = vec![self.players[i].money() >= &player_bets[i]];
282 } else {
283 can_double = vec![false];
284 }
285 if self.config.splitting {
286 // Check if player cards are valid for a split and if player has enough money
287 can_split = crate::game::can_split(&self.players[i].hands()[0]) && can_double[0];
288 } else {
289 can_split = false;
290 }
291
292 // Keep track of stood hands
293 let mut stood = vec![false];
294
295 // Keep track of original bet
296 // Used when doubling after splitting
297 let original_bet = player_bets[i];
298
299 // Active hand
300 let mut hand_count = 1;
301 let mut j = 0;
302 // Get actions from each hand, one at a time
303 // Using a loop and incrementing j manually because for loops would not recheck
304 // length of player.hands() after a split
305 loop {
306 // Check for low cards
307 if self.shoe.len() <= self.config.low_cards_threshold {
308 (self.callback)(DealerRequest::LowCards, None, &self);
309 // Create a new shoe if the option is enabled
310 self.shoe = cards::create_shoe(self.config.shoe_deck_count);
311 cards::shuffle_deck(&mut self.shoe);
312 }
313
314 if j >= hand_count {
315 break;
316 }
317 while !stood[j] {
318 let action =
319 (self.callback)(DealerRequest::Play(j), Some(&self.players[i]), &self);
320 match action {
321 PlayerAction::Hit => {
322 self.hit_card(i, j);
323 can_double[j] = false;
324 }
325 PlayerAction::Stand => stood[j] = true,
326 PlayerAction::DoubleDown => {
327 if can_double[j] {
328 *self.players[i].money_mut() -= original_bet;
329 player_bets[i] += original_bet;
330 stood[j] = true;
331 self.hit_card(i, j);
332 can_double[j] = false;
333 } else {
334 (self.callback)(
335 DealerRequest::Error(PlayerActionError::UnexpectedAction(
336 j, action,
337 )),
338 Some(&self.players[i]),
339 &self,
340 );
341 }
342 }
343 PlayerAction::Split => {
344 if can_split {
345 *self.players[i].money_mut() -= original_bet;
346 player_bets[i] += original_bet;
347 self.players[i].hands_mut().push(Vec::new());
348 stood.push(false);
349 if self.config.double_after_split && self.config.doubling_down {
350 can_double.push(true);
351 } else {
352 can_double[0] = false;
353 can_double.push(false);
354 }
355 // "Draw" card from first hand and place it into second
356 let card = cards::draw_card(
357 self.players[i].hands_mut().get_mut(0).unwrap(),
358 );
359 self.players[i].hands_mut()[1].push(card.unwrap());
360 // Hit another card to each hand
361 self.hit_card(i, 0);
362 self.hit_card(i, 1);
363 hand_count = 2;
364 can_split = false;
365 } else {
366 (self.callback)(
367 DealerRequest::Error(PlayerActionError::UnexpectedAction(
368 j, action,
369 )),
370 Some(&self.players[i]),
371 &self,
372 );
373 }
374 }
375 _ => {
376 let error = PlayerActionError::UnexpectedAction(j, action);
377 (self.callback)(
378 DealerRequest::Error(error),
379 Some(&self.players[i]),
380 &self,
381 );
382 }
383 }
384
385 // Check if the hand is busted
386 if get_hand_value(&self.players[i].hands()[j], true) > 21 {
387 stood[j] = true;
388 }
389 }
390 j += 1;
391 }
392 }
393
394 // Dealer play
395 let mut busted = false;
396 loop {
397 let hand_value = get_hand_value(&self.hand, true);
398 if hand_value > 21 {
399 busted = true;
400 break;
401 } else if hand_value >= 17 {
402 if self.config.stand_soft_17 {
403 break;
404 // Check if hand is exactly 17 contains an ace
405 } else if hand_value == 17 && self.hand.iter().any(|&i| i[1] == 'A') {
406 // Check if ace is acting as an 11 or a 1
407 if hand_value == get_hand_value(&self.hand, false) {
408 let card = cards::draw_card(&mut self.shoe).unwrap();
409 self.hand.push(card);
410 (self.callback)(DealerRequest::HitCard(card), None, &self);
411 } else {
412 break;
413 }
414 }
415 break;
416 } else {
417 let card = cards::draw_card(&mut self.shoe).unwrap();
418 self.hand.push(card);
419 (self.callback)(DealerRequest::HitCard(card), None, &self);
420 }
421 }
422
423 // Pay out winners
424 let dealer_hand_value = get_hand_value(&self.hand, true);
425 for i in 0..self.players.len() {
426 for j in 0..self.players[i].hands().len() {
427 let hand_value = get_hand_value(&self.players[i].hands()[j], true);
428 // Check if player busted
429 if hand_value > 21 {
430 continue;
431 }
432
433 // Pay out normal amount if player did not bust, did not have blackjack,
434 // and beat dealer/dealer busted
435 if hand_value < 21 && (busted || hand_value > dealer_hand_value) {
436 self.players[i].money +=
437 player_bets[i] * 2 / self.players[i].hands().len() as i32;
438 } else if hand_value == 21 && (busted || hand_value > dealer_hand_value) {
439 // Check if player had blackjack
440 if self.players[i].hands()[j].len() == 2 {
441 // Make sure dealer didn't have blackjack
442 if dealer_hand_value == 21 && self.hand.len() == 2 {
443 // Push, refund player
444 self.players[i].money += player_bets[i];
445 } else {
446 // Pay out 3 to 2
447 self.players[i].money += player_bets[i]
448 + (player_bets[i] as f32 * self.config.blackjack_payout) as i32;
449 }
450 } else {
451 self.players[i].money +=
452 player_bets[i] * 2 / self.players[i].hands().len() as i32;
453 }
454 // Push, refund player
455 } else if hand_value == dealer_hand_value {
456 self.players[i].money += player_bets[i];
457 }
458 }
459 }
460
461 (self.callback)(DealerRequest::DealerHand(self.hand.clone()), None, &self);
462 }
463}
464
465impl Player {
466 /// Returns a new Player
467 ///
468 /// # Arguments
469 ///
470 /// * `money` - The amount of money to give the player
471 /// * `hands` - A Vector of hands (`Vec<[char; 2]>`)
472 ///
473 /// # Examples
474 ///
475 /// ```
476 /// use twentyone::game::Player;
477 /// let player = Player::new(100);
478 /// ```
479 pub fn new(money: i32) -> Player {
480 Player {
481 money: money,
482 hands: vec![Vec::new()],
483 }
484 }
485
486 /// Returns a reference to the player's money
487 pub fn money(&self) -> &i32 {
488 &self.money
489 }
490
491 /// Returns a reference to the player's hands
492 pub fn hands(&self) -> &Vec<Vec<[char; 2]>> {
493 &self.hands
494 }
495
496 /// Returns a mutable reference to the player's money
497 pub fn money_mut(&mut self) -> &mut i32 {
498 &mut self.money
499 }
500
501 /// Returns a mutable reference to the player's hands
502 pub fn hands_mut(&mut self) -> &mut Vec<Vec<[char; 2]>> {
503 &mut self.hands
504 }
505}
506
507/// Returns the value of a hand
508///
509/// # Arguments
510///
511/// * `hand` - The hand to get the value of
512///
513/// # Examples
514///
515/// ```
516/// use twentyone::{cards, game};
517/// let mut deck = cards::create_deck();
518/// cards::shuffle_deck(&mut deck);
519/// let mut hand = Vec::new();
520/// cards::hit_card(&mut deck, &mut hand);
521/// cards::hit_card(&mut deck, &mut hand);
522/// println!("{}", game::get_hand_value(&hand, true));
523/// ```
524pub fn get_hand_value(hand: &Vec<[char; 2]>, auto_aces: bool) -> u8 {
525 let mut value = 0;
526 let mut aces = 0;
527 for i in hand.iter() {
528 value += match i[1] {
529 '2' => 2,
530 '3' => 3,
531 '4' => 4,
532 '5' => 5,
533 '6' => 6,
534 '7' => 7,
535 '8' => 8,
536 '9' => 9,
537 'T' | 'J' | 'Q' | 'K' => 10,
538 'A' => {
539 aces += 1;
540 0
541 }
542 _ => 0,
543 }
544 }
545 // Add aces
546 if auto_aces {
547 // Check if an ace being 11 would bust the hand
548 for _ in 0..aces {
549 if value + 11 > 21 {
550 value += 1;
551 } else {
552 value += 11;
553 }
554 }
555 } else {
556 value += 11 * aces;
557 }
558 value
559}
560
561/// Returns whether a hand is able to split
562///
563/// # Arguments
564///
565/// * `hand` - The hand to be split
566///
567/// # Examples
568///
569/// ```
570/// use twentyone::{cards, game};
571/// let mut deck = cards::create_deck();
572/// cards::shuffle_deck(&mut deck);
573/// let mut hand = Vec::new();
574/// cards::hit_card(&mut deck, &mut hand);
575/// cards::hit_card(&mut deck, &mut hand);
576/// println!("{}", game::can_split(&hand));
577/// ```
578pub fn can_split(hand: &Vec<[char; 2]>) -> bool {
579 if hand.len() != 2 {
580 return false;
581 }
582
583 hand[0][1] == hand[1][1]
584}