use super::traits::{Game, GameState}; use crate::blackjack::card_label; use crate::client::view::BlackjackView; pub const BLACKJACK_BOARD_LINES: usize = 6; pub struct BlackjackGame; impl BlackjackGame { pub fn new() -> Self { Self } 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(), } } /// Render a blackjack view to terminal lines. The GUI paints the same view. pub fn render_view(view: &BlackjackView) -> String { let balance = view .balance .map(|balance| balance.to_string()) .unwrap_or_else(|| "?".to_string()); if let Some(error) = &view.error { let bet = view.hand.as_ref().map(|hand| hand.bet).unwrap_or(view.bet); 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) = &view.hand { let bet = if view.status == "player" { hand.bet } else { view.bet }; let text = match view.status.as_str() { "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 view.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, view.bet, "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 ) } } impl Game for BlackjackGame { fn get_name(&self) -> &'static str { "Blackjack" } fn render(&self, state: &GameState) -> String { Self::render_view(&BlackjackView::from_state(state)) } 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() } }