summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-28 23:19:52 +0100
committersalmonik <salmonik@tuta.io>2026-10-28 23:19:52 +0100
commit2915d826f744612e0d975282f5bddf13270b9f3f (patch)
treea2864bfaef91070ac3b4e13b8b731e539a757cc8 /tests
parentv1.0.0: replace slots with 5x3 reels and five paylines (diff)
downloadzagan-bet-2915d826f744612e0d975282f5bddf13270b9f3f.tar.gz
zagan-bet-2915d826f744612e0d975282f5bddf13270b9f3f.zip
v1.1.0: highlight winning symbols on the slots board
Diffstat (limited to '')
-rw-r--r--tests/client_core.rs16
-rw-r--r--tests/server_core.rs2
-rw-r--r--tests/slots.rs43
3 files changed, 40 insertions, 21 deletions
diff --git a/tests/client_core.rs b/tests/client_core.rs
index 80ec587..91ddcab 100644
--- a/tests/client_core.rs
+++ b/tests/client_core.rs
@@ -155,12 +155,12 @@ fn spin_replaces_the_board_and_keeps_the_controls_line() {
155 assert!(h.sent.iter().any(|line| line == "PLAY_START slots 50")); 155 assert!(h.sent.iter().any(|line| line == "PLAY_START slots 50"));
156 assert_eq!(h.screen.lines.len(), lines_before_spin); 156 assert_eq!(h.screen.lines.len(), lines_before_spin);
157 157
158 h.reply("GAME_RESULT session-1 100 50 1050 CCCCLLCLODGSDGS win 1"); 158 h.reply("GAME_RESULT session-1 100 50 1050 CCCCLLCLODGSDGS win 1 0,3,6");
159 assert_eq!(h.board().len(), 9); 159 assert_eq!(h.board().len(), 9);
160 assert!(h 160 assert!(h
161 .board() 161 .board()
162 .get(2) 162 .get(2)
163 .is_some_and(|line| line.contains("[ C ] [ C ] [ C ] [ D ] [ D ]"))); 163 .is_some_and(|line| line.contains("{ C } { C } { C } [ D ] [ D ]")));
164 assert!(h.board()[6].contains("Won 100 PLN")); 164 assert!(h.board()[6].contains("Won 100 PLN"));
165 assert!(h.board()[7].contains("Winning lines: 1")); 165 assert!(h.board()[7].contains("Winning lines: 1"));
166 assert!(h.board()[1].contains("Balance: 1050 PLN")); 166 assert!(h.board()[1].contains("Balance: 1050 PLN"));
@@ -173,7 +173,7 @@ fn changing_the_bet_after_a_spin_does_not_leave_stale_lines() {
173 let mut h = Harness::new(); 173 let mut h = Harness::new();
174 h.enter_slots("50"); 174 h.enter_slots("50");
175 h.key(Key::Char(' ')); 175 h.key(Key::Char(' '));
176 h.reply("GAME_RESULT session-1 100 50 1050 CCCCLLCLODGSDGS win 1"); 176 h.reply("GAME_RESULT session-1 100 50 1050 CCCCLLCLODGSDGS win 1 0,3,6");
177 let lines_before_bet = h.screen.lines.len(); 177 let lines_before_bet = h.screen.lines.len();
178 178
179 h.key(Key::Char('+')); 179 h.key(Key::Char('+'));
@@ -183,7 +183,7 @@ fn changing_the_bet_after_a_spin_does_not_leave_stale_lines() {
183 assert!(h 183 assert!(h
184 .board() 184 .board()
185 .get(2) 185 .get(2)
186 .is_some_and(|line| line.contains("[ C ] [ C ] [ C ] [ D ] [ D ]"))); 186 .is_some_and(|line| line.contains("{ C } { C } { C } [ D ] [ D ]")));
187 assert_eq!(h.screen.lines.len(), lines_before_bet); 187 assert_eq!(h.screen.lines.len(), lines_before_bet);
188 assert_eq!( 188 assert_eq!(
189 h.screen 189 h.screen
@@ -206,6 +206,7 @@ fn a_losing_spin_shows_no_win_and_no_winning_lines() {
206 assert_eq!(h.board().len(), 9); 206 assert_eq!(h.board().len(), 9);
207 assert!(h.board()[6].contains("No win.")); 207 assert!(h.board()[6].contains("No win."));
208 assert_eq!(h.board()[7], ""); 208 assert_eq!(h.board()[7], "");
209 assert!(!h.board()[2].contains('{'));
209} 210}
210 211
211#[test] 212#[test]
@@ -214,11 +215,12 @@ fn a_mega_win_shows_all_winning_lines() {
214 h.enter_slots("10"); 215 h.enter_slots("10");
215 h.key(Key::Char(' ')); 216 h.key(Key::Char(' '));
216 217
217 h.reply(&format!("GAME_RESULT session-1 750 10 1740 {ALL_C} mega 1,2,3,4,5")); 218 h.reply(&format!("GAME_RESULT session-1 750 10 1740 {ALL_C} mega 1,2,3,4,5 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14"));
218 219
219 assert_eq!(h.board().len(), 9); 220 assert_eq!(h.board().len(), 9);
220 assert!(h.board()[6].contains("MEGA WIN! 750 PLN")); 221 assert!(h.board()[6].contains("MEGA WIN! 750 PLN"));
221 assert!(h.board()[7].contains("Winning lines: 1,2,3,4,5")); 222 assert!(h.board()[7].contains("Winning lines: 1,2,3,4,5"));
223 assert_eq!(h.board()[2].matches("{ C }").count(), 5);
222} 224}
223 225
224#[test] 226#[test]
@@ -517,12 +519,13 @@ fn disconnect_clears_connection_state() {
517 519
518#[test] 520#[test]
519fn parses_a_five_by_three_result_with_winning_lines() { 521fn parses_a_five_by_three_result_with_winning_lines() {
520 let response = format!("GAME_RESULT session-1 100 50 1050 {WIN3} win 1"); 522 let response = format!("GAME_RESULT session-1 100 50 1050 {WIN3} win 1 0,3,6");
521 let result = parse_spin_response(&response).expect("valid 5x3 result"); 523 let result = parse_spin_response(&response).expect("valid 5x3 result");
522 524
523 assert_eq!(result.reels.chars().count(), 15); 525 assert_eq!(result.reels.chars().count(), 15);
524 assert_eq!(result.win_type, "win"); 526 assert_eq!(result.win_type, "win");
525 assert_eq!(result.win_lines, vec![1]); 527 assert_eq!(result.win_lines, vec![1]);
528 assert_eq!(result.win_cells, vec![0, 3, 6]);
526} 529}
527 530
528#[test] 531#[test]
@@ -533,6 +536,7 @@ fn accepts_a_legacy_three_symbol_result() {
533 assert_eq!(result.reels, "OSS"); 536 assert_eq!(result.reels, "OSS");
534 assert_eq!(result.win_type, "two"); 537 assert_eq!(result.win_type, "two");
535 assert!(result.win_lines.is_empty()); 538 assert!(result.win_lines.is_empty());
539 assert!(result.win_cells.is_empty());
536} 540}
537 541
538#[test] 542#[test]
diff --git a/tests/server_core.rs b/tests/server_core.rs
index f3736e9..5c3c316 100644
--- a/tests/server_core.rs
+++ b/tests/server_core.rs
@@ -189,7 +189,7 @@ fn a_winning_spin_updates_the_balance_and_the_audit_row() {
189 let response = core.handle_message(&mut state, "PLAY_START slots 10"); 189 let response = core.handle_message(&mut state, "PLAY_START slots 10");
190 assert!(response.starts_with("GAME_RESULT"), "spin failed: {response}"); 190 assert!(response.starts_with("GAME_RESULT"), "spin failed: {response}");
191 assert!(response.contains(" 20 10 1010 "), "response: {response}"); 191 assert!(response.contains(" 20 10 1010 "), "response: {response}");
192 assert!(response.ends_with(" win 1"), "response: {response}"); 192 assert!(response.ends_with(" win 1 0,3,6"), "response: {response}");
193 193
194 assert_eq!(core.balance("alice"), Some(1010)); 194 assert_eq!(core.balance("alice"), Some(1010));
195 assert_eq!(core.play_count("alice"), 1); 195 assert_eq!(core.play_count("alice"), 1);
diff --git a/tests/slots.rs b/tests/slots.rs
index 5fb751a..857e607 100644
--- a/tests/slots.rs
+++ b/tests/slots.rs
@@ -1,6 +1,6 @@
1//! Regression tests for the server-side 5x3 slots game. 1//! Regression tests for the server-side 5x3 slots game.
2 2
3use zagan_bet::server::games::slots::{SlotsGame, PAYLINES, REELS, ROWS}; 3use zagan_bet::server::games::slots::{LineWin, SlotsGame, PAYLINES, REELS, ROWS};
4use zagan_bet::server::games::traits::{Game, GamePhase}; 4use zagan_bet::server::games::traits::{Game, GamePhase};
5 5
6/// Convert row-major test rows into the column-major wire format. 6/// Convert row-major test rows into the column-major wire format.
@@ -15,13 +15,17 @@ fn grid(rows: [&str; 3]) -> String {
15 result 15 result
16} 16}
17 17
18fn payout(rows: [&str; 3], bet: u64) -> (u64, Vec<usize>) { 18fn payout(rows: [&str; 3], bet: u64) -> (u64, Vec<LineWin>) {
19 let reels = grid(rows); 19 let reels = grid(rows);
20 let game = SlotsGame::with_grid(&reels); 20 let game = SlotsGame::with_grid(&reels);
21 let cells: Vec<char> = reels.chars().collect(); 21 let cells: Vec<char> = reels.chars().collect();
22 game.calculate_payout(&cells, bet) 22 game.calculate_payout(&cells, bet)
23} 23}
24 24
25fn lines(wins: &[LineWin]) -> Vec<usize> {
26 wins.iter().map(|win| win.line).collect()
27}
28
25#[test] 29#[test]
26fn the_wire_grid_is_five_reels_by_three_rows() { 30fn the_wire_grid_is_five_reels_by_three_rows() {
27 assert_eq!(REELS, 5); 31 assert_eq!(REELS, 5);
@@ -37,50 +41,60 @@ fn the_wire_grid_is_five_reels_by_three_rows() {
37 41
38#[test] 42#[test]
39fn three_of_a_kind_on_the_top_line_pays() { 43fn three_of_a_kind_on_the_top_line_pays() {
40 let (winnings, lines) = payout(["CCCDD", "CLLGG", "CLOSS"], 10); 44 let (winnings, wins) = payout(["CCCDD", "CLLGG", "CLOSS"], 10);
41 45
42 assert_eq!(winnings, 20, "CCC on line 1 pays 2x total bet"); 46 assert_eq!(winnings, 20, "CCC on line 1 pays 2x total bet");
43 assert_eq!(lines, vec![1]); 47 assert_eq!(lines(&wins), vec![1]);
48 assert_eq!(wins[0].cells, vec![0, 3, 6]);
44} 49}
45 50
46#[test] 51#[test]
47fn four_of_a_kind_pays_more_than_three() { 52fn four_of_a_kind_pays_more_than_three() {
48 let (winnings, lines) = payout(["CCCCD", "LOGDS", "OGDSL"], 10); 53 let (winnings, wins) = payout(["CCCCD", "LOGDS", "OGDSL"], 10);
49 54
50 assert_eq!(winnings, 50, "CCCC pays 5x total bet"); 55 assert_eq!(winnings, 50, "CCCC pays 5x total bet");
51 assert_eq!(lines, vec![1]); 56 assert_eq!(lines(&wins), vec![1]);
57 assert_eq!(wins[0].cells, vec![0, 3, 6, 9]);
52} 58}
53 59
54#[test] 60#[test]
55fn five_of_a_kind_pays_all_lines() { 61fn five_of_a_kind_pays_all_lines() {
56 let (winnings, lines) = payout(["SSSSS", "SSSSS", "SSSSS"], 10); 62 let (winnings, wins) = payout(["SSSSS", "SSSSS", "SSSSS"], 10);
57 63
58 assert_eq!(winnings, 7500, "five SSSSS lines at 150x each"); 64 assert_eq!(winnings, 7500, "five SSSSS lines at 150x each");
59 assert_eq!(lines, vec![1, 2, 3, 4, 5]); 65 assert_eq!(lines(&wins), vec![1, 2, 3, 4, 5]);
66 assert_eq!(wins[0].cells, vec![0, 3, 6, 9, 12]);
67 assert_eq!(wins[3].cells, vec![0, 4, 8, 10, 12]);
68 assert_eq!(wins[4].cells, vec![2, 4, 6, 10, 14]);
60} 69}
61 70
62#[test] 71#[test]
63fn the_v_payline_is_evaluated() { 72fn the_v_payline_is_evaluated() {
64 let (winnings, lines) = payout(["COGDC", "LCLCG", "OGCSD"], 10); 73 let (winnings, wins) = payout(["COGDC", "LCLCG", "OGCSD"], 10);
65 74
66 assert_eq!(winnings, 150, "V line with five C symbols pays 15x"); 75 assert_eq!(winnings, 150, "V line with five C symbols pays 15x");
67 assert_eq!(lines, vec![4]); 76 assert_eq!(lines(&wins), vec![4]);
77 assert_eq!(wins[0].cells, vec![0, 4, 8, 10, 12]);
68} 78}
69 79
70#[test] 80#[test]
71fn no_matching_line_pays_nothing() { 81fn no_matching_line_pays_nothing() {
72 let (winnings, lines) = payout(["CLOGD", "LOSGC", "DGDSC"], 100); 82 let (winnings, wins) = payout(["CLOGD", "LOSGC", "DGDSC"], 100);
73 83
74 assert_eq!(winnings, 0); 84 assert_eq!(winnings, 0);
75 assert!(lines.is_empty()); 85 assert!(wins.is_empty());
76} 86}
77 87
78#[test] 88#[test]
79fn winning_lines_are_added_together() { 89fn winning_lines_are_added_together() {
80 let (winnings, lines) = payout(["SSSSS", "LLLLL", "OOOOO"], 10); 90 let (winnings, wins) = payout(["SSSSS", "LLLLL", "OOOOO"], 10);
81 91
82 assert_eq!(winnings, (150 + 20 + 25) * 10); 92 assert_eq!(winnings, (150 + 20 + 25) * 10);
83 assert_eq!(lines, vec![1, 2, 3]); 93 assert_eq!(lines(&wins), vec![1, 2, 3]);
94
95 let mut cells: Vec<usize> = wins.iter().flat_map(|win| win.cells.clone()).collect();
96 cells.sort_unstable();
97 assert_eq!(cells, (0..15).collect::<Vec<_>>());
84} 98}
85 99
86#[test] 100#[test]
@@ -111,6 +125,7 @@ fn a_spin_produces_a_finished_state_and_a_result_with_win_lines() {
111 assert_eq!(result.winnings, 20); 125 assert_eq!(result.winnings, 20);
112 assert_eq!(result.result_data.get("win_type").unwrap(), "win"); 126 assert_eq!(result.result_data.get("win_type").unwrap(), "win");
113 assert_eq!(result.result_data.get("win_lines").unwrap(), "1"); 127 assert_eq!(result.result_data.get("win_lines").unwrap(), "1");
128 assert_eq!(result.result_data.get("win_cells").unwrap(), "0,3,6");
114} 129}
115 130
116#[test] 131#[test]