summaryrefslogtreecommitdiffstats
path: root/src/client/games/manager.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-28 22:28:20 +0100
committersalmonik <salmonik@tuta.io>2026-10-28 22:28:20 +0100
commit86d5f832c8452aad2313489b82eb8fd514215b95 (patch)
tree596a9be39c3d91ebc20cc8b877549e3d10895901 /src/client/games/manager.rs
parentv0.2.2: split client/server logic into the library (diff)
downloadzagan-bet-86d5f832c8452aad2313489b82eb8fd514215b95.tar.gz
zagan-bet-86d5f832c8452aad2313489b82eb8fd514215b95.zip
v0.2.3: add ClientCore and fix the game board rendering
Diffstat (limited to '')
-rw-r--r--src/client/games/manager.rs140
1 files changed, 87 insertions, 53 deletions
diff --git a/src/client/games/manager.rs b/src/client/games/manager.rs
index be96b1a..71cd28c 100644
--- a/src/client/games/manager.rs
+++ b/src/client/games/manager.rs
@@ -1,11 +1,10 @@
1use super::response::SpinResult;
1use super::slots::SlotsGame; 2use super::slots::SlotsGame;
2use super::traits::{Game, GameError, GamePhase, GameState}; 3use super::traits::{Game, GameError, GamePhase, GameState};
3use std::collections::HashMap; 4use std::collections::HashMap;
4use std::io::Write;
5 5
6pub struct GameManager { 6pub struct GameManager {
7 current_session: Option<GameState>, 7 current_session: Option<GameState>,
8 pending_game_type: Option<String>,
9 games: HashMap<String, Box<dyn Game>>, 8 games: HashMap<String, Box<dyn Game>>,
10} 9}
11 10
@@ -16,16 +15,18 @@ impl GameManager {
16 15
17 Self { 16 Self {
18 current_session: None, 17 current_session: None,
19 pending_game_type: None,
20 games, 18 games,
21 } 19 }
22 } 20 }
23 21
24 pub fn get_available_games(&self) -> Vec<(&str, &str)> { 22 pub fn get_available_games(&self) -> Vec<(&str, &str)> {
25 self.games 23 let mut games: Vec<(&str, &str)> = self
24 .games
26 .iter() 25 .iter()
27 .map(|(k, v)| (k.as_str(), v.get_name())) 26 .map(|(key, game)| (key.as_str(), game.get_name()))
28 .collect() 27 .collect();
28 games.sort_unstable_by_key(|(key, _)| *key);
29 games
29 } 30 }
30 31
31 pub fn enter_game(&mut self, game_type: &str) -> Result<(), GameError> { 32 pub fn enter_game(&mut self, game_type: &str) -> Result<(), GameError> {
@@ -37,8 +38,6 @@ impl GameManager {
37 return Err(GameError::InvalidAction); 38 return Err(GameError::InvalidAction);
38 } 39 }
39 40
40 self.pending_game_type = Some(game_type.to_string());
41
42 self.current_session = Some(GameState { 41 self.current_session = Some(GameState {
43 game_type: game_type.to_string(), 42 game_type: game_type.to_string(),
44 bet_amount: 10, 43 bet_amount: 10,
@@ -49,28 +48,75 @@ impl GameManager {
49 Ok(()) 48 Ok(())
50 } 49 }
51 50
52 pub fn start_game<W: Write>(&mut self, stream: &mut W) -> Result<(), GameError> { 51 /// The wire request for a spin, if the current phase allows one.
53 let game_type = self 52 pub fn spin_request(&self) -> Option<String> {
54 .pending_game_type 53 let session = self.current_session.as_ref()?;
55 .as_ref() 54 if session.phase != GamePhase::WaitingToStart && session.phase != GamePhase::Finished {
56 .ok_or(GameError::InvalidAction)?; 55 return None;
56 }
57 Some(format!(
58 "PLAY_START {} {}",
59 session.game_type, session.bet_amount
60 ))
61 }
57 62
58 let state = self 63 /// Move the board into its spinning state and drop the previous result
64 /// so stale reels/winnings cannot leak into the new spin.
65 pub fn begin_spin(&mut self) -> Result<(), GameError> {
66 let session = self
59 .current_session 67 .current_session
60 .as_mut() 68 .as_mut()
61 .ok_or(GameError::InvalidAction)?; 69 .ok_or(GameError::InvalidAction)?;
62 70 if session.phase != GamePhase::WaitingToStart && session.phase != GamePhase::Finished {
63 if state.phase != GamePhase::WaitingToStart && state.phase != GamePhase::Finished {
64 return Err(GameError::InvalidAction); 71 return Err(GameError::InvalidAction);
65 } 72 }
66 73
67 let bet_amount = state.bet_amount; 74 session.phase = GamePhase::Playing;
75 session.data.remove("error");
76 session.data.remove("reels");
77 session.data.remove("win_type");
78 session.data.remove("winnings");
79 Ok(())
80 }
68 81
69 let writer = stream; 82 /// Store the settled spin so `render()` can draw it in place.
70 writeln!(writer, "PLAY_START {} {}", game_type, bet_amount) 83 pub fn apply_result(&mut self, result: SpinResult) {
71 .map_err(|_| GameError::NetworkError)?; 84 let Some(session) = self.current_session.as_mut() else {
85 return;
86 };
87 session.phase = GamePhase::Finished;
88 session.bet_amount = result.bet_amount;
89 session.data.remove("error");
90 session.data.insert("reels".to_string(), result.reels);
91 session
92 .data
93 .insert("win_type".to_string(), result.win_type);
94 session
95 .data
96 .insert("winnings".to_string(), result.winnings.to_string());
97 session
98 .data
99 .insert("balance".to_string(), result.balance.to_string());
100 }
72 101
73 Ok(()) 102 /// Show a failed spin without leaving the player stuck in `Playing`.
103 pub fn set_error(&mut self, message: impl Into<String>) {
104 let Some(session) = self.current_session.as_mut() else {
105 return;
106 };
107 session.phase = GamePhase::WaitingToStart;
108 session.data.remove("reels");
109 session.data.remove("win_type");
110 session.data.remove("winnings");
111 session.data.insert("error".to_string(), message.into());
112 }
113
114 pub fn set_balance(&mut self, balance: i64) {
115 if let Some(session) = self.current_session.as_mut() {
116 session
117 .data
118 .insert("balance".to_string(), balance.to_string());
119 }
74 } 120 }
75 121
76 pub fn get_current_game(&self) -> Option<&dyn Game> { 122 pub fn get_current_game(&self) -> Option<&dyn Game> {
@@ -83,49 +129,37 @@ impl GameManager {
83 self.current_session.as_ref() 129 self.current_session.as_ref()
84 } 130 }
85 131
86 pub fn get_current_render_lines(&self) -> Option<usize> {
87 let session = self.current_session.as_ref()?;
88 let game = self.games.get(&session.game_type)?;
89 Some(game.render_lines(session))
90 }
91
92 pub fn is_in_game(&self) -> bool { 132 pub fn is_in_game(&self) -> bool {
93 self.current_session.is_some() 133 self.current_session.is_some()
94 } 134 }
95 135
96 pub fn is_game_started(&self) -> bool { 136 pub fn end_game(&mut self) {
97 self.current_session 137 self.current_session = None;
98 .as_ref()
99 .map(|s| s.phase != GamePhase::WaitingToStart)
100 .unwrap_or(false)
101 } 138 }
102 139
103 pub fn reset_phase_to_waiting(&mut self) { 140 pub fn set_bet(&mut self, bet: u64) -> Result<u64, GameError> {
104 if let Some(ref mut session) = self.current_session { 141 let session = self
105 session.phase = GamePhase::WaitingToStart; 142 .current_session
106 session.data.clear(); 143 .as_mut()
144 .ok_or(GameError::InvalidAction)?;
145 if session.phase != GamePhase::WaitingToStart && session.phase != GamePhase::Finished {
146 return Err(GameError::InvalidAction);
107 } 147 }
108 } 148 session.bet_amount = bet.max(10);
109 149 Ok(session.bet_amount)
110 pub fn end_game(&mut self) {
111 self.current_session = None;
112 self.pending_game_type = None;
113 } 150 }
114 151
115 pub fn adjust_bet(&mut self, delta: i64) -> Result<u64, GameError> { 152 pub fn adjust_bet(&mut self, delta: i64) -> Result<u64, GameError> {
116 if let Some(ref session) = self.current_session { 153 let session = self
117 if session.phase != GamePhase::WaitingToStart { 154 .current_session
118 return Err(GameError::InvalidAction); 155 .as_mut()
119 } 156 .ok_or(GameError::InvalidAction)?;
157 if session.phase != GamePhase::WaitingToStart && session.phase != GamePhase::Finished {
158 return Err(GameError::InvalidAction);
120 } 159 }
121 160
122 if let Some(ref mut session) = self.current_session { 161 let current_bet = session.bet_amount as i64;
123 let current_bet = session.bet_amount as i64; 162 session.bet_amount = (current_bet + delta).max(10) as u64;
124 let new_bet = (current_bet + delta).max(10) as u64; 163 Ok(session.bet_amount)
125 session.bet_amount = new_bet;
126 Ok(new_bet)
127 } else {
128 Err(GameError::InvalidAction)
129 }
130 } 164 }
131} 165}