diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-01 21:09:56 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-01 21:09:56 +0100 |
| commit | bf4cf78ab26896faf457273e37d30fc96381c4a4 (patch) | |
| tree | 71d67c300b6be191016a988ed5338e15c8b1bdd3 /src | |
| parent | v4.1.1: pointer-first betting and visible payouts in the GUI (diff) | |
| download | zagan-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')
| -rw-r--r-- | src/client/core.rs | 39 | ||||
| -rw-r--r-- | src/client/games/manager.rs | 6 | ||||
| -rw-r--r-- | src/client/view.rs | 2 |
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 | ||
| 841 | fn 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 | |||
| 827 | fn info_balance(reply: &str) -> Option<i64> { | 850 | fn 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 { | |||
| 246 | pub struct LobbyView { | 246 | pub 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), |
