summaryrefslogtreecommitdiffstats
path: root/src/client
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-01 21:09:56 +0100
committersalmonik <salmonik@tuta.io>2026-11-01 21:09:56 +0100
commitbf4cf78ab26896faf457273e37d30fc96381c4a4 (patch)
tree71d67c300b6be191016a988ed5338e15c8b1bdd3 /src/client
parentv4.1.1: pointer-first betting and visible payouts in the GUI (diff)
downloadzagan-bet-bf4cf78ab26896faf457273e37d30fc96381c4a4.tar.gz
zagan-bet-bf4cf78ab26896faf457273e37d30fc96381c4a4.zip
v4.2.0: wire the GUI to the core, transport and all four games
Phase 4. The egui frontend now shares ClientCore and the client database with the terminal: one background worker owns TLS, trust answers go through a channel, and a 10 s request deadline mirrors the terminal socket timeout. First contact, a changed key and a version mismatch are explicit screens; login can remember the credential and auto-login signs in before the login form appears. Adds the lobby with an update notice, a full-width scrollable history table, and the four games painted from View: slots with reel animation and win lines that stop at the last matching reel, roulette with the hover lattice and wheel spin, blackjack with vector-drawn suits, and a smooth crash curve. Sidebars pin their action block; leaving a live hand or round asks before forfeiting. The header balance holds the staked amount while the reels or wheel turn and only shows the payout when they stop. Refused spins cancel the animation. Live crash multipliers keep full precision for the chart; the terminal still displays two decimals. Verified live against a 4.2.0 server: register, login, auto-login, trust, all four games settling, history (50 rows), logout, disconnect, forfeit confirmation, and the version-mismatch screen. Release: 8.3 MB Linux, 6.8 MB Windows, 61 ms to window, 131 MB idle RSS.
Diffstat (limited to 'src/client')
-rw-r--r--src/client/core.rs39
-rw-r--r--src/client/games/manager.rs6
-rw-r--r--src/client/view.rs2
3 files changed, 37 insertions, 10 deletions
diff --git a/src/client/core.rs b/src/client/core.rs
index 21c1cb2..6d8294c 100644
--- a/src/client/core.rs
+++ b/src/client/core.rs
@@ -62,6 +62,8 @@ pub struct ClientCore {
62 logged_in: Option<String>, 62 logged_in: Option<String>,
63 pending: Option<Pending>, 63 pending: Option<Pending>,
64 balance: Option<i64>, 64 balance: Option<i64>,
65 /// Account number from the last `INFO`, shown by both frontends.
66 account: Option<String>,
65 host: Option<String>, 67 host: Option<String>,
66 fingerprint: Option<String>, 68 fingerprint: Option<String>,
67 /// Local anchor for the crash multiplier, set when a round starts. 69 /// Local anchor for the crash multiplier, set when a round starts.
@@ -82,6 +84,7 @@ impl ClientCore {
82 logged_in: None, 84 logged_in: None,
83 pending: None, 85 pending: None,
84 balance: None, 86 balance: None,
87 account: None,
85 host: None, 88 host: None,
86 fingerprint: None, 89 fingerprint: None,
87 crash_started: None, 90 crash_started: None,
@@ -275,15 +278,18 @@ impl ClientCore {
275 } 278 }
276 Some(Pending::Echo) => print_line(format!("Echo: {}", reply.trim())), 279 Some(Pending::Echo) => print_line(format!("Echo: {}", reply.trim())),
277 Some(Pending::Info) => { 280 Some(Pending::Info) => {
281 // The same reply feeds the balance and the account number.
282 let balance = info_balance(reply);
283 if let Some(account) = info_account(reply) {
284 self.account = Some(account);
285 }
286 if let Some(balance) = balance {
287 self.balance = Some(balance);
288 self.game.set_balance(balance);
289 }
278 if self.game.is_in_game() { 290 if self.game.is_in_game() {
279 // Balance fetch while entering a game. A failure is not 291 // A failure is not fatal: the board keeps showing `?`.
280 // fatal: the board keeps showing `?`. 292 self.board_effects()
281 if let Some(balance) = info_balance(reply) {
282 self.balance = Some(balance);
283 self.game.set_balance(balance);
284 return self.board_effects();
285 }
286 Vec::new()
287 } else { 293 } else {
288 vec![Effect::Print(info_lines(reply))] 294 vec![Effect::Print(info_lines(reply))]
289 } 295 }
@@ -405,6 +411,7 @@ impl ClientCore {
405 None => Vec::new(), 411 None => Vec::new(),
406 }, 412 },
407 Command::PlaceBetAt(target) => self.place_roulette_chip_on(target), 413 Command::PlaceBetAt(target) => self.place_roulette_chip_on(target),
414 Command::RemoveBetAt(target) => self.remove_roulette_chip_on(target),
408 Command::ClearBets => { 415 Command::ClearBets => {
409 self.game.set_roulette_bets(&Bets::default()); 416 self.game.set_roulette_bets(&Bets::default());
410 self.game.set_roulette_status("place"); 417 self.game.set_roulette_status("place");
@@ -489,6 +496,12 @@ impl ClientCore {
489 self.place_roulette_bets(bets) 496 self.place_roulette_bets(bets)
490 } 497 }
491 498
499 fn remove_roulette_chip_on(&mut self, target: BetTarget) -> Vec<Effect> {
500 let mut bets = self.game.roulette_bets();
501 bets.reduce(&target, self.game.roulette_chip());
502 self.place_roulette_bets(bets)
503 }
504
492 fn place_roulette_bets(&mut self, bets: Bets) -> Vec<Effect> { 505 fn place_roulette_bets(&mut self, bets: Bets) -> Vec<Effect> {
493 if let Some(balance) = self.balance { 506 if let Some(balance) = self.balance {
494 if bets.total() > balance.max(0) as u64 { 507 if bets.total() > balance.max(0) as u64 {
@@ -782,6 +795,7 @@ impl ClientCore {
782 Screen::Lobby => View::Lobby(LobbyView { 795 Screen::Lobby => View::Lobby(LobbyView {
783 connected: self.connected, 796 connected: self.connected,
784 user: self.logged_in.clone(), 797 user: self.logged_in.clone(),
798 account: self.account.clone(),
785 balance: self.balance, 799 balance: self.balance,
786 games: self.games(), 800 games: self.games(),
787 login_error: self.login_error.clone(), 801 login_error: self.login_error.clone(),
@@ -824,6 +838,15 @@ fn print_line(message: impl Into<String>) -> Vec<Effect> {
824 vec![Effect::Print(vec![message.into()])] 838 vec![Effect::Print(vec![message.into()])]
825} 839}
826 840
841fn info_account(reply: &str) -> Option<String> {
842 let parts: Vec<&str> = reply.split_whitespace().collect();
843 if parts.first() == Some(&"INFO") && parts.len() == 4 {
844 Some(parts[2].to_string()).filter(|account| account != "-")
845 } else {
846 None
847 }
848}
849
827fn info_balance(reply: &str) -> Option<i64> { 850fn info_balance(reply: &str) -> Option<i64> {
828 let parts: Vec<&str> = reply.split_whitespace().collect(); 851 let parts: Vec<&str> = reply.split_whitespace().collect();
829 if parts.first() == Some(&"INFO") && parts.len() == 4 { 852 if parts.first() == Some(&"INFO") && parts.len() == 4 {
diff --git a/src/client/games/manager.rs b/src/client/games/manager.rs
index bb2a8b5..24ceeaa 100644
--- a/src/client/games/manager.rs
+++ b/src/client/games/manager.rs
@@ -353,7 +353,7 @@ impl GameManager {
353 .insert("balance".to_string(), state.balance.to_string()); 353 .insert("balance".to_string(), state.balance.to_string());
354 session 354 session
355 .data 355 .data
356 .insert("multiplier".to_string(), format!("{:.2}", state.multiplier)); 356 .insert("multiplier".to_string(), state.multiplier.to_string());
357 session 357 session
358 .data 358 .data
359 .insert("potential".to_string(), state.potential.to_string()); 359 .insert("potential".to_string(), state.potential.to_string());
@@ -373,9 +373,11 @@ impl GameManager {
373 return; 373 return;
374 }; 374 };
375 let bet = session.bet_amount; 375 let bet = session.bet_amount;
376 // Keep the full value: the terminal rounds for display, but the GUI
377 // charts it and must not draw a staircase.
376 session 378 session
377 .data 379 .data
378 .insert("multiplier".to_string(), format!("{multiplier:.2}")); 380 .insert("multiplier".to_string(), multiplier.to_string());
379 session 381 session
380 .data 382 .data
381 .insert("potential".to_string(), payout(bet, multiplier).to_string()); 383 .insert("potential".to_string(), payout(bet, multiplier).to_string());
diff --git a/src/client/view.rs b/src/client/view.rs
index d2ccadb..4615720 100644
--- a/src/client/view.rs
+++ b/src/client/view.rs
@@ -246,6 +246,7 @@ impl CrashView {
246pub struct LobbyView { 246pub struct LobbyView {
247 pub connected: bool, 247 pub connected: bool,
248 pub user: Option<String>, 248 pub user: Option<String>,
249 pub account: Option<String>,
249 pub balance: Option<i64>, 250 pub balance: Option<i64>,
250 pub games: Vec<(String, String)>, 251 pub games: Vec<(String, String)>,
251 /// Why the last login or registration attempt failed. 252 /// Why the last login or registration attempt failed.
@@ -297,6 +298,7 @@ pub enum Command {
297 MoveCursor(Direction), 298 MoveCursor(Direction),
298 PlaceBet, 299 PlaceBet,
299 PlaceBetAt(BetTarget), 300 PlaceBetAt(BetTarget),
301 RemoveBetAt(BetTarget),
300 ClearBets, 302 ClearBets,
301 SetChip(u64), 303 SetChip(u64),
302 AdjustChip(i64), 304 AdjustChip(i64),