@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.js CHANGED
@@ -238,15 +238,14 @@ var RandomNumberGenerator = class {
238
238
  return float * (high - low) + low;
239
239
  }
240
240
  weightedRandom(weights) {
241
- const totalWeight = Object.values(weights).reduce(
242
- (sum, weight) => sum + weight,
243
- 0
244
- );
245
- const randomValue = this.randomFloat(0, 1) * totalWeight;
246
- let cumulativeWeight = 0;
247
- for (const [key, weight] of Object.entries(weights)) {
248
- cumulativeWeight += weight;
249
- if (randomValue < cumulativeWeight) {
241
+ let totalWeight = 0;
242
+ for (const key in weights) {
243
+ totalWeight += weights[key];
244
+ }
245
+ let remaining = this.randomFloat(0, 1) * totalWeight;
246
+ for (const key in weights) {
247
+ remaining -= weights[key];
248
+ if (remaining < 0) {
250
249
  return key;
251
250
  }
252
251
  }
@@ -421,7 +420,10 @@ var GameSymbol = class _GameSymbol {
421
420
  constructor(opts) {
422
421
  this.id = opts.id;
423
422
  this.pays = opts.pays;
424
- this.properties = new Map(Object.entries(opts.properties || {}));
423
+ this.properties = /* @__PURE__ */ new Map();
424
+ for (const prop in opts.properties) {
425
+ this.properties.set(prop, opts.properties[prop]);
426
+ }
425
427
  if (this.pays && Object.keys(this.pays).length === 0) {
426
428
  throw new Error(`GameSymbol "${this.id}" must have pays defined.`);
427
429
  }
@@ -437,8 +439,8 @@ var GameSymbol = class _GameSymbol {
437
439
  if (symbolOrProperties instanceof _GameSymbol) {
438
440
  return this.id === symbolOrProperties.id;
439
441
  } else {
440
- for (const [key, value] of Object.entries(symbolOrProperties)) {
441
- if (!this.properties.has(key) || this.properties.get(key) !== value) {
442
+ for (const prop in symbolOrProperties) {
443
+ if (!this.properties.has(prop) || this.properties.get(prop) !== symbolOrProperties[prop]) {
442
444
  return false;
443
445
  }
444
446
  }
@@ -518,8 +520,8 @@ var Board = class {
518
520
  updateSymbol(reelIndex, rowIndex, properties) {
519
521
  const symbol = this.getSymbol(reelIndex, rowIndex);
520
522
  if (symbol) {
521
- for (const [key, value] of Object.entries(properties)) {
522
- symbol.properties.set(key, value);
523
+ for (const prop in properties) {
524
+ symbol.properties.set(prop, properties[prop]);
523
525
  }
524
526
  }
525
527
  }
@@ -535,8 +537,8 @@ var Board = class {
535
537
  if (symbolOrProperties instanceof GameSymbol) {
536
538
  if (symbol.id !== symbolOrProperties.id) matches = false;
537
539
  } else {
538
- for (const [key, value] of Object.entries(symbolOrProperties)) {
539
- if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {
540
+ for (const prop in symbolOrProperties) {
541
+ if (!symbol.properties.has(prop) || symbol.properties.get(prop) !== symbolOrProperties[prop]) {
540
542
  matches = false;
541
543
  break;
542
544
  }
@@ -551,27 +553,23 @@ var Board = class {
551
553
  countSymbolsOnBoard(symbolOrProperties) {
552
554
  let total = 0;
553
555
  const onReel = {};
556
+ const isGameSymbol = symbolOrProperties instanceof GameSymbol;
554
557
  for (const [ridx, reel] of this.reels.entries()) {
555
558
  for (const symbol of reel) {
556
- let matches = true;
557
- if (symbolOrProperties instanceof GameSymbol) {
558
- if (symbol.id !== symbolOrProperties.id) matches = false;
559
+ if (isGameSymbol) {
560
+ if (symbol.id !== symbolOrProperties.id) continue;
559
561
  } else {
560
- for (const [key, value] of Object.entries(symbolOrProperties)) {
561
- if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {
562
+ let matches = true;
563
+ for (const prop in symbolOrProperties) {
564
+ if (!symbol.properties.has(prop) || symbol.properties.get(prop) !== symbolOrProperties[prop]) {
562
565
  matches = false;
563
566
  break;
564
567
  }
565
568
  }
569
+ if (!matches) continue;
566
570
  }
567
- if (matches) {
568
- total++;
569
- if (onReel[ridx] === void 0) {
570
- onReel[ridx] = 1;
571
- } else {
572
- onReel[ridx]++;
573
- }
574
- }
571
+ total++;
572
+ onReel[ridx] = (onReel[ridx] || 0) + 1;
575
573
  }
576
574
  }
577
575
  return [total, onReel];
@@ -670,8 +668,9 @@ var Board = class {
670
668
  ...opts,
671
669
  ...this.reelsLocked.length && { reelsLocked: this.reelsLocked }
672
670
  });
673
- const reelsAmount = opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount;
674
- const symbolsPerReel = opts.symbolsPerReel ?? opts.ctx.services.game.getCurrentGameMode().symbolsPerReel;
671
+ const gameMode = opts.ctx.services.game.getCurrentGameMode();
672
+ const reelsAmount = opts.reelsAmount ?? gameMode.reelsAmount;
673
+ const symbolsPerReel = opts.symbolsPerReel ?? gameMode.symbolsPerReel;
675
674
  const padSymbols = opts.padSymbols ?? opts.ctx.config.padSymbols;
676
675
  const finalReelStops = Array.from(
677
676
  { length: reelsAmount },
@@ -714,15 +713,16 @@ var Board = class {
714
713
  this.lastUsedReels = opts.reels;
715
714
  for (let ridx = 0; ridx < reelsAmount; ridx++) {
716
715
  const reelPos = finalReelStops[ridx];
717
- const reelLength = opts.reels[ridx].length;
716
+ const reel = opts.reels[ridx];
717
+ const reelLength = reel.length;
718
718
  for (let p = padSymbols - 1; p >= 0; p--) {
719
719
  const topPos = ((reelPos - (p + 1)) % reelLength + reelLength) % reelLength;
720
- this.paddingTop[ridx].push(opts.reels[ridx][topPos].clone());
720
+ this.paddingTop[ridx].push(reel[topPos].clone());
721
721
  const bottomPos = (reelPos + symbolsPerReel[ridx] + p) % reelLength;
722
- this.paddingBottom[ridx].unshift(opts.reels[ridx][bottomPos].clone());
722
+ this.paddingBottom[ridx].unshift(reel[bottomPos].clone());
723
723
  }
724
724
  for (let row = 0; row < symbolsPerReel[ridx]; row++) {
725
- const symbol = opts.reels[ridx][(reelPos + row) % reelLength];
725
+ const symbol = reel[(reelPos + row) % reelLength];
726
726
  if (!symbol) {
727
727
  throw new Error(`Failed to get symbol at pos ${reelPos + row} on reel ${ridx}`);
728
728
  }
@@ -738,8 +738,9 @@ var Board = class {
738
738
  }
739
739
  tumbleBoard(opts) {
740
740
  (0, import_assert3.default)(this.lastDrawnReelStops.length > 0, "Cannot tumble board before drawing it.");
741
- const reelsAmount = opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount;
742
- const symbolsPerReel = opts.symbolsPerReel ?? opts.ctx.services.game.getCurrentGameMode().symbolsPerReel;
741
+ const gameMode = opts.ctx.services.game.getCurrentGameMode();
742
+ const reelsAmount = opts.reelsAmount ?? gameMode.reelsAmount;
743
+ const symbolsPerReel = opts.symbolsPerReel ?? gameMode.symbolsPerReel;
743
744
  const padSymbols = opts.padSymbols ?? opts.ctx.config.padSymbols;
744
745
  if (opts.startingStops) {
745
746
  (0, import_assert3.default)(
@@ -785,11 +786,12 @@ var Board = class {
785
786
  const stopBeforePad = previousStop - padSymbols - 1;
786
787
  const symbolsNeeded = symbolsPerReel[ridx] - this.reels[ridx].length;
787
788
  for (let s = 0; s < symbolsNeeded; s++) {
788
- const symbolPos = (stopBeforePad - s + reels[ridx].length) % reels[ridx].length;
789
- let newSymbol = reels[ridx][symbolPos];
789
+ const reel = reels[ridx];
790
+ const symbolPos = (stopBeforePad - s + reel.length) % reel.length;
791
+ let newSymbol = reel[symbolPos];
790
792
  const startStops = opts.startingStops;
791
793
  if (startStops) {
792
- const forcedSym = reels[ridx][startStops?.[ridx]];
794
+ const forcedSym = reel[startStops?.[ridx]];
793
795
  (0, import_assert3.default)(
794
796
  forcedSym,
795
797
  `Failed to get forced symbol for tumbling. Tried to get symbol for position ${startStops?.[ridx]} on reel ${ridx}.`
@@ -810,8 +812,9 @@ var Board = class {
810
812
  const firstSymbolPos = newFirstSymbolPositions[ridx];
811
813
  if (firstSymbolPos === void 0) continue;
812
814
  for (let p = 1; p <= padSymbols; p++) {
813
- const topPos = (firstSymbolPos - p + reels[ridx].length) % reels[ridx].length;
814
- const padSymbol = reels[ridx][topPos]?.clone();
815
+ const reel = reels[ridx];
816
+ const topPos = (firstSymbolPos - p + reel.length) % reel.length;
817
+ const padSymbol = reel[topPos]?.clone();
815
818
  (0, import_assert3.default)(padSymbol, "Failed to get new padding symbol for tumbling.");
816
819
  this.paddingTop[ridx].unshift(padSymbol);
817
820
  if (!newPaddingTopSymbols[ridx]) {
@@ -1105,11 +1108,13 @@ var DataService = class extends AbstractService {
1105
1108
  * Record data for statistical analysis.
1106
1109
  */
1107
1110
  record(data) {
1111
+ const properties = {};
1112
+ for (const key in data) {
1113
+ properties[key] = String(data[key]);
1114
+ }
1108
1115
  this.recorder.pendingRecords.push({
1109
1116
  bookId: this.ctx().state.currentSimulationId,
1110
- properties: Object.fromEntries(
1111
- Object.entries(data).map(([k, v]) => [k, String(v)])
1112
- )
1117
+ properties
1113
1118
  });
1114
1119
  }
1115
1120
  /**
@@ -4005,13 +4010,19 @@ var LinesWinType = class extends WinType {
4005
4010
  this.validateConfig();
4006
4011
  const lineWins = [];
4007
4012
  const reels = board;
4008
- for (const [lineNumStr, line] of Object.entries(this.lines)) {
4013
+ const reelsLength = reels.length;
4014
+ const lineNumbers = Object.keys(this.lines);
4015
+ const numLines = lineNumbers.length;
4016
+ for (let lidx = 0; lidx < numLines; lidx++) {
4017
+ const lineNumStr = lineNumbers[lidx];
4009
4018
  const lineNum = Number(lineNumStr);
4019
+ const line = this.lines[lineNum];
4010
4020
  let baseSymbol;
4011
4021
  const potentialWinLine = [];
4012
4022
  const potentialWildLine = [];
4013
4023
  let isInterrupted = false;
4014
- for (const [ridx, reel] of reels.entries()) {
4024
+ for (let ridx = 0; ridx < reelsLength; ridx++) {
4025
+ const reel = reels[ridx];
4015
4026
  const sidx = line[ridx];
4016
4027
  const thisSymbol = reel[sidx];
4017
4028
  if (!baseSymbol) {
@@ -4042,66 +4053,91 @@ var LinesWinType = class extends WinType {
4042
4053
  break;
4043
4054
  }
4044
4055
  }
4045
- const minSymLine = Math.min(
4046
- ...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
4047
- );
4056
+ const pays = baseSymbol.pays || {};
4057
+ let minSymLine = Infinity;
4058
+ for (const key in pays) {
4059
+ const num = parseInt(key, 10);
4060
+ if (num < minSymLine) minSymLine = num;
4061
+ }
4048
4062
  if (potentialWinLine.length < minSymLine) continue;
4049
4063
  const linePayout = this.getLinePayout(potentialWinLine);
4050
4064
  const wildLinePayout = this.getLinePayout(potentialWildLine);
4051
- let finalLine = {
4052
- kind: potentialWinLine.length,
4053
- baseSymbol,
4054
- symbols: potentialWinLine.map((s) => ({
4055
- symbol: s.symbol,
4056
- isWild: this.isWild(s.symbol),
4057
- reelIndex: s.reel,
4058
- posIndex: s.row
4059
- })),
4060
- lineNumber: lineNum,
4061
- payout: linePayout
4062
- };
4065
+ let finalLine;
4063
4066
  if (wildLinePayout > linePayout) {
4064
4067
  baseSymbol = potentialWildLine[0]?.symbol;
4068
+ const wildSymbols = [];
4069
+ const wildLineLength = potentialWildLine.length;
4070
+ for (let i = 0; i < wildLineLength; i++) {
4071
+ const s = potentialWildLine[i];
4072
+ wildSymbols.push({
4073
+ symbol: s.symbol,
4074
+ isWild: this.isWild(s.symbol),
4075
+ reelIndex: s.reel,
4076
+ posIndex: s.row
4077
+ });
4078
+ }
4065
4079
  finalLine = {
4066
- kind: potentialWildLine.length,
4080
+ kind: wildLineLength,
4067
4081
  baseSymbol,
4068
- symbols: potentialWildLine.map((s) => ({
4082
+ symbols: wildSymbols,
4083
+ lineNumber: lineNum,
4084
+ payout: wildLinePayout
4085
+ };
4086
+ } else {
4087
+ const symbols = [];
4088
+ const lineLength = potentialWinLine.length;
4089
+ for (let i = 0; i < lineLength; i++) {
4090
+ const s = potentialWinLine[i];
4091
+ symbols.push({
4069
4092
  symbol: s.symbol,
4070
4093
  isWild: this.isWild(s.symbol),
4071
4094
  reelIndex: s.reel,
4072
4095
  posIndex: s.row
4073
- })),
4096
+ });
4097
+ }
4098
+ finalLine = {
4099
+ kind: lineLength,
4100
+ baseSymbol,
4101
+ symbols,
4074
4102
  lineNumber: lineNum,
4075
- payout: wildLinePayout
4103
+ payout: linePayout
4076
4104
  };
4077
4105
  }
4078
4106
  lineWins.push(finalLine);
4079
- }
4080
- for (const win of lineWins) {
4081
4107
  this.ctx.services.data.recordSymbolOccurrence({
4082
- kind: win.kind,
4083
- symbolId: win.baseSymbol.id,
4108
+ kind: finalLine.kind,
4109
+ symbolId: finalLine.baseSymbol.id,
4084
4110
  spinType: this.ctx.state.currentSpinType
4085
4111
  });
4086
4112
  }
4087
- this.payout = lineWins.reduce((sum, l) => sum + l.payout, 0);
4113
+ let totalPayout = 0;
4114
+ for (let i = 0; i < lineWins.length; i++) {
4115
+ totalPayout += lineWins[i].payout;
4116
+ }
4117
+ this.payout = totalPayout;
4088
4118
  this.winCombinations = lineWins;
4089
4119
  return this;
4090
4120
  }
4091
4121
  getLinePayout(line) {
4092
- if (line.length === 0) return 0;
4093
- let baseSymbol = line.find((s) => !this.isWild(s.symbol))?.symbol;
4122
+ const lineLength = line.length;
4123
+ if (lineLength === 0) return 0;
4124
+ let baseSymbol;
4125
+ for (let i = 0; i < lineLength; i++) {
4126
+ const s = line[i];
4127
+ if (!this.isWild(s.symbol)) {
4128
+ baseSymbol = s.symbol;
4129
+ break;
4130
+ }
4131
+ }
4094
4132
  if (!baseSymbol) baseSymbol = line[0].symbol;
4095
- const kind = line.length;
4096
- const payout = this.getSymbolPayout(baseSymbol, kind);
4097
- return payout;
4133
+ return this.getSymbolPayout(baseSymbol, lineLength);
4098
4134
  }
4099
4135
  };
4100
4136
 
4101
4137
  // src/win-types/ClusterWinType.ts
4102
4138
  var ClusterWinType = class extends WinType {
4103
- _checked = [];
4104
- _checkedWilds = [];
4139
+ _checked = /* @__PURE__ */ new Set();
4140
+ _checkedWilds = /* @__PURE__ */ new Set();
4105
4141
  _currentBoard = [];
4106
4142
  constructor(opts) {
4107
4143
  super(opts);
@@ -4114,121 +4150,169 @@ var ClusterWinType = class extends WinType {
4114
4150
  */
4115
4151
  evaluateWins(board) {
4116
4152
  this.validateConfig();
4117
- this._checked = [];
4153
+ this._checked.clear();
4118
4154
  this._currentBoard = board;
4119
4155
  const clusterWins = [];
4120
4156
  const potentialClusters = [];
4121
- for (const [ridx, reel] of board.entries()) {
4122
- for (const [sidx, symbol] of reel.entries()) {
4123
- this._checkedWilds = [];
4157
+ const boardLength = board.length;
4158
+ for (let ridx = 0; ridx < boardLength; ridx++) {
4159
+ const reel = board[ridx];
4160
+ const reelLength = reel.length;
4161
+ for (let sidx = 0; sidx < reelLength; sidx++) {
4162
+ const symbol = reel[sidx];
4163
+ this._checkedWilds.clear();
4124
4164
  if (this.isWild(symbol)) continue;
4125
- if (this.isChecked(ridx, sidx)) {
4165
+ const posKey = ridx * 1e4 + sidx;
4166
+ if (this._checked.has(posKey)) {
4126
4167
  continue;
4127
4168
  }
4128
4169
  const thisSymbol = { reel: ridx, row: sidx, symbol };
4129
- this._checked.push(thisSymbol);
4170
+ this._checked.add(posKey);
4130
4171
  const neighbors = this.getNeighbors(ridx, sidx);
4131
4172
  const matchingSymbols = this.evaluateCluster(symbol, neighbors);
4132
- if (matchingSymbols.size >= 1) {
4133
- potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
4173
+ const matchingSize = matchingSymbols.size;
4174
+ if (matchingSize >= 1) {
4175
+ const cluster = [thisSymbol];
4176
+ for (const sym of matchingSymbols.values()) {
4177
+ cluster.push(sym);
4178
+ }
4179
+ potentialClusters.push(cluster);
4134
4180
  }
4135
4181
  }
4136
4182
  }
4137
- for (const [ridx, reel] of board.entries()) {
4138
- for (const [sidx, symbol] of reel.entries()) {
4139
- this._checkedWilds = [];
4183
+ for (let ridx = 0; ridx < boardLength; ridx++) {
4184
+ const reel = board[ridx];
4185
+ const reelLength = reel.length;
4186
+ for (let sidx = 0; sidx < reelLength; sidx++) {
4187
+ const symbol = reel[sidx];
4188
+ this._checkedWilds.clear();
4140
4189
  if (!this.isWild(symbol)) continue;
4141
- if (this.isChecked(ridx, sidx)) {
4190
+ const posKey = ridx * 1e4 + sidx;
4191
+ if (this._checked.has(posKey)) {
4142
4192
  continue;
4143
4193
  }
4144
4194
  const thisSymbol = { reel: ridx, row: sidx, symbol };
4145
- this._checked.push(thisSymbol);
4195
+ this._checked.add(posKey);
4146
4196
  const neighbors = this.getNeighbors(ridx, sidx);
4147
4197
  const matchingSymbols = this.evaluateCluster(symbol, neighbors);
4148
- if (matchingSymbols.size >= 1) {
4149
- potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
4198
+ const matchingSize = matchingSymbols.size;
4199
+ if (matchingSize >= 1) {
4200
+ const cluster = [thisSymbol];
4201
+ for (const sym of matchingSymbols.values()) {
4202
+ cluster.push(sym);
4203
+ }
4204
+ potentialClusters.push(cluster);
4150
4205
  }
4151
4206
  }
4152
4207
  }
4153
- for (const cluster of potentialClusters) {
4208
+ const numClusters = potentialClusters.length;
4209
+ for (let i = 0; i < numClusters; i++) {
4210
+ const cluster = potentialClusters[i];
4154
4211
  const kind = cluster.length;
4155
- let baseSymbol = cluster.find((s) => !this.isWild(s.symbol))?.symbol;
4212
+ let baseSymbol;
4213
+ for (let j = 0; j < kind; j++) {
4214
+ const sym = cluster[j].symbol;
4215
+ if (!this.isWild(sym)) {
4216
+ baseSymbol = sym;
4217
+ break;
4218
+ }
4219
+ }
4156
4220
  if (!baseSymbol) baseSymbol = cluster[0].symbol;
4157
4221
  const payout = this.getSymbolPayout(baseSymbol, kind);
4158
4222
  if (payout === 0) continue;
4159
- if (!baseSymbol.pays || Object.keys(baseSymbol.pays).length === 0) {
4160
- continue;
4223
+ const pays = baseSymbol.pays;
4224
+ if (!pays) continue;
4225
+ let hasPays = false;
4226
+ for (const _ in pays) {
4227
+ hasPays = true;
4228
+ break;
4161
4229
  }
4162
- clusterWins.push({
4163
- payout,
4164
- kind,
4165
- baseSymbol,
4166
- symbols: cluster.map((s) => ({
4230
+ if (!hasPays) continue;
4231
+ const symbols = [];
4232
+ for (let j = 0; j < kind; j++) {
4233
+ const s = cluster[j];
4234
+ symbols.push({
4167
4235
  symbol: s.symbol,
4168
4236
  isWild: this.isWild(s.symbol),
4169
4237
  reelIndex: s.reel,
4170
4238
  posIndex: s.row
4171
- }))
4239
+ });
4240
+ }
4241
+ clusterWins.push({
4242
+ payout,
4243
+ kind,
4244
+ baseSymbol,
4245
+ symbols
4172
4246
  });
4173
- }
4174
- for (const win of clusterWins) {
4175
4247
  this.ctx.services.data.recordSymbolOccurrence({
4176
- kind: win.kind,
4177
- symbolId: win.baseSymbol.id,
4248
+ kind,
4249
+ symbolId: baseSymbol.id,
4178
4250
  spinType: this.ctx.state.currentSpinType
4179
4251
  });
4180
4252
  }
4181
- this.payout = clusterWins.reduce((sum, c) => sum + c.payout, 0);
4253
+ let totalPayout = 0;
4254
+ for (let i = 0; i < clusterWins.length; i++) {
4255
+ totalPayout += clusterWins[i].payout;
4256
+ }
4257
+ this.payout = totalPayout;
4182
4258
  this.winCombinations = clusterWins;
4183
4259
  return this;
4184
4260
  }
4185
4261
  getNeighbors(ridx, sidx) {
4186
4262
  const board = this._currentBoard;
4187
4263
  const neighbors = [];
4188
- const potentialNeighbors = [
4189
- [ridx - 1, sidx],
4190
- [ridx + 1, sidx],
4191
- [ridx, sidx - 1],
4192
- [ridx, sidx + 1]
4193
- ];
4194
- potentialNeighbors.forEach(([nridx, nsidx]) => {
4195
- if (board[nridx] && board[nridx][nsidx]) {
4196
- neighbors.push({ reel: nridx, row: nsidx, symbol: board[nridx][nsidx] });
4264
+ if (ridx > 0) {
4265
+ const leftReel = board[ridx - 1];
4266
+ const leftSymbol = leftReel[sidx];
4267
+ if (leftSymbol !== void 0) {
4268
+ neighbors.push({ reel: ridx - 1, row: sidx, symbol: leftSymbol });
4197
4269
  }
4198
- });
4270
+ }
4271
+ const rightReel = board[ridx + 1];
4272
+ if (rightReel !== void 0) {
4273
+ const rightSymbol = rightReel[sidx];
4274
+ if (rightSymbol !== void 0) {
4275
+ neighbors.push({ reel: ridx + 1, row: sidx, symbol: rightSymbol });
4276
+ }
4277
+ }
4278
+ const currentReel = board[ridx];
4279
+ const topSymbol = currentReel[sidx - 1];
4280
+ if (topSymbol !== void 0) {
4281
+ neighbors.push({ reel: ridx, row: sidx - 1, symbol: topSymbol });
4282
+ }
4283
+ const bottomSymbol = currentReel[sidx + 1];
4284
+ if (bottomSymbol !== void 0) {
4285
+ neighbors.push({ reel: ridx, row: sidx + 1, symbol: bottomSymbol });
4286
+ }
4199
4287
  return neighbors;
4200
4288
  }
4201
4289
  evaluateCluster(rootSymbol, neighbors) {
4202
4290
  const matchingSymbols = /* @__PURE__ */ new Map();
4203
- neighbors.forEach((neighbor) => {
4291
+ const numNeighbors = neighbors.length;
4292
+ for (let i = 0; i < numNeighbors; i++) {
4293
+ const neighbor = neighbors[i];
4204
4294
  const { reel, row, symbol } = neighbor;
4205
- if (this.isChecked(reel, row)) return;
4206
- if (this.isCheckedWild(reel, row)) return;
4295
+ const posKey = reel * 1e4 + row;
4296
+ if (this._checked.has(posKey)) continue;
4297
+ if (this._checkedWilds.has(posKey)) continue;
4207
4298
  if (this.isWild(symbol) || symbol.compare(rootSymbol)) {
4208
- const key = `${reel}-${row}`;
4299
+ const key = String(posKey);
4209
4300
  matchingSymbols.set(key, { reel, row, symbol });
4210
4301
  if (symbol.compare(rootSymbol)) {
4211
- this._checked.push(neighbor);
4302
+ this._checked.add(posKey);
4212
4303
  }
4213
4304
  if (this.isWild(symbol)) {
4214
- this._checkedWilds.push(neighbor);
4305
+ this._checkedWilds.add(posKey);
4215
4306
  }
4216
- const neighbors2 = this.getNeighbors(reel, row);
4217
- const nestedMatches = this.evaluateCluster(rootSymbol, neighbors2);
4218
- nestedMatches.forEach((nsym) => {
4219
- const nkey = `${nsym.reel}-${nsym.row}`;
4307
+ const nestedNeighbors = this.getNeighbors(reel, row);
4308
+ const nestedMatches = this.evaluateCluster(rootSymbol, nestedNeighbors);
4309
+ for (const [nkey, nsym] of nestedMatches.entries()) {
4220
4310
  matchingSymbols.set(nkey, nsym);
4221
- });
4311
+ }
4222
4312
  }
4223
- });
4313
+ }
4224
4314
  return matchingSymbols;
4225
4315
  }
4226
- isChecked(ridx, sidx) {
4227
- return !!this._checked.find((c) => c.reel === ridx && c.row === sidx);
4228
- }
4229
- isCheckedWild(ridx, sidx) {
4230
- return !!this._checkedWilds.find((c) => c.reel === ridx && c.row === sidx);
4231
- }
4232
4316
  };
4233
4317
 
4234
4318
  // src/win-types/ManywaysWinType.ts
@@ -4247,7 +4331,7 @@ var ManywaysWinType = class extends WinType {
4247
4331
  const { jumpGaps = false } = opts;
4248
4332
  const waysWins = [];
4249
4333
  const reels = board;
4250
- const possibleWaysWins = /* @__PURE__ */ new Map();
4334
+ const numReels = reels.length;
4251
4335
  const candidateSymbols = /* @__PURE__ */ new Map();
4252
4336
  if (jumpGaps) {
4253
4337
  for (const reel of reels) {
@@ -4258,7 +4342,7 @@ var ManywaysWinType = class extends WinType {
4258
4342
  } else {
4259
4343
  let searchReelIdx = 0;
4260
4344
  let searchActive = true;
4261
- while (searchActive && searchReelIdx < reels.length) {
4345
+ while (searchActive && searchReelIdx < numReels) {
4262
4346
  const reel = reels[searchReelIdx];
4263
4347
  let hasWild = false;
4264
4348
  for (const symbol of reel) {
@@ -4274,71 +4358,78 @@ var ManywaysWinType = class extends WinType {
4274
4358
  }
4275
4359
  }
4276
4360
  for (const baseSymbol of candidateSymbols.values()) {
4277
- let symbolList = {};
4278
- let isInterrupted = false;
4279
- for (const [ridx, reel] of reels.entries()) {
4280
- if (isInterrupted) break;
4281
- for (const [sidx, symbol] of reel.entries()) {
4361
+ const symbolList = [];
4362
+ let wayLength = 0;
4363
+ let firstNonWildSymbol;
4364
+ let totalWays = 1;
4365
+ for (let ridx = 0; ridx < numReels; ridx++) {
4366
+ const reel = reels[ridx];
4367
+ let reelMatches;
4368
+ for (let sidx = 0; sidx < reel.length; sidx++) {
4369
+ const symbol = reel[sidx];
4282
4370
  const isMatch = baseSymbol.compare(symbol) || this.isWild(symbol);
4283
4371
  if (isMatch) {
4284
- if (!symbolList[ridx]) {
4285
- symbolList[ridx] = [];
4372
+ if (!reelMatches) {
4373
+ reelMatches = [];
4374
+ }
4375
+ reelMatches.push({ reel: ridx, row: sidx, symbol });
4376
+ if (!firstNonWildSymbol && !this.isWild(symbol)) {
4377
+ firstNonWildSymbol = symbol;
4286
4378
  }
4287
- symbolList[ridx].push({ reel: ridx, row: sidx, symbol });
4288
4379
  }
4289
4380
  }
4290
- if (!symbolList[ridx] && !jumpGaps) {
4291
- isInterrupted = true;
4381
+ if (reelMatches) {
4382
+ symbolList[wayLength++] = reelMatches;
4383
+ totalWays *= reelMatches.length;
4384
+ } else if (!jumpGaps) {
4292
4385
  break;
4293
4386
  }
4294
4387
  }
4295
- const minSymLine = Math.min(
4296
- ...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
4297
- );
4298
- const wayLength = this.getWayLength(symbolList);
4388
+ const pays = baseSymbol.pays || {};
4389
+ let minSymLine = Infinity;
4390
+ for (const key in pays) {
4391
+ const num = parseInt(key, 10);
4392
+ if (num < minSymLine) minSymLine = num;
4393
+ }
4299
4394
  if (wayLength >= minSymLine) {
4300
- possibleWaysWins.set(baseSymbol.id, symbolList);
4395
+ const winBaseSymbol = firstNonWildSymbol || symbolList[0][0].symbol;
4396
+ const singleWayPayout = this.getSymbolPayout(winBaseSymbol, wayLength);
4397
+ const totalPayout2 = singleWayPayout * totalWays;
4398
+ const symbols = [];
4399
+ for (let i = 0; i < wayLength; i++) {
4400
+ const reelSyms = symbolList[i];
4401
+ for (let j = 0; j < reelSyms.length; j++) {
4402
+ const s = reelSyms[j];
4403
+ symbols.push({
4404
+ symbol: s.symbol,
4405
+ isWild: this.isWild(s.symbol),
4406
+ reelIndex: s.reel,
4407
+ posIndex: s.row
4408
+ });
4409
+ }
4410
+ }
4411
+ waysWins.push({
4412
+ kind: wayLength,
4413
+ baseSymbol: winBaseSymbol,
4414
+ symbols,
4415
+ ways: totalWays,
4416
+ payout: totalPayout2
4417
+ });
4418
+ this.ctx.services.data.recordSymbolOccurrence({
4419
+ kind: wayLength,
4420
+ symbolId: winBaseSymbol.id,
4421
+ spinType: this.ctx.state.currentSpinType
4422
+ });
4301
4423
  }
4302
4424
  }
4303
- for (const [baseSymbolId, symbolList] of possibleWaysWins.entries()) {
4304
- const wayLength = this.getWayLength(symbolList);
4305
- let baseSymbol = Object.values(symbolList).flatMap((l) => l.map((s) => s)).find((s) => !this.isWild(s.symbol))?.symbol;
4306
- if (!baseSymbol) baseSymbol = symbolList[Object.keys(symbolList)[0]][0].symbol;
4307
- const singleWayPayout = this.getSymbolPayout(baseSymbol, wayLength);
4308
- const totalWays = Object.values(symbolList).reduce(
4309
- (ways, syms) => ways * syms.length,
4310
- 1
4311
- );
4312
- const totalPayout = singleWayPayout * totalWays;
4313
- waysWins.push({
4314
- kind: wayLength,
4315
- baseSymbol,
4316
- symbols: Object.values(symbolList).flatMap(
4317
- (reel) => reel.map((s) => ({
4318
- symbol: s.symbol,
4319
- isWild: this.isWild(s.symbol),
4320
- reelIndex: s.reel,
4321
- posIndex: s.row
4322
- }))
4323
- ),
4324
- ways: totalWays,
4325
- payout: totalPayout
4326
- });
4425
+ let totalPayout = 0;
4426
+ for (let i = 0; i < waysWins.length; i++) {
4427
+ totalPayout += waysWins[i].payout;
4327
4428
  }
4328
- for (const win of waysWins) {
4329
- this.ctx.services.data.recordSymbolOccurrence({
4330
- kind: win.kind,
4331
- symbolId: win.baseSymbol.id,
4332
- spinType: this.ctx.state.currentSpinType
4333
- });
4334
- }
4335
- this.payout = waysWins.reduce((sum, l) => sum + l.payout, 0);
4429
+ this.payout = totalPayout;
4336
4430
  this.winCombinations = waysWins;
4337
4431
  return this;
4338
4432
  }
4339
- getWayLength(symbolList) {
4340
- return Object.keys(symbolList).length;
4341
- }
4342
4433
  };
4343
4434
 
4344
4435
  // src/reel-set/GeneratedReelSet.ts