summaryrefslogtreecommitdiffstats
path: root/src/bin/server/games/slots.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-24 14:48:12 +0200
committersalmonik <salmonik@tuta.io>2026-08-24 14:48:12 +0200
commit5d6b5049c2a34a3f8abe725d4a871ba84a0dd714 (patch)
tree57cb03bf59b3358a96742f455d38c21f4f645396 /src/bin/server/games/slots.rs
parentstable and working, both platforms (diff)
downloadzagan-bet-5d6b5049c2a34a3f8abe725d4a871ba84a0dd714.tar.gz
zagan-bet-5d6b5049c2a34a3f8abe725d4a871ba84a0dd714.zip
TLS transport and games framework on client and server
Diffstat (limited to '')
-rw-r--r--src/bin/server/games/slots.rs179
1 files changed, 179 insertions, 0 deletions
diff --git a/src/bin/server/games/slots.rs b/src/bin/server/games/slots.rs
new file mode 100644
index 0000000..d84e21f
--- /dev/null
+++ b/src/bin/server/games/slots.rs
@@ -0,0 +1,179 @@
1use crate::games::traits::{Game, GameError, GamePhase, GameResult, GameState};
2use rand::Rng;
3use std::collections::HashMap;
4
5pub struct SlotsGame {
6 symbols: Vec<char>,
7 payouts: HashMap<String, u64>,
8}
9
10impl SlotsGame {
11 pub fn new() -> Self {
12 let symbols = vec!['C', 'L', 'O', 'G', 'D', 'S'];
13 let mut payouts = HashMap::new();
14
15 payouts.insert("CCC".to_string(), 5);
16 payouts.insert("LLL".to_string(), 10);
17 payouts.insert("OOO".to_string(), 15);
18 payouts.insert("GGG".to_string(), 20);
19 payouts.insert("DDD".to_string(), 50);
20 payouts.insert("SSS".to_string(), 100);
21
22 payouts.insert("CC".to_string(), 2);
23 payouts.insert("LL".to_string(), 3);
24 payouts.insert("OO".to_string(), 4);
25 payouts.insert("GG".to_string(), 5);
26 payouts.insert("DD".to_string(), 10);
27 payouts.insert("SS".to_string(), 20);
28
29 Self { symbols, payouts }
30 }
31
32 fn spin_reels(&self) -> Vec<char> {
33 let mut rng = rand::thread_rng();
34 (0..3)
35 .map(|_| self.symbols[rng.gen_range(0..self.symbols.len())])
36 .collect()
37 }
38
39 fn calculate_payout(&self, reels: &[char], bet: u64) -> u64 {
40 let reel_string: String = reels.iter().collect();
41
42 if let Some(&multiplier) = self.payouts.get(&reel_string) {
43 return bet * multiplier;
44 }
45
46 for i in 0..2 {
47 if reels[i] == reels[i + 1] {
48 let two_of_kind = format!("{}{}", reels[i], reels[i]);
49 if let Some(&multiplier) = self.payouts.get(&two_of_kind) {
50 return bet * multiplier;
51 }
52 }
53 }
54
55 0
56 }
57}
58
59impl Game for SlotsGame {
60 fn game_type(&self) -> &'static str {
61 "slots"
62 }
63
64 fn get_name(&self) -> &'static str {
65 "Slots"
66 }
67
68 fn get_description(&self) -> &'static str {
69 "Spin the reels and match symbols to win!"
70 }
71
72 fn prepare_state(&self, username: &str, bet_amount: u64) -> GameState {
73 let mut data = HashMap::new();
74 data.insert("username".to_string(), username.to_string());
75 data.insert("status".to_string(), "ready".to_string());
76
77 GameState {
78 session_id: String::new(),
79 game_type: "slots".to_string(),
80 bet_amount,
81 phase: GamePhase::WaitingToStart,
82 data,
83 }
84 }
85
86 fn process_action(
87 &self,
88 state: &GameState,
89 action: &str,
90 _params: &[&str],
91 ) -> Result<GameState, GameError> {
92 if state.phase == GamePhase::Finished {
93 return Err(GameError::GameOver);
94 }
95
96 match action {
97 "spin" => {
98 if state.phase != GamePhase::WaitingToStart && state.phase != GamePhase::Playing {
99 return Err(GameError::InvalidAction(
100 "Cannot spin in current phase".to_string(),
101 ));
102 }
103
104 let reels = self.spin_reels();
105 let reel_string: String = reels.iter().collect();
106
107 let mut new_data = state.data.clone();
108 new_data.insert("reels".to_string(), reel_string.clone());
109 new_data.insert("status".to_string(), "spun".to_string());
110
111 let winnings = self.calculate_payout(&reels, state.bet_amount);
112 new_data.insert("winnings".to_string(), winnings.to_string());
113
114 let is_jackpot = reels[0] == reels[1] && reels[1] == reels[2];
115 let is_two = reels[0] == reels[1] || reels[1] == reels[2];
116 new_data.insert(
117 "win_type".to_string(),
118 if is_jackpot {
119 "jackpot".to_string()
120 } else if is_two {
121 "two".to_string()
122 } else {
123 "none".to_string()
124 },
125 );
126
127 Ok(GameState {
128 session_id: state.session_id.clone(),
129 game_type: state.game_type.clone(),
130 bet_amount: state.bet_amount,
131 phase: GamePhase::Finished,
132 data: new_data,
133 })
134 }
135 _ => Err(GameError::InvalidAction(format!(
136 "Unknown action: {}",
137 action
138 ))),
139 }
140 }
141
142 fn calculate_result(&self, state: &GameState) -> Result<GameResult, GameError> {
143 if state.phase != GamePhase::Finished {
144 return Err(GameError::InternalError("Game not finished".to_string()));
145 }
146
147 let reel_string = state
148 .data
149 .get("reels")
150 .ok_or(GameError::InternalError("No reels found".to_string()))?;
151
152 let reels: Vec<char> = reel_string.chars().collect();
153 let winnings = self.calculate_payout(&reels, state.bet_amount);
154
155 let mut result_data = HashMap::new();
156 result_data.insert("reels".to_string(), reel_string.clone());
157 result_data.insert(
158 "payout_multiplier".to_string(),
159 if winnings > 0 && state.bet_amount > 0 {
160 (winnings / state.bet_amount).to_string()
161 } else {
162 "0".to_string()
163 },
164 );
165 result_data.insert(
166 "win_type".to_string(),
167 state.data.get("win_type").cloned().unwrap_or_default(),
168 );
169
170 Ok(GameResult {
171 session_id: state.session_id.clone(),
172 game_type: state.game_type.clone(),
173 bet_amount: state.bet_amount,
174 winnings,
175 final_balance: 0,
176 result_data,
177 })
178 }
179}