From b5a52fc0312afdf5d5fbe53275dc88d6d881c2ff Mon Sep 17 00:00:00 2001 From: salmonik Date: Thu, 29 Oct 2026 18:22:40 +0100 Subject: v2.2.0: add blackjack with reserved bets and hit/stand/double --- src/client/games/blackjack.rs | 148 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 src/client/games/blackjack.rs (limited to 'src/client/games/blackjack.rs') 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 @@ +use super::traits::{Game, GameState}; +use crate::blackjack::{card_label, BlackjackState}; + +pub const BLACKJACK_BOARD_LINES: usize = 6; + +pub struct BlackjackGame; + +impl BlackjackGame { + pub fn new() -> Self { + Self + } + + fn state(state: &GameState) -> Option { + state + .data + .get("blackjack") + .and_then(|raw| serde_json::from_str(raw).ok()) + } + + fn cards(cards: &[String]) -> String { + cards + .iter() + .map(|card| { + if card == "?" { + "[ ? ]".to_string() + } else { + format!("[{}]", card_label(card)) + } + }) + .collect::>() + .join(" ") + } + + fn result_text(result: &str, winnings: u64) -> String { + match result { + "blackjack" => format!("BLACKJACK! Won {winnings} PLN"), + "win" => format!("Win! Won {winnings} PLN"), + "push" => "Push. Bet returned.".to_string(), + "lose" => "Dealer wins.".to_string(), + "bust" => "Bust.".to_string(), + "cancelled" => "Cancelled.".to_string(), + _ => "Hand complete.".to_string(), + } + } +} + +impl Game for BlackjackGame { + fn get_name(&self) -> &'static str { + "Blackjack" + } + + fn render(&self, state: &GameState) -> String { + let status = state + .data + .get("status") + .map(String::as_str) + .unwrap_or("ready"); + let balance = state + .data + .get("balance") + .map(String::as_str) + .unwrap_or("?"); + let hand = Self::state(state); + + if let Some(error) = state.data.get("error") { + let bet = hand.as_ref().map(|hand| hand.bet).unwrap_or(state.bet_amount); + return format!( + "=== BLACKJACK ===\r\nBalance: {balance} PLN Bet: {bet} PLN\r\n\r\n\r\nERROR: {error}\r\n[Q] Quit" + ); + } + + let (dealer, dealer_total, player, player_total, bet, text, controls) = + if let Some(hand) = &hand { + let bet = if status == "player" { + hand.bet + } else { + state.bet_amount + }; + let text = match status { + "player" => { + if hand.actions.iter().any(|action| action == "double") { + "Hit, stand or double.".to_string() + } else { + "Hit or stand.".to_string() + } + } + "dealing" => "Dealing...".to_string(), + "cancelling" => "Cancelling...".to_string(), + "done" => Self::result_text(&hand.result, hand.winnings), + _ => "Press [SPACE] to deal.".to_string(), + }; + let controls = if status == "player" { + if hand.actions.iter().any(|action| action == "double") { + "[H]it [S]tand [D]ouble [Q]uit".to_string() + } else { + "[H]it [S]tand [Q]uit".to_string() + } + } else { + "[SPACE] New hand [+/-] Bet [Q] Quit".to_string() + }; + ( + Self::cards(&hand.dealer), + hand.dealer_total, + Self::cards(&hand.player), + hand.player_total, + bet, + text, + controls, + ) + } else { + ( + "—".to_string(), + 0, + "—".to_string(), + 0, + state.bet_amount, + "Press [SPACE] to deal.".to_string(), + "[SPACE] Deal [+/-] Bet [Q] Quit".to_string(), + ) + }; + + format!( + "=== BLACKJACK ===\r\nBalance: {} PLN Bet: {} PLN\r\nDealer: {} {}\r\nYou: {} {}\r\n{}\r\n{}", + balance, bet, dealer, dealer_total, player, player_total, text, controls + ) + } + + fn render_lines(&self, _state: &GameState) -> usize { + BLACKJACK_BOARD_LINES + } + + fn get_help(&self) -> String { + "BLACKJACK\n\ + =========\n\ + Six-deck shoe, fresh for every hand.\n\ + Dealer stands on all 17s.\n\ + Blackjack pays 3:2.\n\ + Double is allowed on the first two cards.\n\ + \n\ + H - hit\n\ + S - stand\n\ + D - double (when available)\n\ + SPACE - deal a new hand\n\ + +/- - change the next hand's bet\n\ + Q - leave the table" + .to_string() + } +} -- cgit v1.2.3