summaryrefslogtreecommitdiffstats
path: root/src/blackjack.rs
blob: ac0060a32f26aa44a1481ba65c94c70d603ccf8e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//! Shared blackjack rules and wire state.

use serde::{Deserialize, Serialize};

pub const DECKS: usize = 6;
pub const SUITS: [char; 4] = ['C', 'D', 'H', 'S'];
pub const RANKS: [&str; 13] = [
    "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A",
];

/// A fresh, unshuffled six-deck shoe.
pub fn fresh_shoe() -> Vec<String> {
    let mut shoe = Vec::with_capacity(DECKS * 52);
    for _ in 0..DECKS {
        for suit in SUITS {
            for rank in RANKS {
                shoe.push(format!("{rank}{suit}"));
            }
        }
    }
    shoe
}

pub fn card_value(card: &str) -> u8 {
    let rank = &card[..card.len().saturating_sub(1)];
    match rank {
        "A" => 11,
        "10" | "J" | "Q" | "K" => 10,
        _ => rank.parse().unwrap_or(0),
    }
}

/// Best total: aces count as 11 until that would bust the hand.
pub fn hand_total(cards: &[String]) -> u8 {
    let mut total = 0u16;
    let mut aces = 0u16;
    for card in cards {
        if card_value(card) == 11 {
            aces += 1;
            total += 1;
        } else {
            total += card_value(card) as u16;
        }
    }
    for _ in 0..aces {
        if total + 10 <= 21 {
            total += 10;
        }
    }
    total.min(255) as u8
}

pub fn is_blackjack(cards: &[String]) -> bool {
    cards.len() == 2 && hand_total(cards) == 21
}

pub fn is_red_suit(card: &str) -> bool {
    matches!(card.chars().last(), Some('D') | Some('H'))
}

pub fn card_label(card: &str) -> String {
    let Some(suit) = card.chars().last() else {
        return card.to_string();
    };
    let rank = &card[..card.len() - 1];
    let suit = match suit {
        'C' => '♣',
        'D' => '♦',
        'H' => '♥',
        'S' => '♠',
        other => other,
    };
    format!("{rank}{suit}")
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlackjackState {
    pub session_id: String,
    pub phase: String,
    pub player: Vec<String>,
    pub dealer: Vec<String>,
    pub player_total: u8,
    pub dealer_total: u8,
    pub bet: u64,
    pub base_bet: u64,
    pub balance: i64,
    pub actions: Vec<String>,
    pub result: String,
    pub winnings: u64,
}