From d9f1fe513e6f7696809d3a9b4d44ca485fe99c97 Mon Sep 17 00:00:00 2001 From: salmonik Date: Thu, 29 Oct 2026 17:18:34 +0100 Subject: v2.1.0: add European roulette with split bets and wheel animation --- src/client/core.rs | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 182 insertions(+), 1 deletion(-) (limited to 'src/client/core.rs') diff --git a/src/client/core.rs b/src/client/core.rs index a88991b..e005f2a 100644 --- a/src/client/core.rs +++ b/src/client/core.rs @@ -1,9 +1,10 @@ use crate::client::games::manager::GameManager; -use crate::client::games::response::parse_spin_response; +use crate::client::games::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, }; +use crate::roulette::{Bets, NumberBet}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Key { @@ -33,6 +34,11 @@ pub enum Effect { Print(Vec), /// Replace the game board region in place. ReplaceRegion(Vec), + /// Run the roulette wheel animation, then draw the final board lines. + AnimateRoulette { + winning: u8, + final_lines: Vec, + }, /// Remove the game board region. ClearRegion, /// Redraw the command prompt. `text` is the input without the `> ` prefix. @@ -54,11 +60,20 @@ pub enum Effect { #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Pending { Spin, + RouletteSpin, Ping, Echo, Info, } +#[derive(Debug, Clone, Copy)] +enum RouletteSide { + Red, + Black, + Even, + Odd, +} + pub struct ClientCore { input: String, history: Vec, @@ -160,6 +175,22 @@ impl ClientCore { self.board_effects() } }, + Some(Pending::RouletteSpin) => match parse_roulette_response(reply) { + Ok(result) => { + self.balance = Some(result.balance); + let winning = result.number; + self.game.apply_roulette_result(result); + let final_lines = self.board_lines().unwrap_or_default(); + vec![Effect::AnimateRoulette { + winning, + final_lines, + }] + } + Err(message) => { + self.game.set_error(message); + self.board_effects() + } + }, Some(Pending::Ping) => { if reply.trim() == "PONG" { print_line("Pong") @@ -194,6 +225,15 @@ impl ClientCore { } fn handle_game_key(&mut self, key: Key) -> Vec { + if self + .game + .get_current_state() + .map(|state| state.game_type.as_str()) + == Some("roulette") + { + return self.handle_roulette_key(key); + } + match key { Key::CtrlC => vec![Effect::Quit], Key::Esc | Key::Char('q') => { @@ -230,6 +270,147 @@ impl ClientCore { effects } + fn handle_roulette_key(&mut self, key: Key) -> Vec { + match key { + Key::CtrlC => vec![Effect::Quit], + Key::Char('q') => { + self.game.end_game(); + vec![ + Effect::ClearRegion, + Effect::Print(vec!["Game exited.".to_string()]), + ] + } + Key::Esc => { + self.game.set_roulette_number_input(""); + self.board_effects() + } + Key::Char(' ') => self.spin_roulette(), + Key::Char('r') => self.add_roulette_side(RouletteSide::Red), + Key::Char('b') => self.add_roulette_side(RouletteSide::Black), + Key::Char('e') => self.add_roulette_side(RouletteSide::Even), + Key::Char('o') => self.add_roulette_side(RouletteSide::Odd), + Key::Char('c') => { + self.game.set_roulette_bets(&Bets::default()); + self.game.set_roulette_number_input(""); + self.game.set_roulette_status("place"); + self.board_effects() + } + Key::Char('+') | Key::Char('=') => { + let chip = self.game.roulette_chip().saturating_add(10); + self.game.set_roulette_chip(chip); + self.board_effects() + } + Key::Char('-') => { + let chip = self.game.roulette_chip().saturating_sub(10).max(10); + self.game.set_roulette_chip(chip); + self.board_effects() + } + Key::Char(c) if c.is_ascii_digit() => { + let mut input = self.roulette_input(); + if input.len() < 2 { + input.push(c); + self.game.set_roulette_number_input(&input); + } + self.board_effects() + } + Key::Backspace => { + let mut input = self.roulette_input(); + input.pop(); + self.game.set_roulette_number_input(&input); + self.board_effects() + } + Key::Enter => { + let input = self.roulette_input(); + if !input.is_empty() { + match input.parse::() { + Ok(number) if number <= 36 => { + self.add_roulette_number(number); + self.game.set_roulette_number_input(""); + } + _ => { + self.game.set_roulette_number_input(""); + self.game.set_error("Number must be 0-36"); + } + } + } + self.board_effects() + } + _ => Vec::new(), + } + } + + fn add_roulette_side(&mut self, side: RouletteSide) -> Vec { + let chip = self.game.roulette_chip(); + let mut bets = self.game.roulette_bets(); + match side { + RouletteSide::Red => bets.red = bets.red.saturating_add(chip), + RouletteSide::Black => bets.black = bets.black.saturating_add(chip), + RouletteSide::Even => bets.even = bets.even.saturating_add(chip), + RouletteSide::Odd => bets.odd = bets.odd.saturating_add(chip), + } + self.place_roulette_bets(bets) + } + + fn add_roulette_number(&mut self, number: u8) -> Vec { + let chip = self.game.roulette_chip(); + let mut bets = self.game.roulette_bets(); + if let Some(existing) = bets.numbers.iter_mut().find(|bet| bet.number == number) { + existing.amount = existing.amount.saturating_add(chip); + } else { + bets.numbers.push(NumberBet { + number, + amount: chip, + }); + } + self.place_roulette_bets(bets) + } + + fn place_roulette_bets(&mut self, bets: Bets) -> Vec { + if let Some(balance) = self.balance { + if bets.total() > balance.max(0) as u64 { + self.game.set_error("Insufficient balance"); + return self.board_effects(); + } + } + + self.game.set_roulette_bets(&bets); + self.game.set_roulette_status("place"); + self.board_effects() + } + + fn spin_roulette(&mut self) -> Vec { + let bets = self.game.roulette_bets(); + if bets.is_empty() { + self.game.set_error("Place at least one bet"); + return self.board_effects(); + } + if let Some(balance) = self.balance { + if bets.total() > balance.max(0) as u64 { + self.game.set_error("Insufficient balance"); + return self.board_effects(); + } + } + let Some(request) = self.game.spin_request() else { + return Vec::new(); + }; + if self.game.begin_spin().is_err() { + return Vec::new(); + } + self.pending = Some(Pending::RouletteSpin); + + let mut effects = self.board_effects(); + effects.push(Effect::SendLine(request)); + effects + } + + fn roulette_input(&self) -> String { + self.game + .get_current_state() + .and_then(|state| state.data.get("number_input")) + .cloned() + .unwrap_or_default() + } + fn submit(&mut self) -> Vec { let line = self.input.trim().to_string(); self.input.clear(); -- cgit v1.2.3