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/core.rs | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 149 insertions(+), 5 deletions(-) (limited to 'src/client/core.rs') diff --git a/src/client/core.rs b/src/client/core.rs index e005f2a..6e9c28b 100644 --- a/src/client/core.rs +++ b/src/client/core.rs @@ -1,5 +1,7 @@ use crate::client::games::manager::GameManager; -use crate::client::games::response::{parse_roulette_response, parse_spin_response}; +use crate::client::games::response::{ + parse_blackjack_response, parse_roulette_response, parse_spin_response, +}; use crate::client::ui::{ command_names, completion_lines, display_width, help_lines, input_cursor_column, parse_bet_arg, record_history, @@ -61,6 +63,9 @@ pub enum Effect { enum Pending { Spin, RouletteSpin, + BlackjackStart, + BlackjackAction, + BlackjackCancel, Ping, Echo, Info, @@ -191,6 +196,32 @@ impl ClientCore { self.board_effects() } }, + Some(Pending::BlackjackStart) | Some(Pending::BlackjackAction) => { + match parse_blackjack_response(reply) { + Ok(state) => { + self.balance = Some(state.balance); + self.game.apply_blackjack_state(state); + self.board_effects() + } + Err(message) => { + self.game.set_error(message); + self.board_effects() + } + } + } + Some(Pending::BlackjackCancel) => match parse_blackjack_response(reply) { + Ok(_) => { + self.game.end_game(); + vec![ + Effect::ClearRegion, + Effect::Print(vec!["Game exited.".to_string()]), + ] + } + Err(message) => { + self.game.set_error(message); + self.board_effects() + } + }, Some(Pending::Ping) => { if reply.trim() == "PONG" { print_line("Pong") @@ -225,14 +256,16 @@ impl ClientCore { } fn handle_game_key(&mut self, key: Key) -> Vec { - if self + let game_type = self .game .get_current_state() - .map(|state| state.game_type.as_str()) - == Some("roulette") - { + .map(|state| state.game_type.as_str()); + if game_type == Some("roulette") { return self.handle_roulette_key(key); } + if game_type == Some("blackjack") { + return self.handle_blackjack_key(key); + } match key { Key::CtrlC => vec![Effect::Quit], @@ -411,6 +444,117 @@ impl ClientCore { .unwrap_or_default() } + fn handle_blackjack_key(&mut self, key: Key) -> Vec { + let status = self + .game + .get_current_state() + .and_then(|state| state.data.get("status")) + .cloned() + .unwrap_or_else(|| "ready".to_string()); + let hand = self.game.blackjack_state(); + + match key { + Key::CtrlC => vec![Effect::Quit], + Key::Char('q') => { + if let Some(hand) = hand.filter(|hand| hand.phase == "player") { + self.game.set_blackjack_status("cancelling"); + self.pending = Some(Pending::BlackjackCancel); + vec![Effect::SendLine(format!( + "BLACKJACK_ACTION {} cancel", + hand.session_id + ))] + } else { + self.game.end_game(); + vec![ + Effect::ClearRegion, + Effect::Print(vec!["Game exited.".to_string()]), + ] + } + } + Key::Char(' ') => self.start_blackjack(), + Key::Char('h') + if hand.as_ref().is_some_and(|hand| hand.phase == "player") => + { + self.blackjack_action("hit") + } + Key::Char('s') + if hand.as_ref().is_some_and(|hand| hand.phase == "player") => + { + self.blackjack_action("stand") + } + Key::Char('d') + if hand.as_ref().is_some_and(|hand| { + hand.phase == "player" + && hand.actions.iter().any(|action| action == "double") + }) => + { + self.blackjack_action("double") + } + Key::Char('+') | Key::Char('=') + if matches!(status.as_str(), "ready" | "done") => + { + let _ = self.game.adjust_bet(10); + self.board_effects() + } + Key::Char('-') if matches!(status.as_str(), "ready" | "done") => { + let _ = self.game.adjust_bet(-10); + self.board_effects() + } + _ => Vec::new(), + } + } + + fn start_blackjack(&mut self) -> Vec { + let status = self + .game + .get_current_state() + .and_then(|state| state.data.get("status")) + .cloned() + .unwrap_or_else(|| "ready".to_string()); + if !matches!(status.as_str(), "ready" | "done") { + return Vec::new(); + } + let Some(request) = self.game.spin_request() else { + return Vec::new(); + }; + if let Some(balance) = self.balance { + let bet = self + .game + .get_current_state() + .map(|state| state.bet_amount) + .unwrap_or(0); + if bet as i64 > balance { + self.game.set_error("Insufficient balance"); + return self.board_effects(); + } + } + + self.game.set_blackjack_status("dealing"); + self.pending = Some(Pending::BlackjackStart); + let mut effects = self.board_effects(); + effects.push(Effect::SendLine(request)); + effects + } + + fn blackjack_action(&mut self, action: &str) -> Vec { + let Some(hand) = self.game.blackjack_state() else { + return Vec::new(); + }; + if hand.phase != "player" { + return Vec::new(); + } + if action == "double" + && !hand.actions.iter().any(|available| available == "double") + { + return Vec::new(); + } + self.pending = Some(Pending::BlackjackAction); + vec![Effect::SendLine(format!( + "BLACKJACK_ACTION {} {}", + hand.session_id, action + ))] + } + fn submit(&mut self) -> Vec { let line = self.input.trim().to_string(); self.input.clear(); -- cgit v1.2.3