@omnitronix/bonnys-fortune-game-engine 1.7.2 → 1.8.0
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/dist/__tests__/base-game.test.js +17 -10
- package/dist/__tests__/base-game.test.js.map +1 -1
- package/dist/__tests__/bonus-handlers.test.js +15 -21
- package/dist/__tests__/bonus-handlers.test.js.map +1 -1
- package/dist/__tests__/bonus-meters.test.js.map +1 -1
- package/dist/__tests__/collect-feature-handler.test.js +30 -24
- package/dist/__tests__/collect-feature-handler.test.js.map +1 -1
- package/dist/__tests__/comprehensive.test.js.map +1 -1
- package/dist/__tests__/error-paths.test.js +11 -7
- package/dist/__tests__/error-paths.test.js.map +1 -1
- package/dist/__tests__/helpers/test-engine-factory.d.ts +72 -18
- package/dist/__tests__/helpers/test-engine-factory.js +32 -7
- package/dist/__tests__/helpers/test-engine-factory.js.map +1 -1
- package/dist/__tests__/integration-1000-spin.test.js +33 -37
- package/dist/__tests__/integration-1000-spin.test.js.map +1 -1
- package/dist/__tests__/rng-gli19-compliance.test.js.map +1 -1
- package/dist/__tests__/rng-security.test.js +0 -1
- package/dist/__tests__/rng-security.test.js.map +1 -1
- package/dist/__tests__/rtp-simulation.test.js +11 -10
- package/dist/__tests__/rtp-simulation.test.js.map +1 -1
- package/dist/__tests__/state-transitions.test.js +0 -1
- package/dist/__tests__/state-transitions.test.js.map +1 -1
- package/dist/__tests__/steering-fortune-handler.test.js +18 -14
- package/dist/__tests__/steering-fortune-handler.test.js.map +1 -1
- package/dist/__tests__/symbol-distribution.test.js +13 -16
- package/dist/__tests__/symbol-distribution.test.js.map +1 -1
- package/dist/__tests__/treasure-hunt-handler.test.js +23 -16
- package/dist/__tests__/treasure-hunt-handler.test.js.map +1 -1
- package/dist/__tests__/win-calculator.test.js +10 -7
- package/dist/__tests__/win-calculator.test.js.map +1 -1
- package/dist/bonnys-fortune-v1.game-engine.d.ts +3 -3
- package/dist/bonnys-fortune-v1.game-engine.js +8 -5
- package/dist/bonnys-fortune-v1.game-engine.js.map +1 -1
- package/dist/game-engine.interface.d.ts +1 -1
- package/dist/helpers/optional-boolean-mapper.d.ts +1 -1
- package/dist/helpers/validation-helper.d.ts +1 -1
- package/dist/logic/bonnys-fortune.game-logic.js +2 -0
- package/dist/logic/bonnys-fortune.game-logic.js.map +1 -1
- package/dist/logic/bonnys-fortune.types.d.ts +7 -1
- package/dist/logic/handlers/collect-feature-bonus.handler.js.map +1 -1
- package/dist/logic/handlers/steering-to-the-fortune-bonus.handler.js +6 -5
- package/dist/logic/handlers/steering-to-the-fortune-bonus.handler.js.map +1 -1
- package/dist/logic/handlers/treasure-hunt-bonus.handler.js.map +1 -1
- package/dist/rng/rng-client.factory.js +3 -3
- package/dist/rng/rng-client.factory.js.map +1 -1
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js.map +1 -1
- package/dist/validation/custom-decorators/IsNestedIntArray.js.map +1 -1
- package/dist/validation/game-logic-config-validation.service.d.ts +1 -1
- package/dist/validation/game-logic-config-validation.service.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/base-game.test.ts +26 -17
- package/src/__tests__/bonus-handlers.test.ts +16 -21
- package/src/__tests__/bonus-meters.test.ts +13 -8
- package/src/__tests__/collect-feature-handler.test.ts +39 -26
- package/src/__tests__/comprehensive.test.ts +40 -36
- package/src/__tests__/error-paths.test.ts +29 -18
- package/src/__tests__/helpers/test-engine-factory.ts +127 -40
- package/src/__tests__/integration-1000-spin.test.ts +53 -48
- package/src/__tests__/rng-gli19-compliance.test.ts +17 -17
- package/src/__tests__/rng-security.test.ts +11 -8
- package/src/__tests__/rtp-simulation.test.ts +17 -10
- package/src/__tests__/state-transitions.test.ts +8 -4
- package/src/__tests__/steering-fortune-handler.test.ts +30 -18
- package/src/__tests__/symbol-distribution.test.ts +19 -16
- package/src/__tests__/treasure-hunt-handler.test.ts +36 -22
- package/src/__tests__/win-calculator.test.ts +14 -10
- package/src/bonnys-fortune-v1.game-engine.ts +18 -8
- package/src/domain/types/game-symbols.response.dto.ts +1 -1
- package/src/game-engine.interface.ts +1 -1
- package/src/helpers/validation-helper.ts +1 -1
- package/src/logic/bonnys-fortune.game-logic.ts +3 -0
- package/src/logic/bonnys-fortune.types.ts +10 -1
- package/src/logic/handlers/collect-feature-bonus.handler.ts +5 -2
- package/src/logic/handlers/steering-to-the-fortune-bonus.handler.ts +7 -6
- package/src/logic/handlers/treasure-hunt-bonus.handler.ts +2 -2
- package/src/rng/rng-client.factory.ts +3 -3
- package/src/validation/bonnys-fortune/bonnys-fortune-config.dto.ts +72 -72
- package/src/validation/custom-decorators/IsNestedIntArray.ts +1 -1
- package/src/validation/game-logic-config-validation.service.ts +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Base Game Logic Tests
|
|
2
|
+
* Base Game Logic Tests -- Bonnys Fortune
|
|
3
3
|
*
|
|
4
4
|
* Tests the core base game mechanics:
|
|
5
5
|
* - Screen generation (5x3 grid, 243 ways)
|
|
@@ -20,9 +20,11 @@ import {
|
|
|
20
20
|
nextCommandId,
|
|
21
21
|
resetCommandIdCounter,
|
|
22
22
|
validateRngOutcome,
|
|
23
|
+
getOutcome,
|
|
23
24
|
} from './helpers/test-engine-factory';
|
|
25
|
+
import { MeterState } from '../logic/bonnys-fortune.types';
|
|
24
26
|
|
|
25
|
-
describe('Base Game Logic
|
|
27
|
+
describe('Base Game Logic -- Bonnys Fortune', () => {
|
|
26
28
|
beforeEach(() => {
|
|
27
29
|
resetCommandIdCounter();
|
|
28
30
|
});
|
|
@@ -35,10 +37,11 @@ describe('Base Game Logic — Bonnys Fortune', () => {
|
|
|
35
37
|
const result = await executeSpin(engine, session.publicState, session.privateState);
|
|
36
38
|
expect(result.success).toBe(true);
|
|
37
39
|
|
|
38
|
-
const
|
|
40
|
+
const outcome = getOutcome(result);
|
|
41
|
+
const screen = outcome.result?.screen ?? outcome.screen;
|
|
39
42
|
expect(screen).toBeDefined();
|
|
40
|
-
expect(screen
|
|
41
|
-
for (const reel of screen) {
|
|
43
|
+
expect(screen!.length).toBe(5);
|
|
44
|
+
for (const reel of screen!) {
|
|
42
45
|
expect(reel.length).toBe(3);
|
|
43
46
|
}
|
|
44
47
|
});
|
|
@@ -50,8 +53,9 @@ describe('Base Game Logic — Bonnys Fortune', () => {
|
|
|
50
53
|
const result = await executeSpin(engine, session.publicState, session.privateState);
|
|
51
54
|
expect(result.success).toBe(true);
|
|
52
55
|
|
|
53
|
-
const
|
|
54
|
-
|
|
56
|
+
const outcome = getOutcome(result);
|
|
57
|
+
const screen = outcome.result?.screen ?? outcome.screen;
|
|
58
|
+
for (const reel of screen!) {
|
|
55
59
|
for (const symbol of reel) {
|
|
56
60
|
expect(typeof symbol).toBe('number');
|
|
57
61
|
expect(symbol).toBeGreaterThanOrEqual(0);
|
|
@@ -71,7 +75,7 @@ describe('Base Game Logic — Bonnys Fortune', () => {
|
|
|
71
75
|
|
|
72
76
|
for (let i = 0; i < 50; i++) {
|
|
73
77
|
if (priv.nextSpinType !== 'BASE_GAME_SPIN') {
|
|
74
|
-
// Bonus triggered
|
|
78
|
+
// Bonus triggered -- complete it via extractFinalBonusState
|
|
75
79
|
if (priv.pendingBonuses?.length > 0) {
|
|
76
80
|
const bonus = priv.pendingBonuses[0];
|
|
77
81
|
const bonusResult = await startBonusRound(engine, pub, priv, bonus.bonusType);
|
|
@@ -92,7 +96,8 @@ describe('Base Game Logic — Bonnys Fortune', () => {
|
|
|
92
96
|
|
|
93
97
|
const result = await executeSpin(engine, pub, priv);
|
|
94
98
|
expect(result.success).toBe(true);
|
|
95
|
-
const
|
|
99
|
+
const outcome = getOutcome(result);
|
|
100
|
+
const screen = outcome.result?.screen ?? outcome.screen ?? result.privateState.screen;
|
|
96
101
|
screens.push(JSON.stringify(screen));
|
|
97
102
|
|
|
98
103
|
pub = result.publicState;
|
|
@@ -112,7 +117,8 @@ describe('Base Game Logic — Bonnys Fortune', () => {
|
|
|
112
117
|
const result = await executeSpin(engine, session.publicState, session.privateState);
|
|
113
118
|
expect(result.success).toBe(true);
|
|
114
119
|
|
|
115
|
-
const
|
|
120
|
+
const outcome = getOutcome(result);
|
|
121
|
+
const playerWinning = outcome.result?.playerWinning ?? outcome.playerWinning;
|
|
116
122
|
if (playerWinning !== undefined) {
|
|
117
123
|
expect(playerWinning).toBeGreaterThanOrEqual(0);
|
|
118
124
|
expect(typeof playerWinning).toBe('number');
|
|
@@ -132,7 +138,8 @@ describe('Base Game Logic — Bonnys Fortune', () => {
|
|
|
132
138
|
const result = await executeSpin(engine, pub, priv);
|
|
133
139
|
expect(result.success).toBe(true);
|
|
134
140
|
|
|
135
|
-
const
|
|
141
|
+
const outcome = getOutcome(result);
|
|
142
|
+
const playerWinning = outcome.result?.playerWinning ?? outcome.playerWinning;
|
|
136
143
|
if (playerWinning !== undefined) {
|
|
137
144
|
expect(Number.isFinite(playerWinning)).toBe(true);
|
|
138
145
|
}
|
|
@@ -159,7 +166,7 @@ describe('Base Game Logic — Bonnys Fortune', () => {
|
|
|
159
166
|
|
|
160
167
|
const meters = session.publicState.bonusMeters;
|
|
161
168
|
for (const [, meter] of Object.entries(meters || {})) {
|
|
162
|
-
const m = meter as
|
|
169
|
+
const m = meter as MeterState;
|
|
163
170
|
expect(m.threshold).toBeGreaterThan(0);
|
|
164
171
|
expect(m.progress).toBeGreaterThanOrEqual(0);
|
|
165
172
|
expect(typeof m.threshold).toBe('number');
|
|
@@ -183,7 +190,7 @@ describe('Base Game Logic — Bonnys Fortune', () => {
|
|
|
183
190
|
|
|
184
191
|
// Verify meter structure integrity
|
|
185
192
|
for (const [, meter] of Object.entries(result.publicState.bonusMeters || {})) {
|
|
186
|
-
const m = meter as
|
|
193
|
+
const m = meter as MeterState;
|
|
187
194
|
expect(m.threshold).toBeGreaterThan(0);
|
|
188
195
|
expect(m.progress).toBeGreaterThanOrEqual(0);
|
|
189
196
|
expect(m.progress).toBeLessThanOrEqual(m.threshold);
|
|
@@ -208,7 +215,7 @@ describe('Base Game Logic — Bonnys Fortune', () => {
|
|
|
208
215
|
expect(result.success).toBe(true);
|
|
209
216
|
|
|
210
217
|
for (const [, meter] of Object.entries(result.publicState.bonusMeters || {})) {
|
|
211
|
-
const m = meter as
|
|
218
|
+
const m = meter as MeterState;
|
|
212
219
|
expect(m.progress).toBeLessThanOrEqual(m.threshold);
|
|
213
220
|
}
|
|
214
221
|
|
|
@@ -236,7 +243,7 @@ describe('Base Game Logic — Bonnys Fortune', () => {
|
|
|
236
243
|
expect(result.success).toBe(true);
|
|
237
244
|
expect(result.rngOutcome).toBeDefined();
|
|
238
245
|
|
|
239
|
-
const validation = validateRngOutcome(result.rngOutcome);
|
|
246
|
+
const validation = validateRngOutcome(result.rngOutcome!);
|
|
240
247
|
expect(validation.valid).toBe(true);
|
|
241
248
|
});
|
|
242
249
|
});
|
|
@@ -288,8 +295,10 @@ describe('Base Game Logic — Bonnys Fortune', () => {
|
|
|
288
295
|
expect(result1.success).toBe(true);
|
|
289
296
|
expect(result2.success).toBe(true);
|
|
290
297
|
|
|
291
|
-
const
|
|
292
|
-
const
|
|
298
|
+
const outcome1 = getOutcome(result1);
|
|
299
|
+
const outcome2 = getOutcome(result2);
|
|
300
|
+
const screen1 = JSON.stringify(outcome1.screen || result1.privateState.screen);
|
|
301
|
+
const screen2 = JSON.stringify(outcome2.screen || result2.privateState.screen);
|
|
293
302
|
expect(screen1).toBe(screen2);
|
|
294
303
|
});
|
|
295
304
|
});
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
debugTriggerBonus,
|
|
19
19
|
nextCommandId,
|
|
20
20
|
resetCommandIdCounter,
|
|
21
|
+
getOutcome,
|
|
21
22
|
} from './helpers/test-engine-factory';
|
|
22
23
|
|
|
23
24
|
describe('Bonus Handlers — Bonnys Fortune', () => {
|
|
@@ -93,13 +94,11 @@ describe('Bonus Handlers — Bonnys Fortune', () => {
|
|
|
93
94
|
|
|
94
95
|
expect(bonusResult.success).toBe(true);
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (o.playerWinning !== undefined) {
|
|
102
|
-
expect(o.playerWinning).toBeGreaterThanOrEqual(0);
|
|
97
|
+
const outcome = getOutcome(bonusResult);
|
|
98
|
+
if (outcome.results) {
|
|
99
|
+
for (const r of outcome.results) {
|
|
100
|
+
if (r.result?.playerWinning !== undefined) {
|
|
101
|
+
expect(r.result.playerWinning).toBeGreaterThanOrEqual(0);
|
|
103
102
|
}
|
|
104
103
|
}
|
|
105
104
|
}
|
|
@@ -201,13 +200,11 @@ describe('Bonus Handlers — Bonnys Fortune', () => {
|
|
|
201
200
|
|
|
202
201
|
expect(bonusResult.success).toBe(true);
|
|
203
202
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (o.playerWinning !== undefined) {
|
|
210
|
-
expect(o.playerWinning).toBeGreaterThanOrEqual(0);
|
|
203
|
+
const outcome = getOutcome(bonusResult);
|
|
204
|
+
if (outcome.results) {
|
|
205
|
+
for (const r of outcome.results) {
|
|
206
|
+
if (r.result?.playerWinning !== undefined) {
|
|
207
|
+
expect(r.result.playerWinning).toBeGreaterThanOrEqual(0);
|
|
211
208
|
}
|
|
212
209
|
}
|
|
213
210
|
}
|
|
@@ -364,13 +361,11 @@ describe('Bonus Handlers — Bonnys Fortune', () => {
|
|
|
364
361
|
|
|
365
362
|
expect(bonusResult.success).toBe(true);
|
|
366
363
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (o.playerWinning !== undefined) {
|
|
373
|
-
expect(o.playerWinning).toBeGreaterThanOrEqual(0);
|
|
364
|
+
const outcome = getOutcome(bonusResult);
|
|
365
|
+
if (outcome.results) {
|
|
366
|
+
for (const r of outcome.results) {
|
|
367
|
+
if (r.result?.playerWinning !== undefined) {
|
|
368
|
+
expect(r.result.playerWinning).toBeGreaterThanOrEqual(0);
|
|
374
369
|
}
|
|
375
370
|
}
|
|
376
371
|
}
|
|
@@ -22,9 +22,14 @@ import {
|
|
|
22
22
|
resetCommandIdCounter,
|
|
23
23
|
MockRngClient,
|
|
24
24
|
injectMockRng,
|
|
25
|
+
BonnysFortuneCommandResult,
|
|
25
26
|
} from './helpers/test-engine-factory';
|
|
26
27
|
import { gameLogicConfig } from '../config/game-logic-config/game-logic-config';
|
|
27
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
MeterState,
|
|
30
|
+
BonnysFortunePublicState,
|
|
31
|
+
BonnysFortunePrivateState,
|
|
32
|
+
} from '../logic/bonnys-fortune.types';
|
|
28
33
|
import { BonnysFortuneV1GameEngine } from '../bonnys-fortune-v1.game-engine';
|
|
29
34
|
|
|
30
35
|
/**
|
|
@@ -33,11 +38,11 @@ import { BonnysFortuneV1GameEngine } from '../bonnys-fortune-v1.game-engine';
|
|
|
33
38
|
*/
|
|
34
39
|
async function updateBonusMeterProgress(
|
|
35
40
|
engine: BonnysFortuneV1GameEngine,
|
|
36
|
-
publicState:
|
|
37
|
-
privateState:
|
|
41
|
+
publicState: BonnysFortunePublicState,
|
|
42
|
+
privateState: BonnysFortunePrivateState,
|
|
38
43
|
bonusType: string,
|
|
39
44
|
progress: number,
|
|
40
|
-
): Promise<
|
|
45
|
+
): Promise<BonnysFortuneCommandResult> {
|
|
41
46
|
return engine.processCommand(publicState, privateState, {
|
|
42
47
|
id: nextCommandId('debug-meter'),
|
|
43
48
|
type: 'DEBUG_UPDATE_BONUS_METER_PROGRESS',
|
|
@@ -335,12 +340,12 @@ describe('Bonus Meters - Bonnys Fortune', () => {
|
|
|
335
340
|
expect(result.success).toBe(true);
|
|
336
341
|
|
|
337
342
|
// Meters for bet amount 2.0 should be unchanged (thresholds same)
|
|
338
|
-
const afterMeters2Thresholds = Object.values(
|
|
343
|
+
const afterMeters2Thresholds = (Object.values(
|
|
339
344
|
result.privateState.bonusMetersByAmount[2.0] || {},
|
|
340
|
-
).map((m
|
|
341
|
-
const initialMeters2Thresholds = Object.values(
|
|
345
|
+
) as MeterState[]).map((m) => m.threshold);
|
|
346
|
+
const initialMeters2Thresholds = (Object.values(
|
|
342
347
|
JSON.parse(initialMeters2) || {},
|
|
343
|
-
).map((m
|
|
348
|
+
) as MeterState[]).map((m) => m.threshold);
|
|
344
349
|
|
|
345
350
|
expect(afterMeters2Thresholds).toEqual(initialMeters2Thresholds);
|
|
346
351
|
}
|
|
@@ -19,8 +19,19 @@ import {
|
|
|
19
19
|
nextCommandId,
|
|
20
20
|
resetCommandIdCounter,
|
|
21
21
|
MockRngClient,
|
|
22
|
+
BonnysFortuneCommandResult,
|
|
22
23
|
} from './helpers/test-engine-factory';
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
SpinType,
|
|
26
|
+
SpinResultResponse,
|
|
27
|
+
CollectFeatureSpinResult,
|
|
28
|
+
BonusRoundResult,
|
|
29
|
+
} from '../logic/bonnys-fortune.types';
|
|
30
|
+
|
|
31
|
+
/** Helper to extract collect feature results from a bonus command result */
|
|
32
|
+
function getCollectResults(result: BonnysFortuneCommandResult): SpinResultResponse<CollectFeatureSpinResult>[] {
|
|
33
|
+
return (result.outcome as unknown as BonusRoundResult<CollectFeatureSpinResult>).results;
|
|
34
|
+
}
|
|
24
35
|
|
|
25
36
|
describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
26
37
|
beforeEach(() => {
|
|
@@ -50,11 +61,12 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
50
61
|
);
|
|
51
62
|
|
|
52
63
|
expect(bonusResult.success).toBe(true);
|
|
53
|
-
|
|
54
|
-
expect(
|
|
64
|
+
const results = getCollectResults(bonusResult);
|
|
65
|
+
expect(results).toBeDefined();
|
|
66
|
+
expect(results.length).toBeGreaterThan(0);
|
|
55
67
|
|
|
56
68
|
// Each spin should have symbols array
|
|
57
|
-
for (const spin of
|
|
69
|
+
for (const spin of results) {
|
|
58
70
|
expect(spin.result.symbols).toBeDefined();
|
|
59
71
|
expect(Array.isArray(spin.result.symbols)).toBe(true);
|
|
60
72
|
}
|
|
@@ -84,7 +96,8 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
84
96
|
expect(bonusResult.success).toBe(true);
|
|
85
97
|
|
|
86
98
|
// Check symbol naming convention
|
|
87
|
-
|
|
99
|
+
const results = getCollectResults(bonusResult);
|
|
100
|
+
for (const spin of results) {
|
|
88
101
|
for (const symbol of spin.result.symbols) {
|
|
89
102
|
// Symbols should follow CV (value) or CM (multiplier) pattern
|
|
90
103
|
expect(symbol).toMatch(/^(CV|CM)\d+$/);
|
|
@@ -115,7 +128,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
115
128
|
|
|
116
129
|
expect(bonusResult.success).toBe(true);
|
|
117
130
|
|
|
118
|
-
for (const spin of bonusResult
|
|
131
|
+
for (const spin of getCollectResults(bonusResult)) {
|
|
119
132
|
expect(spin.result.slashed).toBeDefined();
|
|
120
133
|
expect(Array.isArray(spin.result.slashed)).toBe(true);
|
|
121
134
|
// slashed should match symbols length
|
|
@@ -151,8 +164,8 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
151
164
|
expect(bonusResult.success).toBe(true);
|
|
152
165
|
|
|
153
166
|
// Verify sequential spinIndex
|
|
154
|
-
for (let i = 0; i < bonusResult.
|
|
155
|
-
expect(bonusResult
|
|
167
|
+
for (let i = 0; i < getCollectResults(bonusResult).length; i++) {
|
|
168
|
+
expect(getCollectResults(bonusResult)[i].result.spinIndex).toBe(i);
|
|
156
169
|
}
|
|
157
170
|
});
|
|
158
171
|
});
|
|
@@ -181,7 +194,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
181
194
|
|
|
182
195
|
expect(bonusResult.success).toBe(true);
|
|
183
196
|
|
|
184
|
-
for (const spin of bonusResult
|
|
197
|
+
for (const spin of getCollectResults(bonusResult)) {
|
|
185
198
|
expect(spin.result.currentValues).toBeDefined();
|
|
186
199
|
expect(typeof spin.result.currentValues).toBe('number');
|
|
187
200
|
expect(spin.result.currentValues).toBeGreaterThanOrEqual(0);
|
|
@@ -213,7 +226,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
213
226
|
|
|
214
227
|
// totalValues should accumulate monotonically
|
|
215
228
|
let prevTotal = 0;
|
|
216
|
-
for (const spin of bonusResult
|
|
229
|
+
for (const spin of getCollectResults(bonusResult)) {
|
|
217
230
|
expect(spin.result.totalValues).toBeGreaterThanOrEqual(prevTotal);
|
|
218
231
|
prevTotal = spin.result.totalValues;
|
|
219
232
|
}
|
|
@@ -244,7 +257,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
244
257
|
|
|
245
258
|
// Verify accumulation
|
|
246
259
|
let calculatedTotal = 0;
|
|
247
|
-
for (const spin of bonusResult
|
|
260
|
+
for (const spin of getCollectResults(bonusResult)) {
|
|
248
261
|
calculatedTotal += spin.result.currentValues;
|
|
249
262
|
expect(spin.result.totalValues).toBeCloseTo(calculatedTotal, 2);
|
|
250
263
|
}
|
|
@@ -275,7 +288,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
275
288
|
|
|
276
289
|
expect(bonusResult.success).toBe(true);
|
|
277
290
|
|
|
278
|
-
for (const spin of bonusResult
|
|
291
|
+
for (const spin of getCollectResults(bonusResult)) {
|
|
279
292
|
expect(spin.result.currentMultipliers).toBeDefined();
|
|
280
293
|
expect(typeof spin.result.currentMultipliers).toBe('number');
|
|
281
294
|
expect(spin.result.currentMultipliers).toBeGreaterThanOrEqual(0);
|
|
@@ -307,7 +320,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
307
320
|
|
|
308
321
|
// totalMultipliers should accumulate or stay constant
|
|
309
322
|
let prevTotal = 0;
|
|
310
|
-
for (const spin of bonusResult
|
|
323
|
+
for (const spin of getCollectResults(bonusResult)) {
|
|
311
324
|
expect(spin.result.totalMultipliers).toBeGreaterThanOrEqual(prevTotal);
|
|
312
325
|
prevTotal = spin.result.totalMultipliers;
|
|
313
326
|
}
|
|
@@ -337,7 +350,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
337
350
|
expect(bonusResult.success).toBe(true);
|
|
338
351
|
|
|
339
352
|
// First spin should have at least 1 as base multiplier
|
|
340
|
-
const firstSpin = bonusResult
|
|
353
|
+
const firstSpin = getCollectResults(bonusResult)[0];
|
|
341
354
|
expect(firstSpin.result.totalMultipliers).toBeGreaterThanOrEqual(1);
|
|
342
355
|
});
|
|
343
356
|
});
|
|
@@ -367,8 +380,8 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
367
380
|
expect(bonusResult.success).toBe(true);
|
|
368
381
|
|
|
369
382
|
// spinsLeft should decrement
|
|
370
|
-
for (let i = 0; i < bonusResult.
|
|
371
|
-
const spin = bonusResult
|
|
383
|
+
for (let i = 0; i < getCollectResults(bonusResult).length; i++) {
|
|
384
|
+
const spin = getCollectResults(bonusResult)[i];
|
|
372
385
|
expect(spin.result.spinsLeft).toBeDefined();
|
|
373
386
|
expect(spin.result.spinsLeft).toBeGreaterThanOrEqual(0);
|
|
374
387
|
}
|
|
@@ -398,7 +411,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
398
411
|
expect(bonusResult.success).toBe(true);
|
|
399
412
|
|
|
400
413
|
const lastSpin =
|
|
401
|
-
bonusResult
|
|
414
|
+
getCollectResults(bonusResult)[getCollectResults(bonusResult).length - 1];
|
|
402
415
|
expect(lastSpin.result.spinsLeft).toBe(0);
|
|
403
416
|
expect(lastSpin.result.nextSpinType).toBe(SpinType.BASE_GAME_SPIN);
|
|
404
417
|
});
|
|
@@ -427,8 +440,8 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
427
440
|
expect(bonusResult.success).toBe(true);
|
|
428
441
|
|
|
429
442
|
// All spins except last should have COLLECT_FEATURE_SPIN as next
|
|
430
|
-
for (let i = 0; i < bonusResult.
|
|
431
|
-
const spin = bonusResult
|
|
443
|
+
for (let i = 0; i < getCollectResults(bonusResult).length - 1; i++) {
|
|
444
|
+
const spin = getCollectResults(bonusResult)[i];
|
|
432
445
|
if (spin.result.spinsLeft > 0) {
|
|
433
446
|
expect(spin.result.nextSpinType).toBe(SpinType.COLLECT_FEATURE_SPIN);
|
|
434
447
|
}
|
|
@@ -460,7 +473,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
460
473
|
|
|
461
474
|
expect(bonusResult.success).toBe(true);
|
|
462
475
|
|
|
463
|
-
for (const spin of bonusResult
|
|
476
|
+
for (const spin of getCollectResults(bonusResult)) {
|
|
464
477
|
expect(spin.result.playerWinning).toBeDefined();
|
|
465
478
|
expect(typeof spin.result.playerWinning).toBe('number');
|
|
466
479
|
}
|
|
@@ -491,7 +504,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
491
504
|
|
|
492
505
|
// totalWin should accumulate
|
|
493
506
|
let prevTotal = 0;
|
|
494
|
-
for (const spin of bonusResult
|
|
507
|
+
for (const spin of getCollectResults(bonusResult)) {
|
|
495
508
|
expect(spin.result.totalWin).toBeGreaterThanOrEqual(prevTotal);
|
|
496
509
|
prevTotal = spin.result.totalWin;
|
|
497
510
|
}
|
|
@@ -526,7 +539,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
526
539
|
// totalWin should be proportional to totalValues * totalMultipliers
|
|
527
540
|
// (actual formula includes betStake derived from config)
|
|
528
541
|
const lastSpin =
|
|
529
|
-
bonusResult
|
|
542
|
+
getCollectResults(bonusResult)[getCollectResults(bonusResult).length - 1];
|
|
530
543
|
const baseProduct =
|
|
531
544
|
lastSpin.result.totalValues * lastSpin.result.totalMultipliers;
|
|
532
545
|
|
|
@@ -565,7 +578,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
565
578
|
expect(bonusResult.success).toBe(true);
|
|
566
579
|
|
|
567
580
|
const lastSpin =
|
|
568
|
-
bonusResult
|
|
581
|
+
getCollectResults(bonusResult)[getCollectResults(bonusResult).length - 1];
|
|
569
582
|
expect(lastSpin.result.totalWin).toBeGreaterThanOrEqual(0);
|
|
570
583
|
});
|
|
571
584
|
|
|
@@ -599,7 +612,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
599
612
|
|
|
600
613
|
if (bonusResult.success) {
|
|
601
614
|
const lastSpin =
|
|
602
|
-
bonusResult
|
|
615
|
+
getCollectResults(bonusResult)[getCollectResults(bonusResult).length - 1];
|
|
603
616
|
totalWins.push(lastSpin.result.totalWin);
|
|
604
617
|
}
|
|
605
618
|
}
|
|
@@ -696,7 +709,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
696
709
|
|
|
697
710
|
describe('RNG Determinism', () => {
|
|
698
711
|
it('should produce consistent results with same LCG seed', async () => {
|
|
699
|
-
const results:
|
|
712
|
+
const results: SpinResultResponse<CollectFeatureSpinResult>[][] = [];
|
|
700
713
|
|
|
701
714
|
for (let i = 0; i < 2; i++) {
|
|
702
715
|
resetCommandIdCounter();
|
|
@@ -720,7 +733,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
|
|
|
720
733
|
bonus.bonusType,
|
|
721
734
|
);
|
|
722
735
|
|
|
723
|
-
results.push(bonusResult
|
|
736
|
+
results.push(getCollectResults(bonusResult));
|
|
724
737
|
}
|
|
725
738
|
|
|
726
739
|
expect(results.length).toBe(2);
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
|
|
17
17
|
import { BonnysFortuneV1GameEngine } from '../bonnys-fortune-v1.game-engine';
|
|
18
18
|
import { GameActionCommand } from '../game-engine.interface';
|
|
19
|
+
import {
|
|
20
|
+
BonnysFortunePublicState,
|
|
21
|
+
BonnysFortunePrivateState,
|
|
22
|
+
} from '../logic/bonnys-fortune.types';
|
|
19
23
|
|
|
20
24
|
describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
21
25
|
let engine: BonnysFortuneV1GameEngine;
|
|
@@ -38,7 +42,7 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
38
42
|
},
|
|
39
43
|
};
|
|
40
44
|
|
|
41
|
-
const result = await engine.processCommand(null, null, command);
|
|
45
|
+
const result = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, command);
|
|
42
46
|
|
|
43
47
|
expect(result.success).toBe(true);
|
|
44
48
|
expect(result.publicState).toBeDefined();
|
|
@@ -46,7 +50,7 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
46
50
|
});
|
|
47
51
|
|
|
48
52
|
it('should initialize with bonus meters', async () => {
|
|
49
|
-
const result = await engine.processCommand(null, null, {
|
|
53
|
+
const result = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
|
|
50
54
|
id: 'init-meters',
|
|
51
55
|
type: 'INIT_SESSION_STATE',
|
|
52
56
|
payload: { betAmount: 1.0 },
|
|
@@ -60,7 +64,7 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
60
64
|
const betAmounts = [0.5, 1.0, 2.0, 5.0, 10.0];
|
|
61
65
|
|
|
62
66
|
for (const betAmount of betAmounts) {
|
|
63
|
-
const result = await engine.processCommand(null, null, {
|
|
67
|
+
const result = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
|
|
64
68
|
id: `init-${betAmount}`,
|
|
65
69
|
type: 'INIT_SESSION_STATE',
|
|
66
70
|
payload: { betAmount },
|
|
@@ -84,11 +88,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
84
88
|
// BASE GAME SPINS
|
|
85
89
|
// ============================================================
|
|
86
90
|
describe('Base Game Spins', () => {
|
|
87
|
-
let publicState:
|
|
88
|
-
let privateState:
|
|
91
|
+
let publicState: BonnysFortunePublicState;
|
|
92
|
+
let privateState: BonnysFortunePrivateState;
|
|
89
93
|
|
|
90
94
|
beforeEach(async () => {
|
|
91
|
-
const initResult = await engine.processCommand(null, null, {
|
|
95
|
+
const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
|
|
92
96
|
id: 'init',
|
|
93
97
|
type: 'INIT_SESSION_STATE',
|
|
94
98
|
payload: { betAmount: 1.0 },
|
|
@@ -176,11 +180,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
176
180
|
// BONUS GAME 1 - TREASURE HUNT
|
|
177
181
|
// ============================================================
|
|
178
182
|
describe('bonusGame1 (Treasure Hunt) Flow', () => {
|
|
179
|
-
let publicState:
|
|
180
|
-
let privateState:
|
|
183
|
+
let publicState: BonnysFortunePublicState;
|
|
184
|
+
let privateState: BonnysFortunePrivateState;
|
|
181
185
|
|
|
182
186
|
beforeEach(async () => {
|
|
183
|
-
const initResult = await engine.processCommand(null, null, {
|
|
187
|
+
const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
|
|
184
188
|
id: 'init',
|
|
185
189
|
type: 'INIT_SESSION_STATE',
|
|
186
190
|
payload: { betAmount: 1.0 },
|
|
@@ -262,11 +266,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
262
266
|
// BONUS GAME 2 - FREE SPINS
|
|
263
267
|
// ============================================================
|
|
264
268
|
describe('bonusGame2 (Free Spins) Flow', () => {
|
|
265
|
-
let publicState:
|
|
266
|
-
let privateState:
|
|
269
|
+
let publicState: BonnysFortunePublicState;
|
|
270
|
+
let privateState: BonnysFortunePrivateState;
|
|
267
271
|
|
|
268
272
|
beforeEach(async () => {
|
|
269
|
-
const initResult = await engine.processCommand(null, null, {
|
|
273
|
+
const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
|
|
270
274
|
id: 'init',
|
|
271
275
|
type: 'INIT_SESSION_STATE',
|
|
272
276
|
payload: { betAmount: 1.0 },
|
|
@@ -327,11 +331,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
327
331
|
// BONUS GAME 3 - STEERING TO THE FORTUNE
|
|
328
332
|
// ============================================================
|
|
329
333
|
describe('bonusGame3 (Steering to the Fortune) Flow', () => {
|
|
330
|
-
let publicState:
|
|
331
|
-
let privateState:
|
|
334
|
+
let publicState: BonnysFortunePublicState;
|
|
335
|
+
let privateState: BonnysFortunePrivateState;
|
|
332
336
|
|
|
333
337
|
beforeEach(async () => {
|
|
334
|
-
const initResult = await engine.processCommand(null, null, {
|
|
338
|
+
const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
|
|
335
339
|
id: 'init',
|
|
336
340
|
type: 'INIT_SESSION_STATE',
|
|
337
341
|
payload: { betAmount: 1.0 },
|
|
@@ -392,11 +396,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
392
396
|
// COLLECT FEATURE
|
|
393
397
|
// ============================================================
|
|
394
398
|
describe('collectFeature Flow', () => {
|
|
395
|
-
let publicState:
|
|
396
|
-
let privateState:
|
|
399
|
+
let publicState: BonnysFortunePublicState;
|
|
400
|
+
let privateState: BonnysFortunePrivateState;
|
|
397
401
|
|
|
398
402
|
beforeEach(async () => {
|
|
399
|
-
const initResult = await engine.processCommand(null, null, {
|
|
403
|
+
const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
|
|
400
404
|
id: 'init',
|
|
401
405
|
type: 'INIT_SESSION_STATE',
|
|
402
406
|
payload: { betAmount: 1.0 },
|
|
@@ -423,7 +427,7 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
423
427
|
const betAmounts = [1.0, 2.0, 5.0];
|
|
424
428
|
|
|
425
429
|
for (const betAmount of betAmounts) {
|
|
426
|
-
const init = await engine.processCommand(null, null, {
|
|
430
|
+
const init = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
|
|
427
431
|
id: `init-${betAmount}`,
|
|
428
432
|
type: 'INIT_SESSION_STATE',
|
|
429
433
|
payload: { betAmount },
|
|
@@ -452,11 +456,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
452
456
|
// BONUS METER MANIPULATION
|
|
453
457
|
// ============================================================
|
|
454
458
|
describe('Bonus Meter Manipulation', () => {
|
|
455
|
-
let publicState:
|
|
456
|
-
let privateState:
|
|
459
|
+
let publicState: BonnysFortunePublicState;
|
|
460
|
+
let privateState: BonnysFortunePrivateState;
|
|
457
461
|
|
|
458
462
|
beforeEach(async () => {
|
|
459
|
-
const initResult = await engine.processCommand(null, null, {
|
|
463
|
+
const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
|
|
460
464
|
id: 'init',
|
|
461
465
|
type: 'INIT_SESSION_STATE',
|
|
462
466
|
payload: { betAmount: 1.0 },
|
|
@@ -509,11 +513,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
509
513
|
// SPIN/BONUS COMBINATIONS
|
|
510
514
|
// ============================================================
|
|
511
515
|
describe('Spin/Bonus Combinations', () => {
|
|
512
|
-
let publicState:
|
|
513
|
-
let privateState:
|
|
516
|
+
let publicState: BonnysFortunePublicState;
|
|
517
|
+
let privateState: BonnysFortunePrivateState;
|
|
514
518
|
|
|
515
519
|
beforeEach(async () => {
|
|
516
|
-
const initResult = await engine.processCommand(null, null, {
|
|
520
|
+
const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
|
|
517
521
|
id: 'init',
|
|
518
522
|
type: 'INIT_SESSION_STATE',
|
|
519
523
|
payload: { betAmount: 1.0 },
|
|
@@ -653,7 +657,7 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
653
657
|
];
|
|
654
658
|
|
|
655
659
|
for (const { betAmount, bonusType } of configs) {
|
|
656
|
-
const init = await engine.processCommand(null, null, {
|
|
660
|
+
const init = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
|
|
657
661
|
id: `init-${betAmount}`,
|
|
658
662
|
type: 'INIT_SESSION_STATE',
|
|
659
663
|
payload: { betAmount },
|
|
@@ -697,11 +701,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
697
701
|
// DEBUG TRIGGER ERROR CASES
|
|
698
702
|
// ============================================================
|
|
699
703
|
describe('Debug Trigger Error Cases', () => {
|
|
700
|
-
let publicState:
|
|
701
|
-
let privateState:
|
|
704
|
+
let publicState: BonnysFortunePublicState;
|
|
705
|
+
let privateState: BonnysFortunePrivateState;
|
|
702
706
|
|
|
703
707
|
beforeEach(async () => {
|
|
704
|
-
const initResult = await engine.processCommand(null, null, {
|
|
708
|
+
const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
|
|
705
709
|
id: 'init',
|
|
706
710
|
type: 'INIT_SESSION_STATE',
|
|
707
711
|
payload: { betAmount: 1.0 },
|
|
@@ -730,7 +734,7 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
730
734
|
// Engine throws TypeError when state is null
|
|
731
735
|
// This is expected behavior - state must be initialized
|
|
732
736
|
await expect(
|
|
733
|
-
engine.processCommand(null, null, {
|
|
737
|
+
engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
|
|
734
738
|
id: 'no-state',
|
|
735
739
|
type: 'DEBUG_TRIGGER_BONUS',
|
|
736
740
|
payload: {
|
|
@@ -747,11 +751,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
747
751
|
// SESSION MANAGEMENT
|
|
748
752
|
// ============================================================
|
|
749
753
|
describe('Session Management', () => {
|
|
750
|
-
let publicState:
|
|
751
|
-
let privateState:
|
|
754
|
+
let publicState: BonnysFortunePublicState;
|
|
755
|
+
let privateState: BonnysFortunePrivateState;
|
|
752
756
|
|
|
753
757
|
beforeEach(async () => {
|
|
754
|
-
const initResult = await engine.processCommand(null, null, {
|
|
758
|
+
const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
|
|
755
759
|
id: 'init',
|
|
756
760
|
type: 'INIT_SESSION_STATE',
|
|
757
761
|
payload: { betAmount: 1.0 },
|
|
@@ -775,11 +779,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
|
775
779
|
// STATE PERSISTENCE
|
|
776
780
|
// ============================================================
|
|
777
781
|
describe('State Persistence', () => {
|
|
778
|
-
let publicState:
|
|
779
|
-
let privateState:
|
|
782
|
+
let publicState: BonnysFortunePublicState;
|
|
783
|
+
let privateState: BonnysFortunePrivateState;
|
|
780
784
|
|
|
781
785
|
beforeEach(async () => {
|
|
782
|
-
const initResult = await engine.processCommand(null, null, {
|
|
786
|
+
const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
|
|
783
787
|
id: 'init',
|
|
784
788
|
type: 'INIT_SESSION_STATE',
|
|
785
789
|
payload: { betAmount: 1.0 },
|