@slot-engine/core 0.2.8 → 0.2.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
@@ -189,15 +189,14 @@ var RandomNumberGenerator = class {
189
189
  return float * (high - low) + low;
190
190
  }
191
191
  weightedRandom(weights) {
192
- const totalWeight = Object.values(weights).reduce(
193
- (sum, weight) => sum + weight,
194
- 0
195
- );
196
- const randomValue = this.randomFloat(0, 1) * totalWeight;
197
- let cumulativeWeight = 0;
198
- for (const [key, weight] of Object.entries(weights)) {
199
- cumulativeWeight += weight;
200
- if (randomValue < cumulativeWeight) {
192
+ let totalWeight = 0;
193
+ for (const key in weights) {
194
+ totalWeight += weights[key];
195
+ }
196
+ let remaining = this.randomFloat(0, 1) * totalWeight;
197
+ for (const key in weights) {
198
+ remaining -= weights[key];
199
+ if (remaining < 0) {
201
200
  return key;
202
201
  }
203
202
  }
@@ -372,7 +371,10 @@ var GameSymbol = class _GameSymbol {
372
371
  constructor(opts) {
373
372
  this.id = opts.id;
374
373
  this.pays = opts.pays;
375
- this.properties = new Map(Object.entries(opts.properties || {}));
374
+ this.properties = /* @__PURE__ */ new Map();
375
+ for (const prop in opts.properties) {
376
+ this.properties.set(prop, opts.properties[prop]);
377
+ }
376
378
  if (this.pays && Object.keys(this.pays).length === 0) {
377
379
  throw new Error(`GameSymbol "${this.id}" must have pays defined.`);
378
380
  }
@@ -388,8 +390,8 @@ var GameSymbol = class _GameSymbol {
388
390
  if (symbolOrProperties instanceof _GameSymbol) {
389
391
  return this.id === symbolOrProperties.id;
390
392
  } else {
391
- for (const [key, value] of Object.entries(symbolOrProperties)) {
392
- if (!this.properties.has(key) || this.properties.get(key) !== value) {
393
+ for (const prop in symbolOrProperties) {
394
+ if (!this.properties.has(prop) || this.properties.get(prop) !== symbolOrProperties[prop]) {
393
395
  return false;
394
396
  }
395
397
  }
@@ -469,8 +471,8 @@ var Board = class {
469
471
  updateSymbol(reelIndex, rowIndex, properties) {
470
472
  const symbol = this.getSymbol(reelIndex, rowIndex);
471
473
  if (symbol) {
472
- for (const [key, value] of Object.entries(properties)) {
473
- symbol.properties.set(key, value);
474
+ for (const prop in properties) {
475
+ symbol.properties.set(prop, properties[prop]);
474
476
  }
475
477
  }
476
478
  }
@@ -486,8 +488,8 @@ var Board = class {
486
488
  if (symbolOrProperties instanceof GameSymbol) {
487
489
  if (symbol.id !== symbolOrProperties.id) matches = false;
488
490
  } else {
489
- for (const [key, value] of Object.entries(symbolOrProperties)) {
490
- if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {
491
+ for (const prop in symbolOrProperties) {
492
+ if (!symbol.properties.has(prop) || symbol.properties.get(prop) !== symbolOrProperties[prop]) {
491
493
  matches = false;
492
494
  break;
493
495
  }
@@ -502,27 +504,23 @@ var Board = class {
502
504
  countSymbolsOnBoard(symbolOrProperties) {
503
505
  let total = 0;
504
506
  const onReel = {};
507
+ const isGameSymbol = symbolOrProperties instanceof GameSymbol;
505
508
  for (const [ridx, reel] of this.reels.entries()) {
506
509
  for (const symbol of reel) {
507
- let matches = true;
508
- if (symbolOrProperties instanceof GameSymbol) {
509
- if (symbol.id !== symbolOrProperties.id) matches = false;
510
+ if (isGameSymbol) {
511
+ if (symbol.id !== symbolOrProperties.id) continue;
510
512
  } else {
511
- for (const [key, value] of Object.entries(symbolOrProperties)) {
512
- if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {
513
+ let matches = true;
514
+ for (const prop in symbolOrProperties) {
515
+ if (!symbol.properties.has(prop) || symbol.properties.get(prop) !== symbolOrProperties[prop]) {
513
516
  matches = false;
514
517
  break;
515
518
  }
516
519
  }
520
+ if (!matches) continue;
517
521
  }
518
- if (matches) {
519
- total++;
520
- if (onReel[ridx] === void 0) {
521
- onReel[ridx] = 1;
522
- } else {
523
- onReel[ridx]++;
524
- }
525
- }
522
+ total++;
523
+ onReel[ridx] = (onReel[ridx] || 0) + 1;
526
524
  }
527
525
  }
528
526
  return [total, onReel];
@@ -621,8 +619,9 @@ var Board = class {
621
619
  ...opts,
622
620
  ...this.reelsLocked.length && { reelsLocked: this.reelsLocked }
623
621
  });
624
- const reelsAmount = opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount;
625
- const symbolsPerReel = opts.symbolsPerReel ?? opts.ctx.services.game.getCurrentGameMode().symbolsPerReel;
622
+ const gameMode = opts.ctx.services.game.getCurrentGameMode();
623
+ const reelsAmount = opts.reelsAmount ?? gameMode.reelsAmount;
624
+ const symbolsPerReel = opts.symbolsPerReel ?? gameMode.symbolsPerReel;
626
625
  const padSymbols = opts.padSymbols ?? opts.ctx.config.padSymbols;
627
626
  const finalReelStops = Array.from(
628
627
  { length: reelsAmount },
@@ -665,15 +664,16 @@ var Board = class {
665
664
  this.lastUsedReels = opts.reels;
666
665
  for (let ridx = 0; ridx < reelsAmount; ridx++) {
667
666
  const reelPos = finalReelStops[ridx];
668
- const reelLength = opts.reels[ridx].length;
667
+ const reel = opts.reels[ridx];
668
+ const reelLength = reel.length;
669
669
  for (let p = padSymbols - 1; p >= 0; p--) {
670
670
  const topPos = ((reelPos - (p + 1)) % reelLength + reelLength) % reelLength;
671
- this.paddingTop[ridx].push(opts.reels[ridx][topPos].clone());
671
+ this.paddingTop[ridx].push(reel[topPos].clone());
672
672
  const bottomPos = (reelPos + symbolsPerReel[ridx] + p) % reelLength;
673
- this.paddingBottom[ridx].unshift(opts.reels[ridx][bottomPos].clone());
673
+ this.paddingBottom[ridx].unshift(reel[bottomPos].clone());
674
674
  }
675
675
  for (let row = 0; row < symbolsPerReel[ridx]; row++) {
676
- const symbol = opts.reels[ridx][(reelPos + row) % reelLength];
676
+ const symbol = reel[(reelPos + row) % reelLength];
677
677
  if (!symbol) {
678
678
  throw new Error(`Failed to get symbol at pos ${reelPos + row} on reel ${ridx}`);
679
679
  }
@@ -689,8 +689,9 @@ var Board = class {
689
689
  }
690
690
  tumbleBoard(opts) {
691
691
  assert3(this.lastDrawnReelStops.length > 0, "Cannot tumble board before drawing it.");
692
- const reelsAmount = opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount;
693
- const symbolsPerReel = opts.symbolsPerReel ?? opts.ctx.services.game.getCurrentGameMode().symbolsPerReel;
692
+ const gameMode = opts.ctx.services.game.getCurrentGameMode();
693
+ const reelsAmount = opts.reelsAmount ?? gameMode.reelsAmount;
694
+ const symbolsPerReel = opts.symbolsPerReel ?? gameMode.symbolsPerReel;
694
695
  const padSymbols = opts.padSymbols ?? opts.ctx.config.padSymbols;
695
696
  if (opts.startingStops) {
696
697
  assert3(
@@ -736,11 +737,12 @@ var Board = class {
736
737
  const stopBeforePad = previousStop - padSymbols - 1;
737
738
  const symbolsNeeded = symbolsPerReel[ridx] - this.reels[ridx].length;
738
739
  for (let s = 0; s < symbolsNeeded; s++) {
739
- const symbolPos = (stopBeforePad - s + reels[ridx].length) % reels[ridx].length;
740
- let newSymbol = reels[ridx][symbolPos];
740
+ const reel = reels[ridx];
741
+ const symbolPos = (stopBeforePad - s + reel.length) % reel.length;
742
+ let newSymbol = reel[symbolPos];
741
743
  const startStops = opts.startingStops;
742
744
  if (startStops) {
743
- const forcedSym = reels[ridx][startStops?.[ridx]];
745
+ const forcedSym = reel[startStops?.[ridx]];
744
746
  assert3(
745
747
  forcedSym,
746
748
  `Failed to get forced symbol for tumbling. Tried to get symbol for position ${startStops?.[ridx]} on reel ${ridx}.`
@@ -761,8 +763,9 @@ var Board = class {
761
763
  const firstSymbolPos = newFirstSymbolPositions[ridx];
762
764
  if (firstSymbolPos === void 0) continue;
763
765
  for (let p = 1; p <= padSymbols; p++) {
764
- const topPos = (firstSymbolPos - p + reels[ridx].length) % reels[ridx].length;
765
- const padSymbol = reels[ridx][topPos]?.clone();
766
+ const reel = reels[ridx];
767
+ const topPos = (firstSymbolPos - p + reel.length) % reel.length;
768
+ const padSymbol = reel[topPos]?.clone();
766
769
  assert3(padSymbol, "Failed to get new padding symbol for tumbling.");
767
770
  this.paddingTop[ridx].unshift(padSymbol);
768
771
  if (!newPaddingTopSymbols[ridx]) {
@@ -1056,11 +1059,13 @@ var DataService = class extends AbstractService {
1056
1059
  * Record data for statistical analysis.
1057
1060
  */
1058
1061
  record(data) {
1062
+ const properties = {};
1063
+ for (const key in data) {
1064
+ properties[key] = String(data[key]);
1065
+ }
1059
1066
  this.recorder.pendingRecords.push({
1060
1067
  bookId: this.ctx().state.currentSimulationId,
1061
- properties: Object.fromEntries(
1062
- Object.entries(data).map(([k, v]) => [k, String(v)])
1063
- )
1068
+ properties
1064
1069
  });
1065
1070
  }
1066
1071
  /**
@@ -3956,13 +3961,19 @@ var LinesWinType = class extends WinType {
3956
3961
  this.validateConfig();
3957
3962
  const lineWins = [];
3958
3963
  const reels = board;
3959
- for (const [lineNumStr, line] of Object.entries(this.lines)) {
3964
+ const reelsLength = reels.length;
3965
+ const lineNumbers = Object.keys(this.lines);
3966
+ const numLines = lineNumbers.length;
3967
+ for (let lidx = 0; lidx < numLines; lidx++) {
3968
+ const lineNumStr = lineNumbers[lidx];
3960
3969
  const lineNum = Number(lineNumStr);
3970
+ const line = this.lines[lineNum];
3961
3971
  let baseSymbol;
3962
3972
  const potentialWinLine = [];
3963
3973
  const potentialWildLine = [];
3964
3974
  let isInterrupted = false;
3965
- for (const [ridx, reel] of reels.entries()) {
3975
+ for (let ridx = 0; ridx < reelsLength; ridx++) {
3976
+ const reel = reels[ridx];
3966
3977
  const sidx = line[ridx];
3967
3978
  const thisSymbol = reel[sidx];
3968
3979
  if (!baseSymbol) {
@@ -3993,66 +4004,91 @@ var LinesWinType = class extends WinType {
3993
4004
  break;
3994
4005
  }
3995
4006
  }
3996
- const minSymLine = Math.min(
3997
- ...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
3998
- );
4007
+ const pays = baseSymbol.pays || {};
4008
+ let minSymLine = Infinity;
4009
+ for (const key in pays) {
4010
+ const num = parseInt(key, 10);
4011
+ if (num < minSymLine) minSymLine = num;
4012
+ }
3999
4013
  if (potentialWinLine.length < minSymLine) continue;
4000
4014
  const linePayout = this.getLinePayout(potentialWinLine);
4001
4015
  const wildLinePayout = this.getLinePayout(potentialWildLine);
4002
- let finalLine = {
4003
- kind: potentialWinLine.length,
4004
- baseSymbol,
4005
- symbols: potentialWinLine.map((s) => ({
4006
- symbol: s.symbol,
4007
- isWild: this.isWild(s.symbol),
4008
- reelIndex: s.reel,
4009
- posIndex: s.row
4010
- })),
4011
- lineNumber: lineNum,
4012
- payout: linePayout
4013
- };
4016
+ let finalLine;
4014
4017
  if (wildLinePayout > linePayout) {
4015
4018
  baseSymbol = potentialWildLine[0]?.symbol;
4019
+ const wildSymbols = [];
4020
+ const wildLineLength = potentialWildLine.length;
4021
+ for (let i = 0; i < wildLineLength; i++) {
4022
+ const s = potentialWildLine[i];
4023
+ wildSymbols.push({
4024
+ symbol: s.symbol,
4025
+ isWild: this.isWild(s.symbol),
4026
+ reelIndex: s.reel,
4027
+ posIndex: s.row
4028
+ });
4029
+ }
4016
4030
  finalLine = {
4017
- kind: potentialWildLine.length,
4031
+ kind: wildLineLength,
4018
4032
  baseSymbol,
4019
- symbols: potentialWildLine.map((s) => ({
4033
+ symbols: wildSymbols,
4034
+ lineNumber: lineNum,
4035
+ payout: wildLinePayout
4036
+ };
4037
+ } else {
4038
+ const symbols = [];
4039
+ const lineLength = potentialWinLine.length;
4040
+ for (let i = 0; i < lineLength; i++) {
4041
+ const s = potentialWinLine[i];
4042
+ symbols.push({
4020
4043
  symbol: s.symbol,
4021
4044
  isWild: this.isWild(s.symbol),
4022
4045
  reelIndex: s.reel,
4023
4046
  posIndex: s.row
4024
- })),
4047
+ });
4048
+ }
4049
+ finalLine = {
4050
+ kind: lineLength,
4051
+ baseSymbol,
4052
+ symbols,
4025
4053
  lineNumber: lineNum,
4026
- payout: wildLinePayout
4054
+ payout: linePayout
4027
4055
  };
4028
4056
  }
4029
4057
  lineWins.push(finalLine);
4030
- }
4031
- for (const win of lineWins) {
4032
4058
  this.ctx.services.data.recordSymbolOccurrence({
4033
- kind: win.kind,
4034
- symbolId: win.baseSymbol.id,
4059
+ kind: finalLine.kind,
4060
+ symbolId: finalLine.baseSymbol.id,
4035
4061
  spinType: this.ctx.state.currentSpinType
4036
4062
  });
4037
4063
  }
4038
- this.payout = lineWins.reduce((sum, l) => sum + l.payout, 0);
4064
+ let totalPayout = 0;
4065
+ for (let i = 0; i < lineWins.length; i++) {
4066
+ totalPayout += lineWins[i].payout;
4067
+ }
4068
+ this.payout = totalPayout;
4039
4069
  this.winCombinations = lineWins;
4040
4070
  return this;
4041
4071
  }
4042
4072
  getLinePayout(line) {
4043
- if (line.length === 0) return 0;
4044
- let baseSymbol = line.find((s) => !this.isWild(s.symbol))?.symbol;
4073
+ const lineLength = line.length;
4074
+ if (lineLength === 0) return 0;
4075
+ let baseSymbol;
4076
+ for (let i = 0; i < lineLength; i++) {
4077
+ const s = line[i];
4078
+ if (!this.isWild(s.symbol)) {
4079
+ baseSymbol = s.symbol;
4080
+ break;
4081
+ }
4082
+ }
4045
4083
  if (!baseSymbol) baseSymbol = line[0].symbol;
4046
- const kind = line.length;
4047
- const payout = this.getSymbolPayout(baseSymbol, kind);
4048
- return payout;
4084
+ return this.getSymbolPayout(baseSymbol, lineLength);
4049
4085
  }
4050
4086
  };
4051
4087
 
4052
4088
  // src/win-types/ClusterWinType.ts
4053
4089
  var ClusterWinType = class extends WinType {
4054
- _checked = [];
4055
- _checkedWilds = [];
4090
+ _checked = /* @__PURE__ */ new Set();
4091
+ _checkedWilds = /* @__PURE__ */ new Set();
4056
4092
  _currentBoard = [];
4057
4093
  constructor(opts) {
4058
4094
  super(opts);
@@ -4065,121 +4101,169 @@ var ClusterWinType = class extends WinType {
4065
4101
  */
4066
4102
  evaluateWins(board) {
4067
4103
  this.validateConfig();
4068
- this._checked = [];
4104
+ this._checked.clear();
4069
4105
  this._currentBoard = board;
4070
4106
  const clusterWins = [];
4071
4107
  const potentialClusters = [];
4072
- for (const [ridx, reel] of board.entries()) {
4073
- for (const [sidx, symbol] of reel.entries()) {
4074
- this._checkedWilds = [];
4108
+ const boardLength = board.length;
4109
+ for (let ridx = 0; ridx < boardLength; ridx++) {
4110
+ const reel = board[ridx];
4111
+ const reelLength = reel.length;
4112
+ for (let sidx = 0; sidx < reelLength; sidx++) {
4113
+ const symbol = reel[sidx];
4114
+ this._checkedWilds.clear();
4075
4115
  if (this.isWild(symbol)) continue;
4076
- if (this.isChecked(ridx, sidx)) {
4116
+ const posKey = ridx * 1e4 + sidx;
4117
+ if (this._checked.has(posKey)) {
4077
4118
  continue;
4078
4119
  }
4079
4120
  const thisSymbol = { reel: ridx, row: sidx, symbol };
4080
- this._checked.push(thisSymbol);
4121
+ this._checked.add(posKey);
4081
4122
  const neighbors = this.getNeighbors(ridx, sidx);
4082
4123
  const matchingSymbols = this.evaluateCluster(symbol, neighbors);
4083
- if (matchingSymbols.size >= 1) {
4084
- potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
4124
+ const matchingSize = matchingSymbols.size;
4125
+ if (matchingSize >= 1) {
4126
+ const cluster = [thisSymbol];
4127
+ for (const sym of matchingSymbols.values()) {
4128
+ cluster.push(sym);
4129
+ }
4130
+ potentialClusters.push(cluster);
4085
4131
  }
4086
4132
  }
4087
4133
  }
4088
- for (const [ridx, reel] of board.entries()) {
4089
- for (const [sidx, symbol] of reel.entries()) {
4090
- this._checkedWilds = [];
4134
+ for (let ridx = 0; ridx < boardLength; ridx++) {
4135
+ const reel = board[ridx];
4136
+ const reelLength = reel.length;
4137
+ for (let sidx = 0; sidx < reelLength; sidx++) {
4138
+ const symbol = reel[sidx];
4139
+ this._checkedWilds.clear();
4091
4140
  if (!this.isWild(symbol)) continue;
4092
- if (this.isChecked(ridx, sidx)) {
4141
+ const posKey = ridx * 1e4 + sidx;
4142
+ if (this._checked.has(posKey)) {
4093
4143
  continue;
4094
4144
  }
4095
4145
  const thisSymbol = { reel: ridx, row: sidx, symbol };
4096
- this._checked.push(thisSymbol);
4146
+ this._checked.add(posKey);
4097
4147
  const neighbors = this.getNeighbors(ridx, sidx);
4098
4148
  const matchingSymbols = this.evaluateCluster(symbol, neighbors);
4099
- if (matchingSymbols.size >= 1) {
4100
- potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
4149
+ const matchingSize = matchingSymbols.size;
4150
+ if (matchingSize >= 1) {
4151
+ const cluster = [thisSymbol];
4152
+ for (const sym of matchingSymbols.values()) {
4153
+ cluster.push(sym);
4154
+ }
4155
+ potentialClusters.push(cluster);
4101
4156
  }
4102
4157
  }
4103
4158
  }
4104
- for (const cluster of potentialClusters) {
4159
+ const numClusters = potentialClusters.length;
4160
+ for (let i = 0; i < numClusters; i++) {
4161
+ const cluster = potentialClusters[i];
4105
4162
  const kind = cluster.length;
4106
- let baseSymbol = cluster.find((s) => !this.isWild(s.symbol))?.symbol;
4163
+ let baseSymbol;
4164
+ for (let j = 0; j < kind; j++) {
4165
+ const sym = cluster[j].symbol;
4166
+ if (!this.isWild(sym)) {
4167
+ baseSymbol = sym;
4168
+ break;
4169
+ }
4170
+ }
4107
4171
  if (!baseSymbol) baseSymbol = cluster[0].symbol;
4108
4172
  const payout = this.getSymbolPayout(baseSymbol, kind);
4109
4173
  if (payout === 0) continue;
4110
- if (!baseSymbol.pays || Object.keys(baseSymbol.pays).length === 0) {
4111
- continue;
4174
+ const pays = baseSymbol.pays;
4175
+ if (!pays) continue;
4176
+ let hasPays = false;
4177
+ for (const _ in pays) {
4178
+ hasPays = true;
4179
+ break;
4112
4180
  }
4113
- clusterWins.push({
4114
- payout,
4115
- kind,
4116
- baseSymbol,
4117
- symbols: cluster.map((s) => ({
4181
+ if (!hasPays) continue;
4182
+ const symbols = [];
4183
+ for (let j = 0; j < kind; j++) {
4184
+ const s = cluster[j];
4185
+ symbols.push({
4118
4186
  symbol: s.symbol,
4119
4187
  isWild: this.isWild(s.symbol),
4120
4188
  reelIndex: s.reel,
4121
4189
  posIndex: s.row
4122
- }))
4190
+ });
4191
+ }
4192
+ clusterWins.push({
4193
+ payout,
4194
+ kind,
4195
+ baseSymbol,
4196
+ symbols
4123
4197
  });
4124
- }
4125
- for (const win of clusterWins) {
4126
4198
  this.ctx.services.data.recordSymbolOccurrence({
4127
- kind: win.kind,
4128
- symbolId: win.baseSymbol.id,
4199
+ kind,
4200
+ symbolId: baseSymbol.id,
4129
4201
  spinType: this.ctx.state.currentSpinType
4130
4202
  });
4131
4203
  }
4132
- this.payout = clusterWins.reduce((sum, c) => sum + c.payout, 0);
4204
+ let totalPayout = 0;
4205
+ for (let i = 0; i < clusterWins.length; i++) {
4206
+ totalPayout += clusterWins[i].payout;
4207
+ }
4208
+ this.payout = totalPayout;
4133
4209
  this.winCombinations = clusterWins;
4134
4210
  return this;
4135
4211
  }
4136
4212
  getNeighbors(ridx, sidx) {
4137
4213
  const board = this._currentBoard;
4138
4214
  const neighbors = [];
4139
- const potentialNeighbors = [
4140
- [ridx - 1, sidx],
4141
- [ridx + 1, sidx],
4142
- [ridx, sidx - 1],
4143
- [ridx, sidx + 1]
4144
- ];
4145
- potentialNeighbors.forEach(([nridx, nsidx]) => {
4146
- if (board[nridx] && board[nridx][nsidx]) {
4147
- neighbors.push({ reel: nridx, row: nsidx, symbol: board[nridx][nsidx] });
4215
+ if (ridx > 0) {
4216
+ const leftReel = board[ridx - 1];
4217
+ const leftSymbol = leftReel[sidx];
4218
+ if (leftSymbol !== void 0) {
4219
+ neighbors.push({ reel: ridx - 1, row: sidx, symbol: leftSymbol });
4148
4220
  }
4149
- });
4221
+ }
4222
+ const rightReel = board[ridx + 1];
4223
+ if (rightReel !== void 0) {
4224
+ const rightSymbol = rightReel[sidx];
4225
+ if (rightSymbol !== void 0) {
4226
+ neighbors.push({ reel: ridx + 1, row: sidx, symbol: rightSymbol });
4227
+ }
4228
+ }
4229
+ const currentReel = board[ridx];
4230
+ const topSymbol = currentReel[sidx - 1];
4231
+ if (topSymbol !== void 0) {
4232
+ neighbors.push({ reel: ridx, row: sidx - 1, symbol: topSymbol });
4233
+ }
4234
+ const bottomSymbol = currentReel[sidx + 1];
4235
+ if (bottomSymbol !== void 0) {
4236
+ neighbors.push({ reel: ridx, row: sidx + 1, symbol: bottomSymbol });
4237
+ }
4150
4238
  return neighbors;
4151
4239
  }
4152
4240
  evaluateCluster(rootSymbol, neighbors) {
4153
4241
  const matchingSymbols = /* @__PURE__ */ new Map();
4154
- neighbors.forEach((neighbor) => {
4242
+ const numNeighbors = neighbors.length;
4243
+ for (let i = 0; i < numNeighbors; i++) {
4244
+ const neighbor = neighbors[i];
4155
4245
  const { reel, row, symbol } = neighbor;
4156
- if (this.isChecked(reel, row)) return;
4157
- if (this.isCheckedWild(reel, row)) return;
4246
+ const posKey = reel * 1e4 + row;
4247
+ if (this._checked.has(posKey)) continue;
4248
+ if (this._checkedWilds.has(posKey)) continue;
4158
4249
  if (this.isWild(symbol) || symbol.compare(rootSymbol)) {
4159
- const key = `${reel}-${row}`;
4250
+ const key = String(posKey);
4160
4251
  matchingSymbols.set(key, { reel, row, symbol });
4161
4252
  if (symbol.compare(rootSymbol)) {
4162
- this._checked.push(neighbor);
4253
+ this._checked.add(posKey);
4163
4254
  }
4164
4255
  if (this.isWild(symbol)) {
4165
- this._checkedWilds.push(neighbor);
4256
+ this._checkedWilds.add(posKey);
4166
4257
  }
4167
- const neighbors2 = this.getNeighbors(reel, row);
4168
- const nestedMatches = this.evaluateCluster(rootSymbol, neighbors2);
4169
- nestedMatches.forEach((nsym) => {
4170
- const nkey = `${nsym.reel}-${nsym.row}`;
4258
+ const nestedNeighbors = this.getNeighbors(reel, row);
4259
+ const nestedMatches = this.evaluateCluster(rootSymbol, nestedNeighbors);
4260
+ for (const [nkey, nsym] of nestedMatches.entries()) {
4171
4261
  matchingSymbols.set(nkey, nsym);
4172
- });
4262
+ }
4173
4263
  }
4174
- });
4264
+ }
4175
4265
  return matchingSymbols;
4176
4266
  }
4177
- isChecked(ridx, sidx) {
4178
- return !!this._checked.find((c) => c.reel === ridx && c.row === sidx);
4179
- }
4180
- isCheckedWild(ridx, sidx) {
4181
- return !!this._checkedWilds.find((c) => c.reel === ridx && c.row === sidx);
4182
- }
4183
4267
  };
4184
4268
 
4185
4269
  // src/win-types/ManywaysWinType.ts
@@ -4198,7 +4282,7 @@ var ManywaysWinType = class extends WinType {
4198
4282
  const { jumpGaps = false } = opts;
4199
4283
  const waysWins = [];
4200
4284
  const reels = board;
4201
- const possibleWaysWins = /* @__PURE__ */ new Map();
4285
+ const numReels = reels.length;
4202
4286
  const candidateSymbols = /* @__PURE__ */ new Map();
4203
4287
  if (jumpGaps) {
4204
4288
  for (const reel of reels) {
@@ -4209,7 +4293,7 @@ var ManywaysWinType = class extends WinType {
4209
4293
  } else {
4210
4294
  let searchReelIdx = 0;
4211
4295
  let searchActive = true;
4212
- while (searchActive && searchReelIdx < reels.length) {
4296
+ while (searchActive && searchReelIdx < numReels) {
4213
4297
  const reel = reels[searchReelIdx];
4214
4298
  let hasWild = false;
4215
4299
  for (const symbol of reel) {
@@ -4225,71 +4309,78 @@ var ManywaysWinType = class extends WinType {
4225
4309
  }
4226
4310
  }
4227
4311
  for (const baseSymbol of candidateSymbols.values()) {
4228
- let symbolList = {};
4229
- let isInterrupted = false;
4230
- for (const [ridx, reel] of reels.entries()) {
4231
- if (isInterrupted) break;
4232
- for (const [sidx, symbol] of reel.entries()) {
4312
+ const symbolList = [];
4313
+ let wayLength = 0;
4314
+ let firstNonWildSymbol;
4315
+ let totalWays = 1;
4316
+ for (let ridx = 0; ridx < numReels; ridx++) {
4317
+ const reel = reels[ridx];
4318
+ let reelMatches;
4319
+ for (let sidx = 0; sidx < reel.length; sidx++) {
4320
+ const symbol = reel[sidx];
4233
4321
  const isMatch = baseSymbol.compare(symbol) || this.isWild(symbol);
4234
4322
  if (isMatch) {
4235
- if (!symbolList[ridx]) {
4236
- symbolList[ridx] = [];
4323
+ if (!reelMatches) {
4324
+ reelMatches = [];
4325
+ }
4326
+ reelMatches.push({ reel: ridx, row: sidx, symbol });
4327
+ if (!firstNonWildSymbol && !this.isWild(symbol)) {
4328
+ firstNonWildSymbol = symbol;
4237
4329
  }
4238
- symbolList[ridx].push({ reel: ridx, row: sidx, symbol });
4239
4330
  }
4240
4331
  }
4241
- if (!symbolList[ridx] && !jumpGaps) {
4242
- isInterrupted = true;
4332
+ if (reelMatches) {
4333
+ symbolList[wayLength++] = reelMatches;
4334
+ totalWays *= reelMatches.length;
4335
+ } else if (!jumpGaps) {
4243
4336
  break;
4244
4337
  }
4245
4338
  }
4246
- const minSymLine = Math.min(
4247
- ...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
4248
- );
4249
- const wayLength = this.getWayLength(symbolList);
4339
+ const pays = baseSymbol.pays || {};
4340
+ let minSymLine = Infinity;
4341
+ for (const key in pays) {
4342
+ const num = parseInt(key, 10);
4343
+ if (num < minSymLine) minSymLine = num;
4344
+ }
4250
4345
  if (wayLength >= minSymLine) {
4251
- possibleWaysWins.set(baseSymbol.id, symbolList);
4346
+ const winBaseSymbol = firstNonWildSymbol || symbolList[0][0].symbol;
4347
+ const singleWayPayout = this.getSymbolPayout(winBaseSymbol, wayLength);
4348
+ const totalPayout2 = singleWayPayout * totalWays;
4349
+ const symbols = [];
4350
+ for (let i = 0; i < wayLength; i++) {
4351
+ const reelSyms = symbolList[i];
4352
+ for (let j = 0; j < reelSyms.length; j++) {
4353
+ const s = reelSyms[j];
4354
+ symbols.push({
4355
+ symbol: s.symbol,
4356
+ isWild: this.isWild(s.symbol),
4357
+ reelIndex: s.reel,
4358
+ posIndex: s.row
4359
+ });
4360
+ }
4361
+ }
4362
+ waysWins.push({
4363
+ kind: wayLength,
4364
+ baseSymbol: winBaseSymbol,
4365
+ symbols,
4366
+ ways: totalWays,
4367
+ payout: totalPayout2
4368
+ });
4369
+ this.ctx.services.data.recordSymbolOccurrence({
4370
+ kind: wayLength,
4371
+ symbolId: winBaseSymbol.id,
4372
+ spinType: this.ctx.state.currentSpinType
4373
+ });
4252
4374
  }
4253
4375
  }
4254
- for (const [baseSymbolId, symbolList] of possibleWaysWins.entries()) {
4255
- const wayLength = this.getWayLength(symbolList);
4256
- let baseSymbol = Object.values(symbolList).flatMap((l) => l.map((s) => s)).find((s) => !this.isWild(s.symbol))?.symbol;
4257
- if (!baseSymbol) baseSymbol = symbolList[Object.keys(symbolList)[0]][0].symbol;
4258
- const singleWayPayout = this.getSymbolPayout(baseSymbol, wayLength);
4259
- const totalWays = Object.values(symbolList).reduce(
4260
- (ways, syms) => ways * syms.length,
4261
- 1
4262
- );
4263
- const totalPayout = singleWayPayout * totalWays;
4264
- waysWins.push({
4265
- kind: wayLength,
4266
- baseSymbol,
4267
- symbols: Object.values(symbolList).flatMap(
4268
- (reel) => reel.map((s) => ({
4269
- symbol: s.symbol,
4270
- isWild: this.isWild(s.symbol),
4271
- reelIndex: s.reel,
4272
- posIndex: s.row
4273
- }))
4274
- ),
4275
- ways: totalWays,
4276
- payout: totalPayout
4277
- });
4376
+ let totalPayout = 0;
4377
+ for (let i = 0; i < waysWins.length; i++) {
4378
+ totalPayout += waysWins[i].payout;
4278
4379
  }
4279
- for (const win of waysWins) {
4280
- this.ctx.services.data.recordSymbolOccurrence({
4281
- kind: win.kind,
4282
- symbolId: win.baseSymbol.id,
4283
- spinType: this.ctx.state.currentSpinType
4284
- });
4285
- }
4286
- this.payout = waysWins.reduce((sum, l) => sum + l.payout, 0);
4380
+ this.payout = totalPayout;
4287
4381
  this.winCombinations = waysWins;
4288
4382
  return this;
4289
4383
  }
4290
- getWayLength(symbolList) {
4291
- return Object.keys(symbolList).length;
4292
- }
4293
4384
  };
4294
4385
 
4295
4386
  // src/reel-set/GeneratedReelSet.ts