use super::traits::{Game, GameState}; use crate::blackjack::{BlackjackState, card_label}; 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} Kr"), "win" => format!("Win! Won {winnings} Kr"), "push" => "Push. Bet returned.".to_string(), "lose" => "Dealer wins.".to_string(), "bust" => "Bust.".to_string(), "forfeit" => "Forfeited. Bet lost.".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} Kr Bet: {bet} Kr\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(), "forfeiting" => "Forfeiting...".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] Forfeit".to_string() } else { "[H]it [S]tand [Q] Forfeit".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: {} Kr Bet: {} Kr\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 - forfeit an active hand (bet is lost), or leave when no hand is active" .to_string() } }