@slot-engine/core 0.2.9 → 0.2.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.js CHANGED
@@ -420,7 +420,10 @@ var GameSymbol = class _GameSymbol {
420
420
  constructor(opts) {
421
421
  this.id = opts.id;
422
422
  this.pays = opts.pays;
423
- 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
+ }
424
427
  if (this.pays && Object.keys(this.pays).length === 0) {
425
428
  throw new Error(`GameSymbol "${this.id}" must have pays defined.`);
426
429
  }
@@ -436,8 +439,8 @@ var GameSymbol = class _GameSymbol {
436
439
  if (symbolOrProperties instanceof _GameSymbol) {
437
440
  return this.id === symbolOrProperties.id;
438
441
  } else {
439
- for (const [key, value] of Object.entries(symbolOrProperties)) {
440
- 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]) {
441
444
  return false;
442
445
  }
443
446
  }
@@ -517,8 +520,8 @@ var Board = class {
517
520
  updateSymbol(reelIndex, rowIndex, properties) {
518
521
  const symbol = this.getSymbol(reelIndex, rowIndex);
519
522
  if (symbol) {
520
- for (const [key, value] of Object.entries(properties)) {
521
- symbol.properties.set(key, value);
523
+ for (const prop in properties) {
524
+ symbol.properties.set(prop, properties[prop]);
522
525
  }
523
526
  }
524
527
  }
@@ -534,8 +537,8 @@ var Board = class {
534
537
  if (symbolOrProperties instanceof GameSymbol) {
535
538
  if (symbol.id !== symbolOrProperties.id) matches = false;
536
539
  } else {
537
- for (const [key, value] of Object.entries(symbolOrProperties)) {
538
- 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]) {
539
542
  matches = false;
540
543
  break;
541
544
  }
@@ -557,8 +560,8 @@ var Board = class {
557
560
  if (symbol.id !== symbolOrProperties.id) continue;
558
561
  } else {
559
562
  let matches = true;
560
- for (const [key, value] of Object.entries(symbolOrProperties)) {
561
- if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {
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
  }
@@ -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
  /**
@@ -1463,19 +1468,6 @@ var Book = class {
1463
1468
  data: copy(event.data)
1464
1469
  });
1465
1470
  }
1466
- /**
1467
- * Intended for internal use only.
1468
- */
1469
- _serialize() {
1470
- return {
1471
- id: this.id,
1472
- criteria: this.criteria,
1473
- events: this.events,
1474
- payout: this.payout,
1475
- basegameWins: this.basegameWins,
1476
- freespinsWins: this.freespinsWins
1477
- };
1478
- }
1479
1471
  };
1480
1472
 
1481
1473
  // src/wallet/index.ts
@@ -1644,7 +1636,7 @@ var Wallet = class {
1644
1636
  this.currentWin = process2(this.currentWin);
1645
1637
  this.cumulativeWins += this.currentWin;
1646
1638
  let spinTypeWins = 0;
1647
- for (const spinType of Object.keys(this.currentWinPerSpinType)) {
1639
+ for (const spinType in this.currentWinPerSpinType) {
1648
1640
  const st = spinType;
1649
1641
  const spinTypeWin = process2(this.currentWinPerSpinType[st]);
1650
1642
  this.cumulativeWinsPerSpinType[st] += spinTypeWin;
@@ -2449,9 +2441,9 @@ var Simulation = class {
2449
2441
  }
2450
2442
  const chunkIndex = this.bookChunkIndexes.get(index);
2451
2443
  const bookChunkPath = this.PATHS.booksChunk(mode, index, chunkIndex);
2452
- const data = this.bookBuffers.get(index).join("\n") + "\n";
2444
+ const bookLines = this.bookBuffers.get(index);
2453
2445
  await (0, import_promises.pipeline)(
2454
- import_stream.Readable.from([Buffer.from(data, "utf8")]),
2446
+ import_stream.Readable.from(bookLines),
2455
2447
  import_zlib.default.createZstdCompress(),
2456
2448
  import_fs3.default.createWriteStream(bookChunkPath)
2457
2449
  );
@@ -2532,50 +2524,51 @@ var Simulation = class {
2532
2524
  }
2533
2525
  }
2534
2526
  writeChain = writeChain.then(async () => {
2535
- const book = msg.book;
2536
- const bookData = {
2537
- id: book.id,
2538
- payoutMultiplier: book.payout,
2539
- events: book.events
2540
- };
2541
- if (!this.summary[mode]?.criteria[book.criteria]) {
2542
- this.summary[mode].criteria[book.criteria] = {
2527
+ const bookId = msg.bookId;
2528
+ const bookCriteria = msg.bookCriteria;
2529
+ const bookPayout = msg.bookPayout;
2530
+ const bookBasegameWins = msg.bookBasegameWins;
2531
+ const bookFreespinsWins = msg.bookFreespinsWins;
2532
+ const bookLine = msg.bookLine;
2533
+ const bookLineWithNewline = bookLine + "\n";
2534
+ if (!this.summary[mode]?.criteria[bookCriteria]) {
2535
+ this.summary[mode].criteria[bookCriteria] = {
2543
2536
  numSims: 0,
2544
2537
  bsWins: 0,
2545
2538
  fsWins: 0,
2546
2539
  rtp: 0
2547
2540
  };
2548
2541
  }
2549
- const bsWins = round(book.basegameWins, 4);
2550
- const fsWins = round(book.freespinsWins, 4);
2551
- this.summary[mode].criteria[book.criteria].numSims += 1;
2542
+ const bsWins = round(bookBasegameWins, 4);
2543
+ const fsWins = round(bookFreespinsWins, 4);
2544
+ const criteria = this.summary[mode].criteria[bookCriteria];
2545
+ criteria.numSims += 1;
2552
2546
  this.summary[mode].total.bsWins += bsWins;
2553
2547
  this.summary[mode].total.fsWins += fsWins;
2554
- this.summary[mode].criteria[book.criteria].bsWins += bsWins;
2555
- this.summary[mode].criteria[book.criteria].fsWins += fsWins;
2556
- const bookLine = JSON.stringify(bookData);
2557
- const lineSize = Buffer.byteLength(bookLine + "\n", "utf8");
2548
+ criteria.bsWins += bsWins;
2549
+ criteria.fsWins += fsWins;
2550
+ const lineSize = Buffer.byteLength(bookLineWithNewline, "utf8");
2558
2551
  if (this.bookBuffers.has(index)) {
2559
- this.bookBuffers.get(index).push(bookLine);
2552
+ this.bookBuffers.get(index).push(bookLineWithNewline);
2560
2553
  this.bookBufferSizes.set(
2561
2554
  index,
2562
2555
  this.bookBufferSizes.get(index) + lineSize
2563
2556
  );
2564
2557
  } else {
2565
- this.bookBuffers.set(index, [bookLine]);
2558
+ this.bookBuffers.set(index, [bookLineWithNewline]);
2566
2559
  this.bookBufferSizes.set(index, lineSize);
2567
2560
  }
2568
2561
  if (!this.tempBookIndexPaths.includes(booksIndexPath)) {
2569
2562
  this.tempBookIndexPaths.push(booksIndexPath);
2570
2563
  }
2571
2564
  booksIndexBatch.push(
2572
- `${book.id},${index},${this.bookChunkIndexes.get(index) || 0}
2565
+ `${bookId},${index},${this.bookChunkIndexes.get(index) || 0}
2573
2566
  `
2574
2567
  );
2575
- lookupBatch.push(`${book.id},1,${Math.round(book.payout)}
2568
+ lookupBatch.push(`${bookId},1,${Math.round(bookPayout)}
2576
2569
  `);
2577
2570
  lookupSegBatch.push(
2578
- `${book.id},${book.criteria},${book.basegameWins},${book.freespinsWins}
2571
+ `${bookId},${bookCriteria},${bookBasegameWins},${bookFreespinsWins}
2579
2572
  `
2580
2573
  );
2581
2574
  if (booksIndexBatch.length >= WRITE_BATCH_SIZE) {
@@ -2584,15 +2577,10 @@ var Simulation = class {
2584
2577
  if (this.bookBufferSizes.get(index) >= 10 * 1024 * 1024) {
2585
2578
  await flushBookChunk();
2586
2579
  }
2587
- if (this.recordsWriteStream) {
2588
- for (const record of msg.records) {
2589
- const recordPrefix = this.hasWrittenRecord ? "\n" : "";
2590
- await write(
2591
- this.recordsWriteStream,
2592
- recordPrefix + JSON.stringify(record)
2593
- );
2594
- this.hasWrittenRecord = true;
2595
- }
2580
+ if (this.recordsWriteStream && typeof msg.recordsLines === "string" && msg.recordsLines.length) {
2581
+ const recordPrefix = this.hasWrittenRecord ? "\n" : "";
2582
+ await write(this.recordsWriteStream, recordPrefix + msg.recordsLines);
2583
+ this.hasWrittenRecord = true;
2596
2584
  }
2597
2585
  this.wallet.mergeSerialized(msg.wallet);
2598
2586
  worker.postMessage({ type: "credit", amount: 1 });
@@ -2683,9 +2671,11 @@ var Simulation = class {
2683
2671
  });
2684
2672
  }
2685
2673
  }
2686
- ctx.services.wallet._getWallet().writePayoutToBook(ctx);
2687
- ctx.services.wallet._getWallet().confirmWins(ctx);
2688
- if (ctx.services.data._getBook().payout >= ctx.config.maxWinX) {
2674
+ const wallet = ctx.services.wallet._getWallet();
2675
+ wallet.writePayoutToBook(ctx);
2676
+ wallet.confirmWins(ctx);
2677
+ const book = ctx.services.data._getBook();
2678
+ if (book.payout >= ctx.config.maxWinX) {
2689
2679
  ctx.state.triggeredMaxWin = true;
2690
2680
  }
2691
2681
  ctx.services.data.record({
@@ -2693,12 +2683,24 @@ var Simulation = class {
2693
2683
  });
2694
2684
  ctx.config.hooks.onSimulationAccepted?.(ctx);
2695
2685
  this.confirmRecords(ctx);
2686
+ const bookLine = JSON.stringify({
2687
+ id: book.id,
2688
+ payoutMultiplier: book.payout,
2689
+ events: book.events
2690
+ });
2691
+ const records = ctx.services.data._getRecords();
2692
+ const recordsLines = records.length > 0 ? records.map((r) => JSON.stringify(r)).join("\n") : "";
2696
2693
  import_worker_threads2.parentPort?.postMessage({
2697
2694
  type: "complete",
2698
2695
  simId,
2699
- book: ctx.services.data._getBook()._serialize(),
2700
- wallet: ctx.services.wallet._getWallet().serialize(),
2701
- records: ctx.services.data._getRecords()
2696
+ bookLine,
2697
+ bookId: book.id,
2698
+ bookCriteria: book.criteria,
2699
+ bookPayout: book.payout,
2700
+ bookBasegameWins: book.basegameWins,
2701
+ bookFreespinsWins: book.freespinsWins,
2702
+ wallet: wallet.serialize(),
2703
+ recordsLines
2702
2704
  });
2703
2705
  }
2704
2706
  initCreditListener() {
@@ -4005,13 +4007,19 @@ var LinesWinType = class extends WinType {
4005
4007
  this.validateConfig();
4006
4008
  const lineWins = [];
4007
4009
  const reels = board;
4008
- for (const [lineNumStr, line] of Object.entries(this.lines)) {
4010
+ const reelsLength = reels.length;
4011
+ const lineNumbers = Object.keys(this.lines);
4012
+ const numLines = lineNumbers.length;
4013
+ for (let lidx = 0; lidx < numLines; lidx++) {
4014
+ const lineNumStr = lineNumbers[lidx];
4009
4015
  const lineNum = Number(lineNumStr);
4016
+ const line = this.lines[lineNum];
4010
4017
  let baseSymbol;
4011
4018
  const potentialWinLine = [];
4012
4019
  const potentialWildLine = [];
4013
4020
  let isInterrupted = false;
4014
- for (const [ridx, reel] of reels.entries()) {
4021
+ for (let ridx = 0; ridx < reelsLength; ridx++) {
4022
+ const reel = reels[ridx];
4015
4023
  const sidx = line[ridx];
4016
4024
  const thisSymbol = reel[sidx];
4017
4025
  if (!baseSymbol) {
@@ -4042,66 +4050,91 @@ var LinesWinType = class extends WinType {
4042
4050
  break;
4043
4051
  }
4044
4052
  }
4045
- const minSymLine = Math.min(
4046
- ...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
4047
- );
4053
+ const pays = baseSymbol.pays || {};
4054
+ let minSymLine = Infinity;
4055
+ for (const key in pays) {
4056
+ const num = parseInt(key, 10);
4057
+ if (num < minSymLine) minSymLine = num;
4058
+ }
4048
4059
  if (potentialWinLine.length < minSymLine) continue;
4049
4060
  const linePayout = this.getLinePayout(potentialWinLine);
4050
4061
  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
- };
4062
+ let finalLine;
4063
4063
  if (wildLinePayout > linePayout) {
4064
4064
  baseSymbol = potentialWildLine[0]?.symbol;
4065
+ const wildSymbols = [];
4066
+ const wildLineLength = potentialWildLine.length;
4067
+ for (let i = 0; i < wildLineLength; i++) {
4068
+ const s = potentialWildLine[i];
4069
+ wildSymbols.push({
4070
+ symbol: s.symbol,
4071
+ isWild: this.isWild(s.symbol),
4072
+ reelIndex: s.reel,
4073
+ posIndex: s.row
4074
+ });
4075
+ }
4065
4076
  finalLine = {
4066
- kind: potentialWildLine.length,
4077
+ kind: wildLineLength,
4067
4078
  baseSymbol,
4068
- symbols: potentialWildLine.map((s) => ({
4079
+ symbols: wildSymbols,
4080
+ lineNumber: lineNum,
4081
+ payout: wildLinePayout
4082
+ };
4083
+ } else {
4084
+ const symbols = [];
4085
+ const lineLength = potentialWinLine.length;
4086
+ for (let i = 0; i < lineLength; i++) {
4087
+ const s = potentialWinLine[i];
4088
+ symbols.push({
4069
4089
  symbol: s.symbol,
4070
4090
  isWild: this.isWild(s.symbol),
4071
4091
  reelIndex: s.reel,
4072
4092
  posIndex: s.row
4073
- })),
4093
+ });
4094
+ }
4095
+ finalLine = {
4096
+ kind: lineLength,
4097
+ baseSymbol,
4098
+ symbols,
4074
4099
  lineNumber: lineNum,
4075
- payout: wildLinePayout
4100
+ payout: linePayout
4076
4101
  };
4077
4102
  }
4078
4103
  lineWins.push(finalLine);
4079
- }
4080
- for (const win of lineWins) {
4081
4104
  this.ctx.services.data.recordSymbolOccurrence({
4082
- kind: win.kind,
4083
- symbolId: win.baseSymbol.id,
4105
+ kind: finalLine.kind,
4106
+ symbolId: finalLine.baseSymbol.id,
4084
4107
  spinType: this.ctx.state.currentSpinType
4085
4108
  });
4086
4109
  }
4087
- this.payout = lineWins.reduce((sum, l) => sum + l.payout, 0);
4110
+ let totalPayout = 0;
4111
+ for (let i = 0; i < lineWins.length; i++) {
4112
+ totalPayout += lineWins[i].payout;
4113
+ }
4114
+ this.payout = totalPayout;
4088
4115
  this.winCombinations = lineWins;
4089
4116
  return this;
4090
4117
  }
4091
4118
  getLinePayout(line) {
4092
- if (line.length === 0) return 0;
4093
- let baseSymbol = line.find((s) => !this.isWild(s.symbol))?.symbol;
4119
+ const lineLength = line.length;
4120
+ if (lineLength === 0) return 0;
4121
+ let baseSymbol;
4122
+ for (let i = 0; i < lineLength; i++) {
4123
+ const s = line[i];
4124
+ if (!this.isWild(s.symbol)) {
4125
+ baseSymbol = s.symbol;
4126
+ break;
4127
+ }
4128
+ }
4094
4129
  if (!baseSymbol) baseSymbol = line[0].symbol;
4095
- const kind = line.length;
4096
- const payout = this.getSymbolPayout(baseSymbol, kind);
4097
- return payout;
4130
+ return this.getSymbolPayout(baseSymbol, lineLength);
4098
4131
  }
4099
4132
  };
4100
4133
 
4101
4134
  // src/win-types/ClusterWinType.ts
4102
4135
  var ClusterWinType = class extends WinType {
4103
- _checked = [];
4104
- _checkedWilds = [];
4136
+ _checked = /* @__PURE__ */ new Set();
4137
+ _checkedWilds = /* @__PURE__ */ new Set();
4105
4138
  _currentBoard = [];
4106
4139
  constructor(opts) {
4107
4140
  super(opts);
@@ -4114,121 +4147,169 @@ var ClusterWinType = class extends WinType {
4114
4147
  */
4115
4148
  evaluateWins(board) {
4116
4149
  this.validateConfig();
4117
- this._checked = [];
4150
+ this._checked.clear();
4118
4151
  this._currentBoard = board;
4119
4152
  const clusterWins = [];
4120
4153
  const potentialClusters = [];
4121
- for (const [ridx, reel] of board.entries()) {
4122
- for (const [sidx, symbol] of reel.entries()) {
4123
- this._checkedWilds = [];
4154
+ const boardLength = board.length;
4155
+ for (let ridx = 0; ridx < boardLength; ridx++) {
4156
+ const reel = board[ridx];
4157
+ const reelLength = reel.length;
4158
+ for (let sidx = 0; sidx < reelLength; sidx++) {
4159
+ const symbol = reel[sidx];
4160
+ this._checkedWilds.clear();
4124
4161
  if (this.isWild(symbol)) continue;
4125
- if (this.isChecked(ridx, sidx)) {
4162
+ const posKey = ridx * 1e4 + sidx;
4163
+ if (this._checked.has(posKey)) {
4126
4164
  continue;
4127
4165
  }
4128
4166
  const thisSymbol = { reel: ridx, row: sidx, symbol };
4129
- this._checked.push(thisSymbol);
4167
+ this._checked.add(posKey);
4130
4168
  const neighbors = this.getNeighbors(ridx, sidx);
4131
4169
  const matchingSymbols = this.evaluateCluster(symbol, neighbors);
4132
- if (matchingSymbols.size >= 1) {
4133
- potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
4170
+ const matchingSize = matchingSymbols.size;
4171
+ if (matchingSize >= 1) {
4172
+ const cluster = [thisSymbol];
4173
+ for (const sym of matchingSymbols.values()) {
4174
+ cluster.push(sym);
4175
+ }
4176
+ potentialClusters.push(cluster);
4134
4177
  }
4135
4178
  }
4136
4179
  }
4137
- for (const [ridx, reel] of board.entries()) {
4138
- for (const [sidx, symbol] of reel.entries()) {
4139
- this._checkedWilds = [];
4180
+ for (let ridx = 0; ridx < boardLength; ridx++) {
4181
+ const reel = board[ridx];
4182
+ const reelLength = reel.length;
4183
+ for (let sidx = 0; sidx < reelLength; sidx++) {
4184
+ const symbol = reel[sidx];
4185
+ this._checkedWilds.clear();
4140
4186
  if (!this.isWild(symbol)) continue;
4141
- if (this.isChecked(ridx, sidx)) {
4187
+ const posKey = ridx * 1e4 + sidx;
4188
+ if (this._checked.has(posKey)) {
4142
4189
  continue;
4143
4190
  }
4144
4191
  const thisSymbol = { reel: ridx, row: sidx, symbol };
4145
- this._checked.push(thisSymbol);
4192
+ this._checked.add(posKey);
4146
4193
  const neighbors = this.getNeighbors(ridx, sidx);
4147
4194
  const matchingSymbols = this.evaluateCluster(symbol, neighbors);
4148
- if (matchingSymbols.size >= 1) {
4149
- potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
4195
+ const matchingSize = matchingSymbols.size;
4196
+ if (matchingSize >= 1) {
4197
+ const cluster = [thisSymbol];
4198
+ for (const sym of matchingSymbols.values()) {
4199
+ cluster.push(sym);
4200
+ }
4201
+ potentialClusters.push(cluster);
4150
4202
  }
4151
4203
  }
4152
4204
  }
4153
- for (const cluster of potentialClusters) {
4205
+ const numClusters = potentialClusters.length;
4206
+ for (let i = 0; i < numClusters; i++) {
4207
+ const cluster = potentialClusters[i];
4154
4208
  const kind = cluster.length;
4155
- let baseSymbol = cluster.find((s) => !this.isWild(s.symbol))?.symbol;
4209
+ let baseSymbol;
4210
+ for (let j = 0; j < kind; j++) {
4211
+ const sym = cluster[j].symbol;
4212
+ if (!this.isWild(sym)) {
4213
+ baseSymbol = sym;
4214
+ break;
4215
+ }
4216
+ }
4156
4217
  if (!baseSymbol) baseSymbol = cluster[0].symbol;
4157
4218
  const payout = this.getSymbolPayout(baseSymbol, kind);
4158
4219
  if (payout === 0) continue;
4159
- if (!baseSymbol.pays || Object.keys(baseSymbol.pays).length === 0) {
4160
- continue;
4220
+ const pays = baseSymbol.pays;
4221
+ if (!pays) continue;
4222
+ let hasPays = false;
4223
+ for (const _ in pays) {
4224
+ hasPays = true;
4225
+ break;
4161
4226
  }
4162
- clusterWins.push({
4163
- payout,
4164
- kind,
4165
- baseSymbol,
4166
- symbols: cluster.map((s) => ({
4227
+ if (!hasPays) continue;
4228
+ const symbols = [];
4229
+ for (let j = 0; j < kind; j++) {
4230
+ const s = cluster[j];
4231
+ symbols.push({
4167
4232
  symbol: s.symbol,
4168
4233
  isWild: this.isWild(s.symbol),
4169
4234
  reelIndex: s.reel,
4170
4235
  posIndex: s.row
4171
- }))
4236
+ });
4237
+ }
4238
+ clusterWins.push({
4239
+ payout,
4240
+ kind,
4241
+ baseSymbol,
4242
+ symbols
4172
4243
  });
4173
- }
4174
- for (const win of clusterWins) {
4175
4244
  this.ctx.services.data.recordSymbolOccurrence({
4176
- kind: win.kind,
4177
- symbolId: win.baseSymbol.id,
4245
+ kind,
4246
+ symbolId: baseSymbol.id,
4178
4247
  spinType: this.ctx.state.currentSpinType
4179
4248
  });
4180
4249
  }
4181
- this.payout = clusterWins.reduce((sum, c) => sum + c.payout, 0);
4250
+ let totalPayout = 0;
4251
+ for (let i = 0; i < clusterWins.length; i++) {
4252
+ totalPayout += clusterWins[i].payout;
4253
+ }
4254
+ this.payout = totalPayout;
4182
4255
  this.winCombinations = clusterWins;
4183
4256
  return this;
4184
4257
  }
4185
4258
  getNeighbors(ridx, sidx) {
4186
4259
  const board = this._currentBoard;
4187
4260
  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] });
4261
+ if (ridx > 0) {
4262
+ const leftReel = board[ridx - 1];
4263
+ const leftSymbol = leftReel[sidx];
4264
+ if (leftSymbol !== void 0) {
4265
+ neighbors.push({ reel: ridx - 1, row: sidx, symbol: leftSymbol });
4197
4266
  }
4198
- });
4267
+ }
4268
+ const rightReel = board[ridx + 1];
4269
+ if (rightReel !== void 0) {
4270
+ const rightSymbol = rightReel[sidx];
4271
+ if (rightSymbol !== void 0) {
4272
+ neighbors.push({ reel: ridx + 1, row: sidx, symbol: rightSymbol });
4273
+ }
4274
+ }
4275
+ const currentReel = board[ridx];
4276
+ const topSymbol = currentReel[sidx - 1];
4277
+ if (topSymbol !== void 0) {
4278
+ neighbors.push({ reel: ridx, row: sidx - 1, symbol: topSymbol });
4279
+ }
4280
+ const bottomSymbol = currentReel[sidx + 1];
4281
+ if (bottomSymbol !== void 0) {
4282
+ neighbors.push({ reel: ridx, row: sidx + 1, symbol: bottomSymbol });
4283
+ }
4199
4284
  return neighbors;
4200
4285
  }
4201
4286
  evaluateCluster(rootSymbol, neighbors) {
4202
4287
  const matchingSymbols = /* @__PURE__ */ new Map();
4203
- neighbors.forEach((neighbor) => {
4288
+ const numNeighbors = neighbors.length;
4289
+ for (let i = 0; i < numNeighbors; i++) {
4290
+ const neighbor = neighbors[i];
4204
4291
  const { reel, row, symbol } = neighbor;
4205
- if (this.isChecked(reel, row)) return;
4206
- if (this.isCheckedWild(reel, row)) return;
4292
+ const posKey = reel * 1e4 + row;
4293
+ if (this._checked.has(posKey)) continue;
4294
+ if (this._checkedWilds.has(posKey)) continue;
4207
4295
  if (this.isWild(symbol) || symbol.compare(rootSymbol)) {
4208
- const key = `${reel}-${row}`;
4296
+ const key = String(posKey);
4209
4297
  matchingSymbols.set(key, { reel, row, symbol });
4210
4298
  if (symbol.compare(rootSymbol)) {
4211
- this._checked.push(neighbor);
4299
+ this._checked.add(posKey);
4212
4300
  }
4213
4301
  if (this.isWild(symbol)) {
4214
- this._checkedWilds.push(neighbor);
4302
+ this._checkedWilds.add(posKey);
4215
4303
  }
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}`;
4304
+ const nestedNeighbors = this.getNeighbors(reel, row);
4305
+ const nestedMatches = this.evaluateCluster(rootSymbol, nestedNeighbors);
4306
+ for (const [nkey, nsym] of nestedMatches.entries()) {
4220
4307
  matchingSymbols.set(nkey, nsym);
4221
- });
4308
+ }
4222
4309
  }
4223
- });
4310
+ }
4224
4311
  return matchingSymbols;
4225
4312
  }
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
4313
  };
4233
4314
 
4234
4315
  // src/win-types/ManywaysWinType.ts
@@ -4247,7 +4328,7 @@ var ManywaysWinType = class extends WinType {
4247
4328
  const { jumpGaps = false } = opts;
4248
4329
  const waysWins = [];
4249
4330
  const reels = board;
4250
- const possibleWaysWins = /* @__PURE__ */ new Map();
4331
+ const numReels = reels.length;
4251
4332
  const candidateSymbols = /* @__PURE__ */ new Map();
4252
4333
  if (jumpGaps) {
4253
4334
  for (const reel of reels) {
@@ -4258,7 +4339,7 @@ var ManywaysWinType = class extends WinType {
4258
4339
  } else {
4259
4340
  let searchReelIdx = 0;
4260
4341
  let searchActive = true;
4261
- while (searchActive && searchReelIdx < reels.length) {
4342
+ while (searchActive && searchReelIdx < numReels) {
4262
4343
  const reel = reels[searchReelIdx];
4263
4344
  let hasWild = false;
4264
4345
  for (const symbol of reel) {
@@ -4274,71 +4355,78 @@ var ManywaysWinType = class extends WinType {
4274
4355
  }
4275
4356
  }
4276
4357
  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()) {
4358
+ const symbolList = [];
4359
+ let wayLength = 0;
4360
+ let firstNonWildSymbol;
4361
+ let totalWays = 1;
4362
+ for (let ridx = 0; ridx < numReels; ridx++) {
4363
+ const reel = reels[ridx];
4364
+ let reelMatches;
4365
+ for (let sidx = 0; sidx < reel.length; sidx++) {
4366
+ const symbol = reel[sidx];
4282
4367
  const isMatch = baseSymbol.compare(symbol) || this.isWild(symbol);
4283
4368
  if (isMatch) {
4284
- if (!symbolList[ridx]) {
4285
- symbolList[ridx] = [];
4369
+ if (!reelMatches) {
4370
+ reelMatches = [];
4371
+ }
4372
+ reelMatches.push({ reel: ridx, row: sidx, symbol });
4373
+ if (!firstNonWildSymbol && !this.isWild(symbol)) {
4374
+ firstNonWildSymbol = symbol;
4286
4375
  }
4287
- symbolList[ridx].push({ reel: ridx, row: sidx, symbol });
4288
4376
  }
4289
4377
  }
4290
- if (!symbolList[ridx] && !jumpGaps) {
4291
- isInterrupted = true;
4378
+ if (reelMatches) {
4379
+ symbolList[wayLength++] = reelMatches;
4380
+ totalWays *= reelMatches.length;
4381
+ } else if (!jumpGaps) {
4292
4382
  break;
4293
4383
  }
4294
4384
  }
4295
- const minSymLine = Math.min(
4296
- ...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
4297
- );
4298
- const wayLength = this.getWayLength(symbolList);
4385
+ const pays = baseSymbol.pays || {};
4386
+ let minSymLine = Infinity;
4387
+ for (const key in pays) {
4388
+ const num = parseInt(key, 10);
4389
+ if (num < minSymLine) minSymLine = num;
4390
+ }
4299
4391
  if (wayLength >= minSymLine) {
4300
- possibleWaysWins.set(baseSymbol.id, symbolList);
4392
+ const winBaseSymbol = firstNonWildSymbol || symbolList[0][0].symbol;
4393
+ const singleWayPayout = this.getSymbolPayout(winBaseSymbol, wayLength);
4394
+ const totalPayout2 = singleWayPayout * totalWays;
4395
+ const symbols = [];
4396
+ for (let i = 0; i < wayLength; i++) {
4397
+ const reelSyms = symbolList[i];
4398
+ for (let j = 0; j < reelSyms.length; j++) {
4399
+ const s = reelSyms[j];
4400
+ symbols.push({
4401
+ symbol: s.symbol,
4402
+ isWild: this.isWild(s.symbol),
4403
+ reelIndex: s.reel,
4404
+ posIndex: s.row
4405
+ });
4406
+ }
4407
+ }
4408
+ waysWins.push({
4409
+ kind: wayLength,
4410
+ baseSymbol: winBaseSymbol,
4411
+ symbols,
4412
+ ways: totalWays,
4413
+ payout: totalPayout2
4414
+ });
4415
+ this.ctx.services.data.recordSymbolOccurrence({
4416
+ kind: wayLength,
4417
+ symbolId: winBaseSymbol.id,
4418
+ spinType: this.ctx.state.currentSpinType
4419
+ });
4301
4420
  }
4302
4421
  }
4303
- for (const [, 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
- });
4327
- }
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
- });
4422
+ let totalPayout = 0;
4423
+ for (let i = 0; i < waysWins.length; i++) {
4424
+ totalPayout += waysWins[i].payout;
4334
4425
  }
4335
- this.payout = waysWins.reduce((sum, l) => sum + l.payout, 0);
4426
+ this.payout = totalPayout;
4336
4427
  this.winCombinations = waysWins;
4337
4428
  return this;
4338
4429
  }
4339
- getWayLength(symbolList) {
4340
- return Object.keys(symbolList).length;
4341
- }
4342
4430
  };
4343
4431
 
4344
4432
  // src/reel-set/GeneratedReelSet.ts