@slot-engine/core 0.0.9 → 0.0.10
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/index.d.mts +68 -8
- package/dist/index.d.ts +68 -8
- package/dist/index.js +366 -85
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +366 -85
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -13,7 +13,7 @@ function createGameConfig(opts) {
|
|
|
13
13
|
symbols.set(key, value);
|
|
14
14
|
}
|
|
15
15
|
const getAnticipationTrigger = (spinType) => {
|
|
16
|
-
return Math.min(...Object.keys(opts.scatterToFreespins[spinType]).map(Number)) - 1;
|
|
16
|
+
return Math.min(...Object.keys(opts.scatterToFreespins[spinType] || {}).map(Number)) - 1;
|
|
17
17
|
};
|
|
18
18
|
return {
|
|
19
19
|
padSymbols: opts.padSymbols || 1,
|
|
@@ -349,6 +349,16 @@ function createGameState(opts) {
|
|
|
349
349
|
};
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
+
// src/recorder/index.ts
|
|
353
|
+
var Recorder = class {
|
|
354
|
+
records;
|
|
355
|
+
pendingRecords;
|
|
356
|
+
constructor() {
|
|
357
|
+
this.records = [];
|
|
358
|
+
this.pendingRecords = [];
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
352
362
|
// src/board/index.ts
|
|
353
363
|
import assert3 from "assert";
|
|
354
364
|
|
|
@@ -384,6 +394,16 @@ var GameSymbol = class _GameSymbol {
|
|
|
384
394
|
return true;
|
|
385
395
|
}
|
|
386
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Creates a clone of this GameSymbol.
|
|
399
|
+
*/
|
|
400
|
+
clone() {
|
|
401
|
+
return new _GameSymbol({
|
|
402
|
+
id: this.id,
|
|
403
|
+
pays: this.pays ? { ...this.pays } : void 0,
|
|
404
|
+
properties: Object.fromEntries(this.properties)
|
|
405
|
+
});
|
|
406
|
+
}
|
|
387
407
|
};
|
|
388
408
|
|
|
389
409
|
// src/board/index.ts
|
|
@@ -418,6 +438,13 @@ var Board = class {
|
|
|
418
438
|
this.lastDrawnReelStops = [];
|
|
419
439
|
this.lastUsedReels = [];
|
|
420
440
|
}
|
|
441
|
+
getSymbol(reelIndex, rowIndex) {
|
|
442
|
+
return this.reels[reelIndex]?.[rowIndex];
|
|
443
|
+
}
|
|
444
|
+
setSymbol(reelIndex, rowIndex, symbol) {
|
|
445
|
+
this.reels[reelIndex] = this.reels[reelIndex] || [];
|
|
446
|
+
this.reels[reelIndex][rowIndex] = symbol;
|
|
447
|
+
}
|
|
421
448
|
makeEmptyReels(opts) {
|
|
422
449
|
const length = opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount;
|
|
423
450
|
assert3(length, "Cannot make empty reels without context or reelsAmount.");
|
|
@@ -680,6 +707,18 @@ var BoardService = class extends AbstractService {
|
|
|
680
707
|
getAnticipation() {
|
|
681
708
|
return this.board.anticipation;
|
|
682
709
|
}
|
|
710
|
+
/**
|
|
711
|
+
* Gets the symbol at the specified reel and row index.
|
|
712
|
+
*/
|
|
713
|
+
getSymbol(reelIndex, rowIndex) {
|
|
714
|
+
return this.board.getSymbol(reelIndex, rowIndex);
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Sets the symbol at the specified reel and row index.
|
|
718
|
+
*/
|
|
719
|
+
setSymbol(reelIndex, rowIndex, symbol) {
|
|
720
|
+
this.board.setSymbol(reelIndex, rowIndex, symbol);
|
|
721
|
+
}
|
|
683
722
|
resetReels() {
|
|
684
723
|
this.board.resetReels({
|
|
685
724
|
ctx: this.ctx()
|
|
@@ -1105,16 +1144,6 @@ var Book = class _Book {
|
|
|
1105
1144
|
}
|
|
1106
1145
|
};
|
|
1107
1146
|
|
|
1108
|
-
// src/recorder/index.ts
|
|
1109
|
-
var Recorder = class {
|
|
1110
|
-
records;
|
|
1111
|
-
pendingRecords;
|
|
1112
|
-
constructor() {
|
|
1113
|
-
this.records = [];
|
|
1114
|
-
this.pendingRecords = [];
|
|
1115
|
-
}
|
|
1116
|
-
};
|
|
1117
|
-
|
|
1118
1147
|
// src/wallet/index.ts
|
|
1119
1148
|
var Wallet = class {
|
|
1120
1149
|
/**
|
|
@@ -2527,9 +2556,20 @@ var WinType = class {
|
|
|
2527
2556
|
isWild(symbol) {
|
|
2528
2557
|
return !!this.wildSymbol && symbol.compare(this.wildSymbol);
|
|
2529
2558
|
}
|
|
2559
|
+
getSymbolPayout(symbol, count) {
|
|
2560
|
+
if (!symbol.pays) return 0;
|
|
2561
|
+
let clusterSize = 0;
|
|
2562
|
+
const sizes = Object.keys(symbol.pays).map((s) => parseInt(s, 10)).filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
|
|
2563
|
+
for (const size of sizes) {
|
|
2564
|
+
if (size > count) break;
|
|
2565
|
+
clusterSize = size;
|
|
2566
|
+
}
|
|
2567
|
+
return symbol.pays[clusterSize] || 0;
|
|
2568
|
+
}
|
|
2530
2569
|
};
|
|
2531
2570
|
|
|
2532
2571
|
// src/win-types/LinesWinType.ts
|
|
2572
|
+
import assert11 from "assert";
|
|
2533
2573
|
var LinesWinType = class extends WinType {
|
|
2534
2574
|
lines;
|
|
2535
2575
|
constructor(opts) {
|
|
@@ -2570,106 +2610,335 @@ var LinesWinType = class extends WinType {
|
|
|
2570
2610
|
evaluateWins(board) {
|
|
2571
2611
|
this.validateConfig();
|
|
2572
2612
|
const lineWins = [];
|
|
2573
|
-
let payout = 0;
|
|
2574
2613
|
const reels = board;
|
|
2575
|
-
for (const [lineNumStr,
|
|
2614
|
+
for (const [lineNumStr, line] of Object.entries(this.lines)) {
|
|
2576
2615
|
const lineNum = Number(lineNumStr);
|
|
2577
|
-
let baseSymbol
|
|
2578
|
-
|
|
2579
|
-
const
|
|
2580
|
-
const
|
|
2581
|
-
|
|
2582
|
-
const
|
|
2583
|
-
const sym = reels[ridx][rowIdx];
|
|
2584
|
-
if (!sym) throw new Error("Encountered an invalid symbol while evaluating wins.");
|
|
2585
|
-
const wild = this.isWild(sym);
|
|
2586
|
-
if (ridx === 0) {
|
|
2587
|
-
chain.push(sym);
|
|
2588
|
-
details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: wild });
|
|
2589
|
-
if (wild) leadingWilds++;
|
|
2590
|
-
else baseSymbol = sym;
|
|
2591
|
-
continue;
|
|
2592
|
-
}
|
|
2593
|
-
if (wild) {
|
|
2594
|
-
chain.push(sym);
|
|
2595
|
-
details.push({
|
|
2596
|
-
reelIndex: ridx,
|
|
2597
|
-
posIndex: rowIdx,
|
|
2598
|
-
symbol: sym,
|
|
2599
|
-
isWild: true,
|
|
2600
|
-
substitutedFor: baseSymbol || void 0
|
|
2601
|
-
});
|
|
2602
|
-
continue;
|
|
2603
|
-
}
|
|
2616
|
+
let baseSymbol;
|
|
2617
|
+
const potentialWinLine = [];
|
|
2618
|
+
const potentialWildLine = [];
|
|
2619
|
+
for (const [ridx, reel] of reels.entries()) {
|
|
2620
|
+
const sidx = line[ridx];
|
|
2621
|
+
const thisSymbol = reel[sidx];
|
|
2604
2622
|
if (!baseSymbol) {
|
|
2605
|
-
baseSymbol =
|
|
2606
|
-
|
|
2607
|
-
|
|
2623
|
+
baseSymbol = thisSymbol;
|
|
2624
|
+
}
|
|
2625
|
+
assert11(baseSymbol, `No symbol found at line ${lineNum}, reel ${ridx}`);
|
|
2626
|
+
assert11(thisSymbol, `No symbol found at line ${lineNum}, reel ${ridx}`);
|
|
2627
|
+
if (potentialWinLine.length == 0) {
|
|
2628
|
+
if (this.isWild(thisSymbol)) {
|
|
2629
|
+
potentialWildLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2630
|
+
}
|
|
2631
|
+
potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2608
2632
|
continue;
|
|
2609
2633
|
}
|
|
2610
|
-
if (
|
|
2611
|
-
|
|
2612
|
-
|
|
2634
|
+
if (this.isWild(baseSymbol)) {
|
|
2635
|
+
if (this.isWild(thisSymbol)) {
|
|
2636
|
+
potentialWildLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2637
|
+
} else {
|
|
2638
|
+
baseSymbol = thisSymbol;
|
|
2639
|
+
}
|
|
2640
|
+
potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2613
2641
|
continue;
|
|
2614
2642
|
}
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
if (chain.length === 0) continue;
|
|
2618
|
-
const allWild = chain.every((s) => this.isWild(s));
|
|
2619
|
-
const wildRepresentative = this.wildSymbol instanceof GameSymbol ? this.wildSymbol : null;
|
|
2620
|
-
const len = chain.length;
|
|
2621
|
-
let bestPayout = 0;
|
|
2622
|
-
let bestType = null;
|
|
2623
|
-
let payingSymbol = null;
|
|
2624
|
-
if (baseSymbol?.pays && baseSymbol.pays[len]) {
|
|
2625
|
-
bestPayout = baseSymbol.pays[len];
|
|
2626
|
-
bestType = "substituted";
|
|
2627
|
-
payingSymbol = baseSymbol;
|
|
2628
|
-
}
|
|
2629
|
-
if (allWild && wildRepresentative?.pays && wildRepresentative.pays[len]) {
|
|
2630
|
-
const wildPay = wildRepresentative.pays[len];
|
|
2631
|
-
if (wildPay > bestPayout) {
|
|
2632
|
-
bestPayout = wildPay;
|
|
2633
|
-
bestType = "pure-wild";
|
|
2634
|
-
payingSymbol = wildRepresentative;
|
|
2643
|
+
if (baseSymbol.compare(thisSymbol) || this.isWild(thisSymbol)) {
|
|
2644
|
+
potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2635
2645
|
}
|
|
2636
2646
|
}
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
const
|
|
2642
|
-
|
|
2647
|
+
const minSymLine = Math.min(
|
|
2648
|
+
...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
|
|
2649
|
+
);
|
|
2650
|
+
if (potentialWinLine.length < minSymLine) continue;
|
|
2651
|
+
const linePayout = this.getLinePayout(potentialWinLine);
|
|
2652
|
+
const wildLinePayout = this.getLinePayout(potentialWildLine);
|
|
2653
|
+
let finalLine = {
|
|
2654
|
+
kind: potentialWinLine.length,
|
|
2655
|
+
baseSymbol,
|
|
2656
|
+
symbols: potentialWinLine.map((s) => ({
|
|
2657
|
+
symbol: s.symbol,
|
|
2658
|
+
isWild: this.isWild(s.symbol),
|
|
2659
|
+
reelIndex: s.reel,
|
|
2660
|
+
posIndex: s.row
|
|
2661
|
+
})),
|
|
2643
2662
|
lineNumber: lineNum,
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2663
|
+
payout: linePayout
|
|
2664
|
+
};
|
|
2665
|
+
if (wildLinePayout > linePayout) {
|
|
2666
|
+
baseSymbol = potentialWildLine[0]?.symbol;
|
|
2667
|
+
finalLine = {
|
|
2668
|
+
kind: potentialWildLine.length,
|
|
2669
|
+
baseSymbol,
|
|
2670
|
+
symbols: potentialWildLine.map((s) => ({
|
|
2671
|
+
symbol: s.symbol,
|
|
2672
|
+
isWild: this.isWild(s.symbol),
|
|
2673
|
+
reelIndex: s.reel,
|
|
2674
|
+
posIndex: s.row
|
|
2675
|
+
})),
|
|
2676
|
+
lineNumber: lineNum,
|
|
2677
|
+
payout: wildLinePayout
|
|
2678
|
+
};
|
|
2679
|
+
}
|
|
2680
|
+
lineWins.push(finalLine);
|
|
2653
2681
|
}
|
|
2654
2682
|
for (const win of lineWins) {
|
|
2655
2683
|
this.ctx.services.data.recordSymbolOccurrence({
|
|
2656
2684
|
kind: win.kind,
|
|
2657
|
-
symbolId: win.
|
|
2685
|
+
symbolId: win.baseSymbol.id,
|
|
2658
2686
|
spinType: this.ctx.state.currentSpinType
|
|
2659
2687
|
});
|
|
2660
2688
|
}
|
|
2661
|
-
this.payout = payout;
|
|
2689
|
+
this.payout = lineWins.reduce((sum, l) => sum + l.payout, 0);
|
|
2662
2690
|
this.winCombinations = lineWins;
|
|
2663
2691
|
return this;
|
|
2664
2692
|
}
|
|
2693
|
+
getLinePayout(line) {
|
|
2694
|
+
if (line.length === 0) return 0;
|
|
2695
|
+
let baseSymbol = line.find((s) => !this.isWild(s.symbol))?.symbol;
|
|
2696
|
+
if (!baseSymbol) baseSymbol = line[0].symbol;
|
|
2697
|
+
const kind = line.length;
|
|
2698
|
+
const payout = this.getSymbolPayout(baseSymbol, kind);
|
|
2699
|
+
return payout;
|
|
2700
|
+
}
|
|
2665
2701
|
};
|
|
2666
2702
|
|
|
2667
2703
|
// src/win-types/ClusterWinType.ts
|
|
2668
2704
|
var ClusterWinType = class extends WinType {
|
|
2705
|
+
_checked = [];
|
|
2706
|
+
_checkedWilds = [];
|
|
2707
|
+
_currentBoard = [];
|
|
2708
|
+
constructor(opts) {
|
|
2709
|
+
super(opts);
|
|
2710
|
+
}
|
|
2711
|
+
validateConfig() {
|
|
2712
|
+
}
|
|
2713
|
+
/**
|
|
2714
|
+
* Calculates wins based on symbol cluster size and provided board state.\
|
|
2715
|
+
* Retrieve the results using `getWins()` after.
|
|
2716
|
+
*/
|
|
2717
|
+
evaluateWins(board) {
|
|
2718
|
+
this.validateConfig();
|
|
2719
|
+
this._checked = [];
|
|
2720
|
+
this._currentBoard = board;
|
|
2721
|
+
const clusterWins = [];
|
|
2722
|
+
const potentialClusters = [];
|
|
2723
|
+
for (const [ridx, reel] of board.entries()) {
|
|
2724
|
+
for (const [sidx, symbol] of reel.entries()) {
|
|
2725
|
+
this._checkedWilds = [];
|
|
2726
|
+
if (this.isWild(symbol)) continue;
|
|
2727
|
+
if (this.isChecked(ridx, sidx)) {
|
|
2728
|
+
continue;
|
|
2729
|
+
}
|
|
2730
|
+
const thisSymbol = { reel: ridx, row: sidx, symbol };
|
|
2731
|
+
this._checked.push(thisSymbol);
|
|
2732
|
+
const neighbors = this.getNeighbors(ridx, sidx);
|
|
2733
|
+
const matchingSymbols = this.evaluateCluster(symbol, neighbors);
|
|
2734
|
+
if (matchingSymbols.size >= 1) {
|
|
2735
|
+
potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
for (const [ridx, reel] of board.entries()) {
|
|
2740
|
+
for (const [sidx, symbol] of reel.entries()) {
|
|
2741
|
+
this._checkedWilds = [];
|
|
2742
|
+
if (!this.isWild(symbol)) continue;
|
|
2743
|
+
if (this.isChecked(ridx, sidx)) {
|
|
2744
|
+
continue;
|
|
2745
|
+
}
|
|
2746
|
+
const thisSymbol = { reel: ridx, row: sidx, symbol };
|
|
2747
|
+
this._checked.push(thisSymbol);
|
|
2748
|
+
const neighbors = this.getNeighbors(ridx, sidx);
|
|
2749
|
+
const matchingSymbols = this.evaluateCluster(symbol, neighbors);
|
|
2750
|
+
if (matchingSymbols.size >= 1) {
|
|
2751
|
+
potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
potentialClusters.forEach((cluster) => {
|
|
2756
|
+
const kind = cluster.length;
|
|
2757
|
+
let baseSymbol = cluster.find((s) => !this.isWild(s.symbol))?.symbol;
|
|
2758
|
+
if (!baseSymbol) baseSymbol = cluster[0].symbol;
|
|
2759
|
+
const payout = this.getSymbolPayout(baseSymbol, kind);
|
|
2760
|
+
if (!baseSymbol.pays || Object.keys(baseSymbol.pays).length === 0) {
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
clusterWins.push({
|
|
2764
|
+
payout,
|
|
2765
|
+
kind,
|
|
2766
|
+
baseSymbol,
|
|
2767
|
+
symbols: cluster.map((s) => ({
|
|
2768
|
+
symbol: s.symbol,
|
|
2769
|
+
isWild: this.isWild(s.symbol),
|
|
2770
|
+
reelIndex: s.reel,
|
|
2771
|
+
posIndex: s.row
|
|
2772
|
+
}))
|
|
2773
|
+
});
|
|
2774
|
+
});
|
|
2775
|
+
for (const win of clusterWins) {
|
|
2776
|
+
this.ctx.services.data.recordSymbolOccurrence({
|
|
2777
|
+
kind: win.kind,
|
|
2778
|
+
symbolId: win.baseSymbol.id,
|
|
2779
|
+
spinType: this.ctx.state.currentSpinType
|
|
2780
|
+
});
|
|
2781
|
+
}
|
|
2782
|
+
this.payout = clusterWins.reduce((sum, c) => sum + c.payout, 0);
|
|
2783
|
+
this.winCombinations = clusterWins;
|
|
2784
|
+
return this;
|
|
2785
|
+
}
|
|
2786
|
+
getNeighbors(ridx, sidx) {
|
|
2787
|
+
const board = this._currentBoard;
|
|
2788
|
+
const neighbors = [];
|
|
2789
|
+
const potentialNeighbors = [
|
|
2790
|
+
[ridx - 1, sidx],
|
|
2791
|
+
[ridx + 1, sidx],
|
|
2792
|
+
[ridx, sidx - 1],
|
|
2793
|
+
[ridx, sidx + 1]
|
|
2794
|
+
];
|
|
2795
|
+
potentialNeighbors.forEach(([nridx, nsidx]) => {
|
|
2796
|
+
if (board[nridx] && board[nridx][nsidx]) {
|
|
2797
|
+
neighbors.push({ reel: nridx, row: nsidx, symbol: board[nridx][nsidx] });
|
|
2798
|
+
}
|
|
2799
|
+
});
|
|
2800
|
+
return neighbors;
|
|
2801
|
+
}
|
|
2802
|
+
evaluateCluster(rootSymbol, neighbors) {
|
|
2803
|
+
const matchingSymbols = /* @__PURE__ */ new Map();
|
|
2804
|
+
neighbors.forEach((neighbor) => {
|
|
2805
|
+
const { reel, row, symbol } = neighbor;
|
|
2806
|
+
if (this.isChecked(reel, row)) return;
|
|
2807
|
+
if (this.isCheckedWild(reel, row)) return;
|
|
2808
|
+
if (this.isWild(symbol) || symbol.compare(rootSymbol)) {
|
|
2809
|
+
const key = `${reel}-${row}`;
|
|
2810
|
+
matchingSymbols.set(key, { reel, row, symbol });
|
|
2811
|
+
if (symbol.compare(rootSymbol)) {
|
|
2812
|
+
this._checked.push(neighbor);
|
|
2813
|
+
}
|
|
2814
|
+
if (this.isWild(symbol)) {
|
|
2815
|
+
this._checkedWilds.push(neighbor);
|
|
2816
|
+
}
|
|
2817
|
+
const neighbors2 = this.getNeighbors(reel, row);
|
|
2818
|
+
const nestedMatches = this.evaluateCluster(rootSymbol, neighbors2);
|
|
2819
|
+
nestedMatches.forEach((nsym) => {
|
|
2820
|
+
const nkey = `${nsym.reel}-${nsym.row}`;
|
|
2821
|
+
matchingSymbols.set(nkey, nsym);
|
|
2822
|
+
});
|
|
2823
|
+
}
|
|
2824
|
+
});
|
|
2825
|
+
return matchingSymbols;
|
|
2826
|
+
}
|
|
2827
|
+
isChecked(ridx, sidx) {
|
|
2828
|
+
return !!this._checked.find((c) => c.reel === ridx && c.row === sidx);
|
|
2829
|
+
}
|
|
2830
|
+
isCheckedWild(ridx, sidx) {
|
|
2831
|
+
return !!this._checkedWilds.find((c) => c.reel === ridx && c.row === sidx);
|
|
2832
|
+
}
|
|
2669
2833
|
};
|
|
2670
2834
|
|
|
2671
2835
|
// src/win-types/ManywaysWinType.ts
|
|
2672
2836
|
var ManywaysWinType = class extends WinType {
|
|
2837
|
+
_checked = [];
|
|
2838
|
+
_checkedWilds = [];
|
|
2839
|
+
constructor(opts) {
|
|
2840
|
+
super(opts);
|
|
2841
|
+
}
|
|
2842
|
+
validateConfig() {
|
|
2843
|
+
}
|
|
2844
|
+
/**
|
|
2845
|
+
* Calculates wins based on the defined paylines and provided board state.\
|
|
2846
|
+
* Retrieve the results using `getWins()` after.
|
|
2847
|
+
*/
|
|
2848
|
+
evaluateWins(board) {
|
|
2849
|
+
this.validateConfig();
|
|
2850
|
+
const waysWins = [];
|
|
2851
|
+
const reels = board;
|
|
2852
|
+
const possibleWaysWins = /* @__PURE__ */ new Map();
|
|
2853
|
+
const candidateSymbols = /* @__PURE__ */ new Map();
|
|
2854
|
+
let searchReelIdx = 0;
|
|
2855
|
+
let searchActive = true;
|
|
2856
|
+
while (searchActive && searchReelIdx < reels.length) {
|
|
2857
|
+
const reel = reels[searchReelIdx];
|
|
2858
|
+
let hasWild = false;
|
|
2859
|
+
for (const symbol of reel) {
|
|
2860
|
+
candidateSymbols.set(symbol.id, symbol);
|
|
2861
|
+
if (this.isWild(symbol)) {
|
|
2862
|
+
hasWild = true;
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
if (!hasWild) {
|
|
2866
|
+
searchActive = false;
|
|
2867
|
+
}
|
|
2868
|
+
searchReelIdx++;
|
|
2869
|
+
}
|
|
2870
|
+
for (const baseSymbol of candidateSymbols.values()) {
|
|
2871
|
+
let symbolList = {};
|
|
2872
|
+
let isInterrupted = false;
|
|
2873
|
+
for (const [ridx, reel] of reels.entries()) {
|
|
2874
|
+
if (isInterrupted) break;
|
|
2875
|
+
for (const [sidx, symbol] of reel.entries()) {
|
|
2876
|
+
const isMatch = baseSymbol.compare(symbol) || this.isWild(symbol);
|
|
2877
|
+
if (isMatch) {
|
|
2878
|
+
if (!symbolList[ridx]) {
|
|
2879
|
+
symbolList[ridx] = [];
|
|
2880
|
+
}
|
|
2881
|
+
symbolList[ridx].push({ reel: ridx, row: sidx, symbol });
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
if (!symbolList[ridx]) {
|
|
2885
|
+
isInterrupted = true;
|
|
2886
|
+
break;
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
const minSymLine = Math.min(
|
|
2890
|
+
...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
|
|
2891
|
+
);
|
|
2892
|
+
const wayLength = this.getWayLength(symbolList);
|
|
2893
|
+
if (wayLength >= minSymLine) {
|
|
2894
|
+
possibleWaysWins.set(baseSymbol.id, symbolList);
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
for (const [baseSymbolId, symbolList] of possibleWaysWins.entries()) {
|
|
2898
|
+
const wayLength = this.getWayLength(symbolList);
|
|
2899
|
+
let baseSymbol = Object.values(symbolList).flatMap((l) => l.map((s) => s)).find((s) => !this.isWild(s.symbol))?.symbol;
|
|
2900
|
+
if (!baseSymbol) baseSymbol = symbolList[0][0].symbol;
|
|
2901
|
+
const singleWayPayout = this.getSymbolPayout(baseSymbol, wayLength);
|
|
2902
|
+
const totalWays = Object.values(symbolList).reduce(
|
|
2903
|
+
(ways, syms) => ways * syms.length,
|
|
2904
|
+
1
|
|
2905
|
+
);
|
|
2906
|
+
const totalPayout = singleWayPayout * totalWays;
|
|
2907
|
+
waysWins.push({
|
|
2908
|
+
kind: wayLength,
|
|
2909
|
+
baseSymbol,
|
|
2910
|
+
symbols: Object.values(symbolList).flatMap(
|
|
2911
|
+
(reel) => reel.map((s) => ({
|
|
2912
|
+
symbol: s.symbol,
|
|
2913
|
+
isWild: this.isWild(s.symbol),
|
|
2914
|
+
reelIndex: s.reel,
|
|
2915
|
+
posIndex: s.row
|
|
2916
|
+
}))
|
|
2917
|
+
),
|
|
2918
|
+
ways: totalWays,
|
|
2919
|
+
payout: totalPayout
|
|
2920
|
+
});
|
|
2921
|
+
}
|
|
2922
|
+
for (const win of waysWins) {
|
|
2923
|
+
this.ctx.services.data.recordSymbolOccurrence({
|
|
2924
|
+
kind: win.kind,
|
|
2925
|
+
symbolId: win.baseSymbol.id,
|
|
2926
|
+
spinType: this.ctx.state.currentSpinType
|
|
2927
|
+
});
|
|
2928
|
+
}
|
|
2929
|
+
this.payout = waysWins.reduce((sum, l) => sum + l.payout, 0);
|
|
2930
|
+
this.winCombinations = waysWins;
|
|
2931
|
+
return this;
|
|
2932
|
+
}
|
|
2933
|
+
getWayLength(symbolList) {
|
|
2934
|
+
return Math.max(...Object.keys(symbolList).map((k) => parseInt(k, 10))) + 1;
|
|
2935
|
+
}
|
|
2936
|
+
isChecked(ridx, sidx) {
|
|
2937
|
+
return !!this._checked.find((c) => c.reel === ridx && c.row === sidx);
|
|
2938
|
+
}
|
|
2939
|
+
isCheckedWild(ridx, sidx) {
|
|
2940
|
+
return !!this._checkedWilds.find((c) => c.reel === ridx && c.row === sidx);
|
|
2941
|
+
}
|
|
2673
2942
|
};
|
|
2674
2943
|
|
|
2675
2944
|
// src/reel-set/GeneratedReelSet.ts
|
|
@@ -3030,7 +3299,7 @@ var GeneratedReelSet = class extends ReelSet {
|
|
|
3030
3299
|
};
|
|
3031
3300
|
|
|
3032
3301
|
// src/reel-set/StaticReelSet.ts
|
|
3033
|
-
import
|
|
3302
|
+
import assert12 from "assert";
|
|
3034
3303
|
var StaticReelSet = class extends ReelSet {
|
|
3035
3304
|
reels;
|
|
3036
3305
|
csvPath;
|
|
@@ -3040,7 +3309,7 @@ var StaticReelSet = class extends ReelSet {
|
|
|
3040
3309
|
this.reels = [];
|
|
3041
3310
|
this._strReels = opts.reels || [];
|
|
3042
3311
|
this.csvPath = opts.csvPath || "";
|
|
3043
|
-
|
|
3312
|
+
assert12(
|
|
3044
3313
|
opts.reels || opts.csvPath,
|
|
3045
3314
|
`Either 'reels' or 'csvPath' must be provided for StaticReelSet ${this.id}`
|
|
3046
3315
|
);
|
|
@@ -3116,6 +3385,18 @@ var StandaloneBoard = class {
|
|
|
3116
3385
|
getPaddingBottom() {
|
|
3117
3386
|
return this.board.paddingBottom;
|
|
3118
3387
|
}
|
|
3388
|
+
/**
|
|
3389
|
+
* Gets the symbol at the specified reel and row index.
|
|
3390
|
+
*/
|
|
3391
|
+
getSymbol(reelIndex, rowIndex) {
|
|
3392
|
+
return this.board.getSymbol(reelIndex, rowIndex);
|
|
3393
|
+
}
|
|
3394
|
+
/**
|
|
3395
|
+
* Sets the symbol at the specified reel and row index.
|
|
3396
|
+
*/
|
|
3397
|
+
setSymbol(reelIndex, rowIndex, symbol) {
|
|
3398
|
+
this.board.setSymbol(reelIndex, rowIndex, symbol);
|
|
3399
|
+
}
|
|
3119
3400
|
resetReels() {
|
|
3120
3401
|
this.board.resetReels({
|
|
3121
3402
|
ctx: this.ctx
|