summaryrefslogtreecommitdiffstats
path: root/tests/slots.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-28 23:45:43 +0100
committersalmonik <salmonik@tuta.io>2026-10-28 23:45:43 +0100
commit9951e2d5afbcabb4bd8016b6d160653c1ec5ed8e (patch)
treee1179b0523356dfb5d43f3d6d4614203f6fa8dec /tests/slots.rs
parentv1.2.1: track wrapped board lines and split the paytable (diff)
downloadzagan-bet-9951e2d5afbcabb4bd8016b6d160653c1ec5ed8e.tar.gz
zagan-bet-9951e2d5afbcabb4bd8016b6d160653c1ec5ed8e.zip
v1.3.0: simplify payouts to bet x symbol x shape
Diffstat (limited to '')
-rw-r--r--tests/slots.rs40
1 files changed, 27 insertions, 13 deletions
diff --git a/tests/slots.rs b/tests/slots.rs
index bdf8f10..8bdd9af 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::slots::{paytable_lines, PAYTABLE}; 3use zagan_bet::slots::{paytable_lines, LINE_NAMES, SHAPE_MULTIPLIERS, SYMBOL_MULTIPLIERS};
4use zagan_bet::server::games::slots::{LineWin, SlotsGame, PAYLINES, REELS, ROWS}; 4use zagan_bet::server::games::slots::{LineWin, SlotsGame, PAYLINES, REELS, ROWS};
5use zagan_bet::server::games::traits::{Game, GamePhase}; 5use zagan_bet::server::games::traits::{Game, GamePhase};
6 6
@@ -44,7 +44,7 @@ fn the_wire_grid_is_five_reels_by_three_rows() {
44fn three_of_a_kind_on_the_top_line_pays() { 44fn three_of_a_kind_on_the_top_line_pays() {
45 let (winnings, wins) = payout(["CCCDD", "CLLGG", "CLOSS"], 10); 45 let (winnings, wins) = payout(["CCCDD", "CLLGG", "CLOSS"], 10);
46 46
47 assert_eq!(winnings, 20, "CCC on line 1 pays 2x total bet"); 47 assert_eq!(winnings, 10, "CCC: 10 * C(1) * shape3(1)");
48 assert_eq!(lines(&wins), vec![1]); 48 assert_eq!(lines(&wins), vec![1]);
49 assert_eq!(wins[0].cells, vec![0, 3, 6]); 49 assert_eq!(wins[0].cells, vec![0, 3, 6]);
50} 50}
@@ -53,7 +53,7 @@ fn three_of_a_kind_on_the_top_line_pays() {
53fn four_of_a_kind_pays_more_than_three() { 53fn four_of_a_kind_pays_more_than_three() {
54 let (winnings, wins) = payout(["CCCCD", "LOGDS", "OGDSL"], 10); 54 let (winnings, wins) = payout(["CCCCD", "LOGDS", "OGDSL"], 10);
55 55
56 assert_eq!(winnings, 50, "CCCC pays 5x total bet"); 56 assert_eq!(winnings, 30, "CCCC: 10 * C(1) * shape4(3)");
57 assert_eq!(lines(&wins), vec![1]); 57 assert_eq!(lines(&wins), vec![1]);
58 assert_eq!(wins[0].cells, vec![0, 3, 6, 9]); 58 assert_eq!(wins[0].cells, vec![0, 3, 6, 9]);
59} 59}
@@ -62,7 +62,7 @@ fn four_of_a_kind_pays_more_than_three() {
62fn five_of_a_kind_pays_all_lines() { 62fn five_of_a_kind_pays_all_lines() {
63 let (winnings, wins) = payout(["SSSSS", "SSSSS", "SSSSS"], 10); 63 let (winnings, wins) = payout(["SSSSS", "SSSSS", "SSSSS"], 10);
64 64
65 assert_eq!(winnings, 7500, "five SSSSS lines at 150x each"); 65 assert_eq!(winnings, 7000, "five SSSSS lines: 5 * 10 * S(10) * shape5(14)");
66 assert_eq!(lines(&wins), vec![1, 2, 3, 4, 5]); 66 assert_eq!(lines(&wins), vec![1, 2, 3, 4, 5]);
67 assert_eq!(wins[0].cells, vec![0, 3, 6, 9, 12]); 67 assert_eq!(wins[0].cells, vec![0, 3, 6, 9, 12]);
68 assert_eq!(wins[3].cells, vec![0, 4, 8, 10, 12]); 68 assert_eq!(wins[3].cells, vec![0, 4, 8, 10, 12]);
@@ -73,7 +73,7 @@ fn five_of_a_kind_pays_all_lines() {
73fn the_v_payline_is_evaluated() { 73fn the_v_payline_is_evaluated() {
74 let (winnings, wins) = payout(["COGDC", "LCLCG", "OGCSD"], 10); 74 let (winnings, wins) = payout(["COGDC", "LCLCG", "OGCSD"], 10);
75 75
76 assert_eq!(winnings, 150, "V line with five C symbols pays 15x"); 76 assert_eq!(winnings, 140, "V line: 10 * C(1) * shape5(14)");
77 assert_eq!(lines(&wins), vec![4]); 77 assert_eq!(lines(&wins), vec![4]);
78 assert_eq!(wins[0].cells, vec![0, 4, 8, 10, 12]); 78 assert_eq!(wins[0].cells, vec![0, 4, 8, 10, 12]);
79} 79}
@@ -90,7 +90,7 @@ fn no_matching_line_pays_nothing() {
90fn winning_lines_are_added_together() { 90fn winning_lines_are_added_together() {
91 let (winnings, wins) = payout(["SSSSS", "LLLLL", "OOOOO"], 10); 91 let (winnings, wins) = payout(["SSSSS", "LLLLL", "OOOOO"], 10);
92 92
93 assert_eq!(winnings, (150 + 20 + 25) * 10); 93 assert_eq!(winnings, (10 + 2 + 3) * 14 * 10);
94 assert_eq!(lines(&wins), vec![1, 2, 3]); 94 assert_eq!(lines(&wins), vec![1, 2, 3]);
95 95
96 let mut cells: Vec<usize> = wins.iter().flat_map(|win| win.cells.clone()).collect(); 96 let mut cells: Vec<usize> = wins.iter().flat_map(|win| win.cells.clone()).collect();
@@ -102,21 +102,35 @@ fn winning_lines_are_added_together() {
102fn theoretical_rtp_is_just_under_one() { 102fn theoretical_rtp_is_just_under_one() {
103 let rtp = SlotsGame::new().theoretical_rtp(); 103 let rtp = SlotsGame::new().theoretical_rtp();
104 104
105 assert!((rtp - 425.0 / 432.0).abs() < 1e-12); 105 assert!((rtp - 3835.0 / 3888.0).abs() < 1e-12);
106 assert!(rtp < 1.0, "house edge must be positive"); 106 assert!(rtp < 1.0, "house edge must be positive");
107 assert!(rtp > 0.98, "house edge should be tiny"); 107 assert!(rtp > 0.98, "house edge should be tiny");
108} 108}
109 109
110#[test] 110#[test]
111fn the_paytable_lines_list_every_symbol_and_fit_80_columns() { 111fn payline_names_match_the_paylines() {
112 assert_eq!(LINE_NAMES.len(), PAYLINES.len());
113 assert_eq!(LINE_NAMES[3], "V");
114 assert_eq!(LINE_NAMES[4], "inverted V");
115}
116
117#[test]
118fn the_paytable_lines_show_the_product_formula_and_fit_80_columns() {
112 let lines = paytable_lines(); 119 let lines = paytable_lines();
113 let joined = lines.join(" "); 120 let joined = lines.join(" ");
114 121
115 assert_eq!(lines.len(), 2, "paytable should be split into two lines"); 122 assert_eq!(lines.len(), 2, "paytable should be two lines");
116 for (symbol, three, four, five) in PAYTABLE { 123 assert!(joined.contains("Pays bet x symbol x shape"));
124 for (symbol, multiplier) in SYMBOL_MULTIPLIERS {
125 assert!(
126 joined.contains(&format!("{symbol}{multiplier}")),
127 "paytable is missing symbol {symbol}"
128 );
129 }
130 for (length, multiplier) in SHAPE_MULTIPLIERS {
117 assert!( 131 assert!(
118 joined.contains(&format!("{symbol} {three}/{four}/{five}")), 132 joined.contains(&format!("{length}:{multiplier}")),
119 "paytable is missing {symbol}" 133 "paytable is missing shape {length}"
120 ); 134 );
121 } 135 }
122 for line in lines { 136 for line in lines {
@@ -140,7 +154,7 @@ fn a_spin_produces_a_finished_state_and_a_result_with_win_lines() {
140 assert_eq!(state.phase, GamePhase::Finished); 154 assert_eq!(state.phase, GamePhase::Finished);
141 155
142 let result = game.calculate_result(&state).unwrap(); 156 let result = game.calculate_result(&state).unwrap();
143 assert_eq!(result.winnings, 20); 157 assert_eq!(result.winnings, 10);
144 assert_eq!(result.result_data.get("win_type").unwrap(), "win"); 158 assert_eq!(result.result_data.get("win_type").unwrap(), "win");
145 assert_eq!(result.result_data.get("win_lines").unwrap(), "1"); 159 assert_eq!(result.result_data.get("win_lines").unwrap(), "1");
146 assert_eq!(result.result_data.get("win_cells").unwrap(), "0,3,6"); 160 assert_eq!(result.result_data.get("win_cells").unwrap(), "0,3,6");