summaryrefslogtreecommitdiffstats
path: root/src/server/games/roulette.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/server/games/roulette.rs151
1 files changed, 151 insertions, 0 deletions
diff --git a/src/server/games/roulette.rs b/src/server/games/roulette.rs
new file mode 100644
index 0000000..7beb8c9
--- /dev/null
+++ b/src/server/games/roulette.rs
@@ -0,0 +1,151 @@
1use super::traits::{Game, GameError, GamePhase, GameResult, GameState};
2use crate::roulette::{color, Bets};
3use rand::Rng;
4use std::collections::HashMap;
5
6pub struct RouletteGame {
7 fixed_number: Option<u8>,
8}
9
10impl RouletteGame {
11 pub fn new() -> Self {
12 Self { fixed_number: None }
13 }
14
15 pub fn with_result(number: u8) -> Self {
16 Self {
17 fixed_number: Some(number),
18 }
19 }
20
21 fn spin(&self) -> u8 {
22 match self.fixed_number {
23 Some(number) => number,
24 None => rand::thread_rng().gen_range(0..37),
25 }
26 }
27}
28
29impl Game for RouletteGame {
30 fn game_type(&self) -> &'static str {
31 "roulette"
32 }
33
34 fn get_name(&self) -> &'static str {
35 "Roulette"
36 }
37
38 fn get_description(&self) -> &'static str {
39 "European roulette. Bet on red/black, even/odd, or a straight number."
40 }
41
42 fn prepare_state(&self, username: &str, bet_amount: u64) -> GameState {
43 let mut data = HashMap::new();
44 data.insert("username".to_string(), username.to_string());
45 data.insert("status".to_string(), "ready".to_string());
46
47 GameState {
48 session_id: String::new(),
49 game_type: "roulette".to_string(),
50 bet_amount,
51 phase: GamePhase::WaitingToStart,
52 data,
53 }
54 }
55
56 fn process_action(
57 &self,
58 state: &GameState,
59 action: &str,
60 params: &[&str],
61 ) -> Result<GameState, GameError> {
62 if state.phase == GamePhase::Finished {
63 return Err(GameError::GameOver);
64 }
65
66 match action {
67 "spin" => {
68 if state.phase != GamePhase::WaitingToStart
69 && state.phase != GamePhase::Playing
70 {
71 return Err(GameError::InvalidAction(
72 "Cannot spin in current phase".to_string(),
73 ));
74 }
75
76 let payload = params
77 .first()
78 .ok_or(GameError::InvalidAction("Missing roulette bets".to_string()))?;
79 let bets: Bets = serde_json::from_str(payload)
80 .map_err(|error| GameError::InvalidAction(format!("Invalid bets: {error}")))?;
81
82 if bets.is_empty() {
83 return Err(GameError::InvalidBet);
84 }
85 if let Err(message) = bets.validate(state.bet_amount) {
86 return Err(GameError::InvalidAction(message));
87 }
88 if bets.total() != state.bet_amount {
89 return Err(GameError::InvalidBet);
90 }
91
92 let number = self.spin();
93 let bets_json = serde_json::to_string(&bets)
94 .map_err(|error| GameError::InternalError(error.to_string()))?;
95
96 let mut new_data = state.data.clone();
97 new_data.insert("status".to_string(), "spun".to_string());
98 new_data.insert("bets".to_string(), bets_json);
99 new_data.insert("number".to_string(), number.to_string());
100 new_data.insert("color".to_string(), color(number).to_string());
101
102 Ok(GameState {
103 session_id: state.session_id.clone(),
104 game_type: state.game_type.clone(),
105 bet_amount: state.bet_amount,
106 phase: GamePhase::Finished,
107 data: new_data,
108 })
109 }
110 _ => Err(GameError::InvalidAction(format!(
111 "Unknown action: {action}"
112 ))),
113 }
114 }
115
116 fn calculate_result(&self, state: &GameState) -> Result<GameResult, GameError> {
117 if state.phase != GamePhase::Finished {
118 return Err(GameError::InternalError("Game not finished".to_string()));
119 }
120
121 let number: u8 = state
122 .data
123 .get("number")
124 .ok_or(GameError::InternalError("No winning number".to_string()))?
125 .parse()
126 .map_err(|_| GameError::InternalError("Invalid winning number".to_string()))?;
127 let bets_json = state
128 .data
129 .get("bets")
130 .ok_or(GameError::InternalError("No bets".to_string()))?
131 .clone();
132 let bets: Bets = serde_json::from_str(&bets_json)
133 .map_err(|error| GameError::InternalError(format!("Invalid bets: {error}")))?;
134
135 let winnings = bets.winnings(number);
136
137 let mut result_data = HashMap::new();
138 result_data.insert("number".to_string(), number.to_string());
139 result_data.insert("color".to_string(), color(number).to_string());
140 result_data.insert("bets".to_string(), bets_json);
141
142 Ok(GameResult {
143 session_id: state.session_id.clone(),
144 game_type: state.game_type.clone(),
145 bet_amount: state.bet_amount,
146 winnings,
147 final_balance: 0,
148 result_data,
149 })
150 }
151}