summaryrefslogtreecommitdiffstats
path: root/src/client/core.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-29 18:22:40 +0100
committersalmonik <salmonik@tuta.io>2026-10-29 18:22:40 +0100
commitb5a52fc0312afdf5d5fbe53275dc88d6d881c2ff (patch)
treed12e3c7f6c23ae1698199d16ee763551edfd8c12 /src/client/core.rs
parentv2.1.2: ignore ANSI when measuring board height (diff)
downloadzagan-bet-b5a52fc0312afdf5d5fbe53275dc88d6d881c2ff.tar.gz
zagan-bet-b5a52fc0312afdf5d5fbe53275dc88d6d881c2ff.zip
v2.2.0: add blackjack with reserved bets and hit/stand/double
Diffstat (limited to 'src/client/core.rs')
-rw-r--r--src/client/core.rs154
1 files changed, 149 insertions, 5 deletions
diff --git a/src/client/core.rs b/src/client/core.rs
index e005f2a..6e9c28b 100644
--- a/src/client/core.rs
+++ b/src/client/core.rs
@@ -1,5 +1,7 @@
1use crate::client::games::manager::GameManager; 1use crate::client::games::manager::GameManager;
2use crate::client::games::response::{parse_roulette_response, parse_spin_response}; 2use crate::client::games::response::{
3 parse_blackjack_response, parse_roulette_response, parse_spin_response,
4};
3use crate::client::ui::{ 5use crate::client::ui::{
4 command_names, completion_lines, display_width, help_lines, input_cursor_column, 6 command_names, completion_lines, display_width, help_lines, input_cursor_column,
5 parse_bet_arg, record_history, 7 parse_bet_arg, record_history,
@@ -61,6 +63,9 @@ pub enum Effect {
61enum Pending { 63enum Pending {
62 Spin, 64 Spin,
63 RouletteSpin, 65 RouletteSpin,
66 BlackjackStart,
67 BlackjackAction,
68 BlackjackCancel,
64 Ping, 69 Ping,
65 Echo, 70 Echo,
66 Info, 71 Info,
@@ -191,6 +196,32 @@ impl ClientCore {
191 self.board_effects() 196 self.board_effects()
192 } 197 }
193 }, 198 },
199 Some(Pending::BlackjackStart) | Some(Pending::BlackjackAction) => {
200 match parse_blackjack_response(reply) {
201 Ok(state) => {
202 self.balance = Some(state.balance);
203 self.game.apply_blackjack_state(state);
204 self.board_effects()
205 }
206 Err(message) => {
207 self.game.set_error(message);
208 self.board_effects()
209 }
210 }
211 }
212 Some(Pending::BlackjackCancel) => match parse_blackjack_response(reply) {
213 Ok(_) => {
214 self.game.end_game();
215 vec![
216 Effect::ClearRegion,
217 Effect::Print(vec!["Game exited.".to_string()]),
218 ]
219 }
220 Err(message) => {
221 self.game.set_error(message);
222 self.board_effects()
223 }
224 },
194 Some(Pending::Ping) => { 225 Some(Pending::Ping) => {
195 if reply.trim() == "PONG" { 226 if reply.trim() == "PONG" {
196 print_line("Pong") 227 print_line("Pong")
@@ -225,14 +256,16 @@ impl ClientCore {
225 } 256 }
226 257
227 fn handle_game_key(&mut self, key: Key) -> Vec<Effect> { 258 fn handle_game_key(&mut self, key: Key) -> Vec<Effect> {
228 if self 259 let game_type = self
229 .game 260 .game
230 .get_current_state() 261 .get_current_state()
231 .map(|state| state.game_type.as_str()) 262 .map(|state| state.game_type.as_str());
232 == Some("roulette") 263 if game_type == Some("roulette") {
233 {
234 return self.handle_roulette_key(key); 264 return self.handle_roulette_key(key);
235 } 265 }
266 if game_type == Some("blackjack") {
267 return self.handle_blackjack_key(key);
268 }
236 269
237 match key { 270 match key {
238 Key::CtrlC => vec![Effect::Quit], 271 Key::CtrlC => vec![Effect::Quit],
@@ -411,6 +444,117 @@ impl ClientCore {
411 .unwrap_or_default() 444 .unwrap_or_default()
412 } 445 }
413 446
447 fn handle_blackjack_key(&mut self, key: Key) -> Vec<Effect> {
448 let status = self
449 .game
450 .get_current_state()
451 .and_then(|state| state.data.get("status"))
452 .cloned()
453 .unwrap_or_else(|| "ready".to_string());
454 let hand = self.game.blackjack_state();
455
456 match key {
457 Key::CtrlC => vec![Effect::Quit],
458 Key::Char('q') => {
459 if let Some(hand) = hand.filter(|hand| hand.phase == "player") {
460 self.game.set_blackjack_status("cancelling");
461 self.pending = Some(Pending::BlackjackCancel);
462 vec![Effect::SendLine(format!(
463 "BLACKJACK_ACTION {} cancel",
464 hand.session_id
465 ))]
466 } else {
467 self.game.end_game();
468 vec![
469 Effect::ClearRegion,
470 Effect::Print(vec!["Game exited.".to_string()]),
471 ]
472 }
473 }
474 Key::Char(' ') => self.start_blackjack(),
475 Key::Char('h')
476 if hand.as_ref().is_some_and(|hand| hand.phase == "player") =>
477 {
478 self.blackjack_action("hit")
479 }
480 Key::Char('s')
481 if hand.as_ref().is_some_and(|hand| hand.phase == "player") =>
482 {
483 self.blackjack_action("stand")
484 }
485 Key::Char('d')
486 if hand.as_ref().is_some_and(|hand| {
487 hand.phase == "player"
488 && hand.actions.iter().any(|action| action == "double")
489 }) =>
490 {
491 self.blackjack_action("double")
492 }
493 Key::Char('+') | Key::Char('=')
494 if matches!(status.as_str(), "ready" | "done") =>
495 {
496 let _ = self.game.adjust_bet(10);
497 self.board_effects()
498 }
499 Key::Char('-') if matches!(status.as_str(), "ready" | "done") => {
500 let _ = self.game.adjust_bet(-10);
501 self.board_effects()
502 }
503 _ => Vec::new(),
504 }
505 }
506
507 fn start_blackjack(&mut self) -> Vec<Effect> {
508 let status = self
509 .game
510 .get_current_state()
511 .and_then(|state| state.data.get("status"))
512 .cloned()
513 .unwrap_or_else(|| "ready".to_string());
514 if !matches!(status.as_str(), "ready" | "done") {
515 return Vec::new();
516 }
517 let Some(request) = self.game.spin_request() else {
518 return Vec::new();
519 };
520 if let Some(balance) = self.balance {
521 let bet = self
522 .game
523 .get_current_state()
524 .map(|state| state.bet_amount)
525 .unwrap_or(0);
526 if bet as i64 > balance {
527 self.game.set_error("Insufficient balance");
528 return self.board_effects();
529 }
530 }
531
532 self.game.set_blackjack_status("dealing");
533 self.pending = Some(Pending::BlackjackStart);
534 let mut effects = self.board_effects();
535 effects.push(Effect::SendLine(request));
536 effects
537 }
538
539 fn blackjack_action(&mut self, action: &str) -> Vec<Effect> {
540 let Some(hand) = self.game.blackjack_state() else {
541 return Vec::new();
542 };
543 if hand.phase != "player" {
544 return Vec::new();
545 }
546 if action == "double"
547 && !hand.actions.iter().any(|available| available == "double")
548 {
549 return Vec::new();
550 }
551 self.pending = Some(Pending::BlackjackAction);
552 vec![Effect::SendLine(format!(
553 "BLACKJACK_ACTION {} {}",
554 hand.session_id, action
555 ))]
556 }
557
414 fn submit(&mut self) -> Vec<Effect> { 558 fn submit(&mut self) -> Vec<Effect> {
415 let line = self.input.trim().to_string(); 559 let line = self.input.trim().to_string();
416 self.input.clear(); 560 self.input.clear();