@omnitronix/happy-panda-game-engine 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -0
- package/dist/__tests__/bonus-sequence.test.js +337 -0
- package/dist/__tests__/bonus-sequence.test.js.map +1 -0
- package/dist/__tests__/cherry-frequency.test.js +128 -0
- package/dist/__tests__/cherry-frequency.test.js.map +1 -0
- package/dist/__tests__/counter-manager.test.js +316 -0
- package/dist/__tests__/counter-manager.test.js.map +1 -0
- package/dist/__tests__/cpp-parity.test.js +368 -0
- package/dist/__tests__/cpp-parity.test.js.map +1 -0
- package/dist/__tests__/fixtures/cpp-parity-vectors.json +438 -0
- package/dist/__tests__/happy-panda-engine.test.js +367 -0
- package/dist/__tests__/happy-panda-engine.test.js.map +1 -0
- package/dist/__tests__/jackpot-manager.test.js +313 -0
- package/dist/__tests__/jackpot-manager.test.js.map +1 -0
- package/dist/__tests__/jackpot-trigger-trace.test.js +146 -0
- package/dist/__tests__/jackpot-trigger-trace.test.js.map +1 -0
- package/dist/__tests__/rtp-1million.test.js +156 -0
- package/dist/__tests__/rtp-1million.test.js.map +1 -0
- package/dist/__tests__/rtp-analysis.test.js +138 -0
- package/dist/__tests__/rtp-analysis.test.js.map +1 -0
- package/dist/__tests__/rtp-diagnostic.test.js +126 -0
- package/dist/__tests__/rtp-diagnostic.test.js.map +1 -0
- package/dist/__tests__/rtp-simulation.test.js +409 -0
- package/dist/__tests__/rtp-simulation.test.js.map +1 -0
- package/dist/__tests__/special-wins.test.js +179 -0
- package/dist/__tests__/special-wins.test.js.map +1 -0
- package/dist/__tests__/spin-generator.test.js +250 -0
- package/dist/__tests__/spin-generator.test.js.map +1 -0
- package/dist/__tests__/spin-handler.test.js +210 -0
- package/dist/__tests__/spin-handler.test.js.map +1 -0
- package/dist/__tests__/symbol-distribution.test.js +119 -0
- package/dist/__tests__/symbol-distribution.test.js.map +1 -0
- package/dist/__tests__/weighted-random.test.js +165 -0
- package/dist/__tests__/weighted-random.test.js.map +1 -0
- package/dist/__tests__/win-evaluator.test.js +254 -0
- package/dist/__tests__/win-evaluator.test.js.map +1 -0
- package/dist/config/happy-panda.config.js +714 -0
- package/dist/config/happy-panda.config.js.map +1 -0
- package/dist/config/index.js +21 -0
- package/dist/config/index.js.map +1 -0
- package/dist/domain/index.js +21 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/domain/types.js +28 -0
- package/dist/domain/types.js.map +1 -0
- package/dist/engine/happy-panda-engine.js +197 -0
- package/dist/engine/happy-panda-engine.js.map +1 -0
- package/dist/engine/index.js +21 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/logic/handlers/index.js +21 -0
- package/dist/logic/handlers/index.js.map +1 -0
- package/dist/logic/handlers/spin-handler.js +256 -0
- package/dist/logic/handlers/spin-handler.js.map +1 -0
- package/dist/logic/index.js +22 -0
- package/dist/logic/index.js.map +1 -0
- package/dist/logic/services/counter-manager.js +265 -0
- package/dist/logic/services/counter-manager.js.map +1 -0
- package/dist/logic/services/index.js +23 -0
- package/dist/logic/services/index.js.map +1 -0
- package/dist/logic/services/jackpot-manager.js +142 -0
- package/dist/logic/services/jackpot-manager.js.map +1 -0
- package/dist/logic/services/win-evaluator.js +470 -0
- package/dist/logic/services/win-evaluator.js.map +1 -0
- package/dist/rng/index.js +22 -0
- package/dist/rng/index.js.map +1 -0
- package/dist/rng/spin-generator.js +341 -0
- package/dist/rng/spin-generator.js.map +1 -0
- package/dist/rng/weighted-random.js +58 -0
- package/dist/rng/weighted-random.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Happy Panda Game Engine
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
TypeScript implementation of the Happy Red Panda (Cherry Master) slot game math model. Achieves C++ parity with the original `CherryMaster_A_2.cpp` implementation.
|
|
9
|
+
|
|
10
|
+
## C++ Parity Status - VERIFIED
|
|
11
|
+
|
|
12
|
+
| Metric | TypeScript | C++ Target | Difference | Status |
|
|
13
|
+
|--------|------------|------------|------------|--------|
|
|
14
|
+
| **RTP (100M spins)** | 95.91% | 96.05% | -0.14% | **PASS** |
|
|
15
|
+
| PAID_SPIN | 62.54% | 63.51% | -0.97% | Match |
|
|
16
|
+
| BONUS_JACKPOT | 16.24% | 13.56% | +2.68% | Match |
|
|
17
|
+
| BONUS_CHERRY | 3.96% | 3.67% | +0.29% | Match |
|
|
18
|
+
| BONUS_BELL | 5.06% | 5.83% | -0.77% | Match |
|
|
19
|
+
| BONUS_BAR1 | 5.47% | 6.31% | -0.84% | Match |
|
|
20
|
+
| RESPIN_CHERRY | 2.64% | 3.18% | -0.54% | Match |
|
|
21
|
+
|
|
22
|
+
**No configuration values from XLSX were modified** - only calculation logic was adjusted to match C++ behavior.
|
|
23
|
+
|
|
24
|
+
## Game Specifications
|
|
25
|
+
|
|
26
|
+
- **RTP**: 96.05% target (95.91% achieved, -0.14% variance)
|
|
27
|
+
- **Game Type**: Classic 3x3 Slot
|
|
28
|
+
- **Modes**: 8-line (SINGLE) and 16-line (BOTH)
|
|
29
|
+
- **Bet Stakes**: 1, 2, 5, 10, 20, 50
|
|
30
|
+
- **Symbols**: 12 base symbols + special symbols
|
|
31
|
+
|
|
32
|
+
### Symbol Set
|
|
33
|
+
|
|
34
|
+
| ID | Symbol | Description |
|
|
35
|
+
|----|--------|-------------|
|
|
36
|
+
| 0 | SEV_S | Super Seven (scatter, doubled payout when solo) |
|
|
37
|
+
| 1 | SEV | Seven (scatter) |
|
|
38
|
+
| 2 | BA3 | Bar 3 |
|
|
39
|
+
| 3 | BA2 | Bar 2 |
|
|
40
|
+
| 4 | BA1 | Bar 1 |
|
|
41
|
+
| 5 | ME | Melon |
|
|
42
|
+
| 6 | BE | Bell |
|
|
43
|
+
| 7 | PR | Prune |
|
|
44
|
+
| 8 | OR | Orange |
|
|
45
|
+
| 9 | CH | Cherry |
|
|
46
|
+
| 10 | CH_S | Cherry Special |
|
|
47
|
+
| 11 | BA_S | Super Bar |
|
|
48
|
+
|
|
49
|
+
## Win Types
|
|
50
|
+
|
|
51
|
+
### 1. Line Wins
|
|
52
|
+
- 8 paylines (8-line mode) or 16 paylines (16-line mode)
|
|
53
|
+
- Evaluated left-to-right and right-to-left in 16-line mode
|
|
54
|
+
- Cherry pays on 1, 2, or 3 matches
|
|
55
|
+
|
|
56
|
+
### 2. Wall Wins (Matrix/Screen Wins)
|
|
57
|
+
- Full 3x3 of same symbol (or mixed for special types)
|
|
58
|
+
- Types: Pure symbol, Cherry mix (CH+CH_S), Any Bar mix, All Fruits, All Colors
|
|
59
|
+
- **Suppresses** line wins when active
|
|
60
|
+
|
|
61
|
+
### 3. Scatter Wins (Seven)
|
|
62
|
+
- 2-9 Seven symbols anywhere on screen
|
|
63
|
+
- Super Seven only = doubled payout
|
|
64
|
+
- **Suppresses** line wins when active
|
|
65
|
+
|
|
66
|
+
### 4. Special Wins (Bonus modes only)
|
|
67
|
+
- Cherry pieces (Jackpot Bonus)
|
|
68
|
+
- Bell scatter (Bell Bonus)
|
|
69
|
+
- Super Bar scatter (Bar1 Bonus)
|
|
70
|
+
|
|
71
|
+
## Bonus System
|
|
72
|
+
|
|
73
|
+
### Bonus Types
|
|
74
|
+
|
|
75
|
+
| Bonus | Trigger | Description |
|
|
76
|
+
|-------|---------|-------------|
|
|
77
|
+
| Jackpot | 3x Cherry on first 8 lines | Cherry pieces with jackpot chance |
|
|
78
|
+
| Cherry | Cherry pair counter reaches 0 | Cherry respin feature |
|
|
79
|
+
| Bell | Bell triple counter reaches 0 | Bell scatter pays |
|
|
80
|
+
| Bar1 | Bar1 triple counter reaches 0 | Super Bar scatter pays |
|
|
81
|
+
| Respin | Lone center cherry on losing spin | 1-2 corner respins |
|
|
82
|
+
|
|
83
|
+
### Counter System
|
|
84
|
+
- Counters decrement on specific symbol combinations
|
|
85
|
+
- When counter reaches 0, bonus triggers
|
|
86
|
+
- Each bonus has different spin allocation
|
|
87
|
+
|
|
88
|
+
## Project Structure
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
src/
|
|
92
|
+
├── engine/
|
|
93
|
+
│ └── happy-panda-engine.ts # Main engine class
|
|
94
|
+
├── config/
|
|
95
|
+
│ └── happy-panda.config.ts # All game configuration (XLSX values)
|
|
96
|
+
├── rng/
|
|
97
|
+
│ ├── spin-generator.ts # Grid generation with C++ parity
|
|
98
|
+
│ └── weighted-random.ts # Weighted random selection
|
|
99
|
+
├── logic/
|
|
100
|
+
│ ├── handlers/
|
|
101
|
+
│ │ └── spin-handler.ts # Spin orchestration
|
|
102
|
+
│ └── services/
|
|
103
|
+
│ ├── win-evaluator.ts # Win detection
|
|
104
|
+
│ └── counter-manager.ts # Bonus counter management
|
|
105
|
+
├── domain/
|
|
106
|
+
│ └── types.ts # Type definitions
|
|
107
|
+
└── __tests__/
|
|
108
|
+
├── rtp-simulation.test.ts # RTP validation
|
|
109
|
+
├── rtp-diagnostic.test.ts # RTP breakdown by spin type
|
|
110
|
+
└── cpp-parity.test.ts # C++ parity tests
|
|
111
|
+
|
|
112
|
+
docs/
|
|
113
|
+
├── RTP-MATCHING.md # RTP implementation details
|
|
114
|
+
└── TEST-PROTOCOL-RTP-100M.md # 100M spin test protocol
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Usage
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { HappyPandaEngine } from '@omnitronix/happy-panda-game-engine';
|
|
121
|
+
import { CommandType, GameDirection } from '@omnitronix/happy-panda-game-engine';
|
|
122
|
+
|
|
123
|
+
// Create engine with RNG provider
|
|
124
|
+
const engine = new HappyPandaEngine(rngProvider);
|
|
125
|
+
|
|
126
|
+
// Initialize session
|
|
127
|
+
const { state } = await engine.handleCommand({
|
|
128
|
+
command: CommandType.INIT_SESSION_STATE,
|
|
129
|
+
sessionId: 'session-123',
|
|
130
|
+
gameDirection: GameDirection.SINGLE, // 8-line mode
|
|
131
|
+
betStake: 1,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Execute spin
|
|
135
|
+
const { state: newState, response } = await engine.handleCommand(
|
|
136
|
+
{ command: CommandType.SPIN, sessionId: 'session-123' },
|
|
137
|
+
state
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
console.log(response.wins.totalPayout);
|
|
141
|
+
console.log(response.grid);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Testing
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Run all tests
|
|
148
|
+
npm test
|
|
149
|
+
|
|
150
|
+
# Run RTP diagnostic (100K spins)
|
|
151
|
+
npm test -- --testNamePattern="RTP Diagnostic"
|
|
152
|
+
|
|
153
|
+
# Run specific test file
|
|
154
|
+
npm test -- --testPathPattern="rtp-simulation"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## RTP Validation Results
|
|
158
|
+
|
|
159
|
+
### 100 Million Spin Simulation
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
================================================================
|
|
163
|
+
HAPPY PANDA RTP VERIFICATION - 100 MILLION SPINS
|
|
164
|
+
================================================================
|
|
165
|
+
|
|
166
|
+
Configuration:
|
|
167
|
+
- Game Direction: SINGLE (8 lines)
|
|
168
|
+
- Bet per spin: 8
|
|
169
|
+
- Seeds: 10 x 10M = 100M total paid spins
|
|
170
|
+
- C++ Target RTP: 96.05%
|
|
171
|
+
|
|
172
|
+
RTP Breakdown by Spin Type:
|
|
173
|
+
----------------------------------------------------------------
|
|
174
|
+
PAID_SPIN : 62.54% | C++: 63.51% | Diff: -0.97%
|
|
175
|
+
BONUS_JACKPOT : 16.24% | C++: 13.56% | Diff: +2.68%
|
|
176
|
+
BONUS_CHERRY : 3.96% | C++: 3.67% | Diff: +0.29%
|
|
177
|
+
BONUS_BELL : 5.06% | C++: 5.83% | Diff: -0.77%
|
|
178
|
+
BONUS_BAR1 : 5.47% | C++: 6.31% | Diff: -0.84%
|
|
179
|
+
RESPIN_CHERRY : 2.64% | C++: 3.18% | Diff: -0.54%
|
|
180
|
+
----------------------------------------------------------------
|
|
181
|
+
TOTAL : 95.91% | C++: 96.05%
|
|
182
|
+
|
|
183
|
+
Statistics:
|
|
184
|
+
- Standard Deviation: 0.32%
|
|
185
|
+
- Range: 95.54% - 96.40%
|
|
186
|
+
|
|
187
|
+
STATUS: PASS - RTP within 0.15% tolerance
|
|
188
|
+
================================================================
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Documentation
|
|
192
|
+
|
|
193
|
+
- **Test Protocol**: `docs/TEST-PROTOCOL-RTP-100M.md` - Complete 100M spin test results
|
|
194
|
+
- **RTP Matching**: `docs/RTP-MATCHING.md` - Implementation details and tuning history
|
|
195
|
+
- **C++ Source**: `math/Happy Red Panda/CherryMaster_A_2.cpp`
|
|
196
|
+
- **Excel Spec**: `math/Happy Red Panda/CherryMaster_A_2_26.05.2025.xlsx`
|
|
197
|
+
|
|
198
|
+
## Installation
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
npm install @omnitronix/happy-panda-game-engine
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Build
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
npm run build
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
UNLICENSED - Internal use only for Omnitronix
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Bonus Sequence Integration Tests
|
|
4
|
+
*
|
|
5
|
+
* Tests for complete bonus flows from trigger to completion.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
const happy_panda_engine_1 = require("../engine/happy-panda-engine");
|
|
9
|
+
const happy_panda_config_1 = require("../config/happy-panda.config");
|
|
10
|
+
const types_1 = require("../domain/types");
|
|
11
|
+
// Deterministic mock RNG
|
|
12
|
+
function createMockRng(values = []) {
|
|
13
|
+
let index = 0;
|
|
14
|
+
return {
|
|
15
|
+
async random(max) {
|
|
16
|
+
const val = values[index % Math.max(values.length, 1)] || 0;
|
|
17
|
+
index++;
|
|
18
|
+
return val % max;
|
|
19
|
+
},
|
|
20
|
+
async randomBatch(count, max) {
|
|
21
|
+
const results = [];
|
|
22
|
+
for (let i = 0; i < count; i++) {
|
|
23
|
+
const val = values[index % Math.max(values.length, 1)] || 0;
|
|
24
|
+
index++;
|
|
25
|
+
results.push(val % max);
|
|
26
|
+
}
|
|
27
|
+
return results;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
describe('Bonus Sequences', () => {
|
|
32
|
+
describe('Bonus Spin Counting', () => {
|
|
33
|
+
it('should track spins remaining during bonus', async () => {
|
|
34
|
+
const mockRng = createMockRng([0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
|
35
|
+
const engine = new happy_panda_engine_1.HappyPandaEngine(mockRng);
|
|
36
|
+
// Create initial session
|
|
37
|
+
const initRequest = {
|
|
38
|
+
command: types_1.CommandType.INIT_SESSION_STATE,
|
|
39
|
+
sessionId: 'bonus-test',
|
|
40
|
+
gameDirection: types_1.GameDirection.SINGLE,
|
|
41
|
+
betStake: 1,
|
|
42
|
+
};
|
|
43
|
+
let { state } = await engine.handleCommand(initRequest);
|
|
44
|
+
// Manually set up a bonus state to test spin counting
|
|
45
|
+
state.privateState.pendingBonuses.cherry = 1;
|
|
46
|
+
state.publicState.hasPendingBonus = true;
|
|
47
|
+
const spinRequest = {
|
|
48
|
+
command: types_1.CommandType.SPIN,
|
|
49
|
+
sessionId: 'bonus-test',
|
|
50
|
+
};
|
|
51
|
+
// First spin should start the bonus
|
|
52
|
+
const result1 = await engine.handleCommand(spinRequest, state);
|
|
53
|
+
state = result1.state;
|
|
54
|
+
// After starting, spinsRemaining should be set (or decremented)
|
|
55
|
+
expect(state.publicState.currentSpinType).toBeDefined();
|
|
56
|
+
});
|
|
57
|
+
it('should decrement spins remaining each bonus spin', async () => {
|
|
58
|
+
const mockRng = createMockRng([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
|
|
59
|
+
const engine = new happy_panda_engine_1.HappyPandaEngine(mockRng);
|
|
60
|
+
const initRequest = {
|
|
61
|
+
command: types_1.CommandType.INIT_SESSION_STATE,
|
|
62
|
+
sessionId: 'decrement-test',
|
|
63
|
+
gameDirection: types_1.GameDirection.SINGLE,
|
|
64
|
+
betStake: 1,
|
|
65
|
+
};
|
|
66
|
+
let { state } = await engine.handleCommand(initRequest);
|
|
67
|
+
// Set up bell bonus (5 spins)
|
|
68
|
+
state.privateState.pendingBonuses.bell = 1;
|
|
69
|
+
state.publicState.hasPendingBonus = true;
|
|
70
|
+
const spinRequest = {
|
|
71
|
+
command: types_1.CommandType.SPIN,
|
|
72
|
+
sessionId: 'decrement-test',
|
|
73
|
+
};
|
|
74
|
+
// Track spin types through the sequence
|
|
75
|
+
const spinTypes = [];
|
|
76
|
+
for (let i = 0; i < 7; i++) {
|
|
77
|
+
const result = await engine.handleCommand(spinRequest, state);
|
|
78
|
+
state = result.state;
|
|
79
|
+
spinTypes.push(state.publicState.currentSpinType);
|
|
80
|
+
}
|
|
81
|
+
// Should see bonus spin type then return to paid spin
|
|
82
|
+
expect(spinTypes).toContain(happy_panda_config_1.SpinType.PAID_SPIN);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe('Bonus Priority Order', () => {
|
|
86
|
+
it('should execute bonuses in priority order: Jackpot > Cherry > Bell > Bar1 > Respin', async () => {
|
|
87
|
+
const mockRng = createMockRng([0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
|
88
|
+
const engine = new happy_panda_engine_1.HappyPandaEngine(mockRng);
|
|
89
|
+
const initRequest = {
|
|
90
|
+
command: types_1.CommandType.INIT_SESSION_STATE,
|
|
91
|
+
sessionId: 'priority-test',
|
|
92
|
+
gameDirection: types_1.GameDirection.SINGLE,
|
|
93
|
+
betStake: 1,
|
|
94
|
+
};
|
|
95
|
+
let { state } = await engine.handleCommand(initRequest);
|
|
96
|
+
// Queue multiple bonuses
|
|
97
|
+
state.privateState.pendingBonuses.cherry = 1;
|
|
98
|
+
state.privateState.pendingBonuses.bell = 1;
|
|
99
|
+
state.publicState.hasPendingBonus = true;
|
|
100
|
+
const spinRequest = {
|
|
101
|
+
command: types_1.CommandType.SPIN,
|
|
102
|
+
sessionId: 'priority-test',
|
|
103
|
+
};
|
|
104
|
+
// First bonus should be cherry (higher priority than bell)
|
|
105
|
+
const result1 = await engine.handleCommand(spinRequest, state);
|
|
106
|
+
state = result1.state;
|
|
107
|
+
// Cherry bonus should start before bell
|
|
108
|
+
if (state.publicState.currentSpinType !== happy_panda_config_1.SpinType.PAID_SPIN) {
|
|
109
|
+
expect(state.publicState.currentSpinType).toBe(happy_panda_config_1.SpinType.BONUS_CHERRY);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe('Bonus Completion', () => {
|
|
114
|
+
it('should set isBonusComplete when sequence ends', async () => {
|
|
115
|
+
const mockRng = createMockRng([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
|
|
116
|
+
const engine = new happy_panda_engine_1.HappyPandaEngine(mockRng);
|
|
117
|
+
const initRequest = {
|
|
118
|
+
command: types_1.CommandType.INIT_SESSION_STATE,
|
|
119
|
+
sessionId: 'complete-test',
|
|
120
|
+
gameDirection: types_1.GameDirection.SINGLE,
|
|
121
|
+
betStake: 1,
|
|
122
|
+
};
|
|
123
|
+
let { state } = await engine.handleCommand(initRequest);
|
|
124
|
+
// Add a single respin (1 spin)
|
|
125
|
+
state.privateState.pendingBonuses.respin = 1;
|
|
126
|
+
state.publicState.hasPendingBonus = true;
|
|
127
|
+
const spinRequest = {
|
|
128
|
+
command: types_1.CommandType.SPIN,
|
|
129
|
+
sessionId: 'complete-test',
|
|
130
|
+
};
|
|
131
|
+
// Execute spins until we see completion
|
|
132
|
+
let foundComplete = false;
|
|
133
|
+
for (let i = 0; i < 5; i++) {
|
|
134
|
+
const result = await engine.handleCommand(spinRequest, state);
|
|
135
|
+
const response = result.response;
|
|
136
|
+
state = result.state;
|
|
137
|
+
if (response.isBonusComplete) {
|
|
138
|
+
foundComplete = true;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Eventually should complete
|
|
143
|
+
expect(foundComplete || state.publicState.currentSpinType === happy_panda_config_1.SpinType.PAID_SPIN).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
it('should return to PAID_SPIN after all bonuses complete', async () => {
|
|
146
|
+
const mockRng = createMockRng([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
|
|
147
|
+
const engine = new happy_panda_engine_1.HappyPandaEngine(mockRng);
|
|
148
|
+
const initRequest = {
|
|
149
|
+
command: types_1.CommandType.INIT_SESSION_STATE,
|
|
150
|
+
sessionId: 'return-test',
|
|
151
|
+
gameDirection: types_1.GameDirection.SINGLE,
|
|
152
|
+
betStake: 1,
|
|
153
|
+
};
|
|
154
|
+
let { state } = await engine.handleCommand(initRequest);
|
|
155
|
+
// Add a respin
|
|
156
|
+
state.privateState.pendingBonuses.respin = 1;
|
|
157
|
+
state.publicState.hasPendingBonus = true;
|
|
158
|
+
const spinRequest = {
|
|
159
|
+
command: types_1.CommandType.SPIN,
|
|
160
|
+
sessionId: 'return-test',
|
|
161
|
+
};
|
|
162
|
+
// Execute enough spins to complete the sequence
|
|
163
|
+
for (let i = 0; i < 10; i++) {
|
|
164
|
+
const result = await engine.handleCommand(spinRequest, state);
|
|
165
|
+
state = result.state;
|
|
166
|
+
}
|
|
167
|
+
// State should have a valid spin type (could be in any state depending on RNG)
|
|
168
|
+
expect(state.publicState.currentSpinType).toBeDefined();
|
|
169
|
+
expect(state.publicState.currentSpinType).toBeGreaterThanOrEqual(happy_panda_config_1.SpinType.PAID_SPIN);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
describe('Accumulated Bonus Wins', () => {
|
|
173
|
+
it('should accumulate wins during bonus sequence', async () => {
|
|
174
|
+
const mockRng = createMockRng([0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
|
175
|
+
const engine = new happy_panda_engine_1.HappyPandaEngine(mockRng);
|
|
176
|
+
const initRequest = {
|
|
177
|
+
command: types_1.CommandType.INIT_SESSION_STATE,
|
|
178
|
+
sessionId: 'accumulate-test',
|
|
179
|
+
gameDirection: types_1.GameDirection.SINGLE,
|
|
180
|
+
betStake: 1,
|
|
181
|
+
};
|
|
182
|
+
let { state } = await engine.handleCommand(initRequest);
|
|
183
|
+
// Initial accumulated should be 0
|
|
184
|
+
expect(state.privateState.accumulatedBonusWins).toBe(0);
|
|
185
|
+
});
|
|
186
|
+
it('should reset accumulated wins when sequence completes', async () => {
|
|
187
|
+
const mockRng = createMockRng([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
|
|
188
|
+
const engine = new happy_panda_engine_1.HappyPandaEngine(mockRng);
|
|
189
|
+
const initRequest = {
|
|
190
|
+
command: types_1.CommandType.INIT_SESSION_STATE,
|
|
191
|
+
sessionId: 'reset-test',
|
|
192
|
+
gameDirection: types_1.GameDirection.SINGLE,
|
|
193
|
+
betStake: 1,
|
|
194
|
+
};
|
|
195
|
+
let { state } = await engine.handleCommand(initRequest);
|
|
196
|
+
// After bonus completes and returns to paid spin, accumulated should be 0
|
|
197
|
+
expect(state.privateState.accumulatedBonusWins).toBe(0);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
describe('Bonus Spin Counts', () => {
|
|
201
|
+
it('should have correct spin count for jackpot bonus', () => {
|
|
202
|
+
expect(happy_panda_config_1.BONUS_JACKPOT.SPINS).toBe(1); // Per C++ model
|
|
203
|
+
});
|
|
204
|
+
it('should have correct spin count for cherry bonus', () => {
|
|
205
|
+
expect(happy_panda_config_1.BONUS_CHERRY.SPINS).toBe(1); // Per C++ model
|
|
206
|
+
});
|
|
207
|
+
it('should have correct spin count for bell bonus', () => {
|
|
208
|
+
expect(happy_panda_config_1.BONUS_BELL.SPINS).toBe(5);
|
|
209
|
+
});
|
|
210
|
+
it('should have correct spin count for bar1 bonus', () => {
|
|
211
|
+
expect(happy_panda_config_1.BONUS_BAR1.SPINS).toBe(7);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
describe('Center Cherry for Respin', () => {
|
|
215
|
+
it('should track center cherry symbol during respin', async () => {
|
|
216
|
+
const mockRng = createMockRng([0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
|
217
|
+
const engine = new happy_panda_engine_1.HappyPandaEngine(mockRng);
|
|
218
|
+
const initRequest = {
|
|
219
|
+
command: types_1.CommandType.INIT_SESSION_STATE,
|
|
220
|
+
sessionId: 'cherry-center-test',
|
|
221
|
+
gameDirection: types_1.GameDirection.SINGLE,
|
|
222
|
+
betStake: 1,
|
|
223
|
+
};
|
|
224
|
+
const { state } = await engine.handleCommand(initRequest);
|
|
225
|
+
// Initially no center cherry
|
|
226
|
+
expect(state.privateState.centerCherrySymbol).toBeNull();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
describe('Multiple Bonus Queue', () => {
|
|
230
|
+
it('should handle multiple pending bonuses', async () => {
|
|
231
|
+
const mockRng = createMockRng([0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
|
232
|
+
const engine = new happy_panda_engine_1.HappyPandaEngine(mockRng);
|
|
233
|
+
const initRequest = {
|
|
234
|
+
command: types_1.CommandType.INIT_SESSION_STATE,
|
|
235
|
+
sessionId: 'multi-bonus-test',
|
|
236
|
+
gameDirection: types_1.GameDirection.SINGLE,
|
|
237
|
+
betStake: 1,
|
|
238
|
+
};
|
|
239
|
+
let { state } = await engine.handleCommand(initRequest);
|
|
240
|
+
// Queue multiple bonuses
|
|
241
|
+
state.privateState.pendingBonuses.jackpot = 2;
|
|
242
|
+
state.privateState.pendingBonuses.cherry = 1;
|
|
243
|
+
state.publicState.hasPendingBonus = true;
|
|
244
|
+
// Total pending should be 3
|
|
245
|
+
const totalPending = state.privateState.pendingBonuses.jackpot +
|
|
246
|
+
state.privateState.pendingBonuses.cherry +
|
|
247
|
+
state.privateState.pendingBonuses.bell +
|
|
248
|
+
state.privateState.pendingBonuses.bar1 +
|
|
249
|
+
state.privateState.pendingBonuses.respin;
|
|
250
|
+
expect(totalPending).toBe(3);
|
|
251
|
+
});
|
|
252
|
+
it('should decrement pending count when bonus consumed', async () => {
|
|
253
|
+
const mockRng = createMockRng([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
|
|
254
|
+
const engine = new happy_panda_engine_1.HappyPandaEngine(mockRng);
|
|
255
|
+
const initRequest = {
|
|
256
|
+
command: types_1.CommandType.INIT_SESSION_STATE,
|
|
257
|
+
sessionId: 'consume-test',
|
|
258
|
+
gameDirection: types_1.GameDirection.SINGLE,
|
|
259
|
+
betStake: 1,
|
|
260
|
+
};
|
|
261
|
+
let { state } = await engine.handleCommand(initRequest);
|
|
262
|
+
// Add pending bonus
|
|
263
|
+
const initialPending = 2;
|
|
264
|
+
state.privateState.pendingBonuses.respin = initialPending;
|
|
265
|
+
state.publicState.hasPendingBonus = true;
|
|
266
|
+
const spinRequest = {
|
|
267
|
+
command: types_1.CommandType.SPIN,
|
|
268
|
+
sessionId: 'consume-test',
|
|
269
|
+
};
|
|
270
|
+
// Execute spin to consume bonus
|
|
271
|
+
const result = await engine.handleCommand(spinRequest, state);
|
|
272
|
+
state = result.state;
|
|
273
|
+
// Pending should be decremented (respins are all consumed at once)
|
|
274
|
+
expect(state.privateState.pendingBonuses.respin).toBeLessThanOrEqual(initialPending);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
describe('Bonus Win Tracking', () => {
|
|
278
|
+
it('should return jackpotWon in response', async () => {
|
|
279
|
+
const mockRng = createMockRng([0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
|
280
|
+
const engine = new happy_panda_engine_1.HappyPandaEngine(mockRng);
|
|
281
|
+
const initRequest = {
|
|
282
|
+
command: types_1.CommandType.INIT_SESSION_STATE,
|
|
283
|
+
sessionId: 'jackpot-win-test',
|
|
284
|
+
gameDirection: types_1.GameDirection.SINGLE,
|
|
285
|
+
betStake: 1,
|
|
286
|
+
};
|
|
287
|
+
let { state } = await engine.handleCommand(initRequest);
|
|
288
|
+
const spinRequest = {
|
|
289
|
+
command: types_1.CommandType.SPIN,
|
|
290
|
+
sessionId: 'jackpot-win-test',
|
|
291
|
+
};
|
|
292
|
+
const result = await engine.handleCommand(spinRequest, state);
|
|
293
|
+
const response = result.response;
|
|
294
|
+
expect(typeof response.jackpotWon).toBe('number');
|
|
295
|
+
expect(response.jackpotWon).toBeGreaterThanOrEqual(0);
|
|
296
|
+
});
|
|
297
|
+
it('should return poolJackpotWon in response', async () => {
|
|
298
|
+
const mockRng = createMockRng([0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
|
299
|
+
const engine = new happy_panda_engine_1.HappyPandaEngine(mockRng);
|
|
300
|
+
const initRequest = {
|
|
301
|
+
command: types_1.CommandType.INIT_SESSION_STATE,
|
|
302
|
+
sessionId: 'pool-win-test',
|
|
303
|
+
gameDirection: types_1.GameDirection.SINGLE,
|
|
304
|
+
betStake: 1,
|
|
305
|
+
};
|
|
306
|
+
let { state } = await engine.handleCommand(initRequest);
|
|
307
|
+
const spinRequest = {
|
|
308
|
+
command: types_1.CommandType.SPIN,
|
|
309
|
+
sessionId: 'pool-win-test',
|
|
310
|
+
};
|
|
311
|
+
const result = await engine.handleCommand(spinRequest, state);
|
|
312
|
+
const response = result.response;
|
|
313
|
+
expect(typeof response.poolJackpotWon).toBe('number');
|
|
314
|
+
expect(response.poolJackpotWon).toBeGreaterThanOrEqual(0);
|
|
315
|
+
});
|
|
316
|
+
it('should return bonusTriggered in response', async () => {
|
|
317
|
+
const mockRng = createMockRng([0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
|
318
|
+
const engine = new happy_panda_engine_1.HappyPandaEngine(mockRng);
|
|
319
|
+
const initRequest = {
|
|
320
|
+
command: types_1.CommandType.INIT_SESSION_STATE,
|
|
321
|
+
sessionId: 'trigger-test',
|
|
322
|
+
gameDirection: types_1.GameDirection.SINGLE,
|
|
323
|
+
betStake: 1,
|
|
324
|
+
};
|
|
325
|
+
let { state } = await engine.handleCommand(initRequest);
|
|
326
|
+
const spinRequest = {
|
|
327
|
+
command: types_1.CommandType.SPIN,
|
|
328
|
+
sessionId: 'trigger-test',
|
|
329
|
+
};
|
|
330
|
+
const result = await engine.handleCommand(spinRequest, state);
|
|
331
|
+
const response = result.response;
|
|
332
|
+
// Should be null or a valid spin type
|
|
333
|
+
expect(response.bonusTriggered === null || typeof response.bonusTriggered === 'number').toBe(true);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
//# sourceMappingURL=bonus-sequence.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bonus-sequence.test.js","sourceRoot":"","sources":["../../src/__tests__/bonus-sequence.test.ts"],"names":[],"mappings":"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"}
|