use super::response::SpinResult; use super::slots::SlotsGame; use super::traits::{Game, GameError, GamePhase, GameState}; use std::collections::HashMap; pub struct GameManager { current_session: Option, games: HashMap>, } impl GameManager { pub fn new() -> Self { let mut games: HashMap> = HashMap::new(); games.insert("slots".to_string(), Box::new(SlotsGame::new())); Self { current_session: None, games, } } pub fn get_available_games(&self) -> Vec<(&str, &str)> { let mut games: Vec<(&str, &str)> = self .games .iter() .map(|(key, game)| (key.as_str(), game.get_name())) .collect(); games.sort_unstable_by_key(|(key, _)| *key); games } pub fn enter_game(&mut self, game_type: &str) -> Result<(), GameError> { if self.current_session.is_some() { return Err(GameError::InvalidAction); } if !self.games.contains_key(game_type) { return Err(GameError::InvalidAction); } self.current_session = Some(GameState { game_type: game_type.to_string(), bet_amount: 10, phase: GamePhase::WaitingToStart, data: HashMap::new(), }); Ok(()) } /// The wire request for a spin, if the current phase allows one. pub fn spin_request(&self) -> Option { let session = self.current_session.as_ref()?; if session.phase != GamePhase::WaitingToStart && session.phase != GamePhase::Finished { return None; } Some(format!( "PLAY_START {} {}", session.game_type, session.bet_amount )) } /// Move the board into its spinning state and drop the previous result /// so stale reels/winnings cannot leak into the new spin. pub fn begin_spin(&mut self) -> Result<(), GameError> { let session = self .current_session .as_mut() .ok_or(GameError::InvalidAction)?; if session.phase != GamePhase::WaitingToStart && session.phase != GamePhase::Finished { return Err(GameError::InvalidAction); } session.phase = GamePhase::Playing; session.data.remove("error"); session.data.remove("reels"); session.data.remove("win_type"); session.data.remove("win_lines"); session.data.remove("winnings"); Ok(()) } /// Store the settled spin so `render()` can draw it in place. pub fn apply_result(&mut self, result: SpinResult) { let Some(session) = self.current_session.as_mut() else { return; }; session.phase = GamePhase::Finished; session.bet_amount = result.bet_amount; session.data.remove("error"); session.data.insert("reels".to_string(), result.reels); session .data .insert("win_type".to_string(), result.win_type); session.data.insert( "win_lines".to_string(), result .win_lines .iter() .map(usize::to_string) .collect::>() .join(","), ); session .data .insert("winnings".to_string(), result.winnings.to_string()); session .data .insert("balance".to_string(), result.balance.to_string()); } /// Show a failed spin without leaving the player stuck in `Playing`. pub fn set_error(&mut self, message: impl Into) { let Some(session) = self.current_session.as_mut() else { return; }; session.phase = GamePhase::WaitingToStart; session.data.remove("reels"); session.data.remove("win_type"); session.data.remove("win_lines"); session.data.remove("winnings"); session.data.insert("error".to_string(), message.into()); } pub fn set_balance(&mut self, balance: i64) { if let Some(session) = self.current_session.as_mut() { session .data .insert("balance".to_string(), balance.to_string()); } } pub fn get_current_game(&self) -> Option<&dyn Game> { self.current_session .as_ref() .and_then(|session| self.games.get(&session.game_type).map(|game| game.as_ref())) } pub fn get_current_state(&self) -> Option<&GameState> { self.current_session.as_ref() } pub fn is_in_game(&self) -> bool { self.current_session.is_some() } pub fn end_game(&mut self) { self.current_session = None; } pub fn set_bet(&mut self, bet: u64) -> Result { let session = self .current_session .as_mut() .ok_or(GameError::InvalidAction)?; if session.phase != GamePhase::WaitingToStart && session.phase != GamePhase::Finished { return Err(GameError::InvalidAction); } session.bet_amount = bet.max(10); Ok(session.bet_amount) } pub fn adjust_bet(&mut self, delta: i64) -> Result { let session = self .current_session .as_mut() .ok_or(GameError::InvalidAction)?; if session.phase != GamePhase::WaitingToStart && session.phase != GamePhase::Finished { return Err(GameError::InvalidAction); } let current_bet = session.bet_amount as i64; session.bet_amount = (current_bet + delta).max(10) as u64; Ok(session.bet_amount) } }