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() } }