summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--docs/TESTING.md2
-rw-r--r--gui/src/crash.rs34
-rw-r--r--src/client/games/crash.rs45
-rw-r--r--tests/client_core.rs33
4 files changed, 107 insertions, 7 deletions
diff --git a/docs/TESTING.md b/docs/TESTING.md
index fbd510b..141ea75 100644
--- a/docs/TESTING.md
+++ b/docs/TESTING.md
@@ -225,6 +225,8 @@ terminal client; a saved login from one signs in the other.
225 and `sha256(seed) == commit`; recompute the crash point from the seed. 225 and `sha256(seed) == commit`; recompute the crash point from the seed.
2268. Confirm `CRASH_BUSTED` is an `EVT`, not a `RES`, and that a second poll 2268. Confirm `CRASH_BUSTED` is an `EVT`, not a `RES`, and that a second poll
227 produces no repeated settlement. 227 produces no repeated settlement.
2289. The board's fairness line shows the short commit while running, then the
229 short seed and `verified` after the reveal (TUI and GUI).
228 230
229## Manual restart check 231## Manual restart check
230 232
diff --git a/gui/src/crash.rs b/gui/src/crash.rs
index 08113a8..a73c3bd 100644
--- a/gui/src/crash.rs
+++ b/gui/src/crash.rs
@@ -2,9 +2,18 @@
2 2
3use eframe::egui::{Align2, Color32, Pos2, Rect, Sense, Shape, Stroke, StrokeKind, Ui, pos2, vec2}; 3use eframe::egui::{Align2, Color32, Pos2, Rect, Sense, Shape, Stroke, StrokeKind, Ui, pos2, vec2};
4use zagan_bet::client::view::{Command, CrashView}; 4use zagan_bet::client::view::{Command, CrashView};
5use zagan_bet::crash::{crash_point_from_seed, seed_from_hex, verify_seed};
5 6
6use crate::{theme, widgets}; 7use crate::{theme, widgets};
7 8
9fn short_hash(hash: &str) -> String {
10 hash.char_indices()
11 .nth(12)
12 .map(|(index, _)| &hash[..index])
13 .unwrap_or(hash)
14 .to_string()
15}
16
8/// One sample of the running curve. 17/// One sample of the running curve.
9struct Sample { 18struct Sample {
10 seconds: f32, 19 seconds: f32,
@@ -136,6 +145,31 @@ fn sidebar(ui: &mut Ui, view: &CrashView, fx: &mut Fx) -> Option<Command> {
136 theme::GOLD, 145 theme::GOLD,
137 ); 146 );
138 147
148 if let Some(commit) = &view.commit {
149 widgets::row(ui, "Commit", &short_hash(commit), theme::TEXT_FAINT);
150 }
151 if let Some(seed) = &view.seed {
152 let commit_ok = view
153 .commit
154 .as_deref()
155 .map(|commit| verify_seed(seed, commit))
156 .unwrap_or(false);
157 let point_ok = seed_from_hex(seed)
158 .map(|bytes| {
159 view.crash_point
160 .map(|point| {
161 (crash_point_from_seed(&bytes) - point).abs() < 1e-9
162 })
163 .unwrap_or(false)
164 })
165 .unwrap_or(false);
166 if commit_ok && point_ok {
167 widgets::row(ui, "Seed ✓", &short_hash(seed), theme::WIN);
168 } else {
169 widgets::row(ui, "Seed ✗", &short_hash(seed), theme::LOSS);
170 }
171 }
172
139 ui.add_space(theme::SPACE_3); 173 ui.add_space(theme::SPACE_3);
140 status(ui, view); 174 status(ui, view);
141 175
diff --git a/src/client/games/crash.rs b/src/client/games/crash.rs
index affb642..c6dbbd5 100644
--- a/src/client/games/crash.rs
+++ b/src/client/games/crash.rs
@@ -1,7 +1,8 @@
1use super::traits::{Game, GameState}; 1use super::traits::{Game, GameState};
2use crate::client::view::CrashView; 2use crate::client::view::CrashView;
3use crate::crash::{crash_point_from_seed, seed_from_hex, verify_seed};
3 4
4pub const CRASH_BOARD_LINES: usize = 6; 5pub const CRASH_BOARD_LINES: usize = 7;
5 6
6pub struct CrashGame; 7pub struct CrashGame;
7 8
@@ -29,6 +30,38 @@ impl CrashGame {
29 } 30 }
30 } 31 }
31 32
33 /// Commitment line. The seed is shown only after the round ends, and the
34 /// client recomputes both the hash and the crash point.
35 fn fairness_text(view: &CrashView) -> String {
36 let short = |hash: &str| {
37 hash.char_indices()
38 .nth(12)
39 .map(|(index, _)| &hash[..index])
40 .unwrap_or(hash)
41 .to_string()
42 };
43 let Some(commit) = view.commit.as_deref() else {
44 return "Commit: —".to_string();
45 };
46 let Some(seed) = view.seed.as_deref() else {
47 return format!("Commit {} Seed hidden until the round ends", short(commit));
48 };
49 let commit_ok = verify_seed(seed, commit);
50 let point_ok = seed_from_hex(seed)
51 .map(|bytes| {
52 let expected = crash_point_from_seed(&bytes);
53 view.crash_point
54 .map(|point| (expected - point).abs() < 1e-9)
55 .unwrap_or(false)
56 })
57 .unwrap_or(false);
58 if commit_ok && point_ok {
59 format!("Commit {} Seed {} verified", short(commit), short(seed))
60 } else {
61 format!("Commit {} Seed {} VERIFICATION FAILED", short(commit), short(seed))
62 }
63 }
64
32 fn controls(view: &CrashView) -> &'static str { 65 fn controls(view: &CrashView) -> &'static str {
33 if view.status == "running" { 66 if view.status == "running" {
34 "[C] Cash out [Q] Forfeit" 67 "[C] Cash out [Q] Forfeit"
@@ -46,19 +79,21 @@ impl CrashGame {
46 79
47 if let Some(error) = &view.error { 80 if let Some(error) = &view.error {
48 return format!( 81 return format!(
49 "=== CRASH ===\r\nBalance: {balance} Kr Bet: {} Kr\r\n\r\nERROR: {error}\r\n\r\n[SPACE] Play [+/=/-] Bet [Q] Quit", 82 "=== CRASH ===\r\nBalance: {balance} Kr Bet: {} Kr\r\n\r\nERROR: {error}\r\n\r\n{}\r\n[SPACE] Play [+/=/-] Bet [Q] Quit",
50 view.bet 83 view.bet,
84 Self::fairness_text(view)
51 ); 85 );
52 } 86 }
53 87
54 format!( 88 format!(
55 "=== CRASH ===\r\nBalance: {} Kr Bet: {} Kr\r\nMultiplier: {:.2}x\r\nPotential: {} Kr\r\n{}\r\n{}", 89 "=== CRASH ===\r\nBalance: {} Kr Bet: {} Kr\r\nMultiplier: {:.2}x\r\nPotential: {} Kr\r\n{}\r\n{}\r\n{}",
56 balance, 90 balance,
57 view.bet, 91 view.bet,
58 view.multiplier, 92 view.multiplier,
59 view.potential, 93 view.potential,
60 Self::status_text(view), 94 Self::status_text(view),
61 Self::controls(view) 95 Self::controls(view),
96 Self::fairness_text(view)
62 ) 97 )
63 } 98 }
64} 99}
diff --git a/tests/client_core.rs b/tests/client_core.rs
index 3a2fb27..65fd1e6 100644
--- a/tests/client_core.rs
+++ b/tests/client_core.rs
@@ -987,9 +987,10 @@ fn crash_starts_a_round_and_draws_the_multiplier_locally() {
987 let mut h = Harness::new(); 987 let mut h = Harness::new();
988 h.enter_crash(); 988 h.enter_crash();
989 989
990 assert_eq!(h.board().len(), 6); 990 assert_eq!(h.board().len(), 7);
991 assert!(h.board()[4].contains("Press [SPACE] to start.")); 991 assert!(h.board()[4].contains("Press [SPACE] to start."));
992 assert!(h.board()[5].contains("[SPACE] Play")); 992 assert!(h.board()[5].contains("[SPACE] Play"));
993 assert!(h.board()[6].contains("Commit: —"));
993 994
994 h.key(Key::Char(' ')); 995 h.key(Key::Char(' '));
995 assert!(h.sent.iter().any(|line| line == "PLAY_START crash 10")); 996 assert!(h.sent.iter().any(|line| line == "PLAY_START crash 10"));
@@ -997,7 +998,7 @@ fn crash_starts_a_round_and_draws_the_multiplier_locally() {
997 let running = crash_state("running", 1.0, None, 0, 990); 998 let running = crash_state("running", 1.0, None, 0, 990);
998 h.reply(&crash_line("CRASH_START", &running)); 999 h.reply(&crash_line("CRASH_START", &running));
999 1000
1000 assert_eq!(h.board().len(), 6); 1001 assert_eq!(h.board().len(), 7);
1001 assert!(h.board()[2].contains("Multiplier: 1.00x")); 1002 assert!(h.board()[2].contains("Multiplier: 1.00x"));
1002 assert!(h.board()[3].contains("Potential: 10 Kr")); 1003 assert!(h.board()[3].contains("Potential: 10 Kr"));
1003 assert!(h.board()[4].contains("Rising")); 1004 assert!(h.board()[4].contains("Rising"));
@@ -1709,3 +1710,31 @@ fn a_crash_tick_updates_the_board_for_its_own_session() {
1709 h.push("crash.tick session=other t=1700000000200 m=9.0"); 1710 h.push("crash.tick session=other t=1700000000200 m=9.0");
1710 assert!(h.board()[2].contains("Multiplier: 1.50x")); 1711 assert!(h.board()[2].contains("Multiplier: 1.50x"));
1711} 1712}
1713
1714#[test]
1715fn the_crash_board_verifies_the_revealed_seed() {
1716 use zagan_bet::crash::{commit_seed, crash_point_from_seed};
1717
1718 let mut h = Harness::new();
1719 h.enter_crash();
1720 h.key(Key::Char(' '));
1721 let mut seed = [0u8; 32];
1722 seed[..8].copy_from_slice(&(0.67_f64 * 18_446_744_073_709_551_616.0).to_be_bytes());
1723 let commit = commit_seed(&seed);
1724 let point = crash_point_from_seed(&seed);
1725 let hex = seed
1726 .iter()
1727 .map(|byte| format!("{byte:02x}"))
1728 .collect::<String>();
1729
1730 let mut cashed = crash_state("cashed", 2.0, Some(point), 20, 1010);
1731 cashed.commit = Some(commit);
1732 cashed.seed = Some(hex);
1733 h.reply(&crash_line("CRASH_STATE", &cashed));
1734
1735 assert!(
1736 h.board()[6].contains("verified"),
1737 "fairness line: {}",
1738 h.board()[6]
1739 );
1740}