summaryrefslogtreecommitdiffstats
path: root/src/blackjack.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-29 18:22:40 +0100
committersalmonik <salmonik@tuta.io>2026-10-29 18:22:40 +0100
commitb5a52fc0312afdf5d5fbe53275dc88d6d881c2ff (patch)
treed12e3c7f6c23ae1698199d16ee763551edfd8c12 /src/blackjack.rs
parentv2.1.2: ignore ANSI when measuring board height (diff)
downloadzagan-bet-b5a52fc0312afdf5d5fbe53275dc88d6d881c2ff.tar.gz
zagan-bet-b5a52fc0312afdf5d5fbe53275dc88d6d881c2ff.zip
v2.2.0: add blackjack with reserved bets and hit/stand/double
Diffstat (limited to '')
-rw-r--r--src/blackjack.rs90
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
3use serde::{Deserialize, Serialize};
4
5pub const DECKS: usize = 6;
6pub const SUITS: [char; 4] = ['C', 'D', 'H', 'S'];
7pub 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.
12pub 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
24pub 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.
34pub 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
53pub fn is_blackjack(cards: &[String]) -> bool {
54 cards.len() == 2 && hand_total(cards) == 21
55}
56
57pub fn is_red_suit(card: &str) -> bool {
58 matches!(card.chars().last(), Some('D') | Some('H'))
59}
60
61pub 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)]
77pub 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}