summaryrefslogtreecommitdiffstats
path: root/gui
diff options
context:
space:
mode:
Diffstat (limited to 'gui')
-rw-r--r--gui/Cargo.toml2
-rw-r--r--gui/src/app.rs79
-rw-r--r--gui/src/blackjack.rs36
-rw-r--r--gui/src/crash.rs47
-rw-r--r--gui/src/net.rs2
-rw-r--r--gui/src/roulette.rs38
-rw-r--r--gui/src/slots.rs56
7 files changed, 42 insertions, 218 deletions
diff --git a/gui/Cargo.toml b/gui/Cargo.toml
index d3d9c36..5418794 100644
--- a/gui/Cargo.toml
+++ b/gui/Cargo.toml
@@ -1,6 +1,6 @@
1[package] 1[package]
2name = "zagan-bet-gui" 2name = "zagan-bet-gui"
3version = "4.2.0" 3version = "4.2.1"
4edition = "2024" 4edition = "2024"
5license = "GPL-3.0-or-later" 5license = "GPL-3.0-or-later"
6description = "Desktop frontend for zagan-bet" 6description = "Desktop frontend for zagan-bet"
diff --git a/gui/src/app.rs b/gui/src/app.rs
index f0efd3e..af64e5e 100644
--- a/gui/src/app.rs
+++ b/gui/src/app.rs
@@ -49,6 +49,7 @@ pub struct ZaganApp {
49 notices: VecDeque<Notice>, 49 notices: VecDeque<Notice>,
50 awaiting: Option<Instant>, 50 awaiting: Option<Instant>,
51 need_info: bool, 51 need_info: bool,
52 was_logged_in: bool,
52 pending_login: Option<(String, String)>, 53 pending_login: Option<(String, String)>,
53 /// Displayed balance while a result animation runs: stake deducted, no 54 /// Displayed balance while a result animation runs: stake deducted, no
54 /// winnings yet. The view already knows the outcome; the player must not. 55 /// winnings yet. The view already knows the outcome; the player must not.
@@ -62,7 +63,6 @@ struct Session {
62 host: String, 63 host: String,
63 fingerprint: Option<String>, 64 fingerprint: Option<String>,
64 ping_ms: f32, 65 ping_ms: f32,
65 server_version: String,
66 latest: String, 66 latest: String,
67 url: String, 67 url: String,
68 user: Option<String>, 68 user: Option<String>,
@@ -186,6 +186,7 @@ impl ZaganApp {
186 notices: VecDeque::new(), 186 notices: VecDeque::new(),
187 awaiting: None, 187 awaiting: None,
188 need_info: false, 188 need_info: false,
189 was_logged_in: false,
189 pending_login: None, 190 pending_login: None,
190 balance_hold: None, 191 balance_hold: None,
191 balance: None, 192 balance: None,
@@ -348,7 +349,7 @@ impl ZaganApp {
348 reply, 349 reply,
349 }); 350 });
350 } 351 }
351 Event::Connected(connected) => self.connected(ctx, connected), 352 Event::Connected(connected) => self.connected(connected),
352 Event::Failed { 353 Event::Failed {
353 addr, 354 addr,
354 failure, 355 failure,
@@ -376,7 +377,7 @@ impl ZaganApp {
376 } 377 }
377 } 378 }
378 379
379 fn connected(&mut self, ctx: &Context, connected: Connected) { 380 fn connected(&mut self, connected: Connected) {
380 self.connect.pending = false; 381 self.connect.pending = false;
381 self.connect.error = None; 382 self.connect.error = None;
382 self.core.set_connected(true); 383 self.core.set_connected(true);
@@ -393,19 +394,14 @@ impl ZaganApp {
393 host: connected.addr, 394 host: connected.addr,
394 fingerprint: connected.fingerprint, 395 fingerprint: connected.fingerprint,
395 ping_ms: connected.elapsed_ms, 396 ping_ms: connected.elapsed_ms,
396 server_version: connected.server_version,
397 latest: connected.latest, 397 latest: connected.latest,
398 url: connected.url, 398 url: connected.url,
399 user: connected.user, 399 user: connected.user,
400 }); 400 });
401 401
402 self.need_info = self.session.as_ref().is_some_and(|s| s.user.is_some()); 402 self.need_info = false;
403 self.was_logged_in = false;
403 self.account = None; 404 self.account = None;
404 if self.need_info {
405 let effects = self.core.command(Command::Info);
406 self.apply(ctx, effects);
407 self.need_info = false;
408 }
409 } 405 }
410 406
411 fn failed(&mut self, addr: String, failure: Failure, message: String) { 407 fn failed(&mut self, addr: String, failure: Failure, message: String) {
@@ -511,7 +507,6 @@ impl ZaganApp {
511 let command = if registering { 507 let command = if registering {
512 Command::Register { user, credential } 508 Command::Register { user, credential }
513 } else { 509 } else {
514 self.need_info = true;
515 if self.login.remember { 510 if self.login.remember {
516 self.pending_login = Some((user.clone(), credential.clone())); 511 self.pending_login = Some((user.clone(), credential.clone()));
517 } 512 }
@@ -522,19 +517,26 @@ impl ZaganApp {
522 } 517 }
523 518
524 fn tick(&mut self, ctx: &Context) { 519 fn tick(&mut self, ctx: &Context) {
525 // Request deadline, mirroring the terminal's socket timeout. 520 // Fetch the account and balance once each time a session signs in.
526 if self.need_info 521 let signed_in = self
527 && self 522 .core
528 .session 523 .view()
529 .as_ref() 524 .as_ref()
530 .is_some_and(|session| session.user.is_some()) 525 .is_some_and(|view| matches!(view, View::Lobby(lobby) if lobby.user.is_some()));
531 && self.account.is_none() 526 if signed_in != self.was_logged_in {
532 { 527 self.was_logged_in = signed_in;
528 if signed_in {
529 self.need_info = true;
530 }
531 }
532 if self.need_info && signed_in {
533 let effects = self.core.command(Command::Info); 533 let effects = self.core.command(Command::Info);
534 self.apply(ctx, effects); 534 self.apply(ctx, effects);
535 self.need_info = false; 535 self.need_info = false;
536 } 536 }
537 537
538 // Request deadline, mirroring the terminal's socket timeout.
539
538 if let Some(sent) = self.awaiting { 540 if let Some(sent) = self.awaiting {
539 if sent.elapsed() > REQUEST_TIMEOUT { 541 if sent.elapsed() > REQUEST_TIMEOUT {
540 self.awaiting = None; 542 self.awaiting = None;
@@ -1351,13 +1353,7 @@ fn lobby_screen(ui: &mut Ui, app: &mut ZaganApp, lobby: &LobbyView) {
1351 ui.add_space(theme::SPACE_2); 1353 ui.add_space(theme::SPACE_2);
1352 ui.horizontal(|ui| { 1354 ui.horizontal(|ui| {
1353 if let Some(session) = &app.session { 1355 if let Some(session) = &app.session {
1354 widgets::note( 1356 widgets::note(ui, &format!("Signed in to {}", session.host));
1355 ui,
1356 &format!(
1357 "Signed in to {} · server v{}",
1358 session.host, session.server_version
1359 ),
1360 );
1361 } 1357 }
1362 ui.with_layout(Layout::right_to_left(Align::Center), |ui| { 1358 ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
1363 if widgets::ghost(ui, "Forget saved login") { 1359 if widgets::ghost(ui, "Forget saved login") {
@@ -1396,15 +1392,9 @@ fn game_card(ui: &mut Ui, app: &mut ZaganApp, key: &str, name: &str) {
1396 .font(theme::sans(22.0)) 1392 .font(theme::sans(22.0))
1397 .color(theme::TEXT), 1393 .color(theme::TEXT),
1398 ); 1394 );
1399 ui.add_space(2.0);
1400 widgets::note(ui, tagline(key));
1401 ui.add_space(theme::SPACE_1); 1395 ui.add_space(theme::SPACE_1);
1402 ui.label( 1396 widgets::note(ui, how_to(key));
1403 RichText::new(meta(key)) 1397 ui.add_space(theme::SPACE_4);
1404 .font(theme::mono(10.0))
1405 .color(theme::TEXT_FAINT),
1406 );
1407 ui.add_space(theme::SPACE_3);
1408 if widgets::primary(ui, "PLAY", true, 110.0, 36.0) { 1398 if widgets::primary(ui, "PLAY", true, 110.0, 36.0) {
1409 let effects = app.core.command(Command::Play { 1399 let effects = app.core.command(Command::Play {
1410 game: key.to_string(), 1400 game: key.to_string(),
@@ -1415,22 +1405,13 @@ fn game_card(ui: &mut Ui, app: &mut ZaganApp, key: &str, name: &str) {
1415 }); 1405 });
1416} 1406}
1417 1407
1418fn tagline(game: &str) -> &'static str { 1408/// One plain sentence: how to play, not the house's rules sheet.
1419 match game { 1409fn how_to(game: &str) -> &'static str {
1420 "slots" => "Five reels, five paylines, one lever.",
1421 "roulette" => "The European wheel, thirty-seven pockets.",
1422 "blackjack" => "Six decks, dealer stands on seventeen. Pays 3:2.",
1423 "crash" => "The multiplier rises. Cash out before it busts.",
1424 _ => "",
1425 }
1426}
1427
1428fn meta(game: &str) -> &'static str {
1429 match game { 1410 match game {
1430 "slots" => "MIN 10 KR · RTP 96%", 1411 "slots" => "Spin the reels. Three or more matching symbols from the left win.",
1431 "roulette" => "MIN 10 KR · SINGLE ZERO", 1412 "roulette" => "Put chips on the table, then spin. Straight numbers pay the most.",
1432 "blackjack" => "MIN 10 KR · S17 · 3:2", 1413 "blackjack" => "Get closer to 21 than the dealer without going over.",
1433 "crash" => "MIN 10 KR · 1% EDGE", 1414 "crash" => "Start the round, then cash out before the multiplier crashes.",
1434 _ => "", 1415 _ => "",
1435 } 1416 }
1436} 1417}
diff --git a/gui/src/blackjack.rs b/gui/src/blackjack.rs
index d42f000..bbe96fa 100644
--- a/gui/src/blackjack.rs
+++ b/gui/src/blackjack.rs
@@ -70,11 +70,7 @@ fn sidebar(ui: &mut Ui, view: &BlackjackView) -> Option<Command> {
70 .font(theme::sans(22.0)) 70 .font(theme::sans(22.0))
71 .color(theme::TEXT), 71 .color(theme::TEXT),
72 ); 72 );
73 ui.label( 73 widgets::note(ui, "Get close to 21 without going over.");
74 eframe::egui::RichText::new("6 decks · dealer stands on 17 · 3:2")
75 .font(theme::sans(12.0))
76 .color(theme::TEXT_DIM),
77 );
78 74
79 ui.add_space(theme::SPACE_5); 75 ui.add_space(theme::SPACE_5);
80 ui.label(theme::label("Bet")); 76 ui.label(theme::label("Bet"));
@@ -104,12 +100,6 @@ fn sidebar(ui: &mut Ui, view: &BlackjackView) -> Option<Command> {
104 ui.add_space(theme::SPACE_3); 100 ui.add_space(theme::SPACE_3);
105 widgets::error(ui, error); 101 widgets::error(ui, error);
106 } 102 }
107
108 ui.add_space(theme::SPACE_4);
109 ui.separator();
110 ui.add_space(theme::SPACE_2);
111 widgets::note(ui, "Blackjack pays 3:2. The dealer stands on soft 17.");
112 widgets::note(ui, "Double only on the first two cards.");
113 }); 103 });
114 }, 104 },
115 ); 105 );
@@ -189,29 +179,7 @@ fn result_text(result: &str, winnings: u64) -> (String, Color32) {
189} 179}
190 180
191fn board(ui: &mut Ui, view: &BlackjackView) { 181fn board(ui: &mut Ui, view: &BlackjackView) {
192 ui.horizontal(|ui| { 182 ui.add_space(theme::SPACE_2);
193 ui.label(
194 eframe::egui::RichText::new("Table 01")
195 .font(theme::sans(15.0))
196 .color(theme::TEXT),
197 );
198 ui.label(
199 eframe::egui::RichText::new("Blackjack · six decks")
200 .font(theme::sans(12.0))
201 .color(theme::TEXT_DIM),
202 );
203 ui.with_layout(
204 eframe::egui::Layout::right_to_left(eframe::egui::Align::Center),
205 |ui| {
206 ui.label(
207 eframe::egui::RichText::new("3:2 · S17")
208 .font(theme::mono(12.0))
209 .color(theme::TEXT_FAINT),
210 );
211 },
212 );
213 });
214 ui.add_space(theme::SPACE_5);
215 183
216 let Some(hand) = &view.hand else { 184 let Some(hand) = &view.hand else {
217 centered_note(ui, "Place a bet and deal a hand."); 185 centered_note(ui, "Place a bet and deal a hand.");
diff --git a/gui/src/crash.rs b/gui/src/crash.rs
index 14197ae..08113a8 100644
--- a/gui/src/crash.rs
+++ b/gui/src/crash.rs
@@ -119,11 +119,7 @@ fn sidebar(ui: &mut Ui, view: &CrashView, fx: &mut Fx) -> Option<Command> {
119 .font(theme::sans(22.0)) 119 .font(theme::sans(22.0))
120 .color(theme::TEXT), 120 .color(theme::TEXT),
121 ); 121 );
122 ui.label( 122 widgets::note(ui, "Cash out before the multiplier crashes.");
123 eframe::egui::RichText::new("Rise · cash out · don't bust")
124 .font(theme::sans(12.0))
125 .color(theme::TEXT_DIM),
126 );
127 123
128 ui.add_space(theme::SPACE_5); 124 ui.add_space(theme::SPACE_5);
129 ui.label(theme::label("Bet")); 125 ui.label(theme::label("Bet"));
@@ -152,14 +148,6 @@ fn sidebar(ui: &mut Ui, view: &CrashView, fx: &mut Fx) -> Option<Command> {
152 ui.add_space(theme::SPACE_3); 148 ui.add_space(theme::SPACE_3);
153 widgets::error(ui, error); 149 widgets::error(ui, error);
154 } 150 }
155
156 ui.add_space(theme::SPACE_4);
157 ui.separator();
158 ui.add_space(theme::SPACE_2);
159 widgets::note(
160 ui,
161 "The crash point is server-timed. The curve here is display only.",
162 );
163 }); 151 });
164 }, 152 },
165 ); 153 );
@@ -231,29 +219,7 @@ fn status(ui: &mut Ui, view: &CrashView) {
231} 219}
232 220
233fn chart(ui: &mut Ui, view: &CrashView, fx: &Fx) { 221fn chart(ui: &mut Ui, view: &CrashView, fx: &Fx) {
234 ui.horizontal(|ui| { 222 ui.add_space(theme::SPACE_2);
235 ui.label(
236 eframe::egui::RichText::new("Table 01")
237 .font(theme::sans(15.0))
238 .color(theme::TEXT),
239 );
240 ui.label(
241 eframe::egui::RichText::new("Crash · server-timed")
242 .font(theme::sans(12.0))
243 .color(theme::TEXT_DIM),
244 );
245 ui.with_layout(
246 eframe::egui::Layout::right_to_left(eframe::egui::Align::Center),
247 |ui| {
248 ui.label(
249 eframe::egui::RichText::new("Cash out any time before the crash")
250 .font(theme::mono(11.0))
251 .color(theme::TEXT_FAINT),
252 );
253 },
254 );
255 });
256 ui.add_space(theme::SPACE_4);
257 223
258 let available = ui.available_size(); 224 let available = ui.available_size();
259 let size = vec2(available.x, (available.y - 70.0).max(260.0)); 225 let size = vec2(available.x, (available.y - 70.0).max(260.0));
@@ -379,15 +345,6 @@ fn chart(ui: &mut Ui, view: &CrashView, fx: &Fx) {
379 } 345 }
380 } 346 }
381 } 347 }
382
383 ui.add_space(theme::SPACE_4);
384 ui.label(
385 eframe::egui::RichText::new(
386 "The crash point is drawn from the server; the multiplier here is display only.",
387 )
388 .font(theme::sans(11.0))
389 .color(theme::TEXT_FAINT),
390 );
391} 348}
392 349
393fn map_y(plot: Rect, multiplier: f64, max_multiplier: f64) -> f32 { 350fn map_y(plot: Rect, multiplier: f64, max_multiplier: f64) -> f32 {
diff --git a/gui/src/net.rs b/gui/src/net.rs
index d2bc7e4..7cba604 100644
--- a/gui/src/net.rs
+++ b/gui/src/net.rs
@@ -46,7 +46,6 @@ pub struct Connected {
46 pub user: Option<String>, 46 pub user: Option<String>,
47 pub auth_error: Option<String>, 47 pub auth_error: Option<String>,
48 pub fingerprint: Option<String>, 48 pub fingerprint: Option<String>,
49 pub server_version: String,
50 pub latest: String, 49 pub latest: String,
51 pub url: String, 50 pub url: String,
52 pub elapsed_ms: f32, 51 pub elapsed_ms: f32,
@@ -162,7 +161,6 @@ fn worker(
162 user: connected.user, 161 user: connected.user,
163 auth_error: connected.auth_error, 162 auth_error: connected.auth_error,
164 fingerprint, 163 fingerprint,
165 server_version: connected.server_version,
166 latest: connected.latest, 164 latest: connected.latest,
167 url: connected.url, 165 url: connected.url,
168 elapsed_ms: connected.elapsed.as_secs_f64() as f32 * 1000.0, 166 elapsed_ms: connected.elapsed.as_secs_f64() as f32 * 1000.0,
diff --git a/gui/src/roulette.rs b/gui/src/roulette.rs
index c791693..0100d1e 100644
--- a/gui/src/roulette.rs
+++ b/gui/src/roulette.rs
@@ -730,11 +730,7 @@ fn sidebar(
730 .font(theme::sans(22.0)) 730 .font(theme::sans(22.0))
731 .color(theme::TEXT), 731 .color(theme::TEXT),
732 ); 732 );
733 ui.label( 733 super::widgets::note(ui, "Pick numbers or colours, then spin.");
734 eframe::egui::RichText::new("European · single zero")
735 .font(theme::sans(12.0))
736 .color(theme::TEXT_DIM),
737 );
738 734
739 ui.add_space(theme::SPACE_4); 735 ui.add_space(theme::SPACE_4);
740 let wheel_size = ui.available_width().min(210.0); 736 let wheel_size = ui.available_width().min(210.0);
@@ -906,29 +902,7 @@ fn sidebar(
906fn board(ui: &mut Ui, view: &RouletteView, fx: &Fx) -> Option<zagan_bet::client::view::Command> { 902fn board(ui: &mut Ui, view: &RouletteView, fx: &Fx) -> Option<zagan_bet::client::view::Command> {
907 use zagan_bet::client::view::Command; 903 use zagan_bet::client::view::Command;
908 904
909 ui.horizontal(|ui| { 905 ui.add_space(theme::SPACE_2);
910 ui.label(
911 eframe::egui::RichText::new("Table 01")
912 .font(theme::sans(15.0))
913 .color(theme::TEXT),
914 );
915 ui.label(
916 eframe::egui::RichText::new("European · single zero")
917 .font(theme::sans(12.0))
918 .color(theme::TEXT_DIM),
919 );
920 ui.with_layout(
921 eframe::egui::Layout::right_to_left(eframe::egui::Align::Center),
922 |ui| {
923 ui.label(
924 eframe::egui::RichText::new("Limits 10 – 500 Kr")
925 .font(theme::mono(12.0))
926 .color(theme::TEXT_FAINT),
927 );
928 },
929 );
930 });
931 ui.add_space(theme::SPACE_4);
932 906
933 let strip_height = 104.0; 907 let strip_height = 104.0;
934 let available = ui.available_size(); 908 let available = ui.available_size();
@@ -968,11 +942,9 @@ fn board(ui: &mut Ui, view: &RouletteView, fx: &Fx) -> Option<zagan_bet::client:
968 }); 942 });
969 ui.add_space(theme::SPACE_2); 943 ui.add_space(theme::SPACE_2);
970 ui.label( 944 ui.label(
971 eframe::egui::RichText::new( 945 eframe::egui::RichText::new("Click to place a chip · right-click to take one back")
972 "Click to place · right or middle click takes back · C clears · space spins", 946 .font(theme::sans(12.0))
973 ) 947 .color(theme::TEXT_FAINT),
974 .font(theme::sans(12.0))
975 .color(theme::TEXT_FAINT),
976 ); 948 );
977 command 949 command
978} 950}
diff --git a/gui/src/slots.rs b/gui/src/slots.rs
index 6574626..d2a288a 100644
--- a/gui/src/slots.rs
+++ b/gui/src/slots.rs
@@ -109,11 +109,7 @@ fn sidebar(ui: &mut Ui, view: &SlotsView, fx: &mut Fx) -> Option<Command> {
109 .font(theme::sans(22.0)) 109 .font(theme::sans(22.0))
110 .color(theme::TEXT), 110 .color(theme::TEXT),
111 ); 111 );
112 ui.label( 112 widgets::note(ui, "Match three or more symbols from the left.");
113 eframe::egui::RichText::new("5 reels · 5 paylines")
114 .font(theme::sans(12.0))
115 .color(theme::TEXT_DIM),
116 );
117 113
118 ui.add_space(theme::SPACE_5); 114 ui.add_space(theme::SPACE_5);
119 ui.label(theme::label("Bet")); 115 ui.label(theme::label("Bet"));
@@ -128,32 +124,6 @@ fn sidebar(ui: &mut Ui, view: &SlotsView, fx: &mut Fx) -> Option<Command> {
128 ui.add_space(theme::SPACE_3); 124 ui.add_space(theme::SPACE_3);
129 widgets::error(ui, error); 125 widgets::error(ui, error);
130 } 126 }
131
132 ui.add_space(theme::SPACE_4);
133 ui.separator();
134 ui.add_space(theme::SPACE_3);
135 ui.label(theme::label("Paytable"));
136 ui.add_space(theme::SPACE_2);
137 ui.label(
138 eframe::egui::RichText::new("Pays bet × symbol × shape")
139 .font(theme::sans(11.0))
140 .color(theme::TEXT_FAINT),
141 );
142 ui.horizontal(|ui| {
143 ui.spacing_mut().item_spacing.x = theme::SPACE_2;
144 for (symbol, multiplier) in zagan_bet::slots::SYMBOL_MULTIPLIERS {
145 ui.label(
146 eframe::egui::RichText::new(format!("{symbol}{multiplier}"))
147 .font(theme::mono(12.0))
148 .color(symbol_color(symbol)),
149 );
150 }
151 });
152 ui.label(
153 eframe::egui::RichText::new("3×1 4×3 5×14")
154 .font(theme::mono(12.0))
155 .color(theme::TEXT_DIM),
156 );
157 }); 127 });
158 }, 128 },
159 ); 129 );
@@ -240,29 +210,7 @@ fn status(ui: &mut Ui, view: &SlotsView, spinning: bool) {
240} 210}
241 211
242fn board(ui: &mut Ui, view: &SlotsView, fx: &Fx) { 212fn board(ui: &mut Ui, view: &SlotsView, fx: &Fx) {
243 ui.horizontal(|ui| { 213 ui.add_space(theme::SPACE_2);
244 ui.label(
245 eframe::egui::RichText::new("Machine 01")
246 .font(theme::sans(15.0))
247 .color(theme::TEXT),
248 );
249 ui.label(
250 eframe::egui::RichText::new("5 reels · 5 paylines")
251 .font(theme::sans(12.0))
252 .color(theme::TEXT_DIM),
253 );
254 ui.with_layout(
255 eframe::egui::Layout::right_to_left(eframe::egui::Align::Center),
256 |ui| {
257 ui.label(
258 eframe::egui::RichText::new("Bet 10 – 500 Kr")
259 .font(theme::mono(12.0))
260 .color(theme::TEXT_FAINT),
261 );
262 },
263 );
264 });
265 ui.add_space(theme::SPACE_5);
266 214
267 let now = ui.input(|input| input.time); 215 let now = ui.input(|input| input.time);
268 let spinning = fx.spinning(view, now); 216 let spinning = fx.spinning(view, now);