@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
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Spin Handler
|
|
4
|
+
*
|
|
5
|
+
* Coordinates the entire spin flow:
|
|
6
|
+
* 1. Determine spin type
|
|
7
|
+
* 2. Generate grid
|
|
8
|
+
* 3. Evaluate wins
|
|
9
|
+
* 4. Update counters
|
|
10
|
+
* 5. Update jackpots
|
|
11
|
+
* 6. Determine next spin type
|
|
12
|
+
* 7. Build response
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.MAX_BET_STAKE = exports.MIN_BET_STAKE = void 0;
|
|
16
|
+
exports.executeSpin = executeSpin;
|
|
17
|
+
exports.validateBetParams = validateBetParams;
|
|
18
|
+
exports.createInitialState = createInitialState;
|
|
19
|
+
exports.updateBet = updateBet;
|
|
20
|
+
const happy_panda_config_1 = require("../../config/happy-panda.config");
|
|
21
|
+
const types_1 = require("../../domain/types");
|
|
22
|
+
const spin_generator_1 = require("../../rng/spin-generator");
|
|
23
|
+
const win_evaluator_1 = require("../services/win-evaluator");
|
|
24
|
+
const counter_manager_1 = require("../services/counter-manager");
|
|
25
|
+
const jackpot_manager_1 = require("../services/jackpot-manager");
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// SPIN TYPE TO BONUS TYPE MAPPING
|
|
28
|
+
// =============================================================================
|
|
29
|
+
function bonusTypeToSpinType(bonusType) {
|
|
30
|
+
switch (bonusType) {
|
|
31
|
+
case 'jackpot': return happy_panda_config_1.SpinType.BONUS_JACKPOT;
|
|
32
|
+
case 'cherry': return happy_panda_config_1.SpinType.BONUS_CHERRY;
|
|
33
|
+
case 'bell': return happy_panda_config_1.SpinType.BONUS_BELL;
|
|
34
|
+
case 'bar1': return happy_panda_config_1.SpinType.BONUS_BAR1;
|
|
35
|
+
case 'respin': return happy_panda_config_1.SpinType.RESPIN_CHERRY;
|
|
36
|
+
default: return happy_panda_config_1.SpinType.PAID_SPIN;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// =============================================================================
|
|
40
|
+
// MAIN SPIN EXECUTION
|
|
41
|
+
// =============================================================================
|
|
42
|
+
/**
|
|
43
|
+
* Execute a single spin.
|
|
44
|
+
*/
|
|
45
|
+
async function executeSpin(state, rng) {
|
|
46
|
+
const { gameDirection, betStake, betGame, currentSpinType, spinsRemaining, counters, pendingBonuses, jackpots, centerCherrySymbol, } = state;
|
|
47
|
+
// Step 1: Generate grid based on spin type
|
|
48
|
+
const grid = await (0, spin_generator_1.generateGrid)(currentSpinType, gameDirection, rng, currentSpinType === happy_panda_config_1.SpinType.RESPIN_CHERRY ? centerCherrySymbol ?? undefined : undefined);
|
|
49
|
+
// Step 2: Evaluate wins
|
|
50
|
+
const wins = (0, win_evaluator_1.evaluateSpin)(grid, gameDirection, betStake, betGame, currentSpinType);
|
|
51
|
+
// Step 3: Handle special jackpot bonus logic
|
|
52
|
+
let jackpotWon = 0;
|
|
53
|
+
let poolJackpotWon = 0;
|
|
54
|
+
let updatedJackpots = { ...jackpots };
|
|
55
|
+
if (currentSpinType === happy_panda_config_1.SpinType.BONUS_JACKPOT) {
|
|
56
|
+
// Every jackpot bonus spin gets the ADD_WIN bonus (C++ Main Logic line 59)
|
|
57
|
+
// "please note scenario: each screen gets such winning, independent on pieces cherry wins"
|
|
58
|
+
jackpotWon = happy_panda_config_1.BONUS_JACKPOT.ADD_WIN * betGame;
|
|
59
|
+
// Full cherry piece = ALSO triggers progressive jackpot payout
|
|
60
|
+
if ((0, spin_generator_1.isCompleteCherryPiece)(grid)) {
|
|
61
|
+
const { payout, newValue } = (0, jackpot_manager_1.payProgressiveJackpot)(jackpots.bonusJackpotValue, betGame);
|
|
62
|
+
jackpotWon += payout;
|
|
63
|
+
updatedJackpots.bonusJackpotValue = newValue;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Step 4: Update counters (only on paid spin and respin)
|
|
67
|
+
let updatedCounters = { ...counters };
|
|
68
|
+
let updatedPending = { ...pendingBonuses };
|
|
69
|
+
let bonusTriggered = null;
|
|
70
|
+
if (currentSpinType === happy_panda_config_1.SpinType.PAID_SPIN || currentSpinType === happy_panda_config_1.SpinType.RESPIN_CHERRY) {
|
|
71
|
+
const counterResult = await (0, counter_manager_1.updateCounters)(counters, pendingBonuses, wins.lineWins, grid, gameDirection, currentSpinType, wins.totalPayout, rng);
|
|
72
|
+
updatedCounters = counterResult.counters;
|
|
73
|
+
updatedPending = counterResult.pendingBonuses;
|
|
74
|
+
// Determine triggered bonus (for animation/display)
|
|
75
|
+
if (counterResult.triggeredJackpot) {
|
|
76
|
+
bonusTriggered = happy_panda_config_1.SpinType.BONUS_JACKPOT;
|
|
77
|
+
}
|
|
78
|
+
else if (counterResult.triggeredCherry) {
|
|
79
|
+
bonusTriggered = happy_panda_config_1.SpinType.BONUS_CHERRY;
|
|
80
|
+
}
|
|
81
|
+
else if (counterResult.triggeredBell) {
|
|
82
|
+
bonusTriggered = happy_panda_config_1.SpinType.BONUS_BELL;
|
|
83
|
+
// Bell bonus also pays pool jackpot
|
|
84
|
+
const poolResult = (0, jackpot_manager_1.payPoolJackpot)(jackpots.poolJackpotValue, betGame);
|
|
85
|
+
poolJackpotWon = poolResult.payout;
|
|
86
|
+
updatedJackpots.poolJackpotValue = poolResult.newValue;
|
|
87
|
+
}
|
|
88
|
+
else if (counterResult.triggeredBar1) {
|
|
89
|
+
bonusTriggered = happy_panda_config_1.SpinType.BONUS_BAR1;
|
|
90
|
+
}
|
|
91
|
+
else if (counterResult.triggeredRespin > 0) {
|
|
92
|
+
bonusTriggered = happy_panda_config_1.SpinType.RESPIN_CHERRY;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Step 5: Update jackpots (progressive and pool on paid spins)
|
|
96
|
+
if (currentSpinType === happy_panda_config_1.SpinType.PAID_SPIN) {
|
|
97
|
+
const jackpotUpdate = await (0, jackpot_manager_1.updateJackpots)(updatedJackpots, grid, gameDirection, betGame, betStake, currentSpinType, wins.totalPayout, rng);
|
|
98
|
+
updatedJackpots = jackpotUpdate.jackpots;
|
|
99
|
+
}
|
|
100
|
+
// Step 6: Determine next spin type and remaining spins
|
|
101
|
+
let nextSpinType = happy_panda_config_1.SpinType.PAID_SPIN;
|
|
102
|
+
let nextSpinsRemaining = 0;
|
|
103
|
+
let newAccumulatedBonusWins = state.accumulatedBonusWins;
|
|
104
|
+
let newCenterCherrySymbol = state.centerCherrySymbol;
|
|
105
|
+
let isBonusComplete = false;
|
|
106
|
+
// Handle ongoing bonus spins
|
|
107
|
+
if (currentSpinType !== happy_panda_config_1.SpinType.PAID_SPIN && spinsRemaining > 1) {
|
|
108
|
+
// Continue current bonus
|
|
109
|
+
nextSpinType = currentSpinType;
|
|
110
|
+
nextSpinsRemaining = spinsRemaining - 1;
|
|
111
|
+
newAccumulatedBonusWins += wins.totalPayout + jackpotWon + poolJackpotWon;
|
|
112
|
+
}
|
|
113
|
+
else if (currentSpinType !== happy_panda_config_1.SpinType.PAID_SPIN && spinsRemaining === 1) {
|
|
114
|
+
// Bonus sequence ending, check for more pending bonuses
|
|
115
|
+
newAccumulatedBonusWins += wins.totalPayout + jackpotWon + poolJackpotWon;
|
|
116
|
+
const nextBonusType = (0, counter_manager_1.getNextBonusType)(updatedPending);
|
|
117
|
+
if (nextBonusType) {
|
|
118
|
+
// Start next bonus
|
|
119
|
+
const { pending: newPending, spins } = (0, counter_manager_1.consumeBonus)(updatedPending, nextBonusType);
|
|
120
|
+
updatedPending = newPending;
|
|
121
|
+
nextSpinType = bonusTypeToSpinType(nextBonusType);
|
|
122
|
+
nextSpinsRemaining = spins;
|
|
123
|
+
// Set center cherry symbol if starting respin
|
|
124
|
+
if (nextBonusType === 'respin') {
|
|
125
|
+
newCenterCherrySymbol = grid[1][1];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// All bonuses complete, return to paid spin
|
|
130
|
+
nextSpinType = happy_panda_config_1.SpinType.PAID_SPIN;
|
|
131
|
+
nextSpinsRemaining = 0;
|
|
132
|
+
isBonusComplete = true;
|
|
133
|
+
newAccumulatedBonusWins = 0; // Reset for next bonus sequence
|
|
134
|
+
newCenterCherrySymbol = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (currentSpinType === happy_panda_config_1.SpinType.PAID_SPIN) {
|
|
138
|
+
// Check if any bonuses were triggered
|
|
139
|
+
const nextBonusType = (0, counter_manager_1.getNextBonusType)(updatedPending);
|
|
140
|
+
if (nextBonusType) {
|
|
141
|
+
const { pending: newPending, spins } = (0, counter_manager_1.consumeBonus)(updatedPending, nextBonusType);
|
|
142
|
+
updatedPending = newPending;
|
|
143
|
+
nextSpinType = bonusTypeToSpinType(nextBonusType);
|
|
144
|
+
nextSpinsRemaining = spins;
|
|
145
|
+
// Set center cherry symbol if starting respin
|
|
146
|
+
if (nextBonusType === 'respin') {
|
|
147
|
+
newCenterCherrySymbol = grid[1][1];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Build updated state
|
|
152
|
+
const updatedState = {
|
|
153
|
+
...state,
|
|
154
|
+
currentSpinType: nextSpinType,
|
|
155
|
+
nextSpinType,
|
|
156
|
+
spinsRemaining: nextSpinsRemaining,
|
|
157
|
+
counters: updatedCounters,
|
|
158
|
+
pendingBonuses: updatedPending,
|
|
159
|
+
jackpots: updatedJackpots,
|
|
160
|
+
grid,
|
|
161
|
+
accumulatedBonusWins: newAccumulatedBonusWins,
|
|
162
|
+
centerCherrySymbol: newCenterCherrySymbol,
|
|
163
|
+
};
|
|
164
|
+
return {
|
|
165
|
+
grid,
|
|
166
|
+
wins,
|
|
167
|
+
state: updatedState,
|
|
168
|
+
jackpotWon,
|
|
169
|
+
poolJackpotWon,
|
|
170
|
+
bonusTriggered,
|
|
171
|
+
isBonusComplete,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
// =============================================================================
|
|
175
|
+
// STATE INITIALIZATION
|
|
176
|
+
// =============================================================================
|
|
177
|
+
/** Minimum bet stake */
|
|
178
|
+
exports.MIN_BET_STAKE = 1;
|
|
179
|
+
/** Maximum bet stake (reasonable upper bound) */
|
|
180
|
+
exports.MAX_BET_STAKE = 100;
|
|
181
|
+
/**
|
|
182
|
+
* Validate bet parameters.
|
|
183
|
+
* @throws Error if parameters are invalid
|
|
184
|
+
*/
|
|
185
|
+
function validateBetParams(gameDirection, betStake) {
|
|
186
|
+
if (gameDirection !== types_1.GameDirection.SINGLE && gameDirection !== types_1.GameDirection.BOTH) {
|
|
187
|
+
throw new Error(`Invalid game direction: ${gameDirection}. Must be 0 (8 lines) or 1 (16 lines)`);
|
|
188
|
+
}
|
|
189
|
+
if (!Number.isInteger(betStake)) {
|
|
190
|
+
throw new Error(`Bet stake must be an integer, got: ${betStake}`);
|
|
191
|
+
}
|
|
192
|
+
if (betStake < exports.MIN_BET_STAKE || betStake > exports.MAX_BET_STAKE) {
|
|
193
|
+
throw new Error(`Bet stake must be between ${exports.MIN_BET_STAKE} and ${exports.MAX_BET_STAKE}, got: ${betStake}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Create initial game state.
|
|
198
|
+
* @throws Error if parameters are invalid
|
|
199
|
+
*/
|
|
200
|
+
function createInitialState(gameDirection, betStake) {
|
|
201
|
+
validateBetParams(gameDirection, betStake);
|
|
202
|
+
const baseBet = gameDirection === types_1.GameDirection.SINGLE ? 8 : 16;
|
|
203
|
+
const betGame = baseBet * betStake;
|
|
204
|
+
return {
|
|
205
|
+
gameDirection,
|
|
206
|
+
betStake,
|
|
207
|
+
betGame,
|
|
208
|
+
currentSpinType: happy_panda_config_1.SpinType.PAID_SPIN,
|
|
209
|
+
nextSpinType: happy_panda_config_1.SpinType.PAID_SPIN,
|
|
210
|
+
spinsRemaining: 0,
|
|
211
|
+
counters: {
|
|
212
|
+
jackpot: 1,
|
|
213
|
+
cherry: 9, // Will be randomized on first trigger
|
|
214
|
+
bell: 5, // Will be randomized on first trigger
|
|
215
|
+
bar1: 1,
|
|
216
|
+
},
|
|
217
|
+
pendingBonuses: {
|
|
218
|
+
jackpot: 0,
|
|
219
|
+
cherry: 0,
|
|
220
|
+
bell: 0,
|
|
221
|
+
bar1: 0,
|
|
222
|
+
respin: 0,
|
|
223
|
+
},
|
|
224
|
+
jackpots: {
|
|
225
|
+
bonusJackpotValue: 100 * betGame,
|
|
226
|
+
poolJackpotValue: 0,
|
|
227
|
+
},
|
|
228
|
+
grid: [
|
|
229
|
+
[happy_panda_config_1.Symbol.NONE, happy_panda_config_1.Symbol.NONE, happy_panda_config_1.Symbol.NONE],
|
|
230
|
+
[happy_panda_config_1.Symbol.NONE, happy_panda_config_1.Symbol.NONE, happy_panda_config_1.Symbol.NONE],
|
|
231
|
+
[happy_panda_config_1.Symbol.NONE, happy_panda_config_1.Symbol.NONE, happy_panda_config_1.Symbol.NONE],
|
|
232
|
+
],
|
|
233
|
+
accumulatedBonusWins: 0,
|
|
234
|
+
centerCherrySymbol: null,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Update state for new bet.
|
|
239
|
+
* @throws Error if parameters are invalid
|
|
240
|
+
*/
|
|
241
|
+
function updateBet(state, gameDirection, betStake) {
|
|
242
|
+
validateBetParams(gameDirection, betStake);
|
|
243
|
+
// Can only change bet during paid spin with no pending bonuses
|
|
244
|
+
if (state.currentSpinType !== happy_panda_config_1.SpinType.PAID_SPIN || (0, counter_manager_1.hasPendingBonus)(state.pendingBonuses)) {
|
|
245
|
+
throw new Error('Cannot change bet during bonus or with pending bonuses');
|
|
246
|
+
}
|
|
247
|
+
const baseBet = gameDirection === types_1.GameDirection.SINGLE ? 8 : 16;
|
|
248
|
+
const betGame = baseBet * betStake;
|
|
249
|
+
return {
|
|
250
|
+
...state,
|
|
251
|
+
gameDirection,
|
|
252
|
+
betStake,
|
|
253
|
+
betGame,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=spin-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spin-handler.js","sourceRoot":"","sources":["../../../src/logic/handlers/spin-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
/**
|
|
18
|
+
* Logic module exports
|
|
19
|
+
*/
|
|
20
|
+
__exportStar(require("./services"), exports);
|
|
21
|
+
__exportStar(require("./handlers"), exports);
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/logic/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;;GAEG;AACH,6CAA2B;AAC3B,6CAA2B"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Counter Manager Service
|
|
4
|
+
*
|
|
5
|
+
* Manages bonus counters and trigger detection.
|
|
6
|
+
* Each bonus type has a counter that decrements on specific events.
|
|
7
|
+
* When a counter reaches 0, the bonus is triggered.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.createInitialCounters = createInitialCounters;
|
|
11
|
+
exports.createEmptyPendingBonuses = createEmptyPendingBonuses;
|
|
12
|
+
exports.randomizeCherryCounter = randomizeCherryCounter;
|
|
13
|
+
exports.randomizeBellCounter = randomizeBellCounter;
|
|
14
|
+
exports.updateCounters = updateCounters;
|
|
15
|
+
exports.getNextBonusType = getNextBonusType;
|
|
16
|
+
exports.consumeBonus = consumeBonus;
|
|
17
|
+
exports.hasPendingBonus = hasPendingBonus;
|
|
18
|
+
const happy_panda_config_1 = require("../../config/happy-panda.config");
|
|
19
|
+
const weighted_random_1 = require("../../rng/weighted-random");
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// COUNTER INITIALIZATION
|
|
22
|
+
// =============================================================================
|
|
23
|
+
/**
|
|
24
|
+
* Create initial bonus counters.
|
|
25
|
+
*/
|
|
26
|
+
function createInitialCounters() {
|
|
27
|
+
return {
|
|
28
|
+
jackpot: happy_panda_config_1.BONUS_JACKPOT.COUNTER_INIT,
|
|
29
|
+
cherry: happy_panda_config_1.BONUS_CHERRY.COUNTER_INIT[1], // Default to 9, will be randomized
|
|
30
|
+
bell: happy_panda_config_1.BONUS_BELL.COUNTER_INIT[1], // Default to 5, will be randomized
|
|
31
|
+
bar1: happy_panda_config_1.BONUS_BAR1.COUNTER_INIT,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create empty pending bonuses.
|
|
36
|
+
*/
|
|
37
|
+
function createEmptyPendingBonuses() {
|
|
38
|
+
return {
|
|
39
|
+
jackpot: 0,
|
|
40
|
+
cherry: 0,
|
|
41
|
+
bell: 0,
|
|
42
|
+
bar1: 0,
|
|
43
|
+
respin: 0,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Randomize cherry counter initial value (6 or 9).
|
|
48
|
+
*/
|
|
49
|
+
async function randomizeCherryCounter(gameDirection, rng) {
|
|
50
|
+
const weights = happy_panda_config_1.BONUS_CHERRY.COUNTER_INIT_WEIGHTS[gameDirection];
|
|
51
|
+
const idx = await (0, weighted_random_1.selectWeightedIndex)(weights, rng);
|
|
52
|
+
return happy_panda_config_1.BONUS_CHERRY.COUNTER_INIT[idx];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Randomize bell counter initial value (3 or 5).
|
|
56
|
+
*/
|
|
57
|
+
async function randomizeBellCounter(gameDirection, rng) {
|
|
58
|
+
const weights = happy_panda_config_1.BONUS_BELL.COUNTER_INIT_WEIGHTS[gameDirection];
|
|
59
|
+
const idx = await (0, weighted_random_1.selectWeightedIndex)(weights, rng);
|
|
60
|
+
return happy_panda_config_1.BONUS_BELL.COUNTER_INIT[idx];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Update counters based on spin results.
|
|
64
|
+
* Only applies during paid_spin and respin_cherry.
|
|
65
|
+
*/
|
|
66
|
+
async function updateCounters(currentCounters, currentPending, lineWins, grid, gameDirection, spinType, totalWin, rng) {
|
|
67
|
+
// Only update counters during paid spins and respins
|
|
68
|
+
if (spinType !== happy_panda_config_1.SpinType.PAID_SPIN && spinType !== happy_panda_config_1.SpinType.RESPIN_CHERRY) {
|
|
69
|
+
return {
|
|
70
|
+
counters: { ...currentCounters },
|
|
71
|
+
pendingBonuses: { ...currentPending },
|
|
72
|
+
triggeredJackpot: false,
|
|
73
|
+
triggeredCherry: false,
|
|
74
|
+
triggeredBell: false,
|
|
75
|
+
triggeredBar1: false,
|
|
76
|
+
triggeredRespin: 0,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const counters = { ...currentCounters };
|
|
80
|
+
const pendingBonuses = { ...currentPending };
|
|
81
|
+
let triggeredJackpot = false;
|
|
82
|
+
let triggeredCherry = false;
|
|
83
|
+
let triggeredBell = false;
|
|
84
|
+
let triggeredBar1 = false;
|
|
85
|
+
let triggeredRespin = 0;
|
|
86
|
+
// Count specific line win patterns
|
|
87
|
+
let cherryTripleCount = 0; // 3x Cherry wins (only first 8 lines)
|
|
88
|
+
let cherryPairCount = 0; // 2x Cherry wins
|
|
89
|
+
let bellTripleCount = 0; // 3x Bell wins
|
|
90
|
+
let bar1TripleCount = 0; // 3x Bar1 wins
|
|
91
|
+
for (const win of lineWins) {
|
|
92
|
+
// Only first 8 lines count for Jackpot, Bell, and Bar1 triggers (per C++ math model)
|
|
93
|
+
const isFirst8Lines = win.lineIndex < 8;
|
|
94
|
+
if (win.symbol === happy_panda_config_1.Symbol.CHERRY || win.symbol === happy_panda_config_1.Symbol.SUPER_CHERRY) {
|
|
95
|
+
if (win.matchLength === 3 && isFirst8Lines) {
|
|
96
|
+
// C++ BEHAVIOR: During RESPIN_CHERRY, C++ only regenerates corners.
|
|
97
|
+
// Center row (line 1) edges (0,1) and (2,1) are preserved from paid_spin.
|
|
98
|
+
// Since the triggering paid_spin had only 1 cherry at center, these edges are NOT cherry.
|
|
99
|
+
// Line 1 can NEVER form 3x cherry during respin. Other lines may still form 3x cherry
|
|
100
|
+
// through corner positions which are regenerated.
|
|
101
|
+
const isCenterRow = win.lineIndex === 1;
|
|
102
|
+
if (spinType === happy_panda_config_1.SpinType.RESPIN_CHERRY && isCenterRow) {
|
|
103
|
+
// Skip line 1 - center row edges are preserved non-cherry in C++
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
cherryTripleCount++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else if (win.matchLength === 2) {
|
|
110
|
+
// Cherry pairs count on ALL lines for cherry bonus
|
|
111
|
+
cherryPairCount++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (win.symbol === happy_panda_config_1.Symbol.BELL && win.matchLength === 3 && isFirst8Lines) {
|
|
115
|
+
// Bell bonus only counts first 8 lines
|
|
116
|
+
bellTripleCount++;
|
|
117
|
+
}
|
|
118
|
+
else if (win.symbol === happy_panda_config_1.Symbol.BAR1 && win.matchLength === 3 && isFirst8Lines) {
|
|
119
|
+
// Bar1 bonus only counts first 8 lines
|
|
120
|
+
bar1TripleCount++;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// JACKPOT BONUS: Triggered by 3x Cherry on first 8 lines
|
|
124
|
+
// C++ line 945: number_bonus_jackpot+=1 for EACH 3x cherry line
|
|
125
|
+
// C++ line 2464: spins_upto_end = number_bonus_jackpot * BonusJackpot_SpinsNo
|
|
126
|
+
// So C++ runs one bonus spin per cherry line, not per trigger event!
|
|
127
|
+
if (cherryTripleCount > 0) {
|
|
128
|
+
pendingBonuses.jackpot += cherryTripleCount; // Per C++ - one bonus spin per cherry line
|
|
129
|
+
counters.jackpot = 0;
|
|
130
|
+
triggeredJackpot = true;
|
|
131
|
+
}
|
|
132
|
+
// CHERRY BONUS: Triggered when counter reaches 0
|
|
133
|
+
// Decremented by 2x Cherry pairs on any line
|
|
134
|
+
// Per C++ math model: process each pair individually, trigger can occur multiple times
|
|
135
|
+
for (let i = 0; i < cherryPairCount; i++) {
|
|
136
|
+
counters.cherry--;
|
|
137
|
+
if (counters.cherry <= 0) {
|
|
138
|
+
pendingBonuses.cherry++;
|
|
139
|
+
triggeredCherry = true;
|
|
140
|
+
// Reset counter with random value before processing next pair
|
|
141
|
+
counters.cherry = await randomizeCherryCounter(gameDirection, rng);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// BELL BONUS: Triggered when counter reaches 0
|
|
145
|
+
// Decremented by 3x Bell wins - process each win individually
|
|
146
|
+
for (let i = 0; i < bellTripleCount; i++) {
|
|
147
|
+
counters.bell--;
|
|
148
|
+
if (counters.bell <= 0) {
|
|
149
|
+
pendingBonuses.bell++;
|
|
150
|
+
triggeredBell = true;
|
|
151
|
+
// Reset counter with random value before processing next win
|
|
152
|
+
counters.bell = await randomizeBellCounter(gameDirection, rng);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// BAR1 BONUS: Triggered when counter reaches 0
|
|
156
|
+
// Decremented by 3x Bar1 wins - process each win individually
|
|
157
|
+
for (let i = 0; i < bar1TripleCount; i++) {
|
|
158
|
+
counters.bar1--;
|
|
159
|
+
if (counters.bar1 <= 0) {
|
|
160
|
+
pendingBonuses.bar1++;
|
|
161
|
+
triggeredBar1 = true;
|
|
162
|
+
// Reset counter before processing next win
|
|
163
|
+
counters.bar1 = happy_panda_config_1.BONUS_BAR1.COUNTER_INIT;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// CENTER CHERRY RESPIN: Only on paid spins, no other wins
|
|
167
|
+
if (spinType === happy_panda_config_1.SpinType.PAID_SPIN && totalWin === 0) {
|
|
168
|
+
const centerSymbol = grid[1][1];
|
|
169
|
+
if (happy_panda_config_1.CHERRY_SYMBOLS.includes(centerSymbol)) {
|
|
170
|
+
// Check if it's the ONLY cherry on the screen
|
|
171
|
+
let cherryCount = 0;
|
|
172
|
+
for (let reel = 0; reel < 3; reel++) {
|
|
173
|
+
for (let row = 0; row < 3; row++) {
|
|
174
|
+
if (happy_panda_config_1.CHERRY_SYMBOLS.includes(grid[reel][row])) {
|
|
175
|
+
cherryCount++;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (cherryCount === 1) {
|
|
180
|
+
// Award respins: 1 for Cherry, 2 for Super Cherry
|
|
181
|
+
triggeredRespin = centerSymbol === happy_panda_config_1.Symbol.SUPER_CHERRY ? 2 : 1;
|
|
182
|
+
pendingBonuses.respin += triggeredRespin;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
counters,
|
|
188
|
+
pendingBonuses,
|
|
189
|
+
triggeredJackpot,
|
|
190
|
+
triggeredCherry,
|
|
191
|
+
triggeredBell,
|
|
192
|
+
triggeredBar1,
|
|
193
|
+
triggeredRespin,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get the next bonus type to execute (priority order).
|
|
198
|
+
* Returns null if no bonuses pending.
|
|
199
|
+
*/
|
|
200
|
+
function getNextBonusType(pending) {
|
|
201
|
+
// Priority: Jackpot > Cherry > Bell > Bar1 > Respin
|
|
202
|
+
if (pending.jackpot > 0)
|
|
203
|
+
return 'jackpot';
|
|
204
|
+
if (pending.cherry > 0)
|
|
205
|
+
return 'cherry';
|
|
206
|
+
if (pending.bell > 0)
|
|
207
|
+
return 'bell';
|
|
208
|
+
if (pending.bar1 > 0)
|
|
209
|
+
return 'bar1';
|
|
210
|
+
if (pending.respin > 0)
|
|
211
|
+
return 'respin';
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Consume one bonus from pending.
|
|
216
|
+
* Returns updated pending and number of spins for that bonus.
|
|
217
|
+
*/
|
|
218
|
+
function consumeBonus(pending, bonusType) {
|
|
219
|
+
const updated = { ...pending };
|
|
220
|
+
let spins = 0;
|
|
221
|
+
switch (bonusType) {
|
|
222
|
+
case 'jackpot':
|
|
223
|
+
if (updated.jackpot > 0) {
|
|
224
|
+
updated.jackpot--;
|
|
225
|
+
spins = happy_panda_config_1.BONUS_JACKPOT.SPINS;
|
|
226
|
+
}
|
|
227
|
+
break;
|
|
228
|
+
case 'cherry':
|
|
229
|
+
if (updated.cherry > 0) {
|
|
230
|
+
updated.cherry--;
|
|
231
|
+
spins = happy_panda_config_1.BONUS_CHERRY.SPINS;
|
|
232
|
+
}
|
|
233
|
+
break;
|
|
234
|
+
case 'bell':
|
|
235
|
+
if (updated.bell > 0) {
|
|
236
|
+
updated.bell--;
|
|
237
|
+
spins = happy_panda_config_1.BONUS_BELL.SPINS;
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
case 'bar1':
|
|
241
|
+
if (updated.bar1 > 0) {
|
|
242
|
+
updated.bar1--;
|
|
243
|
+
spins = happy_panda_config_1.BONUS_BAR1.SPINS;
|
|
244
|
+
}
|
|
245
|
+
break;
|
|
246
|
+
case 'respin':
|
|
247
|
+
if (updated.respin > 0) {
|
|
248
|
+
spins = updated.respin;
|
|
249
|
+
updated.respin = 0;
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
return { pending: updated, spins };
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Check if any bonus is pending.
|
|
257
|
+
*/
|
|
258
|
+
function hasPendingBonus(pending) {
|
|
259
|
+
return (pending.jackpot > 0 ||
|
|
260
|
+
pending.cherry > 0 ||
|
|
261
|
+
pending.bell > 0 ||
|
|
262
|
+
pending.bar1 > 0 ||
|
|
263
|
+
pending.respin > 0);
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=counter-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"counter-manager.js","sourceRoot":"","sources":["../../../src/logic/services/counter-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
/**
|
|
18
|
+
* Logic services exports
|
|
19
|
+
*/
|
|
20
|
+
__exportStar(require("./win-evaluator"), exports);
|
|
21
|
+
__exportStar(require("./counter-manager"), exports);
|
|
22
|
+
__exportStar(require("./jackpot-manager"), exports);
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/logic/services/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;;GAEG;AACH,kDAAgC;AAChC,oDAAkC;AAClC,oDAAkC"}
|