@slot-engine/core 0.0.8 → 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 +394 -88
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +394 -88
- 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
|
/**
|
|
@@ -2080,14 +2109,39 @@ var Analysis = class {
|
|
|
2080
2109
|
[15e3, 19999.99],
|
|
2081
2110
|
[2e4, 24999.99]
|
|
2082
2111
|
];
|
|
2112
|
+
const payoutRanges = {};
|
|
2083
2113
|
for (const modeStr of gameModes) {
|
|
2084
|
-
|
|
2114
|
+
payoutRanges[modeStr] = {};
|
|
2085
2115
|
const lutOptimized = parseLookupTable(
|
|
2086
2116
|
import_fs3.default.readFileSync(this.filePaths[modeStr].lutOptimized, "utf-8")
|
|
2087
2117
|
);
|
|
2088
|
-
|
|
2089
|
-
|
|
2118
|
+
lutOptimized.forEach(([, , p]) => {
|
|
2119
|
+
const payout = p / 100;
|
|
2120
|
+
for (const [min, max] of winRanges) {
|
|
2121
|
+
if (payout >= min && payout <= max) {
|
|
2122
|
+
const rangeKey = `${min}-${max}`;
|
|
2123
|
+
if (!payoutRanges[modeStr][rangeKey]) {
|
|
2124
|
+
payoutRanges[modeStr][rangeKey] = 0;
|
|
2125
|
+
}
|
|
2126
|
+
payoutRanges[modeStr][rangeKey] += 1;
|
|
2127
|
+
break;
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
});
|
|
2131
|
+
const orderedRanges = {};
|
|
2132
|
+
Object.keys(payoutRanges[modeStr]).sort((a, b) => {
|
|
2133
|
+
const [aMin] = a.split("-").map(Number);
|
|
2134
|
+
const [bMin] = b.split("-").map(Number);
|
|
2135
|
+
return aMin - bMin;
|
|
2136
|
+
}).forEach((key) => {
|
|
2137
|
+
orderedRanges[key] = payoutRanges[modeStr][key];
|
|
2138
|
+
});
|
|
2139
|
+
payoutRanges[modeStr] = orderedRanges;
|
|
2090
2140
|
}
|
|
2141
|
+
writeJsonFile(
|
|
2142
|
+
import_path2.default.join(process.cwd(), this.gameConfig.outputDir, "stats_payouts.json"),
|
|
2143
|
+
payoutRanges
|
|
2144
|
+
);
|
|
2091
2145
|
}
|
|
2092
2146
|
getGameModeConfig(mode) {
|
|
2093
2147
|
const config = this.gameConfig.gameModes[mode];
|
|
@@ -2554,9 +2608,20 @@ var WinType = class {
|
|
|
2554
2608
|
isWild(symbol) {
|
|
2555
2609
|
return !!this.wildSymbol && symbol.compare(this.wildSymbol);
|
|
2556
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
|
+
}
|
|
2557
2621
|
};
|
|
2558
2622
|
|
|
2559
2623
|
// src/win-types/LinesWinType.ts
|
|
2624
|
+
var import_assert11 = __toESM(require("assert"));
|
|
2560
2625
|
var LinesWinType = class extends WinType {
|
|
2561
2626
|
lines;
|
|
2562
2627
|
constructor(opts) {
|
|
@@ -2597,106 +2662,335 @@ var LinesWinType = class extends WinType {
|
|
|
2597
2662
|
evaluateWins(board) {
|
|
2598
2663
|
this.validateConfig();
|
|
2599
2664
|
const lineWins = [];
|
|
2600
|
-
let payout = 0;
|
|
2601
2665
|
const reels = board;
|
|
2602
|
-
for (const [lineNumStr,
|
|
2666
|
+
for (const [lineNumStr, line] of Object.entries(this.lines)) {
|
|
2603
2667
|
const lineNum = Number(lineNumStr);
|
|
2604
|
-
let baseSymbol
|
|
2605
|
-
|
|
2606
|
-
const
|
|
2607
|
-
const
|
|
2608
|
-
|
|
2609
|
-
const
|
|
2610
|
-
const sym = reels[ridx][rowIdx];
|
|
2611
|
-
if (!sym) throw new Error("Encountered an invalid symbol while evaluating wins.");
|
|
2612
|
-
const wild = this.isWild(sym);
|
|
2613
|
-
if (ridx === 0) {
|
|
2614
|
-
chain.push(sym);
|
|
2615
|
-
details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: wild });
|
|
2616
|
-
if (wild) leadingWilds++;
|
|
2617
|
-
else baseSymbol = sym;
|
|
2618
|
-
continue;
|
|
2619
|
-
}
|
|
2620
|
-
if (wild) {
|
|
2621
|
-
chain.push(sym);
|
|
2622
|
-
details.push({
|
|
2623
|
-
reelIndex: ridx,
|
|
2624
|
-
posIndex: rowIdx,
|
|
2625
|
-
symbol: sym,
|
|
2626
|
-
isWild: true,
|
|
2627
|
-
substitutedFor: baseSymbol || void 0
|
|
2628
|
-
});
|
|
2629
|
-
continue;
|
|
2630
|
-
}
|
|
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];
|
|
2631
2674
|
if (!baseSymbol) {
|
|
2632
|
-
baseSymbol =
|
|
2633
|
-
|
|
2634
|
-
|
|
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 });
|
|
2635
2684
|
continue;
|
|
2636
2685
|
}
|
|
2637
|
-
if (
|
|
2638
|
-
|
|
2639
|
-
|
|
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 });
|
|
2640
2693
|
continue;
|
|
2641
2694
|
}
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
if (chain.length === 0) continue;
|
|
2645
|
-
const allWild = chain.every((s) => this.isWild(s));
|
|
2646
|
-
const wildRepresentative = this.wildSymbol instanceof GameSymbol ? this.wildSymbol : null;
|
|
2647
|
-
const len = chain.length;
|
|
2648
|
-
let bestPayout = 0;
|
|
2649
|
-
let bestType = null;
|
|
2650
|
-
let payingSymbol = null;
|
|
2651
|
-
if (baseSymbol?.pays && baseSymbol.pays[len]) {
|
|
2652
|
-
bestPayout = baseSymbol.pays[len];
|
|
2653
|
-
bestType = "substituted";
|
|
2654
|
-
payingSymbol = baseSymbol;
|
|
2655
|
-
}
|
|
2656
|
-
if (allWild && wildRepresentative?.pays && wildRepresentative.pays[len]) {
|
|
2657
|
-
const wildPay = wildRepresentative.pays[len];
|
|
2658
|
-
if (wildPay > bestPayout) {
|
|
2659
|
-
bestPayout = wildPay;
|
|
2660
|
-
bestType = "pure-wild";
|
|
2661
|
-
payingSymbol = wildRepresentative;
|
|
2695
|
+
if (baseSymbol.compare(thisSymbol) || this.isWild(thisSymbol)) {
|
|
2696
|
+
potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2662
2697
|
}
|
|
2663
2698
|
}
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
const
|
|
2669
|
-
|
|
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
|
+
})),
|
|
2670
2714
|
lineNumber: lineNum,
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
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);
|
|
2680
2733
|
}
|
|
2681
2734
|
for (const win of lineWins) {
|
|
2682
2735
|
this.ctx.services.data.recordSymbolOccurrence({
|
|
2683
2736
|
kind: win.kind,
|
|
2684
|
-
symbolId: win.
|
|
2737
|
+
symbolId: win.baseSymbol.id,
|
|
2685
2738
|
spinType: this.ctx.state.currentSpinType
|
|
2686
2739
|
});
|
|
2687
2740
|
}
|
|
2688
|
-
this.payout = payout;
|
|
2741
|
+
this.payout = lineWins.reduce((sum, l) => sum + l.payout, 0);
|
|
2689
2742
|
this.winCombinations = lineWins;
|
|
2690
2743
|
return this;
|
|
2691
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
|
+
}
|
|
2692
2753
|
};
|
|
2693
2754
|
|
|
2694
2755
|
// src/win-types/ClusterWinType.ts
|
|
2695
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
|
+
}
|
|
2696
2885
|
};
|
|
2697
2886
|
|
|
2698
2887
|
// src/win-types/ManywaysWinType.ts
|
|
2699
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
|
+
}
|
|
2700
2994
|
};
|
|
2701
2995
|
|
|
2702
2996
|
// src/reel-set/GeneratedReelSet.ts
|
|
@@ -3057,7 +3351,7 @@ var GeneratedReelSet = class extends ReelSet {
|
|
|
3057
3351
|
};
|
|
3058
3352
|
|
|
3059
3353
|
// src/reel-set/StaticReelSet.ts
|
|
3060
|
-
var
|
|
3354
|
+
var import_assert12 = __toESM(require("assert"));
|
|
3061
3355
|
var StaticReelSet = class extends ReelSet {
|
|
3062
3356
|
reels;
|
|
3063
3357
|
csvPath;
|
|
@@ -3067,7 +3361,7 @@ var StaticReelSet = class extends ReelSet {
|
|
|
3067
3361
|
this.reels = [];
|
|
3068
3362
|
this._strReels = opts.reels || [];
|
|
3069
3363
|
this.csvPath = opts.csvPath || "";
|
|
3070
|
-
(0,
|
|
3364
|
+
(0, import_assert12.default)(
|
|
3071
3365
|
opts.reels || opts.csvPath,
|
|
3072
3366
|
`Either 'reels' or 'csvPath' must be provided for StaticReelSet ${this.id}`
|
|
3073
3367
|
);
|
|
@@ -3143,6 +3437,18 @@ var StandaloneBoard = class {
|
|
|
3143
3437
|
getPaddingBottom() {
|
|
3144
3438
|
return this.board.paddingBottom;
|
|
3145
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
|
+
}
|
|
3146
3452
|
resetReels() {
|
|
3147
3453
|
this.board.resetReels({
|
|
3148
3454
|
ctx: this.ctx
|