From 828eb472701af16b7bd913111273b62d22ea3627 Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 24 Aug 2026 17:36:19 +0200 Subject: Fix critical game bugs: payout, sessions, balance type, render_lines; add AGENTS.md and regression tests --- tests/slots.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 tests/slots.rs (limited to 'tests') diff --git a/tests/slots.rs b/tests/slots.rs new file mode 100644 index 0000000..c50f425 --- /dev/null +++ b/tests/slots.rs @@ -0,0 +1,92 @@ +//! Regression tests for the server-side slots game logic. + +use zagan_bet::server_game_traits::{Game, GamePhase}; +use zagan_bet::server_slots::SlotsGame; + +fn make_game() -> SlotsGame { + SlotsGame::new() +} + +/// Three of a kind pays the three-of-a-kind multiplier. +#[test] +fn three_of_a_kind_pays() { + let game = make_game(); + // CCC = 5x + assert_eq!(game.calculate_payout(&['C', 'C', 'C'], 100), 500); + // SSS = 100x + assert_eq!(game.calculate_payout(&['S', 'S', 'S'], 10), 1000); +} + +/// Two adjacent matching symbols pays the two-of-a-kind multiplier. +#[test] +fn two_adjacent_pays() { + let game = make_game(); + // CC? = 2x + assert_eq!(game.calculate_payout(&['C', 'C', 'D'], 100), 200); + // ?LL = 3x + assert_eq!(game.calculate_payout(&['D', 'L', 'L'], 50), 150); +} + +/// Non-adjacent matching symbols (e.g. [C,D,C]) must also pay. +/// This was a bug: the old code only checked pairs (0,1) and (1,2). +#[test] +fn two_non_adjacent_pays() { + let game = make_game(); + // C,D,C — two cherries at positions 0 and 2 + assert_eq!(game.calculate_payout(&['C', 'D', 'C'], 100), 200); + // S,X,S — two sevens at positions 0 and 2 + assert_eq!(game.calculate_payout(&['S', 'L', 'S'], 10), 200); + // L,O,L + assert_eq!(game.calculate_payout(&['L', 'O', 'L'], 50), 150); +} + +/// No matching symbols returns 0. +#[test] +fn no_match_returns_zero() { + let game = make_game(); + assert_eq!(game.calculate_payout(&['C', 'L', 'O'], 100), 0); + assert_eq!(game.calculate_payout(&['G', 'D', 'S'], 50), 0); +} + +/// process_action produces a finished state with correct reels and win_type. +#[test] +fn process_action_spin_sets_finished() { + let game = make_game(); + let state = game.prepare_state("alice", 100); + assert_eq!(state.phase, GamePhase::WaitingToStart); + + // Manually set reels by going through process_action. + // Since spin is random, we test the state machine rather than the outcome. + let result = game.process_action(&state, "spin", &[]); + assert!(result.is_ok()); + let new_state = result.unwrap(); + assert_eq!(new_state.phase, GamePhase::Finished); + assert!(new_state.data.contains_key("reels")); + assert!(new_state.data.contains_key("win_type")); +} + +/// calculate_result on a finished state returns valid GameResult. +#[test] +fn calculate_result_on_finished_state() { + let game = make_game(); + let mut state = game.prepare_state("alice", 100); + state.phase = GamePhase::Finished; + state + .data + .insert("reels".to_string(), "CCD".to_string()); + state + .data + .insert("win_type".to_string(), "two".to_string()); + + let result = game.calculate_result(&state).unwrap(); + assert_eq!(result.bet_amount, 100); + assert_eq!(result.winnings, 200); // CC = 2x +} + +/// calculate_result on a non-finished state returns an error. +#[test] +fn calculate_result_on_unfinished_state() { + let game = make_game(); + let state = game.prepare_state("alice", 100); + assert!(game.calculate_result(&state).is_err()); +} -- cgit v1.2.3