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 --- tests/client_core.rs | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 200 insertions(+), 1 deletion(-) (limited to 'tests/client_core.rs') diff --git a/tests/client_core.rs b/tests/client_core.rs index 7fb7e29..cfcf608 100644 --- a/tests/client_core.rs +++ b/tests/client_core.rs @@ -4,8 +4,9 @@ //! logical `Effect`s that `ClientCore` emits, so assertions are about what a //! player would see, not about ANSI sequences. +use zagan_bet::blackjack::BlackjackState; use zagan_bet::client::core::{ClientCore, Effect, InteractiveCommand, Key}; -use zagan_bet::client::games::response::parse_spin_response; +use zagan_bet::client::games::response::{parse_blackjack_response, parse_spin_response}; use zagan_bet::client::ui::command_names; const WIN3: &str = "CCCCLLCLODGSDGS"; @@ -139,6 +140,11 @@ impl Harness { self.line("play roulette"); self.reply("INFO alice 1 1000"); } + + fn enter_blackjack(&mut self) { + self.line("play blackjack"); + self.reply("INFO alice 1 1000"); + } } #[test] @@ -647,3 +653,196 @@ fn roulette_rejects_an_out_of_range_number() { assert!(h.board()[4].contains("ERROR: Number must be 0-36")); assert!(h.board()[2].contains("Bets: (none)")); } + +fn bj_state( + session: &str, + phase: &str, + player: &[&str], + dealer: &[&str], + player_total: u8, + dealer_total: u8, + bet: u64, + base_bet: u64, + balance: i64, + actions: &[&str], + result: &str, + winnings: u64, +) -> BlackjackState { + BlackjackState { + session_id: session.to_string(), + phase: phase.to_string(), + player: player.iter().map(|card| card.to_string()).collect(), + dealer: dealer.iter().map(|card| card.to_string()).collect(), + player_total, + dealer_total, + bet, + base_bet, + balance, + actions: actions.iter().map(|action| action.to_string()).collect(), + result: result.to_string(), + winnings, + } +} + +#[test] +fn blackjack_deals_hits_and_settles() { + let mut h = Harness::new(); + h.enter_blackjack(); + + assert_eq!(h.board().len(), 6); + assert!(h.board()[4].contains("Press [SPACE] to deal.")); + + h.key(Key::Char(' ')); + assert!(h + .sent + .iter() + .any(|line| line == "PLAY_START blackjack 10")); + + let player = bj_state( + "bj1", + "player", + &["10S", "6H"], + &["9D", "?"], + 16, + 9, + 10, + 10, + 990, + &["hit", "stand", "double"], + "playing", + 0, + ); + h.reply(&format!( + "BLACKJACK_STATE {}", + serde_json::to_string(&player).unwrap() + )); + + assert_eq!(h.board().len(), 6); + assert!(h.board()[2].contains("Dealer: [9♦] [ ? ] 9")); + assert!(h.board()[3].contains("You: [10♠] [6♥] 16")); + assert!(h.board()[4].contains("Hit, stand or double.")); + assert!(h.board()[5].contains("[D]ouble")); + + h.key(Key::Char('h')); + assert_eq!( + h.sent.last().map(String::as_str), + Some("BLACKJACK_ACTION bj1 hit") + ); + + let done = bj_state( + "bj1", + "done", + &["10S", "6H", "5D"], + &["9D", "8C"], + 21, + 17, + 10, + 10, + 1010, + &[], + "win", + 20, + ); + h.reply(&format!( + "BLACKJACK_STATE {}", + serde_json::to_string(&done).unwrap() + )); + + assert!(h.board()[4].contains("Win! Won 20 PLN")); + assert!(h.board()[5].contains("[SPACE] New hand")); + + h.key(Key::Char('q')); + assert!(h.board().is_empty()); + assert!(h.screen.lines.iter().any(|line| line == "Game exited.")); +} + +#[test] +fn blackjack_cancel_sends_a_cancel_action() { + let mut h = Harness::new(); + h.enter_blackjack(); + h.key(Key::Char(' ')); + + let player = bj_state( + "bj1", + "player", + &["10S", "6H"], + &["9D", "?"], + 16, + 9, + 10, + 10, + 990, + &["hit", "stand", "double"], + "playing", + 0, + ); + h.reply(&format!( + "BLACKJACK_STATE {}", + serde_json::to_string(&player).unwrap() + )); + + h.key(Key::Char('q')); + assert_eq!( + h.sent.last().map(String::as_str), + Some("BLACKJACK_ACTION bj1 cancel") + ); + + let cancelled = bj_state( + "bj1", + "done", + &["10S", "6H"], + &["9D", "?"], + 16, + 9, + 10, + 10, + 1000, + &[], + "cancelled", + 0, + ); + h.reply(&format!( + "BLACKJACK_STATE {}", + serde_json::to_string(&cancelled).unwrap() + )); + + assert!(h.board().is_empty()); + assert!(h.screen.lines.iter().any(|line| line == "Game exited.")); +} + +#[test] +fn blackjack_ready_board_can_change_the_next_bet() { + let mut h = Harness::new(); + h.enter_blackjack(); + + h.key(Key::Char('=')); + assert!(h.board()[1].contains("Bet: 20 PLN")); + + h.key(Key::Char(' ')); + assert!(h + .sent + .iter() + .any(|line| line == "PLAY_START blackjack 20")); +} + +#[test] +fn parses_a_blackjack_state() { + let state = bj_state( + "bj1", + "player", + &["10S", "6H"], + &["9D", "?"], + 16, + 9, + 10, + 10, + 990, + &["hit", "stand"], + "playing", + 0, + ); + let response = format!("BLACKJACK_STATE {}", serde_json::to_string(&state).unwrap()); + + assert_eq!(parse_blackjack_response(&response).unwrap(), state); + assert!(parse_blackjack_response("ERROR Insufficient balance").is_err()); +} -- cgit v1.2.3