@duyquangnvx/spindle 1.0.0-beta.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 +211 -0
- package/dist/delegates-DOwv3sAL.d.cts +463 -0
- package/dist/delegates-DOwv3sAL.d.cts.map +1 -0
- package/dist/delegates-Jm8q1-L0.d.mts +463 -0
- package/dist/delegates-Jm8q1-L0.d.mts.map +1 -0
- package/dist/index.cjs +436 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +12 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +12 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +434 -0
- package/dist/index.mjs.map +1 -0
- package/dist/renderer/index.cjs +548 -0
- package/dist/renderer/index.cjs.map +1 -0
- package/dist/renderer/index.d.cts +279 -0
- package/dist/renderer/index.d.cts.map +1 -0
- package/dist/renderer/index.d.mts +279 -0
- package/dist/renderer/index.d.mts.map +1 -0
- package/dist/renderer/index.mjs +534 -0
- package/dist/renderer/index.mjs.map +1 -0
- package/dist/testing/index.cjs +729 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +76 -0
- package/dist/testing/index.d.cts.map +1 -0
- package/dist/testing/index.d.mts +76 -0
- package/dist/testing/index.d.mts.map +1 -0
- package/dist/testing/index.mjs +721 -0
- package/dist/testing/index.mjs.map +1 -0
- package/dist/types-B1TeoRrL.mjs +9 -0
- package/dist/types-B1TeoRrL.mjs.map +1 -0
- package/dist/types-NL78Eg3j.cjs +15 -0
- package/dist/types-NL78Eg3j.cjs.map +1 -0
- package/package.json +61 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_types = require('./types-NL78Eg3j.cjs');
|
|
3
|
+
|
|
4
|
+
//#region src/config.ts
|
|
5
|
+
/**
|
|
6
|
+
* Validates a GameConfig at construction time.
|
|
7
|
+
* Throws with descriptive error including field name and constraint.
|
|
8
|
+
*/
|
|
9
|
+
function validateConfig(config) {
|
|
10
|
+
if (config.reels < 2) throw new Error(`reels must be >= 2, got ${config.reels}`);
|
|
11
|
+
if (config.rows < 1) throw new Error(`rows must be >= 1, got ${config.rows}`);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Validates a SpinResult against the GameConfig at intake time.
|
|
15
|
+
* Ensures grid.length matches config.reels and every reel is non-empty.
|
|
16
|
+
* Row count per reel is NOT validated (supports jagged arrays / Megaways).
|
|
17
|
+
*/
|
|
18
|
+
function validateSpinResult(result, config) {
|
|
19
|
+
if (result.grid.length !== config.reels) throw new Error(`grid.length must be ${config.reels}, got ${result.grid.length}`);
|
|
20
|
+
for (let i = 0; i < result.grid.length; i++) {
|
|
21
|
+
const reel = result.grid[i];
|
|
22
|
+
if (!Array.isArray(reel) || reel.length === 0) throw new Error(`grid[${i}] must be a non-empty array`);
|
|
23
|
+
}
|
|
24
|
+
if (result.cascadeSteps) for (let s = 0; s < result.cascadeSteps.length; s++) {
|
|
25
|
+
const step = result.cascadeSteps[s];
|
|
26
|
+
if (step.grid.length !== config.reels) throw new Error(`cascadeSteps[${s}].grid.length must be ${config.reels}, got ${step.grid.length}`);
|
|
27
|
+
for (let i = 0; i < step.grid.length; i++) {
|
|
28
|
+
const reel = step.grid[i];
|
|
29
|
+
if (!Array.isArray(reel) || reel.length === 0) throw new Error(`cascadeSteps[${s}].grid[${i}] must be a non-empty array`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/spindle/spin-pipeline.ts
|
|
36
|
+
function withCtx(data, mode, metadata) {
|
|
37
|
+
return {
|
|
38
|
+
...data,
|
|
39
|
+
mode,
|
|
40
|
+
metadata
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Pure presentation pipeline — reels, transforms, wilds, wins, cascades, scatter.
|
|
45
|
+
* Composable: called by spindle.spin() for base game AND by feature runners per spin.
|
|
46
|
+
*
|
|
47
|
+
* Post-win celebrations (big win, jackpot) are NOT handled here — callers
|
|
48
|
+
* manage them based on context (base game vs feature spins).
|
|
49
|
+
*/
|
|
50
|
+
async function runSpin(ctx, result, mode) {
|
|
51
|
+
const grid = result.grid;
|
|
52
|
+
const meta = result.metadata;
|
|
53
|
+
const anticipationSet = /* @__PURE__ */ new Set();
|
|
54
|
+
if (result.anticipationReels && ctx.delegate.presentAnticipation) {
|
|
55
|
+
for (const reelIdx of result.anticipationReels) if (reelIdx >= 0 && reelIdx < grid.length) anticipationSet.add(reelIdx);
|
|
56
|
+
}
|
|
57
|
+
for (let i = 0; i < grid.length; i++) {
|
|
58
|
+
if (anticipationSet.has(i)) await ctx.delegate.presentAnticipation?.(withCtx({ reelIndex: i }, mode, meta));
|
|
59
|
+
await ctx.delegate.presentReelStop(withCtx({
|
|
60
|
+
reelIndex: i,
|
|
61
|
+
symbols: grid[i]
|
|
62
|
+
}, mode, meta));
|
|
63
|
+
}
|
|
64
|
+
if (result.symbolTransforms?.length && ctx.delegate.presentSymbolTransform) await ctx.delegate.presentSymbolTransform(withCtx({ transforms: result.symbolTransforms }, mode, meta));
|
|
65
|
+
if (result.wildEvents) for (const event of result.wildEvents) switch (event.type) {
|
|
66
|
+
case "regular": break;
|
|
67
|
+
case "expanding":
|
|
68
|
+
if (ctx.delegate.presentExpandingWild) await ctx.delegate.presentExpandingWild(withCtx({
|
|
69
|
+
position: event.position,
|
|
70
|
+
expandedPositions: event.expandedPositions
|
|
71
|
+
}, mode, meta));
|
|
72
|
+
break;
|
|
73
|
+
case "sticky":
|
|
74
|
+
if (ctx.delegate.presentStickyWild) await ctx.delegate.presentStickyWild(withCtx({ position: event.position }, mode, meta));
|
|
75
|
+
break;
|
|
76
|
+
case "walking":
|
|
77
|
+
if (ctx.delegate.presentWalkingWild) await ctx.delegate.presentWalkingWild(withCtx({
|
|
78
|
+
from: event.from,
|
|
79
|
+
to: event.to
|
|
80
|
+
}, mode, meta));
|
|
81
|
+
break;
|
|
82
|
+
case "stacked":
|
|
83
|
+
if (ctx.delegate.presentStackedWild) await ctx.delegate.presentStackedWild(withCtx({ positions: event.positions }, mode, meta));
|
|
84
|
+
break;
|
|
85
|
+
case "multiplier":
|
|
86
|
+
if (ctx.delegate.presentMultiplierWild) await ctx.delegate.presentMultiplierWild(withCtx({
|
|
87
|
+
position: event.position,
|
|
88
|
+
multiplier: event.multiplier
|
|
89
|
+
}, mode, meta));
|
|
90
|
+
break;
|
|
91
|
+
case "random":
|
|
92
|
+
if (ctx.delegate.presentRandomWild) await ctx.delegate.presentRandomWild(withCtx({ positions: event.positions }, mode, meta));
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
for (const win of result.wins) switch (win.type) {
|
|
96
|
+
case "ways":
|
|
97
|
+
await ctx.delegate.presentWaysWin?.(withCtx(win, mode, meta));
|
|
98
|
+
break;
|
|
99
|
+
case "payline":
|
|
100
|
+
await ctx.delegate.presentPaylineWin?.(withCtx(win, mode, meta));
|
|
101
|
+
break;
|
|
102
|
+
case "cluster":
|
|
103
|
+
await ctx.delegate.presentClusterWin?.(withCtx(win, mode, meta));
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
if (result.cascadeSteps?.length && ctx.delegate.presentCascadeDestroy) for (let stepIdx = 0; stepIdx < result.cascadeSteps.length; stepIdx++) {
|
|
107
|
+
const step = result.cascadeSteps[stepIdx];
|
|
108
|
+
await ctx.delegate.presentCascadeDestroy(withCtx({
|
|
109
|
+
step: stepIdx,
|
|
110
|
+
positions: step.destroyPositions
|
|
111
|
+
}, mode, meta));
|
|
112
|
+
await ctx.delegate.presentCascadeDrop?.(withCtx({ step: stepIdx }, mode, meta));
|
|
113
|
+
await ctx.delegate.presentCascadeFill?.(withCtx({
|
|
114
|
+
step: stepIdx,
|
|
115
|
+
grid: step.grid,
|
|
116
|
+
multiplier: step.multiplier
|
|
117
|
+
}, mode, meta));
|
|
118
|
+
for (const win of step.wins) switch (win.type) {
|
|
119
|
+
case "ways":
|
|
120
|
+
await ctx.delegate.presentWaysWin?.(withCtx(win, mode, meta));
|
|
121
|
+
break;
|
|
122
|
+
case "payline":
|
|
123
|
+
await ctx.delegate.presentPaylineWin?.(withCtx(win, mode, meta));
|
|
124
|
+
break;
|
|
125
|
+
case "cluster":
|
|
126
|
+
await ctx.delegate.presentClusterWin?.(withCtx(win, mode, meta));
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (result.scatterWin && ctx.delegate.presentScatterWin) await ctx.delegate.presentScatterWin(withCtx(result.scatterWin, mode, meta));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Presents jackpot trigger and award if present on the spin result.
|
|
134
|
+
* Called by spindle.spin() (after big win) and by feature runners per spin.
|
|
135
|
+
*/
|
|
136
|
+
async function presentJackpot(ctx, result, mode) {
|
|
137
|
+
if (result.jackpotWin && ctx.delegate.presentJackpotTrigger) {
|
|
138
|
+
const meta = result.metadata;
|
|
139
|
+
await ctx.delegate.presentJackpotTrigger(withCtx({
|
|
140
|
+
tier: result.jackpotWin.tier,
|
|
141
|
+
amount: result.jackpotWin.amount
|
|
142
|
+
}, mode, meta));
|
|
143
|
+
await ctx.delegate.onJackpotAwarded?.(withCtx({
|
|
144
|
+
tier: result.jackpotWin.tier,
|
|
145
|
+
amount: result.jackpotWin.amount
|
|
146
|
+
}, mode, meta));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/spindle/features/free-spins-runner.ts
|
|
152
|
+
/**
|
|
153
|
+
* Runs a free spins feature to completion.
|
|
154
|
+
* Reuses runSpin() for each spin's presentation pipeline.
|
|
155
|
+
* Returns accumulated win across all free spins.
|
|
156
|
+
*/
|
|
157
|
+
async function runFreeSpins(ctx, feature) {
|
|
158
|
+
if (!ctx.delegate.onFreeSpinsEnter) return 0;
|
|
159
|
+
let remainingSpins = feature.params.totalSpins;
|
|
160
|
+
let totalSpins = feature.params.totalSpins;
|
|
161
|
+
let accumulatedWin = 0;
|
|
162
|
+
await ctx.delegate.onFreeSpinsEnter({ totalSpins });
|
|
163
|
+
try {
|
|
164
|
+
while (remainingSpins > 0) {
|
|
165
|
+
const currentSpin = totalSpins - remainingSpins + 1;
|
|
166
|
+
await ctx.delegate.onFreeSpinStart?.({
|
|
167
|
+
currentSpin,
|
|
168
|
+
totalSpins,
|
|
169
|
+
remaining: remainingSpins,
|
|
170
|
+
accumulated: accumulatedWin
|
|
171
|
+
});
|
|
172
|
+
const result = await ctx.delegate.requestSpinResult({
|
|
173
|
+
mode: "freeSpin",
|
|
174
|
+
currentSpin,
|
|
175
|
+
totalSpins
|
|
176
|
+
});
|
|
177
|
+
validateSpinResult(result, ctx.config);
|
|
178
|
+
await runSpin(ctx, result, "freeSpin");
|
|
179
|
+
await presentJackpot(ctx, result, "freeSpin");
|
|
180
|
+
accumulatedWin += result.spinWin;
|
|
181
|
+
if (result.featureTrigger?.type === "freeSpins") {
|
|
182
|
+
const added = result.featureTrigger.params.totalSpins;
|
|
183
|
+
remainingSpins += added;
|
|
184
|
+
totalSpins += added;
|
|
185
|
+
await ctx.delegate.onFreeSpinsRetrigger?.({
|
|
186
|
+
added,
|
|
187
|
+
totalSpins
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (result.featureTrigger && result.featureTrigger.type !== "freeSpins") accumulatedWin += await ctx.runFeature(result.featureTrigger);
|
|
191
|
+
remainingSpins--;
|
|
192
|
+
}
|
|
193
|
+
} finally {
|
|
194
|
+
await ctx.delegate.onFreeSpinsExit?.({
|
|
195
|
+
totalSpins,
|
|
196
|
+
totalWin: accumulatedWin
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return accumulatedWin;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/spindle/features/gamble-runner.ts
|
|
204
|
+
/**
|
|
205
|
+
* Runs the gamble (double-up) flow to completion.
|
|
206
|
+
* Called after onSpinEnd when totalWin > 0 and gamble delegate is present.
|
|
207
|
+
* Returns the final win amount (potentially modified by gamble rounds).
|
|
208
|
+
*/
|
|
209
|
+
async function runGamble(ctx, initialWin) {
|
|
210
|
+
if (!ctx.delegate.onGambleStart) return initialWin;
|
|
211
|
+
let currentWin = initialWin;
|
|
212
|
+
await ctx.delegate.onGambleStart({ currentWin });
|
|
213
|
+
try {
|
|
214
|
+
while (true) {
|
|
215
|
+
const choice = await ctx.delegate.requestGambleChoice?.({ currentWin });
|
|
216
|
+
if (!choice || choice.action === "collect") break;
|
|
217
|
+
const result = await ctx.delegate.requestGambleResult?.({ currentWin });
|
|
218
|
+
if (!result) break;
|
|
219
|
+
await ctx.delegate.presentGambleResult?.({
|
|
220
|
+
won: result.won,
|
|
221
|
+
amount: result.amount
|
|
222
|
+
});
|
|
223
|
+
currentWin = result.amount;
|
|
224
|
+
if (!result.won) break;
|
|
225
|
+
}
|
|
226
|
+
} finally {
|
|
227
|
+
await ctx.delegate.onGambleEnd?.({ finalWin: currentWin });
|
|
228
|
+
}
|
|
229
|
+
return currentWin;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/spindle/features/hold-and-spin-runner.ts
|
|
234
|
+
/**
|
|
235
|
+
* Runs a hold-and-spin feature to completion.
|
|
236
|
+
* Loops respin rounds, reuses runSpin() for each round's presentation,
|
|
237
|
+
* tracks locked symbols, resets/decrements counter, detects full grid.
|
|
238
|
+
* Returns accumulated win across all rounds.
|
|
239
|
+
*/
|
|
240
|
+
async function runHoldAndSpin(ctx, feature) {
|
|
241
|
+
if (!ctx.delegate.onHoldAndSpinEnter) return 0;
|
|
242
|
+
const { totalRounds, lockedSymbols: initialLocked } = feature.params;
|
|
243
|
+
const totalPositions = ctx.config.reels * ctx.config.rows;
|
|
244
|
+
const allLockedSymbols = [...initialLocked];
|
|
245
|
+
let remainingRounds = totalRounds;
|
|
246
|
+
let accumulatedWin = 0;
|
|
247
|
+
let roundNumber = 0;
|
|
248
|
+
await ctx.delegate.onHoldAndSpinEnter({
|
|
249
|
+
totalRounds,
|
|
250
|
+
lockedSymbols: initialLocked
|
|
251
|
+
});
|
|
252
|
+
try {
|
|
253
|
+
while (remainingRounds > 0 && allLockedSymbols.length < totalPositions) {
|
|
254
|
+
roundNumber++;
|
|
255
|
+
const result = await ctx.delegate.requestSpinResult({
|
|
256
|
+
mode: "holdAndSpin",
|
|
257
|
+
round: roundNumber,
|
|
258
|
+
lockedSymbols: [...allLockedSymbols]
|
|
259
|
+
});
|
|
260
|
+
validateSpinResult(result, ctx.config);
|
|
261
|
+
await runSpin(ctx, result, "holdAndSpin");
|
|
262
|
+
await presentJackpot(ctx, result, "holdAndSpin");
|
|
263
|
+
if (result.featureTrigger) accumulatedWin += await ctx.runFeature(result.featureTrigger);
|
|
264
|
+
const newLocked = result.newLockedSymbols ?? [];
|
|
265
|
+
allLockedSymbols.push(...newLocked);
|
|
266
|
+
accumulatedWin += result.spinWin;
|
|
267
|
+
if (newLocked.length > 0) remainingRounds = totalRounds;
|
|
268
|
+
else remainingRounds--;
|
|
269
|
+
await ctx.delegate.onHoldAndSpinRound?.({
|
|
270
|
+
round: roundNumber,
|
|
271
|
+
remaining: remainingRounds,
|
|
272
|
+
grid: result.grid,
|
|
273
|
+
newLocked,
|
|
274
|
+
allLocked: [...allLockedSymbols]
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
} finally {
|
|
278
|
+
const fullGrid = allLockedSymbols.length >= totalPositions;
|
|
279
|
+
await ctx.delegate.onHoldAndSpinExit?.({
|
|
280
|
+
totalRounds: roundNumber,
|
|
281
|
+
totalWin: accumulatedWin,
|
|
282
|
+
fullGrid
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
return accumulatedWin;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/spindle/features/pick-bonus-runner.ts
|
|
290
|
+
/**
|
|
291
|
+
* Runs a pick bonus feature to completion.
|
|
292
|
+
* Interactive pick-and-reveal loop: player picks, server reveals, engine presents.
|
|
293
|
+
* Loop continues until server signals done via PickReveal.done flag.
|
|
294
|
+
* Returns accumulated prize across all picks.
|
|
295
|
+
*/
|
|
296
|
+
async function runPickBonus(ctx, feature) {
|
|
297
|
+
if (!ctx.delegate.onPickBonusEnter) return 0;
|
|
298
|
+
const { itemCount } = feature.params;
|
|
299
|
+
let accumulated = 0;
|
|
300
|
+
let round = 0;
|
|
301
|
+
await ctx.delegate.onPickBonusEnter({ itemCount });
|
|
302
|
+
try {
|
|
303
|
+
while (true) {
|
|
304
|
+
round++;
|
|
305
|
+
const choice = await ctx.delegate.requestPickChoice?.({
|
|
306
|
+
round,
|
|
307
|
+
accumulated
|
|
308
|
+
});
|
|
309
|
+
if (!choice) break;
|
|
310
|
+
const reveal = await ctx.delegate.requestPickReveal?.({ choiceIndex: choice.choiceIndex });
|
|
311
|
+
if (!reveal) break;
|
|
312
|
+
await ctx.delegate.presentPickReveal?.({
|
|
313
|
+
choiceIndex: reveal.choiceIndex,
|
|
314
|
+
prize: reveal.prize,
|
|
315
|
+
done: reveal.done
|
|
316
|
+
});
|
|
317
|
+
accumulated += reveal.prize;
|
|
318
|
+
if (reveal.done) break;
|
|
319
|
+
}
|
|
320
|
+
} finally {
|
|
321
|
+
await ctx.delegate.onPickBonusExit?.({ totalWin: accumulated });
|
|
322
|
+
}
|
|
323
|
+
return accumulated;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/spindle/features/wheel-bonus-runner.ts
|
|
328
|
+
/**
|
|
329
|
+
* Runs a wheel bonus feature to completion.
|
|
330
|
+
* Server determines the result; engine presents enter/spin/result/exit lifecycle.
|
|
331
|
+
* Supports recursive multi-tier wheels via innerWheel detection.
|
|
332
|
+
* Returns accumulated prize across all tiers.
|
|
333
|
+
*/
|
|
334
|
+
async function runWheelBonus(ctx, feature) {
|
|
335
|
+
if (!ctx.delegate.onWheelBonusEnter) return 0;
|
|
336
|
+
const { segments } = feature.params;
|
|
337
|
+
await ctx.delegate.onWheelBonusEnter({ segments });
|
|
338
|
+
let totalWin = 0;
|
|
339
|
+
try {
|
|
340
|
+
const result = await ctx.delegate.requestWheelSpinResult?.({ segments });
|
|
341
|
+
if (!result) return 0;
|
|
342
|
+
await ctx.delegate.presentWheelResult?.({
|
|
343
|
+
segmentIndex: result.segmentIndex,
|
|
344
|
+
prize: result.prize
|
|
345
|
+
});
|
|
346
|
+
totalWin = result.prize;
|
|
347
|
+
if (result.innerWheel) {
|
|
348
|
+
const innerTrigger = {
|
|
349
|
+
type: "wheelBonus",
|
|
350
|
+
params: result.innerWheel
|
|
351
|
+
};
|
|
352
|
+
totalWin += await runWheelBonus(ctx, innerTrigger);
|
|
353
|
+
}
|
|
354
|
+
} finally {
|
|
355
|
+
await ctx.delegate.onWheelBonusExit?.({ totalWin });
|
|
356
|
+
}
|
|
357
|
+
return totalWin;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
//#endregion
|
|
361
|
+
//#region src/spindle/spindle.ts
|
|
362
|
+
var SpindleImpl = class {
|
|
363
|
+
_isSpinning = false;
|
|
364
|
+
ctx;
|
|
365
|
+
constructor(config, delegate) {
|
|
366
|
+
const ctx = {
|
|
367
|
+
config,
|
|
368
|
+
delegate,
|
|
369
|
+
runFeature: async (trigger) => {
|
|
370
|
+
switch (trigger.type) {
|
|
371
|
+
case "freeSpins": return runFreeSpins(ctx, trigger);
|
|
372
|
+
case "holdAndSpin": return runHoldAndSpin(ctx, trigger);
|
|
373
|
+
case "pickBonus": return runPickBonus(ctx, trigger);
|
|
374
|
+
case "wheelBonus": return runWheelBonus(ctx, trigger);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
this.ctx = ctx;
|
|
379
|
+
}
|
|
380
|
+
get isSpinning() {
|
|
381
|
+
return this._isSpinning;
|
|
382
|
+
}
|
|
383
|
+
async spin() {
|
|
384
|
+
if (this._isSpinning) throw new Error("Cannot spin while already spinning");
|
|
385
|
+
this._isSpinning = true;
|
|
386
|
+
try {
|
|
387
|
+
await this.ctx.delegate.onSpinStart();
|
|
388
|
+
const result = await this.ctx.delegate.requestSpinResult({ mode: "base" });
|
|
389
|
+
validateSpinResult(result, this.ctx.config);
|
|
390
|
+
await runSpin(this.ctx, result, "base");
|
|
391
|
+
if (result.bigWinTier && result.bigWinTier !== "" && this.ctx.delegate.presentBigWin) await this.ctx.delegate.presentBigWin({
|
|
392
|
+
tier: result.bigWinTier,
|
|
393
|
+
amount: result.spinWin,
|
|
394
|
+
mode: "base",
|
|
395
|
+
metadata: result.metadata
|
|
396
|
+
});
|
|
397
|
+
await presentJackpot(this.ctx, result, "base");
|
|
398
|
+
const featureWin = result.featureTrigger ? await this.ctx.runFeature(result.featureTrigger) : 0;
|
|
399
|
+
const totalWin = result.spinWin + featureWin;
|
|
400
|
+
await this.ctx.delegate.onSpinEnd({
|
|
401
|
+
spinWin: result.spinWin,
|
|
402
|
+
featureWin,
|
|
403
|
+
totalWin
|
|
404
|
+
});
|
|
405
|
+
let finalWin = totalWin;
|
|
406
|
+
if (finalWin > 0 && this.ctx.delegate.onGambleStart && result.gambleAvailable !== false) finalWin = await runGamble(this.ctx, finalWin);
|
|
407
|
+
return { totalWin: finalWin };
|
|
408
|
+
} finally {
|
|
409
|
+
this._isSpinning = false;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
async buyFeature(featureType) {
|
|
413
|
+
if (this._isSpinning) throw new Error("Cannot buy feature while spinning");
|
|
414
|
+
if (!this.ctx.delegate.requestBuyFeatureResult) throw new Error("Buy feature delegate is not provided");
|
|
415
|
+
this._isSpinning = true;
|
|
416
|
+
try {
|
|
417
|
+
await this.ctx.delegate.onBuyFeatureStart?.({ featureType });
|
|
418
|
+
const trigger = await this.ctx.delegate.requestBuyFeatureResult({ featureType });
|
|
419
|
+
if (trigger.type !== featureType) throw new Error(`Expected trigger type "${featureType}", got "${trigger.type}"`);
|
|
420
|
+
const featureWin = await this.ctx.runFeature(trigger);
|
|
421
|
+
await this.ctx.delegate.onBuyFeatureEnd?.({ totalWin: featureWin });
|
|
422
|
+
return { totalWin: featureWin };
|
|
423
|
+
} finally {
|
|
424
|
+
this._isSpinning = false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
async function createSpindle(config, delegate) {
|
|
429
|
+
validateConfig(config);
|
|
430
|
+
return new SpindleImpl(config, delegate);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
//#endregion
|
|
434
|
+
exports.createSpindle = createSpindle;
|
|
435
|
+
exports.createSymbolId = require_types.createSymbolId;
|
|
436
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/config.ts","../src/spindle/spin-pipeline.ts","../src/spindle/features/free-spins-runner.ts","../src/spindle/features/gamble-runner.ts","../src/spindle/features/hold-and-spin-runner.ts","../src/spindle/features/pick-bonus-runner.ts","../src/spindle/features/wheel-bonus-runner.ts","../src/spindle/spindle.ts"],"sourcesContent":["/**\r\n * Configuration validation.\r\n */\r\n\r\nimport type { GameConfig, SpinResult } from \"./types.js\";\r\n\r\n// ---------------------------------------------------------------------------\r\n// Config Validation\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Validates a GameConfig at construction time.\r\n * Throws with descriptive error including field name and constraint.\r\n */\r\nexport function validateConfig(config: GameConfig): void {\r\n\tif (config.reels < 2) {\r\n\t\tthrow new Error(`reels must be >= 2, got ${config.reels}`);\r\n\t}\r\n\tif (config.rows < 1) {\r\n\t\tthrow new Error(`rows must be >= 1, got ${config.rows}`);\r\n\t}\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// SpinResult Validation (FR-014)\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Validates a SpinResult against the GameConfig at intake time.\r\n * Ensures grid.length matches config.reels and every reel is non-empty.\r\n * Row count per reel is NOT validated (supports jagged arrays / Megaways).\r\n */\r\nexport function validateSpinResult(result: SpinResult, config: GameConfig): void {\r\n\tif (result.grid.length !== config.reels) {\r\n\t\tthrow new Error(`grid.length must be ${config.reels}, got ${result.grid.length}`);\r\n\t}\r\n\tfor (let i = 0; i < result.grid.length; i++) {\r\n\t\tconst reel = result.grid[i];\r\n\t\tif (!Array.isArray(reel) || reel.length === 0) {\r\n\t\t\tthrow new Error(`grid[${i}] must be a non-empty array`);\r\n\t\t}\r\n\t}\r\n\tif (result.cascadeSteps) {\r\n\t\tfor (let s = 0; s < result.cascadeSteps.length; s++) {\r\n\t\t\tconst step = result.cascadeSteps[s];\r\n\t\t\tif (step.grid.length !== config.reels) {\r\n\t\t\t\tthrow new Error(\r\n\t\t\t\t\t`cascadeSteps[${s}].grid.length must be ${config.reels}, got ${step.grid.length}`,\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t\tfor (let i = 0; i < step.grid.length; i++) {\r\n\t\t\t\tconst reel = step.grid[i];\r\n\t\t\t\tif (!Array.isArray(reel) || reel.length === 0) {\r\n\t\t\t\t\tthrow new Error(`cascadeSteps[${s}].grid[${i}] must be a non-empty array`);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n","import type { PresentationContext, SpinMode, SpinResult } from \"../types.js\";\r\nimport type { SpindleContext } from \"./context.js\";\r\n\r\nfunction withCtx<T>(data: T, mode: SpinMode, metadata?: unknown): T & PresentationContext {\r\n\treturn { ...data, mode, metadata };\r\n}\r\n\r\n/**\r\n * Pure presentation pipeline — reels, transforms, wilds, wins, cascades, scatter.\r\n * Composable: called by spindle.spin() for base game AND by feature runners per spin.\r\n *\r\n * Post-win celebrations (big win, jackpot) are NOT handled here — callers\r\n * manage them based on context (base game vs feature spins).\r\n */\r\nexport async function runSpin(\r\n\tctx: SpindleContext,\r\n\tresult: SpinResult,\r\n\tmode: SpinMode,\r\n): Promise<void> {\r\n\tconst grid = result.grid;\r\n\tconst meta = result.metadata;\r\n\r\n\t// Build anticipation set — O(1) lookup, dedup, range filter\r\n\tconst anticipationSet = new Set<number>();\r\n\tif (result.anticipationReels && ctx.delegate.presentAnticipation) {\r\n\t\tfor (const reelIdx of result.anticipationReels) {\r\n\t\t\tif (reelIdx >= 0 && reelIdx < grid.length) {\r\n\t\t\t\tanticipationSet.add(reelIdx);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// Reel stops — left to right, with anticipation interleave\r\n\tfor (let i = 0; i < grid.length; i++) {\r\n\t\tif (anticipationSet.has(i)) {\r\n\t\t\tawait ctx.delegate.presentAnticipation?.(withCtx({ reelIndex: i }, mode, meta));\r\n\t\t}\r\n\t\tawait ctx.delegate.presentReelStop(withCtx({ reelIndex: i, symbols: grid[i] }, mode, meta));\r\n\t}\r\n\r\n\t// Symbol transform — after reel stops, before wilds\r\n\tif (result.symbolTransforms?.length && ctx.delegate.presentSymbolTransform) {\r\n\t\tawait ctx.delegate.presentSymbolTransform(\r\n\t\t\twithCtx({ transforms: result.symbolTransforms }, mode, meta),\r\n\t\t);\r\n\t}\r\n\r\n\t// Wild event processing — after reel stops, before wins\r\n\tif (result.wildEvents) {\r\n\t\tfor (const event of result.wildEvents) {\r\n\t\t\tswitch (event.type) {\r\n\t\t\t\tcase \"regular\":\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"expanding\":\r\n\t\t\t\t\tif (ctx.delegate.presentExpandingWild) {\r\n\t\t\t\t\t\tawait ctx.delegate.presentExpandingWild(\r\n\t\t\t\t\t\t\twithCtx(\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tposition: event.position,\r\n\t\t\t\t\t\t\t\t\texpandedPositions: event.expandedPositions,\r\n\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t\tmode,\r\n\t\t\t\t\t\t\t\tmeta,\r\n\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"sticky\":\r\n\t\t\t\t\tif (ctx.delegate.presentStickyWild) {\r\n\t\t\t\t\t\tawait ctx.delegate.presentStickyWild(withCtx({ position: event.position }, mode, meta));\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"walking\":\r\n\t\t\t\t\tif (ctx.delegate.presentWalkingWild) {\r\n\t\t\t\t\t\tawait ctx.delegate.presentWalkingWild(\r\n\t\t\t\t\t\t\twithCtx({ from: event.from, to: event.to }, mode, meta),\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"stacked\":\r\n\t\t\t\t\tif (ctx.delegate.presentStackedWild) {\r\n\t\t\t\t\t\tawait ctx.delegate.presentStackedWild(\r\n\t\t\t\t\t\t\twithCtx({ positions: event.positions }, mode, meta),\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"multiplier\":\r\n\t\t\t\t\tif (ctx.delegate.presentMultiplierWild) {\r\n\t\t\t\t\t\tawait ctx.delegate.presentMultiplierWild(\r\n\t\t\t\t\t\t\twithCtx(\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tposition: event.position,\r\n\t\t\t\t\t\t\t\t\tmultiplier: event.multiplier,\r\n\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t\tmode,\r\n\t\t\t\t\t\t\t\tmeta,\r\n\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"random\":\r\n\t\t\t\t\tif (ctx.delegate.presentRandomWild) {\r\n\t\t\t\t\t\tawait ctx.delegate.presentRandomWild(\r\n\t\t\t\t\t\t\twithCtx({ positions: event.positions }, mode, meta),\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// Win presentation\r\n\tfor (const win of result.wins) {\r\n\t\tswitch (win.type) {\r\n\t\t\tcase \"ways\":\r\n\t\t\t\tawait ctx.delegate.presentWaysWin?.(withCtx(win, mode, meta));\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"payline\":\r\n\t\t\t\tawait ctx.delegate.presentPaylineWin?.(withCtx(win, mode, meta));\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"cluster\":\r\n\t\t\t\tawait ctx.delegate.presentClusterWin?.(withCtx(win, mode, meta));\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\t// Cascade loop — before scatter, matching real slot conventions\r\n\tif (result.cascadeSteps?.length && ctx.delegate.presentCascadeDestroy) {\r\n\t\tfor (let stepIdx = 0; stepIdx < result.cascadeSteps.length; stepIdx++) {\r\n\t\t\tconst step = result.cascadeSteps[stepIdx];\r\n\t\t\tawait ctx.delegate.presentCascadeDestroy(\r\n\t\t\t\twithCtx({ step: stepIdx, positions: step.destroyPositions }, mode, meta),\r\n\t\t\t);\r\n\t\t\tawait ctx.delegate.presentCascadeDrop?.(withCtx({ step: stepIdx }, mode, meta));\r\n\t\t\tawait ctx.delegate.presentCascadeFill?.(\r\n\t\t\t\twithCtx(\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tstep: stepIdx,\r\n\t\t\t\t\t\tgrid: step.grid,\r\n\t\t\t\t\t\tmultiplier: step.multiplier,\r\n\t\t\t\t\t},\r\n\t\t\t\t\tmode,\r\n\t\t\t\t\tmeta,\r\n\t\t\t\t),\r\n\t\t\t);\r\n\t\t\tfor (const win of step.wins) {\r\n\t\t\t\tswitch (win.type) {\r\n\t\t\t\t\tcase \"ways\":\r\n\t\t\t\t\t\tawait ctx.delegate.presentWaysWin?.(withCtx(win, mode, meta));\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase \"payline\":\r\n\t\t\t\t\t\tawait ctx.delegate.presentPaylineWin?.(withCtx(win, mode, meta));\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase \"cluster\":\r\n\t\t\t\t\t\tawait ctx.delegate.presentClusterWin?.(withCtx(win, mode, meta));\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// Scatter presentation — after cascade\r\n\tif (result.scatterWin && ctx.delegate.presentScatterWin) {\r\n\t\tawait ctx.delegate.presentScatterWin(withCtx(result.scatterWin, mode, meta));\r\n\t}\r\n}\r\n\r\n/**\r\n * Presents jackpot trigger and award if present on the spin result.\r\n * Called by spindle.spin() (after big win) and by feature runners per spin.\r\n */\r\nexport async function presentJackpot(\r\n\tctx: SpindleContext,\r\n\tresult: SpinResult,\r\n\tmode: SpinMode,\r\n): Promise<void> {\r\n\tif (result.jackpotWin && ctx.delegate.presentJackpotTrigger) {\r\n\t\tconst meta = result.metadata;\r\n\t\tawait ctx.delegate.presentJackpotTrigger(\r\n\t\t\twithCtx(\r\n\t\t\t\t{\r\n\t\t\t\t\ttier: result.jackpotWin.tier,\r\n\t\t\t\t\tamount: result.jackpotWin.amount,\r\n\t\t\t\t},\r\n\t\t\t\tmode,\r\n\t\t\t\tmeta,\r\n\t\t\t),\r\n\t\t);\r\n\t\tawait ctx.delegate.onJackpotAwarded?.(\r\n\t\t\twithCtx(\r\n\t\t\t\t{\r\n\t\t\t\t\ttier: result.jackpotWin.tier,\r\n\t\t\t\t\tamount: result.jackpotWin.amount,\r\n\t\t\t\t},\r\n\t\t\t\tmode,\r\n\t\t\t\tmeta,\r\n\t\t\t),\r\n\t\t);\r\n\t}\r\n}\r\n","import { validateSpinResult } from \"../../config.js\";\r\nimport type { FeatureTrigger } from \"../../types.js\";\r\nimport type { SpindleContext } from \"../context.js\";\r\nimport { presentJackpot, runSpin } from \"../spin-pipeline.js\";\r\n\r\n/**\r\n * Runs a free spins feature to completion.\r\n * Reuses runSpin() for each spin's presentation pipeline.\r\n * Returns accumulated win across all free spins.\r\n */\r\nexport async function runFreeSpins(\r\n\tctx: SpindleContext,\r\n\tfeature: FeatureTrigger & { type: \"freeSpins\" },\r\n): Promise<number> {\r\n\tif (!ctx.delegate.onFreeSpinsEnter) return 0;\r\n\r\n\tlet remainingSpins = feature.params.totalSpins;\r\n\tlet totalSpins = feature.params.totalSpins;\r\n\tlet accumulatedWin = 0;\r\n\r\n\tawait ctx.delegate.onFreeSpinsEnter({ totalSpins });\r\n\r\n\ttry {\r\n\t\twhile (remainingSpins > 0) {\r\n\t\t\tconst currentSpin = totalSpins - remainingSpins + 1;\r\n\r\n\t\t\tawait ctx.delegate.onFreeSpinStart?.({\r\n\t\t\t\tcurrentSpin,\r\n\t\t\t\ttotalSpins,\r\n\t\t\t\tremaining: remainingSpins,\r\n\t\t\t\taccumulated: accumulatedWin,\r\n\t\t\t});\r\n\r\n\t\t\tconst result = await ctx.delegate.requestSpinResult({\r\n\t\t\t\tmode: \"freeSpin\",\r\n\t\t\t\tcurrentSpin,\r\n\t\t\t\ttotalSpins,\r\n\t\t\t});\r\n\r\n\t\t\tvalidateSpinResult(result, ctx.config);\r\n\r\n\t\t\t// Run presentation pipeline (reels, wins, cascades)\r\n\t\t\tawait runSpin(ctx, result, \"freeSpin\");\r\n\t\t\tawait presentJackpot(ctx, result, \"freeSpin\");\r\n\r\n\t\t\t// Accumulate spin-level wins (excludes jackpot — tracked via onJackpotAwarded)\r\n\t\t\taccumulatedWin += result.spinWin;\r\n\r\n\t\t\t// Retrigger check (same-type: adds spins to current loop)\r\n\t\t\tif (result.featureTrigger?.type === \"freeSpins\") {\r\n\t\t\t\tconst added = result.featureTrigger.params.totalSpins;\r\n\t\t\t\tremainingSpins += added;\r\n\t\t\t\ttotalSpins += added;\r\n\t\t\t\tawait ctx.delegate.onFreeSpinsRetrigger?.({ added, totalSpins });\r\n\t\t\t}\r\n\r\n\t\t\t// Nested cross-feature trigger (e.g., free spin triggers hold-and-spin)\r\n\t\t\tif (result.featureTrigger && result.featureTrigger.type !== \"freeSpins\") {\r\n\t\t\t\taccumulatedWin += await ctx.runFeature(result.featureTrigger);\r\n\t\t\t}\r\n\r\n\t\t\tremainingSpins--;\r\n\t\t}\r\n\t} finally {\r\n\t\tawait ctx.delegate.onFreeSpinsExit?.({ totalSpins, totalWin: accumulatedWin });\r\n\t}\r\n\r\n\treturn accumulatedWin;\r\n}\r\n","import type { SpindleContext } from \"../context.js\";\r\n\r\n/**\r\n * Runs the gamble (double-up) flow to completion.\r\n * Called after onSpinEnd when totalWin > 0 and gamble delegate is present.\r\n * Returns the final win amount (potentially modified by gamble rounds).\r\n */\r\nexport async function runGamble(ctx: SpindleContext, initialWin: number): Promise<number> {\r\n\tif (!ctx.delegate.onGambleStart) return initialWin;\r\n\r\n\tlet currentWin = initialWin;\r\n\tawait ctx.delegate.onGambleStart({ currentWin });\r\n\r\n\ttry {\r\n\t\twhile (true) {\r\n\t\t\tconst choice = await ctx.delegate.requestGambleChoice?.({ currentWin });\r\n\t\t\tif (!choice || choice.action === \"collect\") break;\r\n\r\n\t\t\tconst result = await ctx.delegate.requestGambleResult?.({ currentWin });\r\n\t\t\tif (!result) break;\r\n\t\t\tawait ctx.delegate.presentGambleResult?.({ won: result.won, amount: result.amount });\r\n\r\n\t\t\tcurrentWin = result.amount;\r\n\t\t\tif (!result.won) break;\r\n\t\t}\r\n\t} finally {\r\n\t\tawait ctx.delegate.onGambleEnd?.({ finalWin: currentWin });\r\n\t}\r\n\r\n\treturn currentWin;\r\n}\r\n","import { validateSpinResult } from \"../../config.js\";\r\nimport type { FeatureTrigger } from \"../../types.js\";\r\nimport type { SpindleContext } from \"../context.js\";\r\nimport { presentJackpot, runSpin } from \"../spin-pipeline.js\";\r\n\r\n/**\r\n * Runs a hold-and-spin feature to completion.\r\n * Loops respin rounds, reuses runSpin() for each round's presentation,\r\n * tracks locked symbols, resets/decrements counter, detects full grid.\r\n * Returns accumulated win across all rounds.\r\n */\r\nexport async function runHoldAndSpin(\r\n\tctx: SpindleContext,\r\n\tfeature: FeatureTrigger & { type: \"holdAndSpin\" },\r\n): Promise<number> {\r\n\tif (!ctx.delegate.onHoldAndSpinEnter) return 0;\r\n\r\n\tconst { totalRounds, lockedSymbols: initialLocked } = feature.params;\r\n\tconst totalPositions = ctx.config.reels * ctx.config.rows;\r\n\tconst allLockedSymbols = [...initialLocked];\r\n\tlet remainingRounds = totalRounds;\r\n\tlet accumulatedWin = 0;\r\n\tlet roundNumber = 0;\r\n\r\n\tawait ctx.delegate.onHoldAndSpinEnter({\r\n\t\ttotalRounds,\r\n\t\tlockedSymbols: initialLocked,\r\n\t});\r\n\r\n\ttry {\r\n\t\twhile (remainingRounds > 0 && allLockedSymbols.length < totalPositions) {\r\n\t\t\troundNumber++;\r\n\r\n\t\t\tconst result = await ctx.delegate.requestSpinResult({\r\n\t\t\t\tmode: \"holdAndSpin\",\r\n\t\t\t\tround: roundNumber,\r\n\t\t\t\tlockedSymbols: [...allLockedSymbols],\r\n\t\t\t});\r\n\r\n\t\t\tvalidateSpinResult(result, ctx.config);\r\n\t\t\tawait runSpin(ctx, result, \"holdAndSpin\");\r\n\t\t\tawait presentJackpot(ctx, result, \"holdAndSpin\");\r\n\r\n\t\t\t// Nested feature trigger (e.g., hold-and-spin round triggers free spins)\r\n\t\t\tif (result.featureTrigger) {\r\n\t\t\t\taccumulatedWin += await ctx.runFeature(result.featureTrigger);\r\n\t\t\t}\r\n\r\n\t\t\tconst newLocked = result.newLockedSymbols ?? [];\r\n\t\t\tallLockedSymbols.push(...newLocked);\r\n\t\t\taccumulatedWin += result.spinWin;\r\n\r\n\t\t\t// Update counter before delegate call so remaining reflects post-round state\r\n\t\t\tif (newLocked.length > 0) {\r\n\t\t\t\tremainingRounds = totalRounds;\r\n\t\t\t} else {\r\n\t\t\t\tremainingRounds--;\r\n\t\t\t}\r\n\r\n\t\t\tawait ctx.delegate.onHoldAndSpinRound?.({\r\n\t\t\t\tround: roundNumber,\r\n\t\t\t\tremaining: remainingRounds,\r\n\t\t\t\tgrid: result.grid,\r\n\t\t\t\tnewLocked,\r\n\t\t\t\tallLocked: [...allLockedSymbols],\r\n\t\t\t});\r\n\t\t}\r\n\t} finally {\r\n\t\tconst fullGrid = allLockedSymbols.length >= totalPositions;\r\n\t\tawait ctx.delegate.onHoldAndSpinExit?.({\r\n\t\t\ttotalRounds: roundNumber,\r\n\t\t\ttotalWin: accumulatedWin,\r\n\t\t\tfullGrid,\r\n\t\t});\r\n\t}\r\n\r\n\treturn accumulatedWin;\r\n}\r\n","import type { FeatureTrigger } from \"../../types.js\";\r\nimport type { SpindleContext } from \"../context.js\";\r\n\r\n/**\r\n * Runs a pick bonus feature to completion.\r\n * Interactive pick-and-reveal loop: player picks, server reveals, engine presents.\r\n * Loop continues until server signals done via PickReveal.done flag.\r\n * Returns accumulated prize across all picks.\r\n */\r\nexport async function runPickBonus(\r\n\tctx: SpindleContext,\r\n\tfeature: FeatureTrigger & { type: \"pickBonus\" },\r\n): Promise<number> {\r\n\tif (!ctx.delegate.onPickBonusEnter) return 0;\r\n\r\n\tconst { itemCount } = feature.params;\r\n\tlet accumulated = 0;\r\n\tlet round = 0;\r\n\r\n\tawait ctx.delegate.onPickBonusEnter({ itemCount });\r\n\r\n\ttry {\r\n\t\twhile (true) {\r\n\t\t\tround++;\r\n\t\t\tconst choice = await ctx.delegate.requestPickChoice?.({ round, accumulated });\r\n\t\t\tif (!choice) break;\r\n\t\t\tconst reveal = await ctx.delegate.requestPickReveal?.({ choiceIndex: choice.choiceIndex });\r\n\t\t\tif (!reveal) break;\r\n\t\t\tawait ctx.delegate.presentPickReveal?.({\r\n\t\t\t\tchoiceIndex: reveal.choiceIndex,\r\n\t\t\t\tprize: reveal.prize,\r\n\t\t\t\tdone: reveal.done,\r\n\t\t\t});\r\n\t\t\taccumulated += reveal.prize;\r\n\t\t\tif (reveal.done) break;\r\n\t\t}\r\n\t} finally {\r\n\t\tawait ctx.delegate.onPickBonusExit?.({ totalWin: accumulated });\r\n\t}\r\n\r\n\treturn accumulated;\r\n}\r\n","import type { FeatureTrigger } from \"../../types.js\";\r\nimport type { SpindleContext } from \"../context.js\";\r\n\r\n/**\r\n * Runs a wheel bonus feature to completion.\r\n * Server determines the result; engine presents enter/spin/result/exit lifecycle.\r\n * Supports recursive multi-tier wheels via innerWheel detection.\r\n * Returns accumulated prize across all tiers.\r\n */\r\nexport async function runWheelBonus(\r\n\tctx: SpindleContext,\r\n\tfeature: FeatureTrigger & { type: \"wheelBonus\" },\r\n): Promise<number> {\r\n\tif (!ctx.delegate.onWheelBonusEnter) return 0;\r\n\r\n\tconst { segments } = feature.params;\r\n\r\n\tawait ctx.delegate.onWheelBonusEnter({ segments });\r\n\r\n\tlet totalWin = 0;\r\n\ttry {\r\n\t\tconst result = await ctx.delegate.requestWheelSpinResult?.({ segments });\r\n\t\tif (!result) return 0;\r\n\t\tawait ctx.delegate.presentWheelResult?.({\r\n\t\t\tsegmentIndex: result.segmentIndex,\r\n\t\t\tprize: result.prize,\r\n\t\t});\r\n\t\ttotalWin = result.prize;\r\n\r\n\t\tif (result.innerWheel) {\r\n\t\t\tconst innerTrigger = { type: \"wheelBonus\" as const, params: result.innerWheel };\r\n\t\t\ttotalWin += await runWheelBonus(ctx, innerTrigger);\r\n\t\t}\r\n\t} finally {\r\n\t\tawait ctx.delegate.onWheelBonusExit?.({ totalWin });\r\n\t}\r\n\r\n\treturn totalWin;\r\n}\r\n","import { validateConfig, validateSpinResult } from \"../config.js\";\r\nimport type { SpindleDelegate } from \"../delegates.js\";\r\nimport type { FeatureTrigger, FeatureType, GameConfig, SpinFlowResult } from \"../types.js\";\r\nimport type { SpindleContext } from \"./context.js\";\r\nimport { runFreeSpins } from \"./features/free-spins-runner.js\";\r\nimport { runGamble } from \"./features/gamble-runner.js\";\r\nimport { runHoldAndSpin } from \"./features/hold-and-spin-runner.js\";\r\nimport { runPickBonus } from \"./features/pick-bonus-runner.js\";\r\nimport { runWheelBonus } from \"./features/wheel-bonus-runner.js\";\r\nimport { presentJackpot, runSpin } from \"./spin-pipeline.js\";\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public Types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface Spindle {\r\n\tspin(): Promise<SpinFlowResult>;\r\n\tbuyFeature(featureType: FeatureType): Promise<SpinFlowResult>;\r\n\treadonly isSpinning: boolean;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Spindle Implementation\r\n// ---------------------------------------------------------------------------\r\n\r\nclass SpindleImpl implements Spindle {\r\n\tprivate _isSpinning = false;\r\n\tprivate readonly ctx: SpindleContext;\r\n\r\n\tconstructor(config: GameConfig, delegate: SpindleDelegate) {\r\n\t\tconst ctx: SpindleContext = {\r\n\t\t\tconfig,\r\n\t\t\tdelegate,\r\n\t\t\trunFeature: async (trigger: FeatureTrigger): Promise<number> => {\r\n\t\t\t\tswitch (trigger.type) {\r\n\t\t\t\t\tcase \"freeSpins\":\r\n\t\t\t\t\t\treturn runFreeSpins(ctx, trigger);\r\n\t\t\t\t\tcase \"holdAndSpin\":\r\n\t\t\t\t\t\treturn runHoldAndSpin(ctx, trigger);\r\n\t\t\t\t\tcase \"pickBonus\":\r\n\t\t\t\t\t\treturn runPickBonus(ctx, trigger);\r\n\t\t\t\t\tcase \"wheelBonus\":\r\n\t\t\t\t\t\treturn runWheelBonus(ctx, trigger);\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t};\r\n\t\tthis.ctx = ctx;\r\n\t}\r\n\r\n\tget isSpinning(): boolean {\r\n\t\treturn this._isSpinning;\r\n\t}\r\n\r\n\tasync spin(): Promise<SpinFlowResult> {\r\n\t\tif (this._isSpinning) {\r\n\t\t\tthrow new Error(\"Cannot spin while already spinning\");\r\n\t\t}\r\n\t\tthis._isSpinning = true;\r\n\r\n\t\ttry {\r\n\t\t\t// 1. Notify spin start\r\n\t\t\tawait this.ctx.delegate.onSpinStart();\r\n\r\n\t\t\t// 2. Request and validate spin result\r\n\t\t\tconst result = await this.ctx.delegate.requestSpinResult({ mode: \"base\" });\r\n\t\t\tvalidateSpinResult(result, this.ctx.config);\r\n\r\n\t\t\t// 3. Run presentation pipeline (reels, wins, cascades, scatter)\r\n\t\t\tawait runSpin(this.ctx, result, \"base\");\r\n\r\n\t\t\t// 4. Big win — base game only (features have their own exit presentations)\r\n\t\t\tif (result.bigWinTier && result.bigWinTier !== \"\" && this.ctx.delegate.presentBigWin) {\r\n\t\t\t\tawait this.ctx.delegate.presentBigWin({\r\n\t\t\t\t\ttier: result.bigWinTier,\r\n\t\t\t\t\tamount: result.spinWin,\r\n\t\t\t\t\tmode: \"base\",\r\n\t\t\t\t\tmetadata: result.metadata,\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\t// 5. Jackpot — after big win\r\n\t\t\tawait presentJackpot(this.ctx, result, \"base\");\r\n\r\n\t\t\t// 6. Run triggered features\r\n\t\t\tconst featureWin = result.featureTrigger\r\n\t\t\t\t? await this.ctx.runFeature(result.featureTrigger)\r\n\t\t\t\t: 0;\r\n\r\n\t\t\t// 7. Complete spin\r\n\t\t\tconst totalWin = result.spinWin + featureWin;\r\n\t\t\tawait this.ctx.delegate.onSpinEnd({ spinWin: result.spinWin, featureWin, totalWin });\r\n\r\n\t\t\t// 8. Gamble (post-spin modifier)\r\n\t\t\tlet finalWin = totalWin;\r\n\t\t\tif (finalWin > 0 && this.ctx.delegate.onGambleStart && result.gambleAvailable !== false) {\r\n\t\t\t\tfinalWin = await runGamble(this.ctx, finalWin);\r\n\t\t\t}\r\n\r\n\t\t\treturn { totalWin: finalWin };\r\n\t\t} finally {\r\n\t\t\tthis._isSpinning = false;\r\n\t\t}\r\n\t}\r\n\r\n\tasync buyFeature(featureType: FeatureType): Promise<SpinFlowResult> {\r\n\t\tif (this._isSpinning) {\r\n\t\t\tthrow new Error(\"Cannot buy feature while spinning\");\r\n\t\t}\r\n\t\tif (!this.ctx.delegate.requestBuyFeatureResult) {\r\n\t\t\tthrow new Error(\"Buy feature delegate is not provided\");\r\n\t\t}\r\n\r\n\t\tthis._isSpinning = true;\r\n\t\ttry {\r\n\t\t\tawait this.ctx.delegate.onBuyFeatureStart?.({ featureType });\r\n\r\n\t\t\tconst trigger = await this.ctx.delegate.requestBuyFeatureResult({ featureType });\r\n\t\t\tif (trigger.type !== featureType) {\r\n\t\t\t\tthrow new Error(`Expected trigger type \"${featureType}\", got \"${trigger.type}\"`);\r\n\t\t\t}\r\n\r\n\t\t\tconst featureWin = await this.ctx.runFeature(trigger);\r\n\r\n\t\t\tawait this.ctx.delegate.onBuyFeatureEnd?.({ totalWin: featureWin });\r\n\r\n\t\t\treturn { totalWin: featureWin };\r\n\t\t} finally {\r\n\t\t\tthis._isSpinning = false;\r\n\t\t}\r\n\t}\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Factory\r\n// ---------------------------------------------------------------------------\r\n\r\nexport async function createSpindle(\r\n\tconfig: GameConfig,\r\n\tdelegate: SpindleDelegate,\r\n): Promise<Spindle> {\r\n\tvalidateConfig(config);\r\n\r\n\treturn new SpindleImpl(config, delegate);\r\n}\r\n"],"mappings":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { $ as StackedWildEvent, A as FeatureType, B as MultiplierWildEvent, C as WaysWinDelegate, D as ClusterWin, E as CascadeStep, F as GridPosition, G as PresentationContext, H as PickBonusParams, I as HoldAndSpinParams, J as ScatterWin, K as RandomWildEvent, L as HoldAndSpinRoundResult, M as GambleChoice, N as GambleResult, O as ExpandingWildEvent, P as GameConfig, Q as SpinResult, R as JackpotWin, S as WalkingWildDelegate, T as CapabilityType, U as PickChoice, V as PaylineWin, W as PickReveal, X as SpinMode, Y as SpinFlowResult, Z as SpinRequestContext, _ as ScatterWinDelegate, a as ClusterWinDelegate, at as WaysWin, b as StickyWildDelegate, c as ExpandingWildDelegate, ct as WheelSpinResult, d as HoldAndSpinDelegate, dt as WinResult, et as StickyWildEvent, f as JackpotDelegate, ft as WinType, g as RandomWildDelegate, h as PickBonusDelegate, i as CascadeDelegate, it as WalkingWildEvent, j as FreeSpinsParams, k as FeatureTrigger, l as FreeSpinsDelegate, lt as WildEvent, m as PaylineWinDelegate, n as BigWinDelegate, nt as SymbolId, o as CoreDelegate, ot as WheelBonusParams, p as MultiplierWildDelegate, pt as createSymbolId, q as RegularWildEvent, r as BuyFeatureDelegate, rt as SymbolTransform, s as DataDelegate, st as WheelSegment, t as AnticipationDelegate, tt as SymbolGrid, u as GambleDelegate, ut as WildType, v as SpindleDelegate, w as WheelBonusDelegate, x as SymbolTransformDelegate, y as StackedWildDelegate, z as LockedSymbol } from "./delegates-DOwv3sAL.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/spindle/spindle.d.ts
|
|
4
|
+
interface Spindle {
|
|
5
|
+
spin(): Promise<SpinFlowResult>;
|
|
6
|
+
buyFeature(featureType: FeatureType): Promise<SpinFlowResult>;
|
|
7
|
+
readonly isSpinning: boolean;
|
|
8
|
+
}
|
|
9
|
+
declare function createSpindle(config: GameConfig, delegate: SpindleDelegate): Promise<Spindle>;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { type AnticipationDelegate, type BigWinDelegate, type BuyFeatureDelegate, type CapabilityType, type CascadeDelegate, type CascadeStep, type ClusterWin, type ClusterWinDelegate, type CoreDelegate, type DataDelegate, type ExpandingWildDelegate, type ExpandingWildEvent, type FeatureTrigger, type FeatureType, type FreeSpinsDelegate, type FreeSpinsParams, type GambleChoice, type GambleDelegate, type GambleResult, type GameConfig, type GridPosition, type HoldAndSpinDelegate, type HoldAndSpinParams, type HoldAndSpinRoundResult, type JackpotDelegate, type JackpotWin, type LockedSymbol, type MultiplierWildDelegate, type MultiplierWildEvent, type PaylineWin, type PaylineWinDelegate, type PickBonusDelegate, type PickBonusParams, type PickChoice, type PickReveal, type PresentationContext, type RandomWildDelegate, type RandomWildEvent, type RegularWildEvent, type ScatterWin, type ScatterWinDelegate, type SpinFlowResult, type SpinMode, type SpinRequestContext, type SpinResult, type Spindle, type SpindleDelegate, type StackedWildDelegate, type StackedWildEvent, type StickyWildDelegate, type StickyWildEvent, type SymbolGrid, type SymbolId, type SymbolTransform, type SymbolTransformDelegate, type WalkingWildDelegate, type WalkingWildEvent, type WaysWin, type WaysWinDelegate, type WheelBonusDelegate, type WheelBonusParams, type WheelSegment, type WheelSpinResult, type WildEvent, type WildType, type WinResult, type WinType, createSpindle, createSymbolId };
|
|
12
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/spindle/spindle.ts"],"mappings":";;;UAeiB,OAAA;EAChB,IAAA,IAAQ,OAAA,CAAQ,cAAA;EAChB,UAAA,CAAW,WAAA,EAAa,WAAA,GAAc,OAAA,CAAQ,cAAA;EAAA,SACrC,UAAA;AAAA;AAAA,iBAsHY,aAAA,CACrB,MAAA,EAAQ,UAAA,EACR,QAAA,EAAU,eAAA,GACR,OAAA,CAAQ,OAAA"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { $ as StackedWildEvent, A as FeatureType, B as MultiplierWildEvent, C as WaysWinDelegate, D as ClusterWin, E as CascadeStep, F as GridPosition, G as PresentationContext, H as PickBonusParams, I as HoldAndSpinParams, J as ScatterWin, K as RandomWildEvent, L as HoldAndSpinRoundResult, M as GambleChoice, N as GambleResult, O as ExpandingWildEvent, P as GameConfig, Q as SpinResult, R as JackpotWin, S as WalkingWildDelegate, T as CapabilityType, U as PickChoice, V as PaylineWin, W as PickReveal, X as SpinMode, Y as SpinFlowResult, Z as SpinRequestContext, _ as ScatterWinDelegate, a as ClusterWinDelegate, at as WaysWin, b as StickyWildDelegate, c as ExpandingWildDelegate, ct as WheelSpinResult, d as HoldAndSpinDelegate, dt as WinResult, et as StickyWildEvent, f as JackpotDelegate, ft as WinType, g as RandomWildDelegate, h as PickBonusDelegate, i as CascadeDelegate, it as WalkingWildEvent, j as FreeSpinsParams, k as FeatureTrigger, l as FreeSpinsDelegate, lt as WildEvent, m as PaylineWinDelegate, n as BigWinDelegate, nt as SymbolId, o as CoreDelegate, ot as WheelBonusParams, p as MultiplierWildDelegate, pt as createSymbolId, q as RegularWildEvent, r as BuyFeatureDelegate, rt as SymbolTransform, s as DataDelegate, st as WheelSegment, t as AnticipationDelegate, tt as SymbolGrid, u as GambleDelegate, ut as WildType, v as SpindleDelegate, w as WheelBonusDelegate, x as SymbolTransformDelegate, y as StackedWildDelegate, z as LockedSymbol } from "./delegates-Jm8q1-L0.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/spindle/spindle.d.ts
|
|
4
|
+
interface Spindle {
|
|
5
|
+
spin(): Promise<SpinFlowResult>;
|
|
6
|
+
buyFeature(featureType: FeatureType): Promise<SpinFlowResult>;
|
|
7
|
+
readonly isSpinning: boolean;
|
|
8
|
+
}
|
|
9
|
+
declare function createSpindle(config: GameConfig, delegate: SpindleDelegate): Promise<Spindle>;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { type AnticipationDelegate, type BigWinDelegate, type BuyFeatureDelegate, type CapabilityType, type CascadeDelegate, type CascadeStep, type ClusterWin, type ClusterWinDelegate, type CoreDelegate, type DataDelegate, type ExpandingWildDelegate, type ExpandingWildEvent, type FeatureTrigger, type FeatureType, type FreeSpinsDelegate, type FreeSpinsParams, type GambleChoice, type GambleDelegate, type GambleResult, type GameConfig, type GridPosition, type HoldAndSpinDelegate, type HoldAndSpinParams, type HoldAndSpinRoundResult, type JackpotDelegate, type JackpotWin, type LockedSymbol, type MultiplierWildDelegate, type MultiplierWildEvent, type PaylineWin, type PaylineWinDelegate, type PickBonusDelegate, type PickBonusParams, type PickChoice, type PickReveal, type PresentationContext, type RandomWildDelegate, type RandomWildEvent, type RegularWildEvent, type ScatterWin, type ScatterWinDelegate, type SpinFlowResult, type SpinMode, type SpinRequestContext, type SpinResult, type Spindle, type SpindleDelegate, type StackedWildDelegate, type StackedWildEvent, type StickyWildDelegate, type StickyWildEvent, type SymbolGrid, type SymbolId, type SymbolTransform, type SymbolTransformDelegate, type WalkingWildDelegate, type WalkingWildEvent, type WaysWin, type WaysWinDelegate, type WheelBonusDelegate, type WheelBonusParams, type WheelSegment, type WheelSpinResult, type WildEvent, type WildType, type WinResult, type WinType, createSpindle, createSymbolId };
|
|
12
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/spindle/spindle.ts"],"mappings":";;;UAeiB,OAAA;EAChB,IAAA,IAAQ,OAAA,CAAQ,cAAA;EAChB,UAAA,CAAW,WAAA,EAAa,WAAA,GAAc,OAAA,CAAQ,cAAA;EAAA,SACrC,UAAA;AAAA;AAAA,iBAsHY,aAAA,CACrB,MAAA,EAAQ,UAAA,EACR,QAAA,EAAU,eAAA,GACR,OAAA,CAAQ,OAAA"}
|