summaryrefslogtreecommitdiffstats
path: root/src/client/games
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/games')
-rw-r--r--src/client/games/blackjack.rs148
-rw-r--r--src/client/games/manager.rs49
-rw-r--r--src/client/games/mod.rs1
-rw-r--r--src/client/games/response.rs22
4 files changed, 220 insertions, 0 deletions
diff --git a/src/client/games/blackjack.rs b/src/client/games/blackjack.rs
new file mode 100644
index 0000000..a34eb63
--- /dev/null
+++ b/src/client/games/blackjack.rs
@@ -0,0 +1,148 @@
1use super::traits::{Game, GameState};
2use crate::blackjack::{card_label, BlackjackState};
3
4pub const BLACKJACK_BOARD_LINES: usize = 6;
5
6pub struct BlackjackGame;
7
8impl BlackjackGame {
9 pub fn new() -> Self {
10 Self
11 }
12
13 fn state(state: &GameState) -> Option<BlackjackState> {
14 state
15 .data
16 .get("blackjack")
17 .and_then(|raw| serde_json::from_str(raw).ok())
18 }
19
20 fn cards(cards: &[String]) -> String {
21 cards
22 .iter()
23 .map(|card| {
24 if card == "?" {
25 "[ ? ]".to_string()
26 } else {
27 format!("[{}]", card_label(card))
28 }
29 })
30 .collect::<Vec<_>>()
31 .join(" ")
32 }
33
34 fn result_text(result: &str, winnings: u64) -> String {
35 match result {
36 "blackjack" => format!("BLACKJACK! Won {winnings} PLN"),
37 "win" => format!("Win! Won {winnings} PLN"),
38 "push" => "Push. Bet returned.".to_string(),
39 "lose" => "Dealer wins.".to_string(),
40 "bust" => "Bust.".to_string(),
41 "cancelled" => "Cancelled.".to_string(),
42 _ => "Hand complete.".to_string(),
43 }
44 }
45}
46
47impl Game for BlackjackGame {
48 fn get_name(&self) -> &'static str {
49 "Blackjack"
50 }
51
52 fn render(&self, state: &GameState) -> String {
53 let status = state
54 .data
55 .get("status")
56 .map(String::as_str)
57 .unwrap_or("ready");
58 let balance = state
59 .data
60 .get("balance")
61 .map(String::as_str)
62 .unwrap_or("?");
63 let hand = Self::state(state);
64
65 if let Some(error) = state.data.get("error") {
66 let bet = hand.as_ref().map(|hand| hand.bet).unwrap_or(state.bet_amount);
67 return format!(
68 "=== BLACKJACK ===\r\nBalance: {balance} PLN Bet: {bet} PLN\r\n\r\n\r\nERROR: {error}\r\n[Q] Quit"
69 );
70 }
71
72 let (dealer, dealer_total, player, player_total, bet, text, controls) =
73 if let Some(hand) = &hand {
74 let bet = if status == "player" {
75 hand.bet
76 } else {
77 state.bet_amount
78 };
79 let text = match status {
80 "player" => {
81 if hand.actions.iter().any(|action| action == "double") {
82 "Hit, stand or double.".to_string()
83 } else {
84 "Hit or stand.".to_string()
85 }
86 }
87 "dealing" => "Dealing...".to_string(),
88 "cancelling" => "Cancelling...".to_string(),
89 "done" => Self::result_text(&hand.result, hand.winnings),
90 _ => "Press [SPACE] to deal.".to_string(),
91 };
92 let controls = if status == "player" {
93 if hand.actions.iter().any(|action| action == "double") {
94 "[H]it [S]tand [D]ouble [Q]uit".to_string()
95 } else {
96 "[H]it [S]tand [Q]uit".to_string()
97 }
98 } else {
99 "[SPACE] New hand [+/-] Bet [Q] Quit".to_string()
100 };
101 (
102 Self::cards(&hand.dealer),
103 hand.dealer_total,
104 Self::cards(&hand.player),
105 hand.player_total,
106 bet,
107 text,
108 controls,
109 )
110 } else {
111 (
112 "—".to_string(),
113 0,
114 "—".to_string(),
115 0,
116 state.bet_amount,
117 "Press [SPACE] to deal.".to_string(),
118 "[SPACE] Deal [+/-] Bet [Q] Quit".to_string(),
119 )
120 };
121
122 format!(
123 "=== BLACKJACK ===\r\nBalance: {} PLN Bet: {} PLN\r\nDealer: {} {}\r\nYou: {} {}\r\n{}\r\n{}",
124 balance, bet, dealer, dealer_total, player, player_total, text, controls
125 )
126 }
127
128 fn render_lines(&self, _state: &GameState) -> usize {
129 BLACKJACK_BOARD_LINES
130 }
131
132 fn get_help(&self) -> String {
133 "BLACKJACK\n\
134 =========\n\
135 Six-deck shoe, fresh for every hand.\n\
136 Dealer stands on all 17s.\n\
137 Blackjack pays 3:2.\n\
138 Double is allowed on the first two cards.\n\
139 \n\
140 H - hit\n\
141 S - stand\n\
142 D - double (when available)\n\
143 SPACE - deal a new hand\n\
144 +/- - change the next hand's bet\n\
145 Q - leave the table"
146 .to_string()
147 }
148}
diff --git a/src/client/games/manager.rs b/src/client/games/manager.rs
index 02e9451..4e0218b 100644
--- a/src/client/games/manager.rs
+++ b/src/client/games/manager.rs
@@ -1,7 +1,9 @@
1use super::blackjack::BlackjackGame;
1use super::response::{RouletteResult, SpinResult}; 2use super::response::{RouletteResult, SpinResult};
2use super::roulette::RouletteGame; 3use super::roulette::RouletteGame;
3use super::slots::SlotsGame; 4use super::slots::SlotsGame;
4use super::traits::{Game, GameError, GamePhase, GameState}; 5use super::traits::{Game, GameError, GamePhase, GameState};
6use crate::blackjack::BlackjackState;
5use crate::roulette::Bets; 7use crate::roulette::Bets;
6use std::collections::HashMap; 8use std::collections::HashMap;
7 9
@@ -15,6 +17,7 @@ impl GameManager {
15 let mut games: HashMap<String, Box<dyn Game>> = HashMap::new(); 17 let mut games: HashMap<String, Box<dyn Game>> = HashMap::new();
16 games.insert("slots".to_string(), Box::new(SlotsGame::new())); 18 games.insert("slots".to_string(), Box::new(SlotsGame::new()));
17 games.insert("roulette".to_string(), Box::new(RouletteGame::new())); 19 games.insert("roulette".to_string(), Box::new(RouletteGame::new()));
20 games.insert("blackjack".to_string(), Box::new(BlackjackGame::new()));
18 21
19 Self { 22 Self {
20 current_session: None, 23 current_session: None,
@@ -51,6 +54,9 @@ impl GameManager {
51 data.insert("status".to_string(), "place".to_string()); 54 data.insert("status".to_string(), "place".to_string());
52 data.insert("number_input".to_string(), String::new()); 55 data.insert("number_input".to_string(), String::new());
53 0 56 0
57 } else if game_type == "blackjack" {
58 data.insert("status".to_string(), "ready".to_string());
59 10
54 } else { 60 } else {
55 10 61 10
56 }; 62 };
@@ -260,6 +266,49 @@ impl GameManager {
260 .insert("number_input".to_string(), String::new()); 266 .insert("number_input".to_string(), String::new());
261 } 267 }
262 268
269 pub fn apply_blackjack_state(&mut self, state: BlackjackState) {
270 let Some(session) = self.current_session.as_mut() else {
271 return;
272 };
273 session.phase = if state.phase == "player" {
274 GamePhase::Playing
275 } else {
276 GamePhase::Finished
277 };
278 session.bet_amount = state.base_bet;
279 session.data.remove("error");
280 session
281 .data
282 .insert("balance".to_string(), state.balance.to_string());
283 session.data.insert(
284 "status".to_string(),
285 if state.phase == "player" {
286 "player".to_string()
287 } else {
288 "done".to_string()
289 },
290 );
291 session.data.insert(
292 "blackjack".to_string(),
293 serde_json::to_string(&state).unwrap_or_default(),
294 );
295 }
296
297 pub fn blackjack_state(&self) -> Option<BlackjackState> {
298 self.current_session
299 .as_ref()
300 .and_then(|session| session.data.get("blackjack"))
301 .and_then(|raw| serde_json::from_str(raw).ok())
302 }
303
304 pub fn set_blackjack_status(&mut self, status: &str) {
305 if let Some(session) = self.current_session.as_mut() {
306 session
307 .data
308 .insert("status".to_string(), status.to_string());
309 }
310 }
311
263 pub fn get_current_game(&self) -> Option<&dyn Game> { 312 pub fn get_current_game(&self) -> Option<&dyn Game> {
264 self.current_session 313 self.current_session
265 .as_ref() 314 .as_ref()
diff --git a/src/client/games/mod.rs b/src/client/games/mod.rs
index 131ffbb..40ba32a 100644
--- a/src/client/games/mod.rs
+++ b/src/client/games/mod.rs
@@ -1,3 +1,4 @@
1pub mod blackjack;
1pub mod manager; 2pub mod manager;
2pub mod response; 3pub mod response;
3pub mod roulette; 4pub mod roulette;
diff --git a/src/client/games/response.rs b/src/client/games/response.rs
index 73080dd..d9883f7 100644
--- a/src/client/games/response.rs
+++ b/src/client/games/response.rs
@@ -141,3 +141,25 @@ pub fn parse_roulette_response(response: &str) -> Result<RouletteResult, String>
141 bets, 141 bets,
142 }) 142 })
143} 143}
144
145pub fn parse_blackjack_response(
146 response: &str,
147) -> Result<crate::blackjack::BlackjackState, String> {
148 let response = response.trim();
149 if response.is_empty() {
150 return Err("No response from server".to_string());
151 }
152 if let Some(message) = response.strip_prefix("ERROR") {
153 let message = message.trim();
154 return Err(if message.is_empty() {
155 "Server error".to_string()
156 } else {
157 message.to_string()
158 });
159 }
160
161 let raw = response
162 .strip_prefix("BLACKJACK_STATE ")
163 .ok_or_else(|| format!("Unexpected server response: {response}"))?;
164 serde_json::from_str(raw).map_err(|error| format!("Invalid blackjack state: {error}"))
165}