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/games/roulette.rs | 157 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 src/client/games/roulette.rs (limited to 'src/client/games/roulette.rs') diff --git a/src/client/games/roulette.rs b/src/client/games/roulette.rs new file mode 100644 index 0000000..3379317 --- /dev/null +++ b/src/client/games/roulette.rs @@ -0,0 +1,157 @@ +use super::traits::{Game, GameState}; +use crate::roulette::{is_even, is_odd, Bets}; + +pub const ROULETTE_BOARD_LINES: usize = 8; + +pub struct RouletteGame; + +impl RouletteGame { + pub fn new() -> Self { + Self + } + + fn bets(state: &GameState) -> Bets { + state + .data + .get("bets") + .and_then(|raw| serde_json::from_str(raw).ok()) + .unwrap_or_default() + } + + fn bets_line(bets: &Bets) -> String { + let mut parts = Vec::new(); + if bets.red > 0 { + parts.push(format!("RED {}", bets.red)); + } + if bets.black > 0 { + parts.push(format!("BLACK {}", bets.black)); + } + if bets.even > 0 { + parts.push(format!("EVEN {}", bets.even)); + } + if bets.odd > 0 { + parts.push(format!("ODD {}", bets.odd)); + } + for bet in &bets.numbers { + parts.push(format!("#{} {}", bet.number, bet.amount)); + } + + let list = if parts.is_empty() { + "(none)".to_string() + } else { + parts.join(" ") + }; + format!("Bets: {} Total: {} PLN", list, bets.total()) + } + + fn result_lines(state: &GameState) -> (String, String) { + if let Some(error) = state.data.get("error") { + if !error.is_empty() { + return (format!("ERROR: {error}"), String::new()); + } + } + + match state.data.get("status").map(String::as_str) { + Some("result") => { + let number = state + .data + .get("number") + .map(String::as_str) + .unwrap_or("?"); + let color = state + .data + .get("color") + .map(|value| value.to_uppercase()) + .unwrap_or_default(); + let parity = number + .parse::() + .ok() + .map(|number| { + if is_even(number) { + " EVEN" + } else if is_odd(number) { + " ODD" + } else { + "" + } + }) + .unwrap_or(""); + let winnings = state + .data + .get("winnings") + .map(String::as_str) + .unwrap_or("0"); + let result = if winnings == "0" { + "No win.".to_string() + } else { + format!("Won {winnings} PLN") + }; + (format!("Winning: {number} {color}{parity}"), result) + } + Some("spinning") => ("Spinning...".to_string(), String::new()), + _ => ("Place bets, then SPACE to spin.".to_string(), String::new()), + } + } +} + +impl Game for RouletteGame { + fn get_name(&self) -> &'static str { + "Roulette" + } + + fn render(&self, state: &GameState) -> String { + let bets = Self::bets(state); + let chip = state.data.get("chip").map(String::as_str).unwrap_or("10"); + let balance = state + .data + .get("balance") + .map(String::as_str) + .unwrap_or("?"); + let input = state + .data + .get("number_input") + .map(String::as_str) + .unwrap_or(""); + let input_line = if input.is_empty() { + String::new() + } else { + format!("Number: {input}_") + }; + let (status, result) = Self::result_lines(state); + + format!( + "=== ROULETTE ===\r\nBalance: {} PLN Chip: {} PLN\r\n{}\r\n{}\r\n{}\r\n{}\r\n[R]ed [B]lack [E]ven [O]dd [#]Number [C]lear\r\n[+/-] Chip [SPACE] Spin [Q] Quit", + balance, + chip, + Self::bets_line(&bets), + input_line, + status, + result + ) + } + + fn render_lines(&self, _state: &GameState) -> usize { + ROULETTE_BOARD_LINES + } + + fn get_help(&self) -> String { + "ROULETTE (European)\n\ + ==================\n\ + 37 pockets: 0 plus 1-36. 0 is green and loses all outside bets.\n\ + \n\ + Betting:\n\ + R / B / E / O - add the current chip to red/black/even/odd\n\ + digits + Enter - add the current chip to that number\n\ + C - clear all bets\n\ + + or = / - - change the chip size\n\ + SPACE - spin when at least one bet is placed\n\ + Q - exit\n\ + \n\ + Payouts:\n\ + straight number: 35:1 (36x returned)\n\ + red/black: 1:1 (2x returned)\n\ + even/odd: 1:1 (2x returned)\n\ + Multiple bets are settled independently and added together." + .to_string() + } +} -- cgit v1.2.3