diff options
Diffstat (limited to '')
| -rw-r--r-- | src/blackjack.rs | 90 |
1 files changed, 90 insertions, 0 deletions
diff --git a/src/blackjack.rs b/src/blackjack.rs new file mode 100644 index 0000000..ac0060a --- /dev/null +++ b/src/blackjack.rs | |||
| @@ -0,0 +1,90 @@ | |||
| 1 | //! Shared blackjack rules and wire state. | ||
| 2 | |||
| 3 | use serde::{Deserialize, Serialize}; | ||
| 4 | |||
| 5 | pub const DECKS: usize = 6; | ||
| 6 | pub const SUITS: [char; 4] = ['C', 'D', 'H', 'S']; | ||
| 7 | pub const RANKS: [&str; 13] = [ | ||
| 8 | "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", | ||
| 9 | ]; | ||
| 10 | |||
| 11 | /// A fresh, unshuffled six-deck shoe. | ||
| 12 | pub fn fresh_shoe() -> Vec<String> { | ||
| 13 | let mut shoe = Vec::with_capacity(DECKS * 52); | ||
| 14 | for _ in 0..DECKS { | ||
| 15 | for suit in SUITS { | ||
| 16 | for rank in RANKS { | ||
| 17 | shoe.push(format!("{rank}{suit}")); | ||
| 18 | } | ||
| 19 | } | ||
| 20 | } | ||
| 21 | shoe | ||
| 22 | } | ||
| 23 | |||
| 24 | pub fn card_value(card: &str) -> u8 { | ||
| 25 | let rank = &card[..card.len().saturating_sub(1)]; | ||
| 26 | match rank { | ||
| 27 | "A" => 11, | ||
| 28 | "10" | "J" | "Q" | "K" => 10, | ||
| 29 | _ => rank.parse().unwrap_or(0), | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | /// Best total: aces count as 11 until that would bust the hand. | ||
| 34 | pub fn hand_total(cards: &[String]) -> u8 { | ||
| 35 | let mut total = 0u16; | ||
| 36 | let mut aces = 0u16; | ||
| 37 | for card in cards { | ||
| 38 | if card_value(card) == 11 { | ||
| 39 | aces += 1; | ||
| 40 | total += 1; | ||
| 41 | } else { | ||
| 42 | total += card_value(card) as u16; | ||
| 43 | } | ||
| 44 | } | ||
| 45 | for _ in 0..aces { | ||
| 46 | if total + 10 <= 21 { | ||
| 47 | total += 10; | ||
| 48 | } | ||
| 49 | } | ||
| 50 | total.min(255) as u8 | ||
| 51 | } | ||
| 52 | |||
| 53 | pub fn is_blackjack(cards: &[String]) -> bool { | ||
| 54 | cards.len() == 2 && hand_total(cards) == 21 | ||
| 55 | } | ||
| 56 | |||
| 57 | pub fn is_red_suit(card: &str) -> bool { | ||
| 58 | matches!(card.chars().last(), Some('D') | Some('H')) | ||
| 59 | } | ||
| 60 | |||
| 61 | pub fn card_label(card: &str) -> String { | ||
| 62 | let Some(suit) = card.chars().last() else { | ||
| 63 | return card.to_string(); | ||
| 64 | }; | ||
| 65 | let rank = &card[..card.len() - 1]; | ||
| 66 | let suit = match suit { | ||
| 67 | 'C' => '♣', | ||
| 68 | 'D' => '♦', | ||
| 69 | 'H' => '♥', | ||
| 70 | 'S' => '♠', | ||
| 71 | other => other, | ||
| 72 | }; | ||
| 73 | format!("{rank}{suit}") | ||
| 74 | } | ||
| 75 | |||
| 76 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] | ||
| 77 | pub struct BlackjackState { | ||
| 78 | pub session_id: String, | ||
| 79 | pub phase: String, | ||
| 80 | pub player: Vec<String>, | ||
| 81 | pub dealer: Vec<String>, | ||
| 82 | pub player_total: u8, | ||
| 83 | pub dealer_total: u8, | ||
| 84 | pub bet: u64, | ||
| 85 | pub base_bet: u64, | ||
| 86 | pub balance: i64, | ||
| 87 | pub actions: Vec<String>, | ||
| 88 | pub result: String, | ||
| 89 | pub winnings: u64, | ||
| 90 | } | ||
