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