@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.js
CHANGED
|
@@ -65,7 +65,7 @@ function createGameConfig(opts) {
|
|
|
65
65
|
symbols.set(key, value);
|
|
66
66
|
}
|
|
67
67
|
const getAnticipationTrigger = (spinType) => {
|
|
68
|
-
return Math.min(...Object.keys(opts.scatterToFreespins[spinType]).map(Number)) - 1;
|
|
68
|
+
return Math.min(...Object.keys(opts.scatterToFreespins[spinType] || {}).map(Number)) - 1;
|
|
69
69
|
};
|
|
70
70
|
return {
|
|
71
71
|
padSymbols: opts.padSymbols || 1,
|
|
@@ -401,6 +401,16 @@ function createGameState(opts) {
|
|
|
401
401
|
};
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
+
// src/recorder/index.ts
|
|
405
|
+
var Recorder = class {
|
|
406
|
+
records;
|
|
407
|
+
pendingRecords;
|
|
408
|
+
constructor() {
|
|
409
|
+
this.records = [];
|
|
410
|
+
this.pendingRecords = [];
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
404
414
|
// src/board/index.ts
|
|
405
415
|
var import_assert3 = __toESM(require("assert"));
|
|
406
416
|
|
|
@@ -436,6 +446,16 @@ var GameSymbol = class _GameSymbol {
|
|
|
436
446
|
return true;
|
|
437
447
|
}
|
|
438
448
|
}
|
|
449
|
+
/**
|
|
450
|
+
* Creates a clone of this GameSymbol.
|
|
451
|
+
*/
|
|
452
|
+
clone() {
|
|
453
|
+
return new _GameSymbol({
|
|
454
|
+
id: this.id,
|
|
455
|
+
pays: this.pays ? { ...this.pays } : void 0,
|
|
456
|
+
properties: Object.fromEntries(this.properties)
|
|
457
|
+
});
|
|
458
|
+
}
|
|
439
459
|
};
|
|
440
460
|
|
|
441
461
|
// src/board/index.ts
|
|
@@ -470,6 +490,13 @@ var Board = class {
|
|
|
470
490
|
this.lastDrawnReelStops = [];
|
|
471
491
|
this.lastUsedReels = [];
|
|
472
492
|
}
|
|
493
|
+
getSymbol(reelIndex, rowIndex) {
|
|
494
|
+
return this.reels[reelIndex]?.[rowIndex];
|
|
495
|
+
}
|
|
496
|
+
setSymbol(reelIndex, rowIndex, symbol) {
|
|
497
|
+
this.reels[reelIndex] = this.reels[reelIndex] || [];
|
|
498
|
+
this.reels[reelIndex][rowIndex] = symbol;
|
|
499
|
+
}
|
|
473
500
|
makeEmptyReels(opts) {
|
|
474
501
|
const length = opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount;
|
|
475
502
|
(0, import_assert3.default)(length, "Cannot make empty reels without context or reelsAmount.");
|
|
@@ -732,6 +759,18 @@ var BoardService = class extends AbstractService {
|
|
|
732
759
|
getAnticipation() {
|
|
733
760
|
return this.board.anticipation;
|
|
734
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* Gets the symbol at the specified reel and row index.
|
|
764
|
+
*/
|
|
765
|
+
getSymbol(reelIndex, rowIndex) {
|
|
766
|
+
return this.board.getSymbol(reelIndex, rowIndex);
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Sets the symbol at the specified reel and row index.
|
|
770
|
+
*/
|
|
771
|
+
setSymbol(reelIndex, rowIndex, symbol) {
|
|
772
|
+
this.board.setSymbol(reelIndex, rowIndex, symbol);
|
|
773
|
+
}
|
|
735
774
|
resetReels() {
|
|
736
775
|
this.board.resetReels({
|
|
737
776
|
ctx: this.ctx()
|
|
@@ -1157,16 +1196,6 @@ var Book = class _Book {
|
|
|
1157
1196
|
}
|
|
1158
1197
|
};
|
|
1159
1198
|
|
|
1160
|
-
// src/recorder/index.ts
|
|
1161
|
-
var Recorder = class {
|
|
1162
|
-
records;
|
|
1163
|
-
pendingRecords;
|
|
1164
|
-
constructor() {
|
|
1165
|
-
this.records = [];
|
|
1166
|
-
this.pendingRecords = [];
|
|
1167
|
-
}
|
|
1168
|
-
};
|
|
1169
|
-
|
|
1170
1199
|
// src/wallet/index.ts
|
|
1171
1200
|
var Wallet = class {
|
|
1172
1201
|
/**
|
|
@@ -2579,9 +2608,20 @@ var WinType = class {
|
|
|
2579
2608
|
isWild(symbol) {
|
|
2580
2609
|
return !!this.wildSymbol && symbol.compare(this.wildSymbol);
|
|
2581
2610
|
}
|
|
2611
|
+
getSymbolPayout(symbol, count) {
|
|
2612
|
+
if (!symbol.pays) return 0;
|
|
2613
|
+
let clusterSize = 0;
|
|
2614
|
+
const sizes = Object.keys(symbol.pays).map((s) => parseInt(s, 10)).filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
|
|
2615
|
+
for (const size of sizes) {
|
|
2616
|
+
if (size > count) break;
|
|
2617
|
+
clusterSize = size;
|
|
2618
|
+
}
|
|
2619
|
+
return symbol.pays[clusterSize] || 0;
|
|
2620
|
+
}
|
|
2582
2621
|
};
|
|
2583
2622
|
|
|
2584
2623
|
// src/win-types/LinesWinType.ts
|
|
2624
|
+
var import_assert11 = __toESM(require("assert"));
|
|
2585
2625
|
var LinesWinType = class extends WinType {
|
|
2586
2626
|
lines;
|
|
2587
2627
|
constructor(opts) {
|
|
@@ -2622,106 +2662,335 @@ var LinesWinType = class extends WinType {
|
|
|
2622
2662
|
evaluateWins(board) {
|
|
2623
2663
|
this.validateConfig();
|
|
2624
2664
|
const lineWins = [];
|
|
2625
|
-
let payout = 0;
|
|
2626
2665
|
const reels = board;
|
|
2627
|
-
for (const [lineNumStr,
|
|
2666
|
+
for (const [lineNumStr, line] of Object.entries(this.lines)) {
|
|
2628
2667
|
const lineNum = Number(lineNumStr);
|
|
2629
|
-
let baseSymbol
|
|
2630
|
-
|
|
2631
|
-
const
|
|
2632
|
-
const
|
|
2633
|
-
|
|
2634
|
-
const
|
|
2635
|
-
const sym = reels[ridx][rowIdx];
|
|
2636
|
-
if (!sym) throw new Error("Encountered an invalid symbol while evaluating wins.");
|
|
2637
|
-
const wild = this.isWild(sym);
|
|
2638
|
-
if (ridx === 0) {
|
|
2639
|
-
chain.push(sym);
|
|
2640
|
-
details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: wild });
|
|
2641
|
-
if (wild) leadingWilds++;
|
|
2642
|
-
else baseSymbol = sym;
|
|
2643
|
-
continue;
|
|
2644
|
-
}
|
|
2645
|
-
if (wild) {
|
|
2646
|
-
chain.push(sym);
|
|
2647
|
-
details.push({
|
|
2648
|
-
reelIndex: ridx,
|
|
2649
|
-
posIndex: rowIdx,
|
|
2650
|
-
symbol: sym,
|
|
2651
|
-
isWild: true,
|
|
2652
|
-
substitutedFor: baseSymbol || void 0
|
|
2653
|
-
});
|
|
2654
|
-
continue;
|
|
2655
|
-
}
|
|
2668
|
+
let baseSymbol;
|
|
2669
|
+
const potentialWinLine = [];
|
|
2670
|
+
const potentialWildLine = [];
|
|
2671
|
+
for (const [ridx, reel] of reels.entries()) {
|
|
2672
|
+
const sidx = line[ridx];
|
|
2673
|
+
const thisSymbol = reel[sidx];
|
|
2656
2674
|
if (!baseSymbol) {
|
|
2657
|
-
baseSymbol =
|
|
2658
|
-
|
|
2659
|
-
|
|
2675
|
+
baseSymbol = thisSymbol;
|
|
2676
|
+
}
|
|
2677
|
+
(0, import_assert11.default)(baseSymbol, `No symbol found at line ${lineNum}, reel ${ridx}`);
|
|
2678
|
+
(0, import_assert11.default)(thisSymbol, `No symbol found at line ${lineNum}, reel ${ridx}`);
|
|
2679
|
+
if (potentialWinLine.length == 0) {
|
|
2680
|
+
if (this.isWild(thisSymbol)) {
|
|
2681
|
+
potentialWildLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2682
|
+
}
|
|
2683
|
+
potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2660
2684
|
continue;
|
|
2661
2685
|
}
|
|
2662
|
-
if (
|
|
2663
|
-
|
|
2664
|
-
|
|
2686
|
+
if (this.isWild(baseSymbol)) {
|
|
2687
|
+
if (this.isWild(thisSymbol)) {
|
|
2688
|
+
potentialWildLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2689
|
+
} else {
|
|
2690
|
+
baseSymbol = thisSymbol;
|
|
2691
|
+
}
|
|
2692
|
+
potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2665
2693
|
continue;
|
|
2666
2694
|
}
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
if (chain.length === 0) continue;
|
|
2670
|
-
const allWild = chain.every((s) => this.isWild(s));
|
|
2671
|
-
const wildRepresentative = this.wildSymbol instanceof GameSymbol ? this.wildSymbol : null;
|
|
2672
|
-
const len = chain.length;
|
|
2673
|
-
let bestPayout = 0;
|
|
2674
|
-
let bestType = null;
|
|
2675
|
-
let payingSymbol = null;
|
|
2676
|
-
if (baseSymbol?.pays && baseSymbol.pays[len]) {
|
|
2677
|
-
bestPayout = baseSymbol.pays[len];
|
|
2678
|
-
bestType = "substituted";
|
|
2679
|
-
payingSymbol = baseSymbol;
|
|
2680
|
-
}
|
|
2681
|
-
if (allWild && wildRepresentative?.pays && wildRepresentative.pays[len]) {
|
|
2682
|
-
const wildPay = wildRepresentative.pays[len];
|
|
2683
|
-
if (wildPay > bestPayout) {
|
|
2684
|
-
bestPayout = wildPay;
|
|
2685
|
-
bestType = "pure-wild";
|
|
2686
|
-
payingSymbol = wildRepresentative;
|
|
2695
|
+
if (baseSymbol.compare(thisSymbol) || this.isWild(thisSymbol)) {
|
|
2696
|
+
potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2687
2697
|
}
|
|
2688
2698
|
}
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
const
|
|
2694
|
-
|
|
2699
|
+
const minSymLine = Math.min(
|
|
2700
|
+
...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
|
|
2701
|
+
);
|
|
2702
|
+
if (potentialWinLine.length < minSymLine) continue;
|
|
2703
|
+
const linePayout = this.getLinePayout(potentialWinLine);
|
|
2704
|
+
const wildLinePayout = this.getLinePayout(potentialWildLine);
|
|
2705
|
+
let finalLine = {
|
|
2706
|
+
kind: potentialWinLine.length,
|
|
2707
|
+
baseSymbol,
|
|
2708
|
+
symbols: potentialWinLine.map((s) => ({
|
|
2709
|
+
symbol: s.symbol,
|
|
2710
|
+
isWild: this.isWild(s.symbol),
|
|
2711
|
+
reelIndex: s.reel,
|
|
2712
|
+
posIndex: s.row
|
|
2713
|
+
})),
|
|
2695
2714
|
lineNumber: lineNum,
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2715
|
+
payout: linePayout
|
|
2716
|
+
};
|
|
2717
|
+
if (wildLinePayout > linePayout) {
|
|
2718
|
+
baseSymbol = potentialWildLine[0]?.symbol;
|
|
2719
|
+
finalLine = {
|
|
2720
|
+
kind: potentialWildLine.length,
|
|
2721
|
+
baseSymbol,
|
|
2722
|
+
symbols: potentialWildLine.map((s) => ({
|
|
2723
|
+
symbol: s.symbol,
|
|
2724
|
+
isWild: this.isWild(s.symbol),
|
|
2725
|
+
reelIndex: s.reel,
|
|
2726
|
+
posIndex: s.row
|
|
2727
|
+
})),
|
|
2728
|
+
lineNumber: lineNum,
|
|
2729
|
+
payout: wildLinePayout
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
lineWins.push(finalLine);
|
|
2705
2733
|
}
|
|
2706
2734
|
for (const win of lineWins) {
|
|
2707
2735
|
this.ctx.services.data.recordSymbolOccurrence({
|
|
2708
2736
|
kind: win.kind,
|
|
2709
|
-
symbolId: win.
|
|
2737
|
+
symbolId: win.baseSymbol.id,
|
|
2710
2738
|
spinType: this.ctx.state.currentSpinType
|
|
2711
2739
|
});
|
|
2712
2740
|
}
|
|
2713
|
-
this.payout = payout;
|
|
2741
|
+
this.payout = lineWins.reduce((sum, l) => sum + l.payout, 0);
|
|
2714
2742
|
this.winCombinations = lineWins;
|
|
2715
2743
|
return this;
|
|
2716
2744
|
}
|
|
2745
|
+
getLinePayout(line) {
|
|
2746
|
+
if (line.length === 0) return 0;
|
|
2747
|
+
let baseSymbol = line.find((s) => !this.isWild(s.symbol))?.symbol;
|
|
2748
|
+
if (!baseSymbol) baseSymbol = line[0].symbol;
|
|
2749
|
+
const kind = line.length;
|
|
2750
|
+
const payout = this.getSymbolPayout(baseSymbol, kind);
|
|
2751
|
+
return payout;
|
|
2752
|
+
}
|
|
2717
2753
|
};
|
|
2718
2754
|
|
|
2719
2755
|
// src/win-types/ClusterWinType.ts
|
|
2720
2756
|
var ClusterWinType = class extends WinType {
|
|
2757
|
+
_checked = [];
|
|
2758
|
+
_checkedWilds = [];
|
|
2759
|
+
_currentBoard = [];
|
|
2760
|
+
constructor(opts) {
|
|
2761
|
+
super(opts);
|
|
2762
|
+
}
|
|
2763
|
+
validateConfig() {
|
|
2764
|
+
}
|
|
2765
|
+
/**
|
|
2766
|
+
* Calculates wins based on symbol cluster size and provided board state.\
|
|
2767
|
+
* Retrieve the results using `getWins()` after.
|
|
2768
|
+
*/
|
|
2769
|
+
evaluateWins(board) {
|
|
2770
|
+
this.validateConfig();
|
|
2771
|
+
this._checked = [];
|
|
2772
|
+
this._currentBoard = board;
|
|
2773
|
+
const clusterWins = [];
|
|
2774
|
+
const potentialClusters = [];
|
|
2775
|
+
for (const [ridx, reel] of board.entries()) {
|
|
2776
|
+
for (const [sidx, symbol] of reel.entries()) {
|
|
2777
|
+
this._checkedWilds = [];
|
|
2778
|
+
if (this.isWild(symbol)) continue;
|
|
2779
|
+
if (this.isChecked(ridx, sidx)) {
|
|
2780
|
+
continue;
|
|
2781
|
+
}
|
|
2782
|
+
const thisSymbol = { reel: ridx, row: sidx, symbol };
|
|
2783
|
+
this._checked.push(thisSymbol);
|
|
2784
|
+
const neighbors = this.getNeighbors(ridx, sidx);
|
|
2785
|
+
const matchingSymbols = this.evaluateCluster(symbol, neighbors);
|
|
2786
|
+
if (matchingSymbols.size >= 1) {
|
|
2787
|
+
potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
for (const [ridx, reel] of board.entries()) {
|
|
2792
|
+
for (const [sidx, symbol] of reel.entries()) {
|
|
2793
|
+
this._checkedWilds = [];
|
|
2794
|
+
if (!this.isWild(symbol)) continue;
|
|
2795
|
+
if (this.isChecked(ridx, sidx)) {
|
|
2796
|
+
continue;
|
|
2797
|
+
}
|
|
2798
|
+
const thisSymbol = { reel: ridx, row: sidx, symbol };
|
|
2799
|
+
this._checked.push(thisSymbol);
|
|
2800
|
+
const neighbors = this.getNeighbors(ridx, sidx);
|
|
2801
|
+
const matchingSymbols = this.evaluateCluster(symbol, neighbors);
|
|
2802
|
+
if (matchingSymbols.size >= 1) {
|
|
2803
|
+
potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
potentialClusters.forEach((cluster) => {
|
|
2808
|
+
const kind = cluster.length;
|
|
2809
|
+
let baseSymbol = cluster.find((s) => !this.isWild(s.symbol))?.symbol;
|
|
2810
|
+
if (!baseSymbol) baseSymbol = cluster[0].symbol;
|
|
2811
|
+
const payout = this.getSymbolPayout(baseSymbol, kind);
|
|
2812
|
+
if (!baseSymbol.pays || Object.keys(baseSymbol.pays).length === 0) {
|
|
2813
|
+
return;
|
|
2814
|
+
}
|
|
2815
|
+
clusterWins.push({
|
|
2816
|
+
payout,
|
|
2817
|
+
kind,
|
|
2818
|
+
baseSymbol,
|
|
2819
|
+
symbols: cluster.map((s) => ({
|
|
2820
|
+
symbol: s.symbol,
|
|
2821
|
+
isWild: this.isWild(s.symbol),
|
|
2822
|
+
reelIndex: s.reel,
|
|
2823
|
+
posIndex: s.row
|
|
2824
|
+
}))
|
|
2825
|
+
});
|
|
2826
|
+
});
|
|
2827
|
+
for (const win of clusterWins) {
|
|
2828
|
+
this.ctx.services.data.recordSymbolOccurrence({
|
|
2829
|
+
kind: win.kind,
|
|
2830
|
+
symbolId: win.baseSymbol.id,
|
|
2831
|
+
spinType: this.ctx.state.currentSpinType
|
|
2832
|
+
});
|
|
2833
|
+
}
|
|
2834
|
+
this.payout = clusterWins.reduce((sum, c) => sum + c.payout, 0);
|
|
2835
|
+
this.winCombinations = clusterWins;
|
|
2836
|
+
return this;
|
|
2837
|
+
}
|
|
2838
|
+
getNeighbors(ridx, sidx) {
|
|
2839
|
+
const board = this._currentBoard;
|
|
2840
|
+
const neighbors = [];
|
|
2841
|
+
const potentialNeighbors = [
|
|
2842
|
+
[ridx - 1, sidx],
|
|
2843
|
+
[ridx + 1, sidx],
|
|
2844
|
+
[ridx, sidx - 1],
|
|
2845
|
+
[ridx, sidx + 1]
|
|
2846
|
+
];
|
|
2847
|
+
potentialNeighbors.forEach(([nridx, nsidx]) => {
|
|
2848
|
+
if (board[nridx] && board[nridx][nsidx]) {
|
|
2849
|
+
neighbors.push({ reel: nridx, row: nsidx, symbol: board[nridx][nsidx] });
|
|
2850
|
+
}
|
|
2851
|
+
});
|
|
2852
|
+
return neighbors;
|
|
2853
|
+
}
|
|
2854
|
+
evaluateCluster(rootSymbol, neighbors) {
|
|
2855
|
+
const matchingSymbols = /* @__PURE__ */ new Map();
|
|
2856
|
+
neighbors.forEach((neighbor) => {
|
|
2857
|
+
const { reel, row, symbol } = neighbor;
|
|
2858
|
+
if (this.isChecked(reel, row)) return;
|
|
2859
|
+
if (this.isCheckedWild(reel, row)) return;
|
|
2860
|
+
if (this.isWild(symbol) || symbol.compare(rootSymbol)) {
|
|
2861
|
+
const key = `${reel}-${row}`;
|
|
2862
|
+
matchingSymbols.set(key, { reel, row, symbol });
|
|
2863
|
+
if (symbol.compare(rootSymbol)) {
|
|
2864
|
+
this._checked.push(neighbor);
|
|
2865
|
+
}
|
|
2866
|
+
if (this.isWild(symbol)) {
|
|
2867
|
+
this._checkedWilds.push(neighbor);
|
|
2868
|
+
}
|
|
2869
|
+
const neighbors2 = this.getNeighbors(reel, row);
|
|
2870
|
+
const nestedMatches = this.evaluateCluster(rootSymbol, neighbors2);
|
|
2871
|
+
nestedMatches.forEach((nsym) => {
|
|
2872
|
+
const nkey = `${nsym.reel}-${nsym.row}`;
|
|
2873
|
+
matchingSymbols.set(nkey, nsym);
|
|
2874
|
+
});
|
|
2875
|
+
}
|
|
2876
|
+
});
|
|
2877
|
+
return matchingSymbols;
|
|
2878
|
+
}
|
|
2879
|
+
isChecked(ridx, sidx) {
|
|
2880
|
+
return !!this._checked.find((c) => c.reel === ridx && c.row === sidx);
|
|
2881
|
+
}
|
|
2882
|
+
isCheckedWild(ridx, sidx) {
|
|
2883
|
+
return !!this._checkedWilds.find((c) => c.reel === ridx && c.row === sidx);
|
|
2884
|
+
}
|
|
2721
2885
|
};
|
|
2722
2886
|
|
|
2723
2887
|
// src/win-types/ManywaysWinType.ts
|
|
2724
2888
|
var ManywaysWinType = class extends WinType {
|
|
2889
|
+
_checked = [];
|
|
2890
|
+
_checkedWilds = [];
|
|
2891
|
+
constructor(opts) {
|
|
2892
|
+
super(opts);
|
|
2893
|
+
}
|
|
2894
|
+
validateConfig() {
|
|
2895
|
+
}
|
|
2896
|
+
/**
|
|
2897
|
+
* Calculates wins based on the defined paylines and provided board state.\
|
|
2898
|
+
* Retrieve the results using `getWins()` after.
|
|
2899
|
+
*/
|
|
2900
|
+
evaluateWins(board) {
|
|
2901
|
+
this.validateConfig();
|
|
2902
|
+
const waysWins = [];
|
|
2903
|
+
const reels = board;
|
|
2904
|
+
const possibleWaysWins = /* @__PURE__ */ new Map();
|
|
2905
|
+
const candidateSymbols = /* @__PURE__ */ new Map();
|
|
2906
|
+
let searchReelIdx = 0;
|
|
2907
|
+
let searchActive = true;
|
|
2908
|
+
while (searchActive && searchReelIdx < reels.length) {
|
|
2909
|
+
const reel = reels[searchReelIdx];
|
|
2910
|
+
let hasWild = false;
|
|
2911
|
+
for (const symbol of reel) {
|
|
2912
|
+
candidateSymbols.set(symbol.id, symbol);
|
|
2913
|
+
if (this.isWild(symbol)) {
|
|
2914
|
+
hasWild = true;
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
if (!hasWild) {
|
|
2918
|
+
searchActive = false;
|
|
2919
|
+
}
|
|
2920
|
+
searchReelIdx++;
|
|
2921
|
+
}
|
|
2922
|
+
for (const baseSymbol of candidateSymbols.values()) {
|
|
2923
|
+
let symbolList = {};
|
|
2924
|
+
let isInterrupted = false;
|
|
2925
|
+
for (const [ridx, reel] of reels.entries()) {
|
|
2926
|
+
if (isInterrupted) break;
|
|
2927
|
+
for (const [sidx, symbol] of reel.entries()) {
|
|
2928
|
+
const isMatch = baseSymbol.compare(symbol) || this.isWild(symbol);
|
|
2929
|
+
if (isMatch) {
|
|
2930
|
+
if (!symbolList[ridx]) {
|
|
2931
|
+
symbolList[ridx] = [];
|
|
2932
|
+
}
|
|
2933
|
+
symbolList[ridx].push({ reel: ridx, row: sidx, symbol });
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
if (!symbolList[ridx]) {
|
|
2937
|
+
isInterrupted = true;
|
|
2938
|
+
break;
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
const minSymLine = Math.min(
|
|
2942
|
+
...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
|
|
2943
|
+
);
|
|
2944
|
+
const wayLength = this.getWayLength(symbolList);
|
|
2945
|
+
if (wayLength >= minSymLine) {
|
|
2946
|
+
possibleWaysWins.set(baseSymbol.id, symbolList);
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
for (const [baseSymbolId, symbolList] of possibleWaysWins.entries()) {
|
|
2950
|
+
const wayLength = this.getWayLength(symbolList);
|
|
2951
|
+
let baseSymbol = Object.values(symbolList).flatMap((l) => l.map((s) => s)).find((s) => !this.isWild(s.symbol))?.symbol;
|
|
2952
|
+
if (!baseSymbol) baseSymbol = symbolList[0][0].symbol;
|
|
2953
|
+
const singleWayPayout = this.getSymbolPayout(baseSymbol, wayLength);
|
|
2954
|
+
const totalWays = Object.values(symbolList).reduce(
|
|
2955
|
+
(ways, syms) => ways * syms.length,
|
|
2956
|
+
1
|
|
2957
|
+
);
|
|
2958
|
+
const totalPayout = singleWayPayout * totalWays;
|
|
2959
|
+
waysWins.push({
|
|
2960
|
+
kind: wayLength,
|
|
2961
|
+
baseSymbol,
|
|
2962
|
+
symbols: Object.values(symbolList).flatMap(
|
|
2963
|
+
(reel) => reel.map((s) => ({
|
|
2964
|
+
symbol: s.symbol,
|
|
2965
|
+
isWild: this.isWild(s.symbol),
|
|
2966
|
+
reelIndex: s.reel,
|
|
2967
|
+
posIndex: s.row
|
|
2968
|
+
}))
|
|
2969
|
+
),
|
|
2970
|
+
ways: totalWays,
|
|
2971
|
+
payout: totalPayout
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
for (const win of waysWins) {
|
|
2975
|
+
this.ctx.services.data.recordSymbolOccurrence({
|
|
2976
|
+
kind: win.kind,
|
|
2977
|
+
symbolId: win.baseSymbol.id,
|
|
2978
|
+
spinType: this.ctx.state.currentSpinType
|
|
2979
|
+
});
|
|
2980
|
+
}
|
|
2981
|
+
this.payout = waysWins.reduce((sum, l) => sum + l.payout, 0);
|
|
2982
|
+
this.winCombinations = waysWins;
|
|
2983
|
+
return this;
|
|
2984
|
+
}
|
|
2985
|
+
getWayLength(symbolList) {
|
|
2986
|
+
return Math.max(...Object.keys(symbolList).map((k) => parseInt(k, 10))) + 1;
|
|
2987
|
+
}
|
|
2988
|
+
isChecked(ridx, sidx) {
|
|
2989
|
+
return !!this._checked.find((c) => c.reel === ridx && c.row === sidx);
|
|
2990
|
+
}
|
|
2991
|
+
isCheckedWild(ridx, sidx) {
|
|
2992
|
+
return !!this._checkedWilds.find((c) => c.reel === ridx && c.row === sidx);
|
|
2993
|
+
}
|
|
2725
2994
|
};
|
|
2726
2995
|
|
|
2727
2996
|
// src/reel-set/GeneratedReelSet.ts
|
|
@@ -3082,7 +3351,7 @@ var GeneratedReelSet = class extends ReelSet {
|
|
|
3082
3351
|
};
|
|
3083
3352
|
|
|
3084
3353
|
// src/reel-set/StaticReelSet.ts
|
|
3085
|
-
var
|
|
3354
|
+
var import_assert12 = __toESM(require("assert"));
|
|
3086
3355
|
var StaticReelSet = class extends ReelSet {
|
|
3087
3356
|
reels;
|
|
3088
3357
|
csvPath;
|
|
@@ -3092,7 +3361,7 @@ var StaticReelSet = class extends ReelSet {
|
|
|
3092
3361
|
this.reels = [];
|
|
3093
3362
|
this._strReels = opts.reels || [];
|
|
3094
3363
|
this.csvPath = opts.csvPath || "";
|
|
3095
|
-
(0,
|
|
3364
|
+
(0, import_assert12.default)(
|
|
3096
3365
|
opts.reels || opts.csvPath,
|
|
3097
3366
|
`Either 'reels' or 'csvPath' must be provided for StaticReelSet ${this.id}`
|
|
3098
3367
|
);
|
|
@@ -3168,6 +3437,18 @@ var StandaloneBoard = class {
|
|
|
3168
3437
|
getPaddingBottom() {
|
|
3169
3438
|
return this.board.paddingBottom;
|
|
3170
3439
|
}
|
|
3440
|
+
/**
|
|
3441
|
+
* Gets the symbol at the specified reel and row index.
|
|
3442
|
+
*/
|
|
3443
|
+
getSymbol(reelIndex, rowIndex) {
|
|
3444
|
+
return this.board.getSymbol(reelIndex, rowIndex);
|
|
3445
|
+
}
|
|
3446
|
+
/**
|
|
3447
|
+
* Sets the symbol at the specified reel and row index.
|
|
3448
|
+
*/
|
|
3449
|
+
setSymbol(reelIndex, rowIndex, symbol) {
|
|
3450
|
+
this.board.setSymbol(reelIndex, rowIndex, symbol);
|
|
3451
|
+
}
|
|
3171
3452
|
resetReels() {
|
|
3172
3453
|
this.board.resetReels({
|
|
3173
3454
|
ctx: this.ctx
|