summaryrefslogtreecommitdiffstats
path: root/tests/slots.rs
blob: 3035800a6a02da26aa5df24bfcc3de86f1cf9733 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
//! Regression tests for the server-side slots game logic.

use zagan_bet::server::games::slots::SlotsGame;
use zagan_bet::server::games::traits::{Game, GamePhase};

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 = 2x
    assert_eq!(game.calculate_payout(&['C', 'C', 'C'], 100), 200);
    // SSS = 28x
    assert_eq!(game.calculate_payout(&['S', 'S', 'S'], 10), 280);
}

/// Two adjacent matching symbols pays the two-of-a-kind multiplier.
#[test]
fn two_adjacent_pays() {
    let game = make_game();
    // CC? = 1x
    assert_eq!(game.calculate_payout(&['C', 'C', 'D'], 100), 100);
    // ?LL = 1x
    assert_eq!(game.calculate_payout(&['D', 'L', 'L'], 50), 50);
}

/// 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), 100);
    // S,X,S — two sevens at positions 0 and 2
    assert_eq!(game.calculate_payout(&['S', 'L', 'S'], 10), 30);
    // L,O,L
    assert_eq!(game.calculate_payout(&['L', 'O', 'L'], 50), 50);
}

/// 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);
}

#[test]
fn an_enormous_bet_saturates_instead_of_overflowing() {
    let game = make_game();
    assert_eq!(game.calculate_payout(&['S', 'S', 'S'], u64::MAX), u64::MAX);
    assert_eq!(game.calculate_payout(&['S', 'L', 'S'], u64::MAX), u64::MAX);
}

/// 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, 100); // CC = 1x
}

/// 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());
}