diff options
| author | salmonik <salmonik@tuta.io> | 2026-10-28 23:19:52 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-10-28 23:19:52 +0100 |
| commit | 2915d826f744612e0d975282f5bddf13270b9f3f (patch) | |
| tree | a2864bfaef91070ac3b4e13b8b731e539a757cc8 /tests | |
| parent | v1.0.0: replace slots with 5x3 reels and five paylines (diff) | |
| download | zagan-bet-2915d826f744612e0d975282f5bddf13270b9f3f.tar.gz zagan-bet-2915d826f744612e0d975282f5bddf13270b9f3f.zip | |
v1.1.0: highlight winning symbols on the slots board
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/client_core.rs | 16 | ||||
| -rw-r--r-- | tests/server_core.rs | 2 | ||||
| -rw-r--r-- | tests/slots.rs | 43 |
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] |
| 519 | fn parses_a_five_by_three_result_with_winning_lines() { | 521 | fn 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 | ||
| 3 | use zagan_bet::server::games::slots::{SlotsGame, PAYLINES, REELS, ROWS}; | 3 | use zagan_bet::server::games::slots::{LineWin, SlotsGame, PAYLINES, REELS, ROWS}; |
| 4 | use zagan_bet::server::games::traits::{Game, GamePhase}; | 4 | use 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 | ||
| 18 | fn payout(rows: [&str; 3], bet: u64) -> (u64, Vec<usize>) { | 18 | fn 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 | ||
| 25 | fn lines(wins: &[LineWin]) -> Vec<usize> { | ||
| 26 | wins.iter().map(|win| win.line).collect() | ||
| 27 | } | ||
| 28 | |||
| 25 | #[test] | 29 | #[test] |
| 26 | fn the_wire_grid_is_five_reels_by_three_rows() { | 30 | fn 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] |
| 39 | fn three_of_a_kind_on_the_top_line_pays() { | 43 | fn 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] |
| 47 | fn four_of_a_kind_pays_more_than_three() { | 52 | fn 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] |
| 55 | fn five_of_a_kind_pays_all_lines() { | 61 | fn 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] |
| 63 | fn the_v_payline_is_evaluated() { | 72 | fn 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] |
| 71 | fn no_matching_line_pays_nothing() { | 81 | fn 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] |
| 79 | fn winning_lines_are_added_together() { | 89 | fn 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] |
