@slot-engine/core 0.0.9 → 0.0.11
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 +86 -13
- package/dist/index.d.ts +86 -13
- package/dist/index.js +409 -99
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +409 -99
- 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.");
|
|
@@ -623,7 +650,11 @@ var Board = class {
|
|
|
623
650
|
for (const [r, stopPos] of Object.entries(opts.forcedStops)) {
|
|
624
651
|
const reelIdx = Number(r);
|
|
625
652
|
const symCount = symbolsPerReel[reelIdx];
|
|
626
|
-
|
|
653
|
+
if (opts.forcedStopsOffset !== false) {
|
|
654
|
+
finalReelStops[reelIdx] = stopPos - Math.round(opts.ctx.services.rng.randomFloat(0, symCount - 1));
|
|
655
|
+
} else {
|
|
656
|
+
finalReelStops[reelIdx] = stopPos;
|
|
657
|
+
}
|
|
627
658
|
if (finalReelStops[reelIdx] < 0) {
|
|
628
659
|
finalReelStops[reelIdx] = opts.reels[reelIdx].length + finalReelStops[reelIdx];
|
|
629
660
|
}
|
|
@@ -666,7 +697,8 @@ var Board = class {
|
|
|
666
697
|
);
|
|
667
698
|
}
|
|
668
699
|
const reels = this.lastUsedReels;
|
|
669
|
-
opts.symbolsToDelete.
|
|
700
|
+
const sortedDeletions = [...opts.symbolsToDelete].sort((a, b) => b.rowIdx - a.rowIdx);
|
|
701
|
+
sortedDeletions.forEach(({ reelIdx, rowIdx }) => {
|
|
670
702
|
this.reels[reelIdx].splice(rowIdx, 1);
|
|
671
703
|
});
|
|
672
704
|
const newFirstSymbolPositions = {};
|
|
@@ -692,6 +724,7 @@ var Board = class {
|
|
|
692
724
|
}
|
|
693
725
|
for (let ridx = 0; ridx < reelsAmount; ridx++) {
|
|
694
726
|
const firstSymbolPos = newFirstSymbolPositions[ridx];
|
|
727
|
+
if (firstSymbolPos === void 0) continue;
|
|
695
728
|
for (let p = 1; p <= padSymbols; p++) {
|
|
696
729
|
const topPos = (firstSymbolPos - p + reels[ridx].length) % reels[ridx].length;
|
|
697
730
|
const padSymbol = reels[ridx][topPos];
|
|
@@ -732,6 +765,18 @@ var BoardService = class extends AbstractService {
|
|
|
732
765
|
getAnticipation() {
|
|
733
766
|
return this.board.anticipation;
|
|
734
767
|
}
|
|
768
|
+
/**
|
|
769
|
+
* Gets the symbol at the specified reel and row index.
|
|
770
|
+
*/
|
|
771
|
+
getSymbol(reelIndex, rowIndex) {
|
|
772
|
+
return this.board.getSymbol(reelIndex, rowIndex);
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Sets the symbol at the specified reel and row index.
|
|
776
|
+
*/
|
|
777
|
+
setSymbol(reelIndex, rowIndex, symbol) {
|
|
778
|
+
this.board.setSymbol(reelIndex, rowIndex, symbol);
|
|
779
|
+
}
|
|
735
780
|
resetReels() {
|
|
736
781
|
this.board.resetReels({
|
|
737
782
|
ctx: this.ctx()
|
|
@@ -806,8 +851,8 @@ var BoardService = class extends AbstractService {
|
|
|
806
851
|
/**
|
|
807
852
|
* Draws a board using specified reel stops.
|
|
808
853
|
*/
|
|
809
|
-
drawBoardWithForcedStops(
|
|
810
|
-
this.drawBoardMixed(reels, forcedStops);
|
|
854
|
+
drawBoardWithForcedStops(opts) {
|
|
855
|
+
this.drawBoardMixed(opts.reels, opts.forcedStops, opts.randomOffset);
|
|
811
856
|
}
|
|
812
857
|
/**
|
|
813
858
|
* Draws a board using random reel stops.
|
|
@@ -815,11 +860,12 @@ var BoardService = class extends AbstractService {
|
|
|
815
860
|
drawBoardWithRandomStops(reels) {
|
|
816
861
|
this.drawBoardMixed(reels);
|
|
817
862
|
}
|
|
818
|
-
drawBoardMixed(reels, forcedStops) {
|
|
863
|
+
drawBoardMixed(reels, forcedStops, forcedStopsOffset) {
|
|
819
864
|
this.board.drawBoardMixed({
|
|
820
865
|
ctx: this.ctx(),
|
|
821
866
|
reels,
|
|
822
|
-
forcedStops
|
|
867
|
+
forcedStops,
|
|
868
|
+
forcedStopsOffset
|
|
823
869
|
});
|
|
824
870
|
}
|
|
825
871
|
/**
|
|
@@ -919,6 +965,25 @@ var GameService = class extends AbstractService {
|
|
|
919
965
|
constructor(ctx) {
|
|
920
966
|
super(ctx);
|
|
921
967
|
}
|
|
968
|
+
/**
|
|
969
|
+
* Intended for internal use only.\
|
|
970
|
+
* Generates reels for all reel sets in the game configuration.
|
|
971
|
+
*/
|
|
972
|
+
_generateReels() {
|
|
973
|
+
const config = this.ctx().config;
|
|
974
|
+
for (const mode of Object.values(config.gameModes)) {
|
|
975
|
+
if (mode.reelSets && mode.reelSets.length > 0) {
|
|
976
|
+
for (const reelSet of Object.values(mode.reelSets)) {
|
|
977
|
+
reelSet.associatedGameModeName = mode.name;
|
|
978
|
+
reelSet.generateReels(config);
|
|
979
|
+
}
|
|
980
|
+
} else {
|
|
981
|
+
throw new Error(
|
|
982
|
+
`Game mode "${mode.name}" has no reel sets defined. Cannot generate reelset files.`
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
922
987
|
/**
|
|
923
988
|
* Retrieves a reel set by its ID within a specific game mode.
|
|
924
989
|
*/
|
|
@@ -1157,16 +1222,6 @@ var Book = class _Book {
|
|
|
1157
1222
|
}
|
|
1158
1223
|
};
|
|
1159
1224
|
|
|
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
1225
|
// src/wallet/index.ts
|
|
1171
1226
|
var Wallet = class {
|
|
1172
1227
|
/**
|
|
@@ -1810,7 +1865,7 @@ Simulating game mode: ${mode}`);
|
|
|
1810
1865
|
if (mode.reelSets && mode.reelSets.length > 0) {
|
|
1811
1866
|
for (const reelSet of Object.values(mode.reelSets)) {
|
|
1812
1867
|
reelSet.associatedGameModeName = mode.name;
|
|
1813
|
-
reelSet.generateReels(this);
|
|
1868
|
+
reelSet.generateReels(this.gameConfig);
|
|
1814
1869
|
}
|
|
1815
1870
|
} else {
|
|
1816
1871
|
throw new Error(
|
|
@@ -2579,9 +2634,20 @@ var WinType = class {
|
|
|
2579
2634
|
isWild(symbol) {
|
|
2580
2635
|
return !!this.wildSymbol && symbol.compare(this.wildSymbol);
|
|
2581
2636
|
}
|
|
2637
|
+
getSymbolPayout(symbol, count) {
|
|
2638
|
+
if (!symbol.pays) return 0;
|
|
2639
|
+
let clusterSize = 0;
|
|
2640
|
+
const sizes = Object.keys(symbol.pays).map((s) => parseInt(s, 10)).filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
|
|
2641
|
+
for (const size of sizes) {
|
|
2642
|
+
if (size > count) break;
|
|
2643
|
+
clusterSize = size;
|
|
2644
|
+
}
|
|
2645
|
+
return symbol.pays[clusterSize] || 0;
|
|
2646
|
+
}
|
|
2582
2647
|
};
|
|
2583
2648
|
|
|
2584
2649
|
// src/win-types/LinesWinType.ts
|
|
2650
|
+
var import_assert11 = __toESM(require("assert"));
|
|
2585
2651
|
var LinesWinType = class extends WinType {
|
|
2586
2652
|
lines;
|
|
2587
2653
|
constructor(opts) {
|
|
@@ -2622,106 +2688,335 @@ var LinesWinType = class extends WinType {
|
|
|
2622
2688
|
evaluateWins(board) {
|
|
2623
2689
|
this.validateConfig();
|
|
2624
2690
|
const lineWins = [];
|
|
2625
|
-
let payout = 0;
|
|
2626
2691
|
const reels = board;
|
|
2627
|
-
for (const [lineNumStr,
|
|
2692
|
+
for (const [lineNumStr, line] of Object.entries(this.lines)) {
|
|
2628
2693
|
const lineNum = Number(lineNumStr);
|
|
2629
|
-
let baseSymbol
|
|
2630
|
-
|
|
2631
|
-
const
|
|
2632
|
-
const
|
|
2633
|
-
|
|
2634
|
-
const
|
|
2635
|
-
|
|
2636
|
-
|
|
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;
|
|
2694
|
+
let baseSymbol;
|
|
2695
|
+
const potentialWinLine = [];
|
|
2696
|
+
const potentialWildLine = [];
|
|
2697
|
+
for (const [ridx, reel] of reels.entries()) {
|
|
2698
|
+
const sidx = line[ridx];
|
|
2699
|
+
const thisSymbol = reel[sidx];
|
|
2700
|
+
if (!baseSymbol) {
|
|
2701
|
+
baseSymbol = thisSymbol;
|
|
2644
2702
|
}
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
substitutedFor: baseSymbol || void 0
|
|
2653
|
-
});
|
|
2703
|
+
(0, import_assert11.default)(baseSymbol, `No symbol found at line ${lineNum}, reel ${ridx}`);
|
|
2704
|
+
(0, import_assert11.default)(thisSymbol, `No symbol found at line ${lineNum}, reel ${ridx}`);
|
|
2705
|
+
if (potentialWinLine.length == 0) {
|
|
2706
|
+
if (this.isWild(thisSymbol)) {
|
|
2707
|
+
potentialWildLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2708
|
+
}
|
|
2709
|
+
potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2654
2710
|
continue;
|
|
2655
2711
|
}
|
|
2656
|
-
if (
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2712
|
+
if (this.isWild(baseSymbol)) {
|
|
2713
|
+
if (this.isWild(thisSymbol)) {
|
|
2714
|
+
potentialWildLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2715
|
+
} else {
|
|
2716
|
+
baseSymbol = thisSymbol;
|
|
2717
|
+
}
|
|
2718
|
+
potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2660
2719
|
continue;
|
|
2661
2720
|
}
|
|
2662
|
-
if (
|
|
2663
|
-
|
|
2664
|
-
details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: false });
|
|
2665
|
-
continue;
|
|
2721
|
+
if (baseSymbol.compare(thisSymbol) || this.isWild(thisSymbol)) {
|
|
2722
|
+
potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
|
|
2666
2723
|
}
|
|
2667
|
-
break;
|
|
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
2724
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2725
|
+
const minSymLine = Math.min(
|
|
2726
|
+
...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
|
|
2727
|
+
);
|
|
2728
|
+
if (potentialWinLine.length < minSymLine) continue;
|
|
2729
|
+
const linePayout = this.getLinePayout(potentialWinLine);
|
|
2730
|
+
const wildLinePayout = this.getLinePayout(potentialWildLine);
|
|
2731
|
+
let finalLine = {
|
|
2732
|
+
kind: potentialWinLine.length,
|
|
2733
|
+
baseSymbol,
|
|
2734
|
+
symbols: potentialWinLine.map((s) => ({
|
|
2735
|
+
symbol: s.symbol,
|
|
2736
|
+
isWild: this.isWild(s.symbol),
|
|
2737
|
+
reelIndex: s.reel,
|
|
2738
|
+
posIndex: s.row
|
|
2739
|
+
})),
|
|
2695
2740
|
lineNumber: lineNum,
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2741
|
+
payout: linePayout
|
|
2742
|
+
};
|
|
2743
|
+
if (wildLinePayout > linePayout) {
|
|
2744
|
+
baseSymbol = potentialWildLine[0]?.symbol;
|
|
2745
|
+
finalLine = {
|
|
2746
|
+
kind: potentialWildLine.length,
|
|
2747
|
+
baseSymbol,
|
|
2748
|
+
symbols: potentialWildLine.map((s) => ({
|
|
2749
|
+
symbol: s.symbol,
|
|
2750
|
+
isWild: this.isWild(s.symbol),
|
|
2751
|
+
reelIndex: s.reel,
|
|
2752
|
+
posIndex: s.row
|
|
2753
|
+
})),
|
|
2754
|
+
lineNumber: lineNum,
|
|
2755
|
+
payout: wildLinePayout
|
|
2756
|
+
};
|
|
2757
|
+
}
|
|
2758
|
+
lineWins.push(finalLine);
|
|
2705
2759
|
}
|
|
2706
2760
|
for (const win of lineWins) {
|
|
2707
2761
|
this.ctx.services.data.recordSymbolOccurrence({
|
|
2708
2762
|
kind: win.kind,
|
|
2709
|
-
symbolId: win.
|
|
2763
|
+
symbolId: win.baseSymbol.id,
|
|
2710
2764
|
spinType: this.ctx.state.currentSpinType
|
|
2711
2765
|
});
|
|
2712
2766
|
}
|
|
2713
|
-
this.payout = payout;
|
|
2767
|
+
this.payout = lineWins.reduce((sum, l) => sum + l.payout, 0);
|
|
2714
2768
|
this.winCombinations = lineWins;
|
|
2715
2769
|
return this;
|
|
2716
2770
|
}
|
|
2771
|
+
getLinePayout(line) {
|
|
2772
|
+
if (line.length === 0) return 0;
|
|
2773
|
+
let baseSymbol = line.find((s) => !this.isWild(s.symbol))?.symbol;
|
|
2774
|
+
if (!baseSymbol) baseSymbol = line[0].symbol;
|
|
2775
|
+
const kind = line.length;
|
|
2776
|
+
const payout = this.getSymbolPayout(baseSymbol, kind);
|
|
2777
|
+
return payout;
|
|
2778
|
+
}
|
|
2717
2779
|
};
|
|
2718
2780
|
|
|
2719
2781
|
// src/win-types/ClusterWinType.ts
|
|
2720
2782
|
var ClusterWinType = class extends WinType {
|
|
2783
|
+
_checked = [];
|
|
2784
|
+
_checkedWilds = [];
|
|
2785
|
+
_currentBoard = [];
|
|
2786
|
+
constructor(opts) {
|
|
2787
|
+
super(opts);
|
|
2788
|
+
}
|
|
2789
|
+
validateConfig() {
|
|
2790
|
+
}
|
|
2791
|
+
/**
|
|
2792
|
+
* Calculates wins based on symbol cluster size and provided board state.\
|
|
2793
|
+
* Retrieve the results using `getWins()` after.
|
|
2794
|
+
*/
|
|
2795
|
+
evaluateWins(board) {
|
|
2796
|
+
this.validateConfig();
|
|
2797
|
+
this._checked = [];
|
|
2798
|
+
this._currentBoard = board;
|
|
2799
|
+
const clusterWins = [];
|
|
2800
|
+
const potentialClusters = [];
|
|
2801
|
+
for (const [ridx, reel] of board.entries()) {
|
|
2802
|
+
for (const [sidx, symbol] of reel.entries()) {
|
|
2803
|
+
this._checkedWilds = [];
|
|
2804
|
+
if (this.isWild(symbol)) continue;
|
|
2805
|
+
if (this.isChecked(ridx, sidx)) {
|
|
2806
|
+
continue;
|
|
2807
|
+
}
|
|
2808
|
+
const thisSymbol = { reel: ridx, row: sidx, symbol };
|
|
2809
|
+
this._checked.push(thisSymbol);
|
|
2810
|
+
const neighbors = this.getNeighbors(ridx, sidx);
|
|
2811
|
+
const matchingSymbols = this.evaluateCluster(symbol, neighbors);
|
|
2812
|
+
if (matchingSymbols.size >= 1) {
|
|
2813
|
+
potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
for (const [ridx, reel] of board.entries()) {
|
|
2818
|
+
for (const [sidx, symbol] of reel.entries()) {
|
|
2819
|
+
this._checkedWilds = [];
|
|
2820
|
+
if (!this.isWild(symbol)) continue;
|
|
2821
|
+
if (this.isChecked(ridx, sidx)) {
|
|
2822
|
+
continue;
|
|
2823
|
+
}
|
|
2824
|
+
const thisSymbol = { reel: ridx, row: sidx, symbol };
|
|
2825
|
+
this._checked.push(thisSymbol);
|
|
2826
|
+
const neighbors = this.getNeighbors(ridx, sidx);
|
|
2827
|
+
const matchingSymbols = this.evaluateCluster(symbol, neighbors);
|
|
2828
|
+
if (matchingSymbols.size >= 1) {
|
|
2829
|
+
potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
potentialClusters.forEach((cluster) => {
|
|
2834
|
+
const kind = cluster.length;
|
|
2835
|
+
let baseSymbol = cluster.find((s) => !this.isWild(s.symbol))?.symbol;
|
|
2836
|
+
if (!baseSymbol) baseSymbol = cluster[0].symbol;
|
|
2837
|
+
const payout = this.getSymbolPayout(baseSymbol, kind);
|
|
2838
|
+
if (!baseSymbol.pays || Object.keys(baseSymbol.pays).length === 0) {
|
|
2839
|
+
return;
|
|
2840
|
+
}
|
|
2841
|
+
clusterWins.push({
|
|
2842
|
+
payout,
|
|
2843
|
+
kind,
|
|
2844
|
+
baseSymbol,
|
|
2845
|
+
symbols: cluster.map((s) => ({
|
|
2846
|
+
symbol: s.symbol,
|
|
2847
|
+
isWild: this.isWild(s.symbol),
|
|
2848
|
+
reelIndex: s.reel,
|
|
2849
|
+
posIndex: s.row
|
|
2850
|
+
}))
|
|
2851
|
+
});
|
|
2852
|
+
});
|
|
2853
|
+
for (const win of clusterWins) {
|
|
2854
|
+
this.ctx.services.data.recordSymbolOccurrence({
|
|
2855
|
+
kind: win.kind,
|
|
2856
|
+
symbolId: win.baseSymbol.id,
|
|
2857
|
+
spinType: this.ctx.state.currentSpinType
|
|
2858
|
+
});
|
|
2859
|
+
}
|
|
2860
|
+
this.payout = clusterWins.reduce((sum, c) => sum + c.payout, 0);
|
|
2861
|
+
this.winCombinations = clusterWins;
|
|
2862
|
+
return this;
|
|
2863
|
+
}
|
|
2864
|
+
getNeighbors(ridx, sidx) {
|
|
2865
|
+
const board = this._currentBoard;
|
|
2866
|
+
const neighbors = [];
|
|
2867
|
+
const potentialNeighbors = [
|
|
2868
|
+
[ridx - 1, sidx],
|
|
2869
|
+
[ridx + 1, sidx],
|
|
2870
|
+
[ridx, sidx - 1],
|
|
2871
|
+
[ridx, sidx + 1]
|
|
2872
|
+
];
|
|
2873
|
+
potentialNeighbors.forEach(([nridx, nsidx]) => {
|
|
2874
|
+
if (board[nridx] && board[nridx][nsidx]) {
|
|
2875
|
+
neighbors.push({ reel: nridx, row: nsidx, symbol: board[nridx][nsidx] });
|
|
2876
|
+
}
|
|
2877
|
+
});
|
|
2878
|
+
return neighbors;
|
|
2879
|
+
}
|
|
2880
|
+
evaluateCluster(rootSymbol, neighbors) {
|
|
2881
|
+
const matchingSymbols = /* @__PURE__ */ new Map();
|
|
2882
|
+
neighbors.forEach((neighbor) => {
|
|
2883
|
+
const { reel, row, symbol } = neighbor;
|
|
2884
|
+
if (this.isChecked(reel, row)) return;
|
|
2885
|
+
if (this.isCheckedWild(reel, row)) return;
|
|
2886
|
+
if (this.isWild(symbol) || symbol.compare(rootSymbol)) {
|
|
2887
|
+
const key = `${reel}-${row}`;
|
|
2888
|
+
matchingSymbols.set(key, { reel, row, symbol });
|
|
2889
|
+
if (symbol.compare(rootSymbol)) {
|
|
2890
|
+
this._checked.push(neighbor);
|
|
2891
|
+
}
|
|
2892
|
+
if (this.isWild(symbol)) {
|
|
2893
|
+
this._checkedWilds.push(neighbor);
|
|
2894
|
+
}
|
|
2895
|
+
const neighbors2 = this.getNeighbors(reel, row);
|
|
2896
|
+
const nestedMatches = this.evaluateCluster(rootSymbol, neighbors2);
|
|
2897
|
+
nestedMatches.forEach((nsym) => {
|
|
2898
|
+
const nkey = `${nsym.reel}-${nsym.row}`;
|
|
2899
|
+
matchingSymbols.set(nkey, nsym);
|
|
2900
|
+
});
|
|
2901
|
+
}
|
|
2902
|
+
});
|
|
2903
|
+
return matchingSymbols;
|
|
2904
|
+
}
|
|
2905
|
+
isChecked(ridx, sidx) {
|
|
2906
|
+
return !!this._checked.find((c) => c.reel === ridx && c.row === sidx);
|
|
2907
|
+
}
|
|
2908
|
+
isCheckedWild(ridx, sidx) {
|
|
2909
|
+
return !!this._checkedWilds.find((c) => c.reel === ridx && c.row === sidx);
|
|
2910
|
+
}
|
|
2721
2911
|
};
|
|
2722
2912
|
|
|
2723
2913
|
// src/win-types/ManywaysWinType.ts
|
|
2724
2914
|
var ManywaysWinType = class extends WinType {
|
|
2915
|
+
_checked = [];
|
|
2916
|
+
_checkedWilds = [];
|
|
2917
|
+
constructor(opts) {
|
|
2918
|
+
super(opts);
|
|
2919
|
+
}
|
|
2920
|
+
validateConfig() {
|
|
2921
|
+
}
|
|
2922
|
+
/**
|
|
2923
|
+
* Calculates wins based on the defined paylines and provided board state.\
|
|
2924
|
+
* Retrieve the results using `getWins()` after.
|
|
2925
|
+
*/
|
|
2926
|
+
evaluateWins(board) {
|
|
2927
|
+
this.validateConfig();
|
|
2928
|
+
const waysWins = [];
|
|
2929
|
+
const reels = board;
|
|
2930
|
+
const possibleWaysWins = /* @__PURE__ */ new Map();
|
|
2931
|
+
const candidateSymbols = /* @__PURE__ */ new Map();
|
|
2932
|
+
let searchReelIdx = 0;
|
|
2933
|
+
let searchActive = true;
|
|
2934
|
+
while (searchActive && searchReelIdx < reels.length) {
|
|
2935
|
+
const reel = reels[searchReelIdx];
|
|
2936
|
+
let hasWild = false;
|
|
2937
|
+
for (const symbol of reel) {
|
|
2938
|
+
candidateSymbols.set(symbol.id, symbol);
|
|
2939
|
+
if (this.isWild(symbol)) {
|
|
2940
|
+
hasWild = true;
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
if (!hasWild) {
|
|
2944
|
+
searchActive = false;
|
|
2945
|
+
}
|
|
2946
|
+
searchReelIdx++;
|
|
2947
|
+
}
|
|
2948
|
+
for (const baseSymbol of candidateSymbols.values()) {
|
|
2949
|
+
let symbolList = {};
|
|
2950
|
+
let isInterrupted = false;
|
|
2951
|
+
for (const [ridx, reel] of reels.entries()) {
|
|
2952
|
+
if (isInterrupted) break;
|
|
2953
|
+
for (const [sidx, symbol] of reel.entries()) {
|
|
2954
|
+
const isMatch = baseSymbol.compare(symbol) || this.isWild(symbol);
|
|
2955
|
+
if (isMatch) {
|
|
2956
|
+
if (!symbolList[ridx]) {
|
|
2957
|
+
symbolList[ridx] = [];
|
|
2958
|
+
}
|
|
2959
|
+
symbolList[ridx].push({ reel: ridx, row: sidx, symbol });
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
if (!symbolList[ridx]) {
|
|
2963
|
+
isInterrupted = true;
|
|
2964
|
+
break;
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
const minSymLine = Math.min(
|
|
2968
|
+
...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
|
|
2969
|
+
);
|
|
2970
|
+
const wayLength = this.getWayLength(symbolList);
|
|
2971
|
+
if (wayLength >= minSymLine) {
|
|
2972
|
+
possibleWaysWins.set(baseSymbol.id, symbolList);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
for (const [baseSymbolId, symbolList] of possibleWaysWins.entries()) {
|
|
2976
|
+
const wayLength = this.getWayLength(symbolList);
|
|
2977
|
+
let baseSymbol = Object.values(symbolList).flatMap((l) => l.map((s) => s)).find((s) => !this.isWild(s.symbol))?.symbol;
|
|
2978
|
+
if (!baseSymbol) baseSymbol = symbolList[0][0].symbol;
|
|
2979
|
+
const singleWayPayout = this.getSymbolPayout(baseSymbol, wayLength);
|
|
2980
|
+
const totalWays = Object.values(symbolList).reduce(
|
|
2981
|
+
(ways, syms) => ways * syms.length,
|
|
2982
|
+
1
|
|
2983
|
+
);
|
|
2984
|
+
const totalPayout = singleWayPayout * totalWays;
|
|
2985
|
+
waysWins.push({
|
|
2986
|
+
kind: wayLength,
|
|
2987
|
+
baseSymbol,
|
|
2988
|
+
symbols: Object.values(symbolList).flatMap(
|
|
2989
|
+
(reel) => reel.map((s) => ({
|
|
2990
|
+
symbol: s.symbol,
|
|
2991
|
+
isWild: this.isWild(s.symbol),
|
|
2992
|
+
reelIndex: s.reel,
|
|
2993
|
+
posIndex: s.row
|
|
2994
|
+
}))
|
|
2995
|
+
),
|
|
2996
|
+
ways: totalWays,
|
|
2997
|
+
payout: totalPayout
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
for (const win of waysWins) {
|
|
3001
|
+
this.ctx.services.data.recordSymbolOccurrence({
|
|
3002
|
+
kind: win.kind,
|
|
3003
|
+
symbolId: win.baseSymbol.id,
|
|
3004
|
+
spinType: this.ctx.state.currentSpinType
|
|
3005
|
+
});
|
|
3006
|
+
}
|
|
3007
|
+
this.payout = waysWins.reduce((sum, l) => sum + l.payout, 0);
|
|
3008
|
+
this.winCombinations = waysWins;
|
|
3009
|
+
return this;
|
|
3010
|
+
}
|
|
3011
|
+
getWayLength(symbolList) {
|
|
3012
|
+
return Math.max(...Object.keys(symbolList).map((k) => parseInt(k, 10))) + 1;
|
|
3013
|
+
}
|
|
3014
|
+
isChecked(ridx, sidx) {
|
|
3015
|
+
return !!this._checked.find((c) => c.reel === ridx && c.row === sidx);
|
|
3016
|
+
}
|
|
3017
|
+
isCheckedWild(ridx, sidx) {
|
|
3018
|
+
return !!this._checkedWilds.find((c) => c.reel === ridx && c.row === sidx);
|
|
3019
|
+
}
|
|
2725
3020
|
};
|
|
2726
3021
|
|
|
2727
3022
|
// src/reel-set/GeneratedReelSet.ts
|
|
@@ -2744,7 +3039,7 @@ var ReelSet = class {
|
|
|
2744
3039
|
this.rng = new RandomNumberGenerator();
|
|
2745
3040
|
this.rng.setSeed(opts.seed ?? 0);
|
|
2746
3041
|
}
|
|
2747
|
-
generateReels(
|
|
3042
|
+
generateReels(config) {
|
|
2748
3043
|
throw new Error("Not implemented");
|
|
2749
3044
|
}
|
|
2750
3045
|
/**
|
|
@@ -2916,7 +3211,7 @@ var GeneratedReelSet = class extends ReelSet {
|
|
|
2916
3211
|
}
|
|
2917
3212
|
return false;
|
|
2918
3213
|
}
|
|
2919
|
-
generateReels(
|
|
3214
|
+
generateReels(config) {
|
|
2920
3215
|
this.validateConfig(config);
|
|
2921
3216
|
const gameMode = config.gameModes[this.associatedGameModeName];
|
|
2922
3217
|
if (!gameMode) {
|
|
@@ -2931,7 +3226,7 @@ var GeneratedReelSet = class extends ReelSet {
|
|
|
2931
3226
|
const exists = import_fs5.default.existsSync(filePath);
|
|
2932
3227
|
if (exists && !this.overrideExisting) {
|
|
2933
3228
|
this.reels = this.parseReelsetCSV(filePath, config);
|
|
2934
|
-
return;
|
|
3229
|
+
return this;
|
|
2935
3230
|
}
|
|
2936
3231
|
if (!exists && this.symbolWeights.size === 0) {
|
|
2937
3232
|
throw new Error(
|
|
@@ -3078,11 +3373,12 @@ var GeneratedReelSet = class extends ReelSet {
|
|
|
3078
3373
|
`Generated reelset ${this.id} for game mode ${this.associatedGameModeName}`
|
|
3079
3374
|
);
|
|
3080
3375
|
}
|
|
3376
|
+
return this;
|
|
3081
3377
|
}
|
|
3082
3378
|
};
|
|
3083
3379
|
|
|
3084
3380
|
// src/reel-set/StaticReelSet.ts
|
|
3085
|
-
var
|
|
3381
|
+
var import_assert12 = __toESM(require("assert"));
|
|
3086
3382
|
var StaticReelSet = class extends ReelSet {
|
|
3087
3383
|
reels;
|
|
3088
3384
|
csvPath;
|
|
@@ -3092,7 +3388,7 @@ var StaticReelSet = class extends ReelSet {
|
|
|
3092
3388
|
this.reels = [];
|
|
3093
3389
|
this._strReels = opts.reels || [];
|
|
3094
3390
|
this.csvPath = opts.csvPath || "";
|
|
3095
|
-
(0,
|
|
3391
|
+
(0, import_assert12.default)(
|
|
3096
3392
|
opts.reels || opts.csvPath,
|
|
3097
3393
|
`Either 'reels' or 'csvPath' must be provided for StaticReelSet ${this.id}`
|
|
3098
3394
|
);
|
|
@@ -3113,7 +3409,7 @@ var StaticReelSet = class extends ReelSet {
|
|
|
3113
3409
|
);
|
|
3114
3410
|
}
|
|
3115
3411
|
}
|
|
3116
|
-
generateReels(
|
|
3412
|
+
generateReels(config) {
|
|
3117
3413
|
this.validateConfig(config);
|
|
3118
3414
|
if (this._strReels.length > 0) {
|
|
3119
3415
|
this.reels = this._strReels.map((reel) => {
|
|
@@ -3131,6 +3427,7 @@ var StaticReelSet = class extends ReelSet {
|
|
|
3131
3427
|
if (this.csvPath) {
|
|
3132
3428
|
this.reels = this.parseReelsetCSV(this.csvPath, config);
|
|
3133
3429
|
}
|
|
3430
|
+
return this;
|
|
3134
3431
|
}
|
|
3135
3432
|
};
|
|
3136
3433
|
|
|
@@ -3168,6 +3465,18 @@ var StandaloneBoard = class {
|
|
|
3168
3465
|
getPaddingBottom() {
|
|
3169
3466
|
return this.board.paddingBottom;
|
|
3170
3467
|
}
|
|
3468
|
+
/**
|
|
3469
|
+
* Gets the symbol at the specified reel and row index.
|
|
3470
|
+
*/
|
|
3471
|
+
getSymbol(reelIndex, rowIndex) {
|
|
3472
|
+
return this.board.getSymbol(reelIndex, rowIndex);
|
|
3473
|
+
}
|
|
3474
|
+
/**
|
|
3475
|
+
* Sets the symbol at the specified reel and row index.
|
|
3476
|
+
*/
|
|
3477
|
+
setSymbol(reelIndex, rowIndex, symbol) {
|
|
3478
|
+
this.board.setSymbol(reelIndex, rowIndex, symbol);
|
|
3479
|
+
}
|
|
3171
3480
|
resetReels() {
|
|
3172
3481
|
this.board.resetReels({
|
|
3173
3482
|
ctx: this.ctx
|
|
@@ -3243,8 +3552,8 @@ var StandaloneBoard = class {
|
|
|
3243
3552
|
/**
|
|
3244
3553
|
* Draws a board using specified reel stops.
|
|
3245
3554
|
*/
|
|
3246
|
-
drawBoardWithForcedStops(
|
|
3247
|
-
this.drawBoardMixed(reels, forcedStops);
|
|
3555
|
+
drawBoardWithForcedStops(opts) {
|
|
3556
|
+
this.drawBoardMixed(opts.reels, opts.forcedStops, opts.randomOffset);
|
|
3248
3557
|
}
|
|
3249
3558
|
/**
|
|
3250
3559
|
* Draws a board using random reel stops.
|
|
@@ -3252,11 +3561,12 @@ var StandaloneBoard = class {
|
|
|
3252
3561
|
drawBoardWithRandomStops(reels) {
|
|
3253
3562
|
this.drawBoardMixed(reels);
|
|
3254
3563
|
}
|
|
3255
|
-
drawBoardMixed(reels, forcedStops) {
|
|
3564
|
+
drawBoardMixed(reels, forcedStops, forcedStopsOffset) {
|
|
3256
3565
|
this.board.drawBoardMixed({
|
|
3257
3566
|
ctx: this.ctx,
|
|
3258
3567
|
reels,
|
|
3259
3568
|
forcedStops,
|
|
3569
|
+
forcedStopsOffset,
|
|
3260
3570
|
reelsAmount: this.reelsAmount,
|
|
3261
3571
|
symbolsPerReel: this.symbolsPerReel,
|
|
3262
3572
|
padSymbols: this.padSymbols
|