summaryrefslogtreecommitdiffstats
path: root/src/client/games/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/client/games/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 'src/client/games/blackjack.rs')
-rw-r--r--src/client/games/blackjack.rs148
1 files changed, 148 insertions, 0 deletions
diff --git a/src/client/games/blackjack.rs b/src/client/games/blackjack.rs
new file mode 100644
index 0000000..a34eb63
--- /dev/null
+++ b/src/client/games/blackjack.rs
@@ -0,0 +1,148 @@
1use super::traits::{Game, GameState};
2use crate::blackjack::{card_label, BlackjackState};
3
4pub const BLACKJACK_BOARD_LINES: usize = 6;
5
6pub struct BlackjackGame;
7
8impl BlackjackGame {
9 pub fn new() -> Self {
10 Self
11 }
12
13 fn state(state: &GameState) -> Option<BlackjackState> {
14 state
15 .data
16 .get("blackjack")
17 .and_then(|raw| serde_json::from_str(raw).ok())
18 }
19
20 fn cards(cards: &[String]) -> String {
21 cards
22 .iter()
23 .map(|card| {
24 if card == "?" {
25 "[ ? ]".to_string()
26 } else {
27 format!("[{}]", card_label(card))
28 }
29 })
30 .collect::<Vec<_>>()
31 .join(" ")
32 }
33
34 fn result_text(result: &str, winnings: u64) -> String {
35 match result {
36 "blackjack" => format!("BLACKJACK! Won {winnings} PLN"),
37 "win" => format!("Win! Won {winnings} PLN"),
38 "push" => "Push. Bet returned.".to_string(),
39 "lose" => "Dealer wins.".to_string(),
40 "bust" => "Bust.".to_string(),
41 "cancelled" => "Cancelled.".to_string(),
42 _ => "Hand complete.".to_string(),
43 }
44 }
45}
46
47impl Game for BlackjackGame {
48 fn get_name(&self) -> &'static str {
49 "Blackjack"
50 }
51
52 fn render(&self, state: &GameState) -> String {
53 let status = state
54 .data
55 .get("status")
56 .map(String::as_str)
57 .unwrap_or("ready");
58 let balance = state
59 .data
60 .get("balance")
61 .map(String::as_str)
62 .unwrap_or("?");
63 let hand = Self::state(state);
64
65 if let Some(error) = state.data.get("error") {
66 let bet = hand.as_ref().map(|hand| hand.bet).unwrap_or(state.bet_amount);
67 return format!(
68 "=== BLACKJACK ===\r\nBalance: {balance} PLN Bet: {bet} PLN\r\n\r\n\r\nERROR: {error}\r\n[Q] Quit"
69 );
70 }
71
72 let (dealer, dealer_total, player, player_total, bet, text, controls) =
73 if let Some(hand) = &hand {
74 let bet = if status == "player" {
75 hand.bet
76 } else {
77 state.bet_amount
78 };
79 let text = match status {
80 "player" => {
81 if hand.actions.iter().any(|action| action == "double") {
82 "Hit, stand or double.".to_string()
83 } else {
84 "Hit or stand.".to_string()
85 }
86 }
87 "dealing" => "Dealing...".to_string(),
88 "cancelling" => "Cancelling...".to_string(),
89 "done" => Self::result_text(&hand.result, hand.winnings),
90 _ => "Press [SPACE] to deal.".to_string(),
91 };
92 let controls = if status == "player" {
93 if hand.actions.iter().any(|action| action == "double") {
94 "[H]it [S]tand [D]ouble [Q]uit".to_string()
95 } else {
96 "[H]it [S]tand [Q]uit".to_string()
97 }
98 } else {
99 "[SPACE] New hand [+/-] Bet [Q] Quit".to_string()
100 };
101 (
102 Self::cards(&hand.dealer),
103 hand.dealer_total,
104 Self::cards(&hand.player),
105 hand.player_total,
106 bet,
107 text,
108 controls,
109 )
110 } else {
111 (
112 "—".to_string(),
113 0,
114 "—".to_string(),
115 0,
116 state.bet_amount,
117 "Press [SPACE] to deal.".to_string(),
118 "[SPACE] Deal [+/-] Bet [Q] Quit".to_string(),
119 )
120 };
121
122 format!(
123 "=== BLACKJACK ===\r\nBalance: {} PLN Bet: {} PLN\r\nDealer: {} {}\r\nYou: {} {}\r\n{}\r\n{}",
124 balance, bet, dealer, dealer_total, player, player_total, text, controls
125 )
126 }
127
128 fn render_lines(&self, _state: &GameState) -> usize {
129 BLACKJACK_BOARD_LINES
130 }
131
132 fn get_help(&self) -> String {
133 "BLACKJACK\n\
134 =========\n\
135 Six-deck shoe, fresh for every hand.\n\
136 Dealer stands on all 17s.\n\
137 Blackjack pays 3:2.\n\
138 Double is allowed on the first two cards.\n\
139 \n\
140 H - hit\n\
141 S - stand\n\
142 D - double (when available)\n\
143 SPACE - deal a new hand\n\
144 +/- - change the next hand's bet\n\
145 Q - leave the table"
146 .to_string()
147 }
148}