@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.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
|
/**
|
|
@@ -2028,14 +2057,39 @@ var Analysis = class {
|
|
|
2028
2057
|
[15e3, 19999.99],
|
|
2029
2058
|
[2e4, 24999.99]
|
|
2030
2059
|
];
|
|
2060
|
+
const payoutRanges = {};
|
|
2031
2061
|
for (const modeStr of gameModes) {
|
|
2032
|
-
|
|
2062
|
+
payoutRanges[modeStr] = {};
|
|
2033
2063
|
const lutOptimized = parseLookupTable(
|
|
2034
2064
|
fs3.readFileSync(this.filePaths[modeStr].lutOptimized, "utf-8")
|
|
2035
2065
|
);
|
|
2036
|
-
|
|
2037
|
-
|
|
2066
|
+
lutOptimized.forEach(([, , p]) => {
|
|
2067
|
+
const payout = p / 100;
|
|
2068
|
+
for (const [min, max] of winRanges) {
|
|
2069
|
+
if (payout >= min && payout <= max) {
|
|
2070
|
+
const rangeKey = `${min}-${max}`;
|
|
2071
|
+
if (!payoutRanges[modeStr][rangeKey]) {
|
|
2072
|
+
payoutRanges[modeStr][rangeKey] = 0;
|
|
2073
|
+
}
|
|
2074
|
+
payoutRanges[modeStr][rangeKey] += 1;
|
|
2075
|
+
break;
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
});
|
|
2079
|
+
const orderedRanges = {};
|
|
2080
|
+
Object.keys(payoutRanges[modeStr]).sort((a, b) => {
|
|
2081
|
+
const [aMin] = a.split("-").map(Number);
|
|
2082
|
+
const [bMin] = b.split("-").map(Number);
|
|
2083
|
+
return aMin - bMin;
|
|
2084
|
+
}).forEach((key) => {
|
|
2085
|
+
orderedRanges[key] = payoutRanges[modeStr][key];
|
|
2086
|
+
});
|
|
2087
|
+
payoutRanges[modeStr] = orderedRanges;
|
|
2038
2088
|
}
|
|
2089
|
+
writeJsonFile(
|
|
2090
|
+
path2.join(process.cwd(), this.gameConfig.outputDir, "stats_payouts.json"),
|
|
2091
|
+
payoutRanges
|
|
2092
|
+
);
|
|
2039
2093
|
}
|
|
2040
2094
|
getGameModeConfig(mode) {
|
|
2041
2095
|
const config = this.gameConfig.gameModes[mode];
|
|
@@ -2502,9 +2556,20 @@ var WinType = class {
|
|
|
2502
2556
|
isWild(symbol) {
|
|
2503
2557
|
return !!this.wildSymbol && symbol.compare(this.wildSymbol);
|
|
2504
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
|
+
}
|
|
2505
2569
|
};
|
|
2506
2570
|
|
|
2507
2571
|
// src/win-types/LinesWinType.ts
|
|
2572
|
+
import assert11 from "assert";
|
|
2508
2573
|
var LinesWinType = class extends WinType {
|
|
2509
2574
|
lines;
|
|
2510
2575
|
constructor(opts) {
|
|
@@ -2545,106 +2610,335 @@ var LinesWinType = class extends WinType {
|
|
|
2545
2610
|
evaluateWins(board) {
|
|
2546
2611
|
this.validateConfig();
|
|
2547
2612
|
const lineWins = [];
|
|
2548
|
-
let payout = 0;
|
|
2549
2613
|
const reels = board;
|
|
2550
|
-
for (const [lineNumStr,
|
|
2614
|
+
for (const [lineNumStr, line] of Object.entries(this.lines)) {
|
|
2551
2615
|
const lineNum = Number(lineNumStr);
|
|
2552
|
-
let baseSymbol
|
|
2553
|
-
|
|
2554
|
-
const
|
|
2555
|
-
const
|
|
2556
|
-
|
|
2557
|
-
const
|
|
2558
|
-
const sym = reels[ridx][rowIdx];
|
|
2559
|
-
if (!sym) throw new Error("Encountered an invalid symbol while evaluating wins.");
|
|
2560
|
-
const wild = this.isWild(sym);
|
|
2561
|
-
if (ridx === 0) {
|
|
2562
|
-
chain.push(sym);
|
|
2563
|
-
details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: wild });
|
|
2564
|
-
if (wild) leadingWilds++;
|
|
2565
|
-
else baseSymbol = sym;
|
|
2566
|
-
continue;
|
|
2567
|
-
}
|
|
2568
|
-
if (wild) {
|
|
2569
|
-
chain.push(sym);
|
|
2570
|
-
details.push({
|
|
2571
|
-
reelIndex: ridx,
|
|
2572
|
-
posIndex: rowIdx,
|
|
2573
|
-
symbol: sym,
|
|
2574
|
-
isWild: true,
|
|
2575
|
-
substitutedFor: baseSymbol || void 0
|
|
2576
|
-
});
|
|
2577
|
-
continue;
|
|
2578
|
-
}
|
|
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];
|
|
2579
2622
|
if (!baseSymbol) {
|
|
2580
|
-
baseSymbol =
|
|
2581
|
-
|
|
2582
|
-
|
|
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 });
|
|
2583
2632
|
continue;
|
|
2584
2633
|
}
|
|
2585
|
-
if (
|
|
2586
|
-
|
|
2587
|
-
|
|
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 });
|
|
2588
2641
|
continue;
|
|
2589
2642
|
}
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
if (chain.length === 0) continue;
|
|
2593
|
-
const allWild = chain.every((s) => this.isWild(s));
|
|
2594
|
-
const wildRepresentative = this.wildSymbol instanceof GameSymbol ? this.wildSymbol : null;
|
|
2595
|
-
const len = chain.length;
|
|
2596
|
-
let bestPayout = 0;
|
|
2597
|
-
let bestType = null;
|
|
2598
|
-
let payingSymbol = null;
|
|
2599
|
-
if (baseSymbol?.pays && baseSymbol.pays[len]) {
|
|
2600
|
-
bestPayout = baseSymbol.pays[len];
|
|
2601
|
-
bestType = "substituted";
|
|
2602
|
-
payingSymbol = baseSymbol;
|
|
2603
|
-
}
|
|
2604
|
-
if (allWild && wildRepresentative?.pays && wildRepresentative.pays[len]) {
|
|
2605
|
-
const wildPay = wildRepresentative.pays[len];
|
|
2606
|
-
if (wildPay > bestPayout) {
|
|
2607
|
-
bestPayout = wildPay;
|
|
2608
|
-
bestType = "pure-wild";
|
|
2609
|
-
payingSymbol = wildRepresentative;
|
|
2643
|
+
if (baseSymbol.compare(thisSymbol) || this.isWild(thisSymbol)) {
|
|
2644
|
+
potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2610
2645
|
}
|
|
2611
2646
|
}
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
const
|
|
2617
|
-
|
|
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
|
+
})),
|
|
2618
2662
|
lineNumber: lineNum,
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
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);
|
|
2628
2681
|
}
|
|
2629
2682
|
for (const win of lineWins) {
|
|
2630
2683
|
this.ctx.services.data.recordSymbolOccurrence({
|
|
2631
2684
|
kind: win.kind,
|
|
2632
|
-
symbolId: win.
|
|
2685
|
+
symbolId: win.baseSymbol.id,
|
|
2633
2686
|
spinType: this.ctx.state.currentSpinType
|
|
2634
2687
|
});
|
|
2635
2688
|
}
|
|
2636
|
-
this.payout = payout;
|
|
2689
|
+
this.payout = lineWins.reduce((sum, l) => sum + l.payout, 0);
|
|
2637
2690
|
this.winCombinations = lineWins;
|
|
2638
2691
|
return this;
|
|
2639
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
|
+
}
|
|
2640
2701
|
};
|
|
2641
2702
|
|
|
2642
2703
|
// src/win-types/ClusterWinType.ts
|
|
2643
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
|
+
}
|
|
2644
2833
|
};
|
|
2645
2834
|
|
|
2646
2835
|
// src/win-types/ManywaysWinType.ts
|
|
2647
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
|
+
}
|
|
2648
2942
|
};
|
|
2649
2943
|
|
|
2650
2944
|
// src/reel-set/GeneratedReelSet.ts
|
|
@@ -3005,7 +3299,7 @@ var GeneratedReelSet = class extends ReelSet {
|
|
|
3005
3299
|
};
|
|
3006
3300
|
|
|
3007
3301
|
// src/reel-set/StaticReelSet.ts
|
|
3008
|
-
import
|
|
3302
|
+
import assert12 from "assert";
|
|
3009
3303
|
var StaticReelSet = class extends ReelSet {
|
|
3010
3304
|
reels;
|
|
3011
3305
|
csvPath;
|
|
@@ -3015,7 +3309,7 @@ var StaticReelSet = class extends ReelSet {
|
|
|
3015
3309
|
this.reels = [];
|
|
3016
3310
|
this._strReels = opts.reels || [];
|
|
3017
3311
|
this.csvPath = opts.csvPath || "";
|
|
3018
|
-
|
|
3312
|
+
assert12(
|
|
3019
3313
|
opts.reels || opts.csvPath,
|
|
3020
3314
|
`Either 'reels' or 'csvPath' must be provided for StaticReelSet ${this.id}`
|
|
3021
3315
|
);
|
|
@@ -3091,6 +3385,18 @@ var StandaloneBoard = class {
|
|
|
3091
3385
|
getPaddingBottom() {
|
|
3092
3386
|
return this.board.paddingBottom;
|
|
3093
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
|
+
}
|
|
3094
3400
|
resetReels() {
|
|
3095
3401
|
this.board.resetReels({
|
|
3096
3402
|
ctx: this.ctx
|