use super::traits::{Game, GameError, GamePhase, GameResult, GameState}; use crate::roulette::{color, Bets}; use rand::Rng; use std::collections::HashMap; pub struct RouletteGame { fixed_number: Option, } impl RouletteGame { pub fn new() -> Self { Self { fixed_number: None } } pub fn with_result(number: u8) -> Self { Self { fixed_number: Some(number), } } fn spin(&self) -> u8 { match self.fixed_number { Some(number) => number, None => rand::thread_rng().gen_range(0..37), } } } impl Game for RouletteGame { fn game_type(&self) -> &'static str { "roulette" } fn get_name(&self) -> &'static str { "Roulette" } fn get_description(&self) -> &'static str { "European roulette. Bet on red/black, even/odd, or a straight number." } fn prepare_state(&self, username: &str, bet_amount: u64) -> GameState { let mut data = HashMap::new(); data.insert("username".to_string(), username.to_string()); data.insert("status".to_string(), "ready".to_string()); GameState { session_id: String::new(), game_type: "roulette".to_string(), bet_amount, phase: GamePhase::WaitingToStart, data, } } fn process_action( &self, state: &GameState, action: &str, params: &[&str], ) -> Result { if state.phase == GamePhase::Finished { return Err(GameError::GameOver); } match action { "spin" => { if state.phase != GamePhase::WaitingToStart && state.phase != GamePhase::Playing { return Err(GameError::InvalidAction( "Cannot spin in current phase".to_string(), )); } let payload = params .first() .ok_or(GameError::InvalidAction("Missing roulette bets".to_string()))?; let bets: Bets = serde_json::from_str(payload) .map_err(|error| GameError::InvalidAction(format!("Invalid bets: {error}")))?; if bets.is_empty() { return Err(GameError::InvalidBet); } if let Err(message) = bets.validate(state.bet_amount) { return Err(GameError::InvalidAction(message)); } if bets.total() != state.bet_amount { return Err(GameError::InvalidBet); } let number = self.spin(); let bets_json = serde_json::to_string(&bets) .map_err(|error| GameError::InternalError(error.to_string()))?; let mut new_data = state.data.clone(); new_data.insert("status".to_string(), "spun".to_string()); new_data.insert("bets".to_string(), bets_json); new_data.insert("number".to_string(), number.to_string()); new_data.insert("color".to_string(), color(number).to_string()); Ok(GameState { session_id: state.session_id.clone(), game_type: state.game_type.clone(), bet_amount: state.bet_amount, phase: GamePhase::Finished, data: new_data, }) } _ => Err(GameError::InvalidAction(format!( "Unknown action: {action}" ))), } } fn calculate_result(&self, state: &GameState) -> Result { if state.phase != GamePhase::Finished { return Err(GameError::InternalError("Game not finished".to_string())); } let number: u8 = state .data .get("number") .ok_or(GameError::InternalError("No winning number".to_string()))? .parse() .map_err(|_| GameError::InternalError("Invalid winning number".to_string()))?; let bets_json = state .data .get("bets") .ok_or(GameError::InternalError("No bets".to_string()))? .clone(); let bets: Bets = serde_json::from_str(&bets_json) .map_err(|error| GameError::InternalError(format!("Invalid bets: {error}")))?; let winnings = bets.winnings(number); let mut result_data = HashMap::new(); result_data.insert("number".to_string(), number.to_string()); result_data.insert("color".to_string(), color(number).to_string()); result_data.insert("bets".to_string(), bets_json); Ok(GameResult { session_id: state.session_id.clone(), game_type: state.game_type.clone(), bet_amount: state.bet_amount, winnings, final_balance: 0, result_data, }) } }