summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-29 17:23:23 +0100
committersalmonik <salmonik@tuta.io>2026-10-29 17:23:23 +0100
commitffa1de686ce4ec6e3548047a2c5a158b99dc8af3 (patch)
treea125c9b40ee0a59674460c40802cd69430787eec
parentv2.1.0: add European roulette with split bets and wheel animation (diff)
downloadzagan-bet-ffa1de686ce4ec6e3548047a2c5a158b99dc8af3.tar.gz
zagan-bet-ffa1de686ce4ec6e3548047a2c5a158b99dc8af3.zip
v2.1.1: color-code roulette numbers red and green
-rw-r--r--Cargo.toml2
-rw-r--r--docs/GAMES.md3
-rw-r--r--docs/TESTING.md4
-rw-r--r--src/bin/client/repl.rs76
4 files changed, 81 insertions, 4 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 9432fcd..08a4a3b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
1[package] 1[package]
2name = "zagan-bet" 2name = "zagan-bet"
3version = "2.1.0" 3version = "2.1.1"
4edition = "2024" 4edition = "2024"
5 5
6[dependencies] 6[dependencies]
diff --git a/docs/GAMES.md b/docs/GAMES.md
index 52ab1bf..12b0e7c 100644
--- a/docs/GAMES.md
+++ b/docs/GAMES.md
@@ -25,6 +25,9 @@ redesign the loop first.
25- Bets can be split across all of those; every winning bet is added together. 25- Bets can be split across all of those; every winning bet is added together.
26- Straight pays 35:1 (36x returned), outside bets pay 1:1 (2x returned). 26- Straight pays 35:1 (36x returned), outside bets pay 1:1 (2x returned).
27- 0 loses all outside bets. RTP is 36/37 = 97.30%. 27- 0 loses all outside bets. RTP is 36/37 = 97.30%.
28- Numbers are color-coded in the TUI: red numbers are red, 0 is green, and
29 black numbers stay at the default terminal color. Bet amounts and balances
30 are not colored.
28 31
29Client controls inside roulette: 32Client controls inside roulette:
30 33
diff --git a/docs/TESTING.md b/docs/TESTING.md
index 2dcb01f..d944b42 100644
--- a/docs/TESTING.md
+++ b/docs/TESTING.md
@@ -74,7 +74,9 @@ Use an 80-column terminal because several output lines are width-sensitive.
744. Check a split result: 744. Check a split result:
75 - `17 black odd`: black+odd pay, red+even lose, straight 17 pays 36x. 75 - `17 black odd`: black+odd pay, red+even lose, straight 17 pays 36x.
76 - `0`: all outside bets lose; a straight 0 pays 36x. 76 - `0`: all outside bets lose; a straight 0 pays 36x.
775. The result board shows the winning number/color/parity, the payout, and the 775. Numbers are color-coded: red numbers red, 0 green, black default. Check the
78 bets line, the rolling strip, and the winning number.
796. The result board shows the winning number/color/parity, the payout, and the
78 new balance. The next spin reuses the placed bets. 80 new balance. The next spin reuses the placed bets.
79 81
80## Manual server checks 82## Manual server checks
diff --git a/src/bin/client/repl.rs b/src/bin/client/repl.rs
index e028cc2..f85af47 100644
--- a/src/bin/client/repl.rs
+++ b/src/bin/client/repl.rs
@@ -20,7 +20,7 @@ use zagan_bet::client::ui::{
20 visual_line_count, 20 visual_line_count,
21}; 21};
22use zagan_bet::protocol::io_failure_message; 22use zagan_bet::protocol::io_failure_message;
23use zagan_bet::roulette::WHEEL; 23use zagan_bet::roulette::{color as roulette_color, WHEEL};
24 24
25use crate::connection::TlsStream; 25use crate::connection::TlsStream;
26 26
@@ -247,11 +247,22 @@ impl ClientState {
247 execute!(stdout(), MoveUp(lines_to_move_up(self.game_lines))).unwrap(); 247 execute!(stdout(), MoveUp(lines_to_move_up(self.game_lines))).unwrap();
248 } 248 }
249 execute!(stdout(), MoveToColumn(0), Clear(ClearType::FromCursorDown)).unwrap(); 249 execute!(stdout(), MoveToColumn(0), Clear(ClearType::FromCursorDown)).unwrap();
250 let roulette = lines
251 .first()
252 .is_some_and(|first| first.contains("ROULETTE"));
250 let rendered: Vec<String> = lines 253 let rendered: Vec<String> = lines
251 .iter() 254 .iter()
252 .map(|line| { 255 .map(|line| {
253 if line.starts_with("===") { 256 if line.starts_with("===") {
254 line.as_str().bold().cyan().to_string() 257 line.as_str().bold().cyan().to_string()
258 } else if roulette {
259 if line.starts_with("Won ") {
260 line.as_str().bold().green().to_string()
261 } else if line.starts_with("ERROR:") {
262 line.as_str().red().to_string()
263 } else {
264 style_roulette_numbers(line)
265 }
255 } else if line.contains('{') { 266 } else if line.contains('{') {
256 style_winning_cells(line) 267 style_winning_cells(line)
257 } else if line.contains("WIN!") || line.starts_with("Won ") { 268 } else if line.contains("WIN!") || line.starts_with("Won ") {
@@ -309,7 +320,13 @@ impl ClientState {
309 let strip = (0..9) 320 let strip = (0..9)
310 .map(|offset| { 321 .map(|offset| {
311 let index = (center - 4 + offset).rem_euclid(WHEEL.len() as isize) as usize; 322 let index = (center - 4 + offset).rem_euclid(WHEEL.len() as isize) as usize;
312 format!("[ {:>2} ]", WHEEL[index]) 323 let number = WHEEL[index];
324 let cell = format!("[ {:>2} ]", number);
325 match roulette_color(number) {
326 "red" => cell.red().to_string(),
327 "green" => cell.green().to_string(),
328 _ => cell,
329 }
313 }) 330 })
314 .collect::<Vec<_>>() 331 .collect::<Vec<_>>()
315 .join(" "); 332 .join(" ");
@@ -498,6 +515,61 @@ impl ClientState {
498 } 515 }
499} 516}
500 517
518fn style_roulette_number(text: &str, number: u8) -> String {
519 match roulette_color(number) {
520 "red" => text.red().to_string(),
521 "green" => text.green().to_string(),
522 _ => text.to_string(),
523 }
524}
525
526fn style_roulette_numbers(line: &str) -> String {
527 if line.starts_with("Bets:") {
528 let mut out = String::new();
529 let mut rest = line;
530 while let Some(index) = rest.find('#') {
531 out.push_str(&rest[..=index]);
532 let after = &rest[index + 1..];
533 let digits: String = after
534 .chars()
535 .take_while(|character| character.is_ascii_digit())
536 .collect();
537 if let Ok(number) = digits.parse::<u8>() {
538 if number <= 36 {
539 out.push_str(&style_roulette_number(&digits, number));
540 } else {
541 out.push_str(&digits);
542 }
543 }
544 rest = &after[digits.len()..];
545 }
546 out.push_str(rest);
547 return out;
548 }
549
550 for label in ["Winning: ", "Number: "] {
551 if let Some(index) = line.find(label) {
552 let start = index + label.len();
553 let digits: String = line[start..]
554 .chars()
555 .take_while(|character| character.is_ascii_digit())
556 .collect();
557 if let Ok(number) = digits.parse::<u8>() {
558 if number <= 36 {
559 return format!(
560 "{}{}{}",
561 &line[..start],
562 style_roulette_number(&digits, number),
563 &line[start + digits.len()..]
564 );
565 }
566 }
567 }
568 }
569
570 line.to_string()
571}
572
501fn style_winning_cells(line: &str) -> String { 573fn style_winning_cells(line: &str) -> String {
502 let chars: Vec<char> = line.chars().collect(); 574 let chars: Vec<char> = line.chars().collect();
503 let mut out = String::new(); 575 let mut out = String::new();