summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-29 23:08:25 +0100
committersalmonik <salmonik@tuta.io>2026-10-29 23:08:25 +0100
commit80d9aa13251f7f5c3d3eeaa877bc79dac4435341 (patch)
tree62e8b341749fa0644b9877ae621583929a9c1a29
parentv3.1.0: warn on shrink, but let a live round finish (diff)
downloadzagan-bet-80d9aa13251f7f5c3d3eeaa877bc79dac4435341.tar.gz
zagan-bet-80d9aa13251f7f5c3d3eeaa877bc79dac4435341.zip
PLN changed to Kromer
-rw-r--r--docs/GAMES.md6
-rw-r--r--docs/SLOTS.md6
-rw-r--r--src/client/core.rs16
-rw-r--r--src/client/games/blackjack.rs10
-rw-r--r--src/client/games/crash.rs6
-rw-r--r--src/client/games/roulette.rs25
-rw-r--r--src/client/games/slots.rs10
-rw-r--r--src/client/ui.rs8
-rw-r--r--src/crash.rs2
-rw-r--r--tests/client_core.rs199
-rw-r--r--tests/crash.rs8
11 files changed, 153 insertions, 143 deletions
diff --git a/docs/GAMES.md b/docs/GAMES.md
index 364cfef..8cc668f 100644
--- a/docs/GAMES.md
+++ b/docs/GAMES.md
@@ -37,8 +37,8 @@ and broadcast, which is a different product.
37- Because `P(crash = 1.00x) = 1%`, an instant cashout returns the stake 99% of 37- Because `P(crash = 1.00x) = 1%`, an instant cashout returns the stake 99% of
38 the time and nothing 1% of the time. There is no risk-free window, which is 38 the time and nothing 1% of the time. There is no risk-free window, which is
39 why there is no cashout lockout. 39 why there is no cashout lockout.
40- Payouts are `floor(bet * multiplier)` in whole PLN, so anything under 1.10x 40- Payouts are `floor(bet * multiplier)` in whole Kr, so anything under 1.10x
41 on a 10 PLN bet returns 10. 41 on a 10 Kr bet returns 10.
42- When the crash time passes, the server pushes `CRASH_BUSTED` on the same 42- When the crash time passes, the server pushes `CRASH_BUSTED` on the same
43 connection without being asked. The client's event loop polls for keys and 43 connection without being asked. The client's event loop polls for keys and
44 drains the socket every 25ms. 44 drains the socket every 25ms.
@@ -58,7 +58,7 @@ Q forfeit an active round (bet is lost), or leave when idle
58 58
59- Six-deck shoe, shuffled fresh for every hand and discarded afterwards. 59- Six-deck shoe, shuffled fresh for every hand and discarded afterwards.
60- Dealer stands on all 17s (including soft 17). 60- Dealer stands on all 17s (including soft 17).
61- Blackjack pays 3:2, rounded down to whole PLN. 61- Blackjack pays 3:2, rounded down to whole Kr.
62- Hit, stand, and double; no split, insurance, or surrender. 62- Hit, stand, and double; no split, insurance, or surrender.
63- The bet is reserved when the hand starts. Leaving an active hand with Q, or 63- The bet is reserved when the hand starts. Leaving an active hand with Q, or
64 disconnecting mid-hand, forfeits the reserved bet; it is not refunded. A 64 disconnecting mid-hand, forfeits the reserved bet; it is not refunded. A
diff --git a/docs/SLOTS.md b/docs/SLOTS.md
index 1dbaba5..403f616 100644
--- a/docs/SLOTS.md
+++ b/docs/SLOTS.md
@@ -80,13 +80,13 @@ The board is ten lines:
80 80
81``` 81```
82=== SLOTS === 82=== SLOTS ===
83Bet: 10 PLN Balance: 1000 PLN 83Bet: 10 Kr Balance: 1000 Kr
84{ G } [ L ] [ S ] [ O ] [ G ] 84{ G } [ L ] [ S ] [ O ] [ G ]
85[ D ] { G } [ L ] [ S ] [ C ] 85[ D ] { G } [ L ] [ S ] [ C ]
86[ O ] [ O ] { G } [ L ] [ O ] 86[ O ] [ O ] { G } [ L ] [ O ]
87Pays bet x symbol x shape 87Pays bet x symbol x shape
88Symbol C1 L2 O3 G4 D6 S10 Shape 3:1 4:3 5:14 88Symbol C1 L2 O3 G4 D6 S10 Shape 3:1 4:3 5:14
89Won 40 PLN 89Won 40 Kr
90Winning lines: 4 (V) 90Winning lines: 4 (V)
91[+/-] Bet [SPACE] Spin [Q] Quit 91[+/-] Bet [SPACE] Spin [Q] Quit
92``` 92```
@@ -118,7 +118,7 @@ In the example above, line 4 (V) contains `G G G S G`:
118- reels 1-3 are `G`, so three `G` symbols pay; 118- reels 1-3 are `G`, so three `G` symbols pay;
119- reel 4 is `S`, which breaks the run, so the `G` on reel 5 does not count; 119- reel 4 is `S`, which breaks the run, so the `G` on reel 5 does not count;
120- `G` has symbol multiplier 4 and 3-of-a-kind has shape multiplier 1, so the 120- `G` has symbol multiplier 4 and 3-of-a-kind has shape multiplier 1, so the
121 win is `10 x 4 x 1 = 40 PLN`. 121 win is `10 x 4 x 1 = 40 Kr`.
122 122
123A diagonal of `L` symbols that is not one of the five paylines does not win, 123A diagonal of `L` symbols that is not one of the five paylines does not win,
124and a run must start on reel 1. That is why only the three `G` cells are 124and a run must start on reel 1. That is why only the three `G` cells are
diff --git a/src/client/core.rs b/src/client/core.rs
index 95fa050..54a6434 100644
--- a/src/client/core.rs
+++ b/src/client/core.rs
@@ -2,12 +2,12 @@ use crate::client::games::manager::GameManager;
2use crate::client::games::response::{ 2use crate::client::games::response::{
3 parse_blackjack_response, parse_crash_response, parse_roulette_response, parse_spin_response, 3 parse_blackjack_response, parse_crash_response, parse_roulette_response, parse_spin_response,
4}; 4};
5use crate::client::games::roulette::Direction;
5use crate::client::ui::{ 6use crate::client::ui::{
6 command_names, completion_lines, display_width, help_lines, input_cursor_column, parse_bet_arg, 7 command_names, completion_lines, display_width, help_lines, input_cursor_column, parse_bet_arg,
7 record_history, 8 record_history,
8}; 9};
9use crate::crash::{multiplier_at, CrashState}; 10use crate::crash::{CrashState, multiplier_at};
10use crate::client::games::roulette::Direction;
11use crate::roulette::Bets; 11use crate::roulette::Bets;
12use std::time::{Duration, Instant}; 12use std::time::{Duration, Instant};
13 13
@@ -240,7 +240,7 @@ impl ClientCore {
240 Ok(state) => { 240 Ok(state) => {
241 self.game.end_game(); 241 self.game.end_game();
242 let message = format!( 242 let message = format!(
243 "Game exited. Active hand forfeited ({} PLN lost).", 243 "Game exited. Active hand forfeited ({} Kr lost).",
244 state.bet 244 state.bet
245 ); 245 );
246 vec![Effect::ClearRegion, Effect::Print(vec![message])] 246 vec![Effect::ClearRegion, Effect::Print(vec![message])]
@@ -273,7 +273,7 @@ impl ClientCore {
273 Ok(state) => { 273 Ok(state) => {
274 self.crash_started = None; 274 self.crash_started = None;
275 self.game.end_game(); 275 self.game.end_game();
276 let message = format!("Game exited. Round forfeited ({} PLN lost).", state.bet); 276 let message = format!("Game exited. Round forfeited ({} Kr lost).", state.bet);
277 vec![Effect::ClearRegion, Effect::Print(vec![message])] 277 vec![Effect::ClearRegion, Effect::Print(vec![message])]
278 } 278 }
279 Err(_) if self.game.crash_status() != "running" => { 279 Err(_) if self.game.crash_status() != "running" => {
@@ -865,9 +865,9 @@ impl ClientCore {
865 Some(fingerprint) => { 865 Some(fingerprint) => {
866 effects.push(Effect::Print(vec![format!("Fingerprint: {fingerprint}")])) 866 effects.push(Effect::Print(vec![format!("Fingerprint: {fingerprint}")]))
867 } 867 }
868 None => effects.push(Effect::Print( 868 None => effects.push(Effect::Print(vec![
869 vec!["Fingerprint: (not trusted)".to_string()], 869 "Fingerprint: (not trusted)".to_string(),
870 )), 870 ])),
871 } 871 }
872 872
873 self.pending = Some(Pending::Info); 873 self.pending = Some(Pending::Info);
@@ -953,7 +953,7 @@ fn info_lines(reply: &str) -> Vec<String> {
953 vec![ 953 vec![
954 format!("User: {}", parts[1]), 954 format!("User: {}", parts[1]),
955 format!("Account: {}", parts[2]), 955 format!("Account: {}", parts[2]),
956 format!("Balance: {} PLN", parts[3]), 956 format!("Balance: {} Kr", parts[3]),
957 ] 957 ]
958 } else { 958 } else {
959 vec![reply.trim().to_string()] 959 vec![reply.trim().to_string()]
diff --git a/src/client/games/blackjack.rs b/src/client/games/blackjack.rs
index d5f45b3..8fa5693 100644
--- a/src/client/games/blackjack.rs
+++ b/src/client/games/blackjack.rs
@@ -1,5 +1,5 @@
1use super::traits::{Game, GameState}; 1use super::traits::{Game, GameState};
2use crate::blackjack::{card_label, BlackjackState}; 2use crate::blackjack::{BlackjackState, card_label};
3 3
4pub const BLACKJACK_BOARD_LINES: usize = 6; 4pub const BLACKJACK_BOARD_LINES: usize = 6;
5 5
@@ -33,8 +33,8 @@ impl BlackjackGame {
33 33
34 fn result_text(result: &str, winnings: u64) -> String { 34 fn result_text(result: &str, winnings: u64) -> String {
35 match result { 35 match result {
36 "blackjack" => format!("BLACKJACK! Won {winnings} PLN"), 36 "blackjack" => format!("BLACKJACK! Won {winnings} Kr"),
37 "win" => format!("Win! Won {winnings} PLN"), 37 "win" => format!("Win! Won {winnings} Kr"),
38 "push" => "Push. Bet returned.".to_string(), 38 "push" => "Push. Bet returned.".to_string(),
39 "lose" => "Dealer wins.".to_string(), 39 "lose" => "Dealer wins.".to_string(),
40 "bust" => "Bust.".to_string(), 40 "bust" => "Bust.".to_string(),
@@ -65,7 +65,7 @@ impl Game for BlackjackGame {
65 .map(|hand| hand.bet) 65 .map(|hand| hand.bet)
66 .unwrap_or(state.bet_amount); 66 .unwrap_or(state.bet_amount);
67 return format!( 67 return format!(
68 "=== BLACKJACK ===\r\nBalance: {balance} PLN Bet: {bet} PLN\r\n\r\n\r\nERROR: {error}\r\n[Q] Quit" 68 "=== BLACKJACK ===\r\nBalance: {balance} Kr Bet: {bet} Kr\r\n\r\n\r\nERROR: {error}\r\n[Q] Quit"
69 ); 69 );
70 } 70 }
71 71
@@ -120,7 +120,7 @@ impl Game for BlackjackGame {
120 }; 120 };
121 121
122 format!( 122 format!(
123 "=== BLACKJACK ===\r\nBalance: {} PLN Bet: {} PLN\r\nDealer: {} {}\r\nYou: {} {}\r\n{}\r\n{}", 123 "=== BLACKJACK ===\r\nBalance: {} Kr Bet: {} Kr\r\nDealer: {} {}\r\nYou: {} {}\r\n{}\r\n{}",
124 balance, bet, dealer, dealer_total, player, player_total, text, controls 124 balance, bet, dealer, dealer_total, player, player_total, text, controls
125 ) 125 )
126 } 126 }
diff --git a/src/client/games/crash.rs b/src/client/games/crash.rs
index 19733f2..383e807 100644
--- a/src/client/games/crash.rs
+++ b/src/client/games/crash.rs
@@ -19,7 +19,7 @@ impl CrashGame {
19 "starting" => "Starting...".to_string(), 19 "starting" => "Starting...".to_string(),
20 "running" => "Rising... cash out before it crashes.".to_string(), 20 "running" => "Rising... cash out before it crashes.".to_string(),
21 "cashed" => format!( 21 "cashed" => format!(
22 "Cashed out at {}x Won {} PLN", 22 "Cashed out at {}x Won {} Kr",
23 Self::value(state, "multiplier").unwrap_or("1.00"), 23 Self::value(state, "multiplier").unwrap_or("1.00"),
24 Self::value(state, "payout").unwrap_or("0") 24 Self::value(state, "payout").unwrap_or("0")
25 ), 25 ),
@@ -55,13 +55,13 @@ impl Game for CrashGame {
55 55
56 if let Some(error) = Self::value(state, "error") { 56 if let Some(error) = Self::value(state, "error") {
57 return format!( 57 return format!(
58 "=== CRASH ===\r\nBalance: {balance} PLN Bet: {} PLN\r\n\r\nERROR: {error}\r\n\r\n[SPACE] Play [+/=/-] Bet [Q] Quit", 58 "=== CRASH ===\r\nBalance: {balance} Kr Bet: {} Kr\r\n\r\nERROR: {error}\r\n\r\n[SPACE] Play [+/=/-] Bet [Q] Quit",
59 state.bet_amount 59 state.bet_amount
60 ); 60 );
61 } 61 }
62 62
63 format!( 63 format!(
64 "=== CRASH ===\r\nBalance: {} PLN Bet: {} PLN\r\nMultiplier: {}x\r\nPotential: {} PLN\r\n{}\r\n{}", 64 "=== CRASH ===\r\nBalance: {} Kr Bet: {} Kr\r\nMultiplier: {}x\r\nPotential: {} Kr\r\n{}\r\n{}",
65 balance, 65 balance,
66 state.bet_amount, 66 state.bet_amount,
67 multiplier, 67 multiplier,
diff --git a/src/client/games/roulette.rs b/src/client/games/roulette.rs
index 3cb1c58..96f8791 100644
--- a/src/client/games/roulette.rs
+++ b/src/client/games/roulette.rs
@@ -1,5 +1,5 @@
1use super::traits::{Game, GameState}; 1use super::traits::{Game, GameState};
2use crate::roulette::{is_even, is_odd, BetTarget, Bets, Outside}; 2use crate::roulette::{BetTarget, Bets, Outside, is_even, is_odd};
3 3
4pub const ROULETTE_BOARD_LINES: usize = 12; 4pub const ROULETTE_BOARD_LINES: usize = 12;
5 5
@@ -96,7 +96,9 @@ impl Cursor {
96 96
97 /// Numbers the cursor would cover, for highlighting the felt. 97 /// Numbers the cursor would cover, for highlighting the felt.
98 pub fn numbers(&self) -> Vec<u8> { 98 pub fn numbers(&self) -> Vec<u8> {
99 self.target().map(|target| target.numbers()).unwrap_or_default() 99 self.target()
100 .map(|target| target.numbers())
101 .unwrap_or_default()
100 } 102 }
101 103
102 pub fn moved(&self, direction: Direction) -> Cursor { 104 pub fn moved(&self, direction: Direction) -> Cursor {
@@ -130,9 +132,7 @@ impl Cursor {
130 (Cursor::Column(index), Direction::Up) => Cursor::Dozen(index), 132 (Cursor::Column(index), Direction::Up) => Cursor::Dozen(index),
131 (Cursor::Column(index), Direction::Down) => Cursor::Outside(index * 2), 133 (Cursor::Column(index), Direction::Down) => Cursor::Outside(index * 2),
132 134
133 (Cursor::Outside(index), Direction::Left) => { 135 (Cursor::Outside(index), Direction::Left) => Cursor::Outside(index.saturating_sub(1)),
134 Cursor::Outside(index.saturating_sub(1))
135 }
136 (Cursor::Outside(index), Direction::Right) => { 136 (Cursor::Outside(index), Direction::Right) => {
137 Cursor::Outside((index + 1).min(OUTSIDE_ORDER.len() - 1)) 137 Cursor::Outside((index + 1).min(OUTSIDE_ORDER.len() - 1))
138 } 138 }
@@ -249,7 +249,11 @@ impl RouletteGame {
249 .iter() 249 .iter()
250 .enumerate() 250 .enumerate()
251 .map(|(index, kind)| { 251 .map(|(index, kind)| {
252 Self::wide_cell(kind.label(), OUTSIDE_WIDTH, cursor == Cursor::Outside(index)) 252 Self::wide_cell(
253 kind.label(),
254 OUTSIDE_WIDTH,
255 cursor == Cursor::Outside(index),
256 )
253 }) 257 })
254 .collect() 258 .collect()
255 } 259 }
@@ -268,7 +272,7 @@ impl RouletteGame {
268 if list.chars().count() > 40 { 272 if list.chars().count() > 40 {
269 list = list.chars().take(39).collect::<String>() + "…"; 273 list = list.chars().take(39).collect::<String>() + "…";
270 } 274 }
271 format!("Bets: {list} Total: {} PLN", bets.total()) 275 format!("Bets: {list} Total: {} Kr", bets.total())
272 } 276 }
273 277
274 fn status_line(state: &GameState) -> String { 278 fn status_line(state: &GameState) -> String {
@@ -309,7 +313,7 @@ impl RouletteGame {
309 format!("No win at {number} {color}{parity}") 313 format!("No win at {number} {color}{parity}")
310 } else { 314 } else {
311 let profit = winnings.parse::<u64>().unwrap_or(0).saturating_sub(stake); 315 let profit = winnings.parse::<u64>().unwrap_or(0).saturating_sub(stake);
312 format!("Won {winnings} PLN at {number} {color}{parity} (+{profit})") 316 format!("Won {winnings} Kr at {number} {color}{parity} (+{profit})")
313 } 317 }
314 } 318 }
315 Some("spinning") => "Spinning...".to_string(), 319 Some("spinning") => "Spinning...".to_string(),
@@ -332,7 +336,7 @@ impl Game for RouletteGame {
332 336
333 let lines = [ 337 let lines = [
334 "=== ROULETTE ===".to_string(), 338 "=== ROULETTE ===".to_string(),
335 format!("Balance: {balance} PLN Chip: {chip} PLN"), 339 format!("Balance: {balance} Kr Chip: {chip} Kr"),
336 Self::zero_cell(cursor), 340 Self::zero_cell(cursor),
337 Self::number_row(0, &covered), 341 Self::number_row(0, &covered),
338 Self::number_row(1, &covered), 342 Self::number_row(1, &covered),
@@ -342,8 +346,7 @@ impl Game for RouletteGame {
342 Self::outside_row(cursor), 346 Self::outside_row(cursor),
343 Self::bets_line(&bets), 347 Self::bets_line(&bets),
344 Self::status_line(state), 348 Self::status_line(state),
345 "[ARROWS] Move [ENTER] Bet [C]lear [+/-] Chip [SPACE] Spin [Q] Quit" 349 "[ARROWS] Move [ENTER] Bet [C]lear [+/-] Chip [SPACE] Spin [Q] Quit".to_string(),
346 .to_string(),
347 ]; 350 ];
348 lines.join("\r\n") 351 lines.join("\r\n")
349 } 352 }
diff --git a/src/client/games/slots.rs b/src/client/games/slots.rs
index 2ec354e..5bffc4c 100644
--- a/src/client/games/slots.rs
+++ b/src/client/games/slots.rs
@@ -1,5 +1,5 @@
1use super::traits::{Game, GamePhase, GameState}; 1use super::traits::{Game, GamePhase, GameState};
2use crate::slots::{paytable_lines, LINE_NAMES, REELS, ROWS}; 2use crate::slots::{LINE_NAMES, REELS, ROWS, paytable_lines};
3 3
4/// Height of the game board. It must never depend on the spin result. 4/// Height of the game board. It must never depend on the spin result.
5pub const SLOT_BOARD_LINES: usize = 10; 5pub const SLOT_BOARD_LINES: usize = 10;
@@ -65,9 +65,9 @@ impl SlotsGame {
65 .unwrap_or("0"); 65 .unwrap_or("0");
66 if winnings != "0" { 66 if winnings != "0" {
67 return match state.data.get("win_type").map(String::as_str) { 67 return match state.data.get("win_type").map(String::as_str) {
68 Some("mega") => format!("MEGA WIN! {winnings} PLN"), 68 Some("mega") => format!("MEGA WIN! {winnings} Kr"),
69 Some("big") => format!("BIG WIN! {winnings} PLN"), 69 Some("big") => format!("BIG WIN! {winnings} Kr"),
70 _ => format!("Won {winnings} PLN"), 70 _ => format!("Won {winnings} Kr"),
71 }; 71 };
72 } 72 }
73 return "No win.".to_string(); 73 return "No win.".to_string();
@@ -115,7 +115,7 @@ impl Game for SlotsGame {
115 let paytable = paytable_lines(); 115 let paytable = paytable_lines();
116 116
117 format!( 117 format!(
118 "=== SLOTS ===\r\nBet: {} PLN Balance: {} PLN\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n[+/-] Bet [SPACE] Spin [Q] Quit", 118 "=== SLOTS ===\r\nBet: {} Kr Balance: {} Kr\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n{}\r\n[+/-] Bet [SPACE] Spin [Q] Quit",
119 state.bet_amount, 119 state.bet_amount,
120 balance, 120 balance,
121 grid[0], 121 grid[0],
diff --git a/src/client/ui.rs b/src/client/ui.rs
index ca001c2..e7f510b 100644
--- a/src/client/ui.rs
+++ b/src/client/ui.rs
@@ -145,7 +145,7 @@ pub fn parse_bet_arg(raw: &str) -> Result<u64, String> {
145 .parse::<u64>() 145 .parse::<u64>()
146 .map_err(|_| format!("Invalid bet: {raw}"))?; 146 .map_err(|_| format!("Invalid bet: {raw}"))?;
147 if bet < 10 { 147 if bet < 10 {
148 return Err("Bet must be at least 10 PLN".to_string()); 148 return Err("Bet must be at least 10 Kr".to_string());
149 } 149 }
150 Ok(bet) 150 Ok(bet)
151} 151}
@@ -189,11 +189,7 @@ fn char_width(c: char) -> usize {
189 if is_zero_width(cp) || is_combining(cp) { 189 if is_zero_width(cp) || is_combining(cp) {
190 return 0; 190 return 0;
191 } 191 }
192 if is_wide(cp) { 192 if is_wide(cp) { 2 } else { 1 }
193 2
194 } else {
195 1
196 }
197} 193}
198 194
199fn is_zero_width(cp: u32) -> bool { 195fn is_zero_width(cp: u32) -> bool {
diff --git a/src/crash.rs b/src/crash.rs
index 2936b99..9d6e98c 100644
--- a/src/crash.rs
+++ b/src/crash.rs
@@ -47,7 +47,7 @@ pub fn crash_point_from_uniform(u: f64) -> f64 {
47 ((1.0 - HOUSE_EDGE) / (1.0 - u)).clamp(1.0, MAX_MULTIPLIER) 47 ((1.0 - HOUSE_EDGE) / (1.0 - u)).clamp(1.0, MAX_MULTIPLIER)
48} 48}
49 49
50/// Whole-PLN cashout value. Balances are integers, so the payout is floored. 50/// Whole-Kr cashout value. Balances are integers, so the payout is floored.
51pub fn payout(bet: u64, multiplier: f64) -> u64 { 51pub fn payout(bet: u64, multiplier: f64) -> u64 {
52 if bet == 0 || !multiplier.is_finite() || multiplier <= 0.0 { 52 if bet == 0 || !multiplier.is_finite() || multiplier <= 0.0 {
53 return 0; 53 return 0;
diff --git a/tests/client_core.rs b/tests/client_core.rs
index a7c5716..bb8772e 100644
--- a/tests/client_core.rs
+++ b/tests/client_core.rs
@@ -173,7 +173,7 @@ fn play_shows_a_ten_line_board_and_fetches_the_balance() {
173 h.line("play slots 50"); 173 h.line("play slots 50");
174 174
175 assert_eq!(h.board().len(), 10); 175 assert_eq!(h.board().len(), 10);
176 assert!(h.board()[1].contains("Bet: 50 PLN")); 176 assert!(h.board()[1].contains("Bet: 50 Kr"));
177 assert!(!h.board()[1].contains("Lines:")); 177 assert!(!h.board()[1].contains("Lines:"));
178 assert!(h.board()[5].contains("Pays bet x symbol x shape")); 178 assert!(h.board()[5].contains("Pays bet x symbol x shape"));
179 assert!(h.board()[6].contains("Symbol C1 L2 O3 G4 D6 S10")); 179 assert!(h.board()[6].contains("Symbol C1 L2 O3 G4 D6 S10"));
@@ -182,7 +182,7 @@ fn play_shows_a_ten_line_board_and_fetches_the_balance() {
182 182
183 h.reply("INFO alice 1 1000"); 183 h.reply("INFO alice 1 1000");
184 assert_eq!(h.board().len(), 10); 184 assert_eq!(h.board().len(), 10);
185 assert!(h.board()[1].contains("Balance: 1000 PLN")); 185 assert!(h.board()[1].contains("Balance: 1000 Kr"));
186} 186}
187 187
188#[test] 188#[test]
@@ -200,13 +200,14 @@ fn spin_replaces_the_board_and_keeps_the_controls_line() {
200 200
201 h.reply("GAME_RESULT session-1 50 50 1000 CCCCLLCLODGSDGS win 1 0,3,6"); 201 h.reply("GAME_RESULT session-1 50 50 1000 CCCCLLCLODGSDGS win 1 0,3,6");
202 assert_eq!(h.board().len(), 10); 202 assert_eq!(h.board().len(), 10);
203 assert!(h 203 assert!(
204 .board() 204 h.board()
205 .get(2) 205 .get(2)
206 .is_some_and(|line| line.contains("{ C } { C } { C } [ D ] [ D ]"))); 206 .is_some_and(|line| line.contains("{ C } { C } { C } [ D ] [ D ]"))
207 assert!(h.board()[7].contains("Won 50 PLN")); 207 );
208 assert!(h.board()[7].contains("Won 50 Kr"));
208 assert!(h.board()[8].contains("Winning lines: 1 (top)")); 209 assert!(h.board()[8].contains("Winning lines: 1 (top)"));
209 assert!(h.board()[1].contains("Balance: 1000 PLN")); 210 assert!(h.board()[1].contains("Balance: 1000 Kr"));
210 assert!(h.board()[9].contains("[Q] Quit")); 211 assert!(h.board()[9].contains("[Q] Quit"));
211 assert_eq!(h.screen.lines.len(), lines_before_spin); 212 assert_eq!(h.screen.lines.len(), lines_before_spin);
212} 213}
@@ -219,7 +220,7 @@ fn equals_increases_the_bet_like_plus() {
219 h.key(Key::Char('=')); 220 h.key(Key::Char('='));
220 221
221 assert_eq!(h.board().len(), 10); 222 assert_eq!(h.board().len(), 10);
222 assert!(h.board()[1].contains("Bet: 20 PLN")); 223 assert!(h.board()[1].contains("Bet: 20 Kr"));
223} 224}
224 225
225#[test] 226#[test]
@@ -233,11 +234,12 @@ fn changing_the_bet_after_a_spin_does_not_leave_stale_lines() {
233 h.key(Key::Char('+')); 234 h.key(Key::Char('+'));
234 235
235 assert_eq!(h.board().len(), 10); 236 assert_eq!(h.board().len(), 10);
236 assert!(h.board()[1].contains("Bet: 60 PLN")); 237 assert!(h.board()[1].contains("Bet: 60 Kr"));
237 assert!(h 238 assert!(
238 .board() 239 h.board()
239 .get(2) 240 .get(2)
240 .is_some_and(|line| line.contains("{ C } { C } { C } [ D ] [ D ]"))); 241 .is_some_and(|line| line.contains("{ C } { C } { C } [ D ] [ D ]"))
242 );
241 assert_eq!(h.screen.lines.len(), lines_before_bet); 243 assert_eq!(h.screen.lines.len(), lines_before_bet);
242 assert_eq!( 244 assert_eq!(
243 h.screen 245 h.screen
@@ -272,9 +274,11 @@ fn a_mega_win_shows_all_winning_lines() {
272 h.reply(&format!("GAME_RESULT session-1 700 10 1690 {ALL_C} mega 1,2,3,4,5 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14")); 274 h.reply(&format!("GAME_RESULT session-1 700 10 1690 {ALL_C} mega 1,2,3,4,5 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14"));
273 275
274 assert_eq!(h.board().len(), 10); 276 assert_eq!(h.board().len(), 10);
275 assert!(h.board()[7].contains("MEGA WIN! 700 PLN")); 277 assert!(h.board()[7].contains("MEGA WIN! 700 Kr"));
276 assert!(h.board()[8] 278 assert!(
277 .contains("Winning lines: 1 (top), 2 (middle), 3 (bottom), 4 (V), 5 (inverted V)")); 279 h.board()[8]
280 .contains("Winning lines: 1 (top), 2 (middle), 3 (bottom), 4 (V), 5 (inverted V)")
281 );
278 assert_eq!(h.board()[2].matches("{ C }").count(), 5); 282 assert_eq!(h.board()[2].matches("{ C }").count(), 5);
279} 283}
280 284
@@ -371,11 +375,12 @@ fn tab_completion_list_wraps() {
371 375
372 h.key(Key::Tab); 376 h.key(Key::Tab);
373 377
374 assert!(h 378 assert!(
375 .screen 379 h.screen
376 .lines 380 .lines
377 .iter() 381 .iter()
378 .any(|line| line == "Possible completions:")); 382 .any(|line| line == "Possible completions:")
383 );
379 for line in h 384 for line in h
380 .screen 385 .screen
381 .lines 386 .lines
@@ -398,7 +403,7 @@ fn play_without_a_bet_uses_the_default() {
398 h.line("play slots"); 403 h.line("play slots");
399 404
400 assert_eq!(h.board().len(), 10); 405 assert_eq!(h.board().len(), 10);
401 assert!(h.board()[1].contains("Bet: 10 PLN")); 406 assert!(h.board()[1].contains("Bet: 10 Kr"));
402} 407}
403 408
404#[test] 409#[test]
@@ -407,11 +412,12 @@ fn an_invalid_bet_does_not_start_a_game() {
407 h.line("play slots 5"); 412 h.line("play slots 5");
408 413
409 assert!(h.board().is_empty()); 414 assert!(h.board().is_empty());
410 assert!(h 415 assert!(
411 .screen 416 h.screen
412 .lines 417 .lines
413 .iter() 418 .iter()
414 .any(|line| line.contains("Bet must be at least 10 PLN"))); 419 .any(|line| line.contains("Bet must be at least 10 Kr"))
420 );
415} 421}
416 422
417#[test] 423#[test]
@@ -438,20 +444,22 @@ fn play_requires_a_connection_and_a_login() {
438 let mut h = Harness::new(); 444 let mut h = Harness::new();
439 h.core.set_connected(false); 445 h.core.set_connected(false);
440 h.line("play slots"); 446 h.line("play slots");
441 assert!(h 447 assert!(
442 .screen 448 h.screen
443 .lines 449 .lines
444 .iter() 450 .iter()
445 .any(|line| line.contains("Not connected"))); 451 .any(|line| line.contains("Not connected"))
452 );
446 453
447 let mut h = Harness::new(); 454 let mut h = Harness::new();
448 h.core.set_logged_in(None); 455 h.core.set_logged_in(None);
449 h.line("play slots"); 456 h.line("play slots");
450 assert!(h 457 assert!(
451 .screen 458 h.screen
452 .lines 459 .lines
453 .iter() 460 .iter()
454 .any(|line| line.contains("Not logged in"))); 461 .any(|line| line.contains("Not logged in"))
462 );
455} 463}
456 464
457#[test] 465#[test]
@@ -489,26 +497,24 @@ fn info_shows_the_host_and_the_server_fields() {
489 ); 497 );
490 498
491 h.line("info"); 499 h.line("info");
492 assert!(h 500 assert!(
493 .screen 501 h.screen
494 .lines 502 .lines
495 .iter() 503 .iter()
496 .any(|line| line.contains("Connected to: 127.0.0.1:11525"))); 504 .any(|line| line.contains("Connected to: 127.0.0.1:11525"))
497 assert!(h 505 );
498 .screen 506 assert!(
499 .lines 507 h.screen
500 .iter() 508 .lines
501 .any(|line| line.contains("Fingerprint: deadbeef"))); 509 .iter()
510 .any(|line| line.contains("Fingerprint: deadbeef"))
511 );
502 512
503 h.reply("INFO alice 42 1234"); 513 h.reply("INFO alice 42 1234");
504 514
505 assert!(h.screen.lines.iter().any(|line| line == "User: alice")); 515 assert!(h.screen.lines.iter().any(|line| line == "User: alice"));
506 assert!(h.screen.lines.iter().any(|line| line == "Account: 42")); 516 assert!(h.screen.lines.iter().any(|line| line == "Account: 42"));
507 assert!(h 517 assert!(h.screen.lines.iter().any(|line| line == "Balance: 1234 Kr"));
508 .screen
509 .lines
510 .iter()
511 .any(|line| line == "Balance: 1234 PLN"));
512} 518}
513 519
514#[test] 520#[test]
@@ -518,11 +524,12 @@ fn info_without_a_connection_does_not_send() {
518 524
519 h.line("info"); 525 h.line("info");
520 526
521 assert!(h 527 assert!(
522 .screen 528 h.screen
523 .lines 529 .lines
524 .iter() 530 .iter()
525 .any(|line| line.contains("Not connected to any host"))); 531 .any(|line| line.contains("Not connected to any host"))
532 );
526 assert!(h.sent.is_empty()); 533 assert!(h.sent.is_empty());
527} 534}
528 535
@@ -554,11 +561,12 @@ fn disconnect_clears_connection_state() {
554 assert!(h.screen.lines.iter().any(|line| line == "Disconnected")); 561 assert!(h.screen.lines.iter().any(|line| line == "Disconnected"));
555 562
556 h.line("play slots"); 563 h.line("play slots");
557 assert!(h 564 assert!(
558 .screen 565 h.screen
559 .lines 566 .lines
560 .iter() 567 .iter()
561 .any(|line| line.contains("Not connected"))); 568 .any(|line| line.contains("Not connected"))
569 );
562} 570}
563 571
564#[test] 572#[test]
@@ -597,8 +605,8 @@ fn roulette_cursor_places_bets_and_spins() {
597 605
598 assert_eq!(h.board().len(), 12); 606 assert_eq!(h.board().len(), 12);
599 assert_eq!(h.board()[0], "=== ROULETTE ==="); 607 assert_eq!(h.board()[0], "=== ROULETTE ===");
600 assert!(h.board()[1].contains("Balance: 1000 PLN")); 608 assert!(h.board()[1].contains("Balance: 1000 Kr"));
601 assert!(h.board()[1].contains("Chip: 10 PLN")); 609 assert!(h.board()[1].contains("Chip: 10 Kr"));
602 assert!(h.board()[9].contains("Bets: (none)")); 610 assert!(h.board()[9].contains("Bets: (none)"));
603 // The cursor starts on number 1, the bottom-left corner. 611 // The cursor starts on number 1, the bottom-left corner.
604 assert!(h.board()[5].contains("[ 1 ]")); 612 assert!(h.board()[5].contains("[ 1 ]"));
@@ -611,14 +619,14 @@ fn roulette_cursor_places_bets_and_spins() {
611 619
612 h.key(Key::Enter); 620 h.key(Key::Enter);
613 assert!(h.board()[9].contains("2/3 10")); 621 assert!(h.board()[9].contains("2/3 10"));
614 assert!(h.board()[9].contains("Total: 10 PLN")); 622 assert!(h.board()[9].contains("Total: 10 Kr"));
615 623
616 // Back down to number 1 and place a straight. 624 // Back down to number 1 and place a straight.
617 h.key(Key::Down); 625 h.key(Key::Down);
618 h.key(Key::Down); 626 h.key(Key::Down);
619 h.key(Key::Enter); 627 h.key(Key::Enter);
620 assert!(h.board()[9].contains("1 10")); 628 assert!(h.board()[9].contains("1 10"));
621 assert!(h.board()[9].contains("Total: 20 PLN")); 629 assert!(h.board()[9].contains("Total: 20 Kr"));
622 630
623 h.key(Key::Char(' ')); 631 h.key(Key::Char(' '));
624 let request = h 632 let request = h
@@ -637,8 +645,8 @@ fn roulette_cursor_places_bets_and_spins() {
637 )); 645 ));
638 646
639 assert_eq!(h.board().len(), 12); 647 assert_eq!(h.board().len(), 12);
640 assert!(h.board()[1].contains("Balance: 1160 PLN")); 648 assert!(h.board()[1].contains("Balance: 1160 Kr"));
641 assert!(h.board()[10].contains("Won 180 PLN at 3 RED ODD")); 649 assert!(h.board()[10].contains("Won 180 Kr at 3 RED ODD"));
642 assert!(h.board()[10].contains("+160")); 650 assert!(h.board()[10].contains("+160"));
643} 651}
644 652
@@ -672,16 +680,17 @@ fn roulette_rejects_an_empty_spin_and_accepts_the_equals_chip_key() {
672 680
673 h.key(Key::Char(' ')); 681 h.key(Key::Char(' '));
674 assert!(h.board()[10].contains("ERROR: Place at least one bet")); 682 assert!(h.board()[10].contains("ERROR: Place at least one bet"));
675 assert!(!h 683 assert!(
676 .sent 684 !h.sent
677 .iter() 685 .iter()
678 .any(|line| line.starts_with("PLAY_START roulette"))); 686 .any(|line| line.starts_with("PLAY_START roulette"))
687 );
679 688
680 h.key(Key::Char('=')); 689 h.key(Key::Char('='));
681 assert!(h.board()[1].contains("Chip: 20 PLN")); 690 assert!(h.board()[1].contains("Chip: 20 Kr"));
682 h.key(Key::Enter); 691 h.key(Key::Enter);
683 assert!(h.board()[9].contains("1 20")); 692 assert!(h.board()[9].contains("1 20"));
684 assert!(h.board()[9].contains("Total: 20 PLN")); 693 assert!(h.board()[9].contains("Total: 20 Kr"));
685} 694}
686 695
687fn bj_state( 696fn bj_state(
@@ -776,7 +785,7 @@ fn blackjack_deals_hits_and_settles() {
776 serde_json::to_string(&done).unwrap() 785 serde_json::to_string(&done).unwrap()
777 )); 786 ));
778 787
779 assert!(h.board()[4].contains("Win! Won 20 PLN")); 788 assert!(h.board()[4].contains("Win! Won 20 Kr"));
780 assert!(h.board()[5].contains("[SPACE] New hand")); 789 assert!(h.board()[5].contains("[SPACE] New hand"));
781 790
782 h.key(Key::Char('q')); 791 h.key(Key::Char('q'));
@@ -835,11 +844,12 @@ fn blackjack_forfeit_is_sent_when_leaving_an_active_hand() {
835 )); 844 ));
836 845
837 assert!(h.board().is_empty()); 846 assert!(h.board().is_empty());
838 assert!(h 847 assert!(
839 .screen 848 h.screen
840 .lines 849 .lines
841 .iter() 850 .iter()
842 .any(|line| line.contains("forfeited (10 PLN lost)"))); 851 .any(|line| line.contains("forfeited (10 Kr lost)"))
852 );
843} 853}
844 854
845#[test] 855#[test]
@@ -848,7 +858,7 @@ fn blackjack_ready_board_can_change_the_next_bet() {
848 h.enter_blackjack(); 858 h.enter_blackjack();
849 859
850 h.key(Key::Char('=')); 860 h.key(Key::Char('='));
851 assert!(h.board()[1].contains("Bet: 20 PLN")); 861 assert!(h.board()[1].contains("Bet: 20 Kr"));
852 862
853 h.key(Key::Char(' ')); 863 h.key(Key::Char(' '));
854 assert!(h.sent.iter().any(|line| line == "PLAY_START blackjack 20")); 864 assert!(h.sent.iter().any(|line| line == "PLAY_START blackjack 20"));
@@ -917,14 +927,14 @@ fn crash_starts_a_round_and_draws_the_multiplier_locally() {
917 927
918 assert_eq!(h.board().len(), 6); 928 assert_eq!(h.board().len(), 6);
919 assert!(h.board()[2].contains("Multiplier: 1.00x")); 929 assert!(h.board()[2].contains("Multiplier: 1.00x"));
920 assert!(h.board()[3].contains("Potential: 10 PLN")); 930 assert!(h.board()[3].contains("Potential: 10 Kr"));
921 assert!(h.board()[4].contains("Rising")); 931 assert!(h.board()[4].contains("Rising"));
922 assert!(h.board()[5].contains("[C] Cash out")); 932 assert!(h.board()[5].contains("[C] Cash out"));
923 933
924 // The multiplier is drawn from the local clock, with no server round trip. 934 // The multiplier is drawn from the local clock, with no server round trip.
925 h.tick(Duration::from_secs(5)); 935 h.tick(Duration::from_secs(5));
926 assert!(h.board()[2].contains("Multiplier: 2.00x")); 936 assert!(h.board()[2].contains("Multiplier: 2.00x"));
927 assert!(h.board()[3].contains("Potential: 20 PLN")); 937 assert!(h.board()[3].contains("Potential: 20 Kr"));
928 938
929 h.key(Key::Char('c')); 939 h.key(Key::Char('c'));
930 assert_eq!(h.sent.last().map(String::as_str), Some("CRASH_CASHOUT cr1")); 940 assert_eq!(h.sent.last().map(String::as_str), Some("CRASH_CASHOUT cr1"));
@@ -932,9 +942,9 @@ fn crash_starts_a_round_and_draws_the_multiplier_locally() {
932 let cashed = crash_state("cashed", 2.0, Some(3.0), 20, 1010); 942 let cashed = crash_state("cashed", 2.0, Some(3.0), 20, 1010);
933 h.reply(&crash_line("CRASH_STATE", &cashed)); 943 h.reply(&crash_line("CRASH_STATE", &cashed));
934 944
935 assert!(h.board()[4].contains("Cashed out at 2.00x Won 20 PLN")); 945 assert!(h.board()[4].contains("Cashed out at 2.00x Won 20 Kr"));
936 assert!(h.board()[5].contains("[SPACE] Play")); 946 assert!(h.board()[5].contains("[SPACE] Play"));
937 assert_eq!(h.board()[1], "Balance: 1010 PLN Bet: 10 PLN"); 947 assert_eq!(h.board()[1], "Balance: 1010 Kr Bet: 10 Kr");
938} 948}
939 949
940#[test] 950#[test]
@@ -977,11 +987,12 @@ fn crash_q_forfeits_an_active_round() {
977 h.reply(&crash_line("CRASH_STATE", &forfeited)); 987 h.reply(&crash_line("CRASH_STATE", &forfeited));
978 988
979 assert!(h.board().is_empty()); 989 assert!(h.board().is_empty());
980 assert!(h 990 assert!(
981 .screen 991 h.screen
982 .lines 992 .lines
983 .iter() 993 .iter()
984 .any(|line| line.contains("Round forfeited (10 PLN lost)"))); 994 .any(|line| line.contains("Round forfeited (10 Kr lost)"))
995 );
985} 996}
986 997
987#[test] 998#[test]
@@ -990,8 +1001,8 @@ fn crash_ready_board_changes_the_next_bet() {
990 h.enter_crash(); 1001 h.enter_crash();
991 1002
992 h.key(Key::Char('=')); 1003 h.key(Key::Char('='));
993 assert!(h.board()[1].contains("Bet: 20 PLN")); 1004 assert!(h.board()[1].contains("Bet: 20 Kr"));
994 assert!(h.board()[3].contains("Potential: 20 PLN")); 1005 assert!(h.board()[3].contains("Potential: 20 Kr"));
995 1006
996 h.key(Key::Char(' ')); 1007 h.key(Key::Char(' '));
997 assert!(h.sent.iter().any(|line| line == "PLAY_START crash 20")); 1008 assert!(h.sent.iter().any(|line| line == "PLAY_START crash 20"));
@@ -1058,7 +1069,7 @@ fn a_forfeit_that_arrives_after_the_bust_still_leaves_cleanly() {
1058 1069
1059 assert!(h.board().is_empty()); 1070 assert!(h.board().is_empty());
1060 assert!(h.screen.lines.iter().any(|line| line == "Game exited.")); 1071 assert!(h.screen.lines.iter().any(|line| line == "Game exited."));
1061 assert!(!h.screen.lines.iter().any(|line| line.contains("PLN lost"))); 1072 assert!(!h.screen.lines.iter().any(|line| line.contains("Kr lost")));
1062} 1073}
1063 1074
1064#[test] 1075#[test]
diff --git a/tests/crash.rs b/tests/crash.rs
index 49eaf89..c985ebe 100644
--- a/tests/crash.rs
+++ b/tests/crash.rs
@@ -2,8 +2,8 @@
2 2
3use std::time::Duration; 3use std::time::Duration;
4use zagan_bet::crash::{ 4use zagan_bet::crash::{
5 crash_point_from_uniform, multiplier_at, multiplier_at_secs, payout, time_for_multiplier, 5 HOUSE_EDGE, MAX_MULTIPLIER, crash_point_from_uniform, multiplier_at, multiplier_at_secs,
6 HOUSE_EDGE, MAX_MULTIPLIER, 6 payout, time_for_multiplier,
7}; 7};
8 8
9fn close(a: f64, b: f64, tolerance: f64) { 9fn close(a: f64, b: f64, tolerance: f64) {
@@ -38,9 +38,9 @@ fn the_time_for_a_multiplier_is_the_inverse_of_the_curve() {
38} 38}
39 39
40#[test] 40#[test]
41fn cashout_value_is_floored_to_whole_pln() { 41fn cashout_value_is_floored_to_whole_Kr() {
42 assert_eq!(payout(10, 1.0), 10); 42 assert_eq!(payout(10, 1.0), 10);
43 assert_eq!(payout(10, 1.05), 10, "half a PLN is not paid out"); 43 assert_eq!(payout(10, 1.05), 10, "half a Kr is not paid out");
44 assert_eq!(payout(10, 1.10), 11); 44 assert_eq!(payout(10, 1.10), 11);
45 assert_eq!(payout(10, 2.47), 24); 45 assert_eq!(payout(10, 2.47), 24);
46 assert_eq!(payout(7, 3.0), 21); 46 assert_eq!(payout(7, 3.0), 21);