@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.mjs CHANGED
@@ -371,7 +371,10 @@ var GameSymbol = class _GameSymbol {
371
371
  constructor(opts) {
372
372
  this.id = opts.id;
373
373
  this.pays = opts.pays;
374
- 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
+ }
375
378
  if (this.pays && Object.keys(this.pays).length === 0) {
376
379
  throw new Error(`GameSymbol "${this.id}" must have pays defined.`);
377
380
  }
@@ -387,8 +390,8 @@ var GameSymbol = class _GameSymbol {
387
390
  if (symbolOrProperties instanceof _GameSymbol) {
388
391
  return this.id === symbolOrProperties.id;
389
392
  } else {
390
- for (const [key, value] of Object.entries(symbolOrProperties)) {
391
- 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]) {
392
395
  return false;
393
396
  }
394
397
  }
@@ -468,8 +471,8 @@ var Board = class {
468
471
  updateSymbol(reelIndex, rowIndex, properties) {
469
472
  const symbol = this.getSymbol(reelIndex, rowIndex);
470
473
  if (symbol) {
471
- for (const [key, value] of Object.entries(properties)) {
472
- symbol.properties.set(key, value);
474
+ for (const prop in properties) {
475
+ symbol.properties.set(prop, properties[prop]);
473
476
  }
474
477
  }
475
478
  }
@@ -485,8 +488,8 @@ var Board = class {
485
488
  if (symbolOrProperties instanceof GameSymbol) {
486
489
  if (symbol.id !== symbolOrProperties.id) matches = false;
487
490
  } else {
488
- for (const [key, value] of Object.entries(symbolOrProperties)) {
489
- 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]) {
490
493
  matches = false;
491
494
  break;
492
495
  }
@@ -508,8 +511,8 @@ var Board = class {
508
511
  if (symbol.id !== symbolOrProperties.id) continue;
509
512
  } else {
510
513
  let matches = true;
511
- for (const [key, value] of Object.entries(symbolOrProperties)) {
512
- if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {
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
  }
@@ -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
  /**
@@ -1414,19 +1419,6 @@ var Book = class {
1414
1419
  data: copy(event.data)
1415
1420
  });
1416
1421
  }
1417
- /**
1418
- * Intended for internal use only.
1419
- */
1420
- _serialize() {
1421
- return {
1422
- id: this.id,
1423
- criteria: this.criteria,
1424
- events: this.events,
1425
- payout: this.payout,
1426
- basegameWins: this.basegameWins,
1427
- freespinsWins: this.freespinsWins
1428
- };
1429
- }
1430
1422
  };
1431
1423
 
1432
1424
  // src/wallet/index.ts
@@ -1595,7 +1587,7 @@ var Wallet = class {
1595
1587
  this.currentWin = process2(this.currentWin);
1596
1588
  this.cumulativeWins += this.currentWin;
1597
1589
  let spinTypeWins = 0;
1598
- for (const spinType of Object.keys(this.currentWinPerSpinType)) {
1590
+ for (const spinType in this.currentWinPerSpinType) {
1599
1591
  const st = spinType;
1600
1592
  const spinTypeWin = process2(this.currentWinPerSpinType[st]);
1601
1593
  this.cumulativeWinsPerSpinType[st] += spinTypeWin;
@@ -2400,9 +2392,9 @@ var Simulation = class {
2400
2392
  }
2401
2393
  const chunkIndex = this.bookChunkIndexes.get(index);
2402
2394
  const bookChunkPath = this.PATHS.booksChunk(mode, index, chunkIndex);
2403
- const data = this.bookBuffers.get(index).join("\n") + "\n";
2395
+ const bookLines = this.bookBuffers.get(index);
2404
2396
  await pipeline(
2405
- Readable.from([Buffer.from(data, "utf8")]),
2397
+ Readable.from(bookLines),
2406
2398
  zlib.createZstdCompress(),
2407
2399
  fs3.createWriteStream(bookChunkPath)
2408
2400
  );
@@ -2483,50 +2475,51 @@ var Simulation = class {
2483
2475
  }
2484
2476
  }
2485
2477
  writeChain = writeChain.then(async () => {
2486
- const book = msg.book;
2487
- const bookData = {
2488
- id: book.id,
2489
- payoutMultiplier: book.payout,
2490
- events: book.events
2491
- };
2492
- if (!this.summary[mode]?.criteria[book.criteria]) {
2493
- this.summary[mode].criteria[book.criteria] = {
2478
+ const bookId = msg.bookId;
2479
+ const bookCriteria = msg.bookCriteria;
2480
+ const bookPayout = msg.bookPayout;
2481
+ const bookBasegameWins = msg.bookBasegameWins;
2482
+ const bookFreespinsWins = msg.bookFreespinsWins;
2483
+ const bookLine = msg.bookLine;
2484
+ const bookLineWithNewline = bookLine + "\n";
2485
+ if (!this.summary[mode]?.criteria[bookCriteria]) {
2486
+ this.summary[mode].criteria[bookCriteria] = {
2494
2487
  numSims: 0,
2495
2488
  bsWins: 0,
2496
2489
  fsWins: 0,
2497
2490
  rtp: 0
2498
2491
  };
2499
2492
  }
2500
- const bsWins = round(book.basegameWins, 4);
2501
- const fsWins = round(book.freespinsWins, 4);
2502
- this.summary[mode].criteria[book.criteria].numSims += 1;
2493
+ const bsWins = round(bookBasegameWins, 4);
2494
+ const fsWins = round(bookFreespinsWins, 4);
2495
+ const criteria = this.summary[mode].criteria[bookCriteria];
2496
+ criteria.numSims += 1;
2503
2497
  this.summary[mode].total.bsWins += bsWins;
2504
2498
  this.summary[mode].total.fsWins += fsWins;
2505
- this.summary[mode].criteria[book.criteria].bsWins += bsWins;
2506
- this.summary[mode].criteria[book.criteria].fsWins += fsWins;
2507
- const bookLine = JSON.stringify(bookData);
2508
- const lineSize = Buffer.byteLength(bookLine + "\n", "utf8");
2499
+ criteria.bsWins += bsWins;
2500
+ criteria.fsWins += fsWins;
2501
+ const lineSize = Buffer.byteLength(bookLineWithNewline, "utf8");
2509
2502
  if (this.bookBuffers.has(index)) {
2510
- this.bookBuffers.get(index).push(bookLine);
2503
+ this.bookBuffers.get(index).push(bookLineWithNewline);
2511
2504
  this.bookBufferSizes.set(
2512
2505
  index,
2513
2506
  this.bookBufferSizes.get(index) + lineSize
2514
2507
  );
2515
2508
  } else {
2516
- this.bookBuffers.set(index, [bookLine]);
2509
+ this.bookBuffers.set(index, [bookLineWithNewline]);
2517
2510
  this.bookBufferSizes.set(index, lineSize);
2518
2511
  }
2519
2512
  if (!this.tempBookIndexPaths.includes(booksIndexPath)) {
2520
2513
  this.tempBookIndexPaths.push(booksIndexPath);
2521
2514
  }
2522
2515
  booksIndexBatch.push(
2523
- `${book.id},${index},${this.bookChunkIndexes.get(index) || 0}
2516
+ `${bookId},${index},${this.bookChunkIndexes.get(index) || 0}
2524
2517
  `
2525
2518
  );
2526
- lookupBatch.push(`${book.id},1,${Math.round(book.payout)}
2519
+ lookupBatch.push(`${bookId},1,${Math.round(bookPayout)}
2527
2520
  `);
2528
2521
  lookupSegBatch.push(
2529
- `${book.id},${book.criteria},${book.basegameWins},${book.freespinsWins}
2522
+ `${bookId},${bookCriteria},${bookBasegameWins},${bookFreespinsWins}
2530
2523
  `
2531
2524
  );
2532
2525
  if (booksIndexBatch.length >= WRITE_BATCH_SIZE) {
@@ -2535,15 +2528,10 @@ var Simulation = class {
2535
2528
  if (this.bookBufferSizes.get(index) >= 10 * 1024 * 1024) {
2536
2529
  await flushBookChunk();
2537
2530
  }
2538
- if (this.recordsWriteStream) {
2539
- for (const record of msg.records) {
2540
- const recordPrefix = this.hasWrittenRecord ? "\n" : "";
2541
- await write(
2542
- this.recordsWriteStream,
2543
- recordPrefix + JSON.stringify(record)
2544
- );
2545
- this.hasWrittenRecord = true;
2546
- }
2531
+ if (this.recordsWriteStream && typeof msg.recordsLines === "string" && msg.recordsLines.length) {
2532
+ const recordPrefix = this.hasWrittenRecord ? "\n" : "";
2533
+ await write(this.recordsWriteStream, recordPrefix + msg.recordsLines);
2534
+ this.hasWrittenRecord = true;
2547
2535
  }
2548
2536
  this.wallet.mergeSerialized(msg.wallet);
2549
2537
  worker.postMessage({ type: "credit", amount: 1 });
@@ -2634,9 +2622,11 @@ var Simulation = class {
2634
2622
  });
2635
2623
  }
2636
2624
  }
2637
- ctx.services.wallet._getWallet().writePayoutToBook(ctx);
2638
- ctx.services.wallet._getWallet().confirmWins(ctx);
2639
- if (ctx.services.data._getBook().payout >= ctx.config.maxWinX) {
2625
+ const wallet = ctx.services.wallet._getWallet();
2626
+ wallet.writePayoutToBook(ctx);
2627
+ wallet.confirmWins(ctx);
2628
+ const book = ctx.services.data._getBook();
2629
+ if (book.payout >= ctx.config.maxWinX) {
2640
2630
  ctx.state.triggeredMaxWin = true;
2641
2631
  }
2642
2632
  ctx.services.data.record({
@@ -2644,12 +2634,24 @@ var Simulation = class {
2644
2634
  });
2645
2635
  ctx.config.hooks.onSimulationAccepted?.(ctx);
2646
2636
  this.confirmRecords(ctx);
2637
+ const bookLine = JSON.stringify({
2638
+ id: book.id,
2639
+ payoutMultiplier: book.payout,
2640
+ events: book.events
2641
+ });
2642
+ const records = ctx.services.data._getRecords();
2643
+ const recordsLines = records.length > 0 ? records.map((r) => JSON.stringify(r)).join("\n") : "";
2647
2644
  parentPort2?.postMessage({
2648
2645
  type: "complete",
2649
2646
  simId,
2650
- book: ctx.services.data._getBook()._serialize(),
2651
- wallet: ctx.services.wallet._getWallet().serialize(),
2652
- records: ctx.services.data._getRecords()
2647
+ bookLine,
2648
+ bookId: book.id,
2649
+ bookCriteria: book.criteria,
2650
+ bookPayout: book.payout,
2651
+ bookBasegameWins: book.basegameWins,
2652
+ bookFreespinsWins: book.freespinsWins,
2653
+ wallet: wallet.serialize(),
2654
+ recordsLines
2653
2655
  });
2654
2656
  }
2655
2657
  initCreditListener() {
@@ -3956,13 +3958,19 @@ var LinesWinType = class extends WinType {
3956
3958
  this.validateConfig();
3957
3959
  const lineWins = [];
3958
3960
  const reels = board;
3959
- for (const [lineNumStr, line] of Object.entries(this.lines)) {
3961
+ const reelsLength = reels.length;
3962
+ const lineNumbers = Object.keys(this.lines);
3963
+ const numLines = lineNumbers.length;
3964
+ for (let lidx = 0; lidx < numLines; lidx++) {
3965
+ const lineNumStr = lineNumbers[lidx];
3960
3966
  const lineNum = Number(lineNumStr);
3967
+ const line = this.lines[lineNum];
3961
3968
  let baseSymbol;
3962
3969
  const potentialWinLine = [];
3963
3970
  const potentialWildLine = [];
3964
3971
  let isInterrupted = false;
3965
- for (const [ridx, reel] of reels.entries()) {
3972
+ for (let ridx = 0; ridx < reelsLength; ridx++) {
3973
+ const reel = reels[ridx];
3966
3974
  const sidx = line[ridx];
3967
3975
  const thisSymbol = reel[sidx];
3968
3976
  if (!baseSymbol) {
@@ -3993,66 +4001,91 @@ var LinesWinType = class extends WinType {
3993
4001
  break;
3994
4002
  }
3995
4003
  }
3996
- const minSymLine = Math.min(
3997
- ...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
3998
- );
4004
+ const pays = baseSymbol.pays || {};
4005
+ let minSymLine = Infinity;
4006
+ for (const key in pays) {
4007
+ const num = parseInt(key, 10);
4008
+ if (num < minSymLine) minSymLine = num;
4009
+ }
3999
4010
  if (potentialWinLine.length < minSymLine) continue;
4000
4011
  const linePayout = this.getLinePayout(potentialWinLine);
4001
4012
  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
- };
4013
+ let finalLine;
4014
4014
  if (wildLinePayout > linePayout) {
4015
4015
  baseSymbol = potentialWildLine[0]?.symbol;
4016
+ const wildSymbols = [];
4017
+ const wildLineLength = potentialWildLine.length;
4018
+ for (let i = 0; i < wildLineLength; i++) {
4019
+ const s = potentialWildLine[i];
4020
+ wildSymbols.push({
4021
+ symbol: s.symbol,
4022
+ isWild: this.isWild(s.symbol),
4023
+ reelIndex: s.reel,
4024
+ posIndex: s.row
4025
+ });
4026
+ }
4016
4027
  finalLine = {
4017
- kind: potentialWildLine.length,
4028
+ kind: wildLineLength,
4018
4029
  baseSymbol,
4019
- symbols: potentialWildLine.map((s) => ({
4030
+ symbols: wildSymbols,
4031
+ lineNumber: lineNum,
4032
+ payout: wildLinePayout
4033
+ };
4034
+ } else {
4035
+ const symbols = [];
4036
+ const lineLength = potentialWinLine.length;
4037
+ for (let i = 0; i < lineLength; i++) {
4038
+ const s = potentialWinLine[i];
4039
+ symbols.push({
4020
4040
  symbol: s.symbol,
4021
4041
  isWild: this.isWild(s.symbol),
4022
4042
  reelIndex: s.reel,
4023
4043
  posIndex: s.row
4024
- })),
4044
+ });
4045
+ }
4046
+ finalLine = {
4047
+ kind: lineLength,
4048
+ baseSymbol,
4049
+ symbols,
4025
4050
  lineNumber: lineNum,
4026
- payout: wildLinePayout
4051
+ payout: linePayout
4027
4052
  };
4028
4053
  }
4029
4054
  lineWins.push(finalLine);
4030
- }
4031
- for (const win of lineWins) {
4032
4055
  this.ctx.services.data.recordSymbolOccurrence({
4033
- kind: win.kind,
4034
- symbolId: win.baseSymbol.id,
4056
+ kind: finalLine.kind,
4057
+ symbolId: finalLine.baseSymbol.id,
4035
4058
  spinType: this.ctx.state.currentSpinType
4036
4059
  });
4037
4060
  }
4038
- this.payout = lineWins.reduce((sum, l) => sum + l.payout, 0);
4061
+ let totalPayout = 0;
4062
+ for (let i = 0; i < lineWins.length; i++) {
4063
+ totalPayout += lineWins[i].payout;
4064
+ }
4065
+ this.payout = totalPayout;
4039
4066
  this.winCombinations = lineWins;
4040
4067
  return this;
4041
4068
  }
4042
4069
  getLinePayout(line) {
4043
- if (line.length === 0) return 0;
4044
- let baseSymbol = line.find((s) => !this.isWild(s.symbol))?.symbol;
4070
+ const lineLength = line.length;
4071
+ if (lineLength === 0) return 0;
4072
+ let baseSymbol;
4073
+ for (let i = 0; i < lineLength; i++) {
4074
+ const s = line[i];
4075
+ if (!this.isWild(s.symbol)) {
4076
+ baseSymbol = s.symbol;
4077
+ break;
4078
+ }
4079
+ }
4045
4080
  if (!baseSymbol) baseSymbol = line[0].symbol;
4046
- const kind = line.length;
4047
- const payout = this.getSymbolPayout(baseSymbol, kind);
4048
- return payout;
4081
+ return this.getSymbolPayout(baseSymbol, lineLength);
4049
4082
  }
4050
4083
  };
4051
4084
 
4052
4085
  // src/win-types/ClusterWinType.ts
4053
4086
  var ClusterWinType = class extends WinType {
4054
- _checked = [];
4055
- _checkedWilds = [];
4087
+ _checked = /* @__PURE__ */ new Set();
4088
+ _checkedWilds = /* @__PURE__ */ new Set();
4056
4089
  _currentBoard = [];
4057
4090
  constructor(opts) {
4058
4091
  super(opts);
@@ -4065,121 +4098,169 @@ var ClusterWinType = class extends WinType {
4065
4098
  */
4066
4099
  evaluateWins(board) {
4067
4100
  this.validateConfig();
4068
- this._checked = [];
4101
+ this._checked.clear();
4069
4102
  this._currentBoard = board;
4070
4103
  const clusterWins = [];
4071
4104
  const potentialClusters = [];
4072
- for (const [ridx, reel] of board.entries()) {
4073
- for (const [sidx, symbol] of reel.entries()) {
4074
- this._checkedWilds = [];
4105
+ const boardLength = board.length;
4106
+ for (let ridx = 0; ridx < boardLength; ridx++) {
4107
+ const reel = board[ridx];
4108
+ const reelLength = reel.length;
4109
+ for (let sidx = 0; sidx < reelLength; sidx++) {
4110
+ const symbol = reel[sidx];
4111
+ this._checkedWilds.clear();
4075
4112
  if (this.isWild(symbol)) continue;
4076
- if (this.isChecked(ridx, sidx)) {
4113
+ const posKey = ridx * 1e4 + sidx;
4114
+ if (this._checked.has(posKey)) {
4077
4115
  continue;
4078
4116
  }
4079
4117
  const thisSymbol = { reel: ridx, row: sidx, symbol };
4080
- this._checked.push(thisSymbol);
4118
+ this._checked.add(posKey);
4081
4119
  const neighbors = this.getNeighbors(ridx, sidx);
4082
4120
  const matchingSymbols = this.evaluateCluster(symbol, neighbors);
4083
- if (matchingSymbols.size >= 1) {
4084
- potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
4121
+ const matchingSize = matchingSymbols.size;
4122
+ if (matchingSize >= 1) {
4123
+ const cluster = [thisSymbol];
4124
+ for (const sym of matchingSymbols.values()) {
4125
+ cluster.push(sym);
4126
+ }
4127
+ potentialClusters.push(cluster);
4085
4128
  }
4086
4129
  }
4087
4130
  }
4088
- for (const [ridx, reel] of board.entries()) {
4089
- for (const [sidx, symbol] of reel.entries()) {
4090
- this._checkedWilds = [];
4131
+ for (let ridx = 0; ridx < boardLength; ridx++) {
4132
+ const reel = board[ridx];
4133
+ const reelLength = reel.length;
4134
+ for (let sidx = 0; sidx < reelLength; sidx++) {
4135
+ const symbol = reel[sidx];
4136
+ this._checkedWilds.clear();
4091
4137
  if (!this.isWild(symbol)) continue;
4092
- if (this.isChecked(ridx, sidx)) {
4138
+ const posKey = ridx * 1e4 + sidx;
4139
+ if (this._checked.has(posKey)) {
4093
4140
  continue;
4094
4141
  }
4095
4142
  const thisSymbol = { reel: ridx, row: sidx, symbol };
4096
- this._checked.push(thisSymbol);
4143
+ this._checked.add(posKey);
4097
4144
  const neighbors = this.getNeighbors(ridx, sidx);
4098
4145
  const matchingSymbols = this.evaluateCluster(symbol, neighbors);
4099
- if (matchingSymbols.size >= 1) {
4100
- potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
4146
+ const matchingSize = matchingSymbols.size;
4147
+ if (matchingSize >= 1) {
4148
+ const cluster = [thisSymbol];
4149
+ for (const sym of matchingSymbols.values()) {
4150
+ cluster.push(sym);
4151
+ }
4152
+ potentialClusters.push(cluster);
4101
4153
  }
4102
4154
  }
4103
4155
  }
4104
- for (const cluster of potentialClusters) {
4156
+ const numClusters = potentialClusters.length;
4157
+ for (let i = 0; i < numClusters; i++) {
4158
+ const cluster = potentialClusters[i];
4105
4159
  const kind = cluster.length;
4106
- let baseSymbol = cluster.find((s) => !this.isWild(s.symbol))?.symbol;
4160
+ let baseSymbol;
4161
+ for (let j = 0; j < kind; j++) {
4162
+ const sym = cluster[j].symbol;
4163
+ if (!this.isWild(sym)) {
4164
+ baseSymbol = sym;
4165
+ break;
4166
+ }
4167
+ }
4107
4168
  if (!baseSymbol) baseSymbol = cluster[0].symbol;
4108
4169
  const payout = this.getSymbolPayout(baseSymbol, kind);
4109
4170
  if (payout === 0) continue;
4110
- if (!baseSymbol.pays || Object.keys(baseSymbol.pays).length === 0) {
4111
- continue;
4171
+ const pays = baseSymbol.pays;
4172
+ if (!pays) continue;
4173
+ let hasPays = false;
4174
+ for (const _ in pays) {
4175
+ hasPays = true;
4176
+ break;
4112
4177
  }
4113
- clusterWins.push({
4114
- payout,
4115
- kind,
4116
- baseSymbol,
4117
- symbols: cluster.map((s) => ({
4178
+ if (!hasPays) continue;
4179
+ const symbols = [];
4180
+ for (let j = 0; j < kind; j++) {
4181
+ const s = cluster[j];
4182
+ symbols.push({
4118
4183
  symbol: s.symbol,
4119
4184
  isWild: this.isWild(s.symbol),
4120
4185
  reelIndex: s.reel,
4121
4186
  posIndex: s.row
4122
- }))
4187
+ });
4188
+ }
4189
+ clusterWins.push({
4190
+ payout,
4191
+ kind,
4192
+ baseSymbol,
4193
+ symbols
4123
4194
  });
4124
- }
4125
- for (const win of clusterWins) {
4126
4195
  this.ctx.services.data.recordSymbolOccurrence({
4127
- kind: win.kind,
4128
- symbolId: win.baseSymbol.id,
4196
+ kind,
4197
+ symbolId: baseSymbol.id,
4129
4198
  spinType: this.ctx.state.currentSpinType
4130
4199
  });
4131
4200
  }
4132
- this.payout = clusterWins.reduce((sum, c) => sum + c.payout, 0);
4201
+ let totalPayout = 0;
4202
+ for (let i = 0; i < clusterWins.length; i++) {
4203
+ totalPayout += clusterWins[i].payout;
4204
+ }
4205
+ this.payout = totalPayout;
4133
4206
  this.winCombinations = clusterWins;
4134
4207
  return this;
4135
4208
  }
4136
4209
  getNeighbors(ridx, sidx) {
4137
4210
  const board = this._currentBoard;
4138
4211
  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] });
4212
+ if (ridx > 0) {
4213
+ const leftReel = board[ridx - 1];
4214
+ const leftSymbol = leftReel[sidx];
4215
+ if (leftSymbol !== void 0) {
4216
+ neighbors.push({ reel: ridx - 1, row: sidx, symbol: leftSymbol });
4148
4217
  }
4149
- });
4218
+ }
4219
+ const rightReel = board[ridx + 1];
4220
+ if (rightReel !== void 0) {
4221
+ const rightSymbol = rightReel[sidx];
4222
+ if (rightSymbol !== void 0) {
4223
+ neighbors.push({ reel: ridx + 1, row: sidx, symbol: rightSymbol });
4224
+ }
4225
+ }
4226
+ const currentReel = board[ridx];
4227
+ const topSymbol = currentReel[sidx - 1];
4228
+ if (topSymbol !== void 0) {
4229
+ neighbors.push({ reel: ridx, row: sidx - 1, symbol: topSymbol });
4230
+ }
4231
+ const bottomSymbol = currentReel[sidx + 1];
4232
+ if (bottomSymbol !== void 0) {
4233
+ neighbors.push({ reel: ridx, row: sidx + 1, symbol: bottomSymbol });
4234
+ }
4150
4235
  return neighbors;
4151
4236
  }
4152
4237
  evaluateCluster(rootSymbol, neighbors) {
4153
4238
  const matchingSymbols = /* @__PURE__ */ new Map();
4154
- neighbors.forEach((neighbor) => {
4239
+ const numNeighbors = neighbors.length;
4240
+ for (let i = 0; i < numNeighbors; i++) {
4241
+ const neighbor = neighbors[i];
4155
4242
  const { reel, row, symbol } = neighbor;
4156
- if (this.isChecked(reel, row)) return;
4157
- if (this.isCheckedWild(reel, row)) return;
4243
+ const posKey = reel * 1e4 + row;
4244
+ if (this._checked.has(posKey)) continue;
4245
+ if (this._checkedWilds.has(posKey)) continue;
4158
4246
  if (this.isWild(symbol) || symbol.compare(rootSymbol)) {
4159
- const key = `${reel}-${row}`;
4247
+ const key = String(posKey);
4160
4248
  matchingSymbols.set(key, { reel, row, symbol });
4161
4249
  if (symbol.compare(rootSymbol)) {
4162
- this._checked.push(neighbor);
4250
+ this._checked.add(posKey);
4163
4251
  }
4164
4252
  if (this.isWild(symbol)) {
4165
- this._checkedWilds.push(neighbor);
4253
+ this._checkedWilds.add(posKey);
4166
4254
  }
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}`;
4255
+ const nestedNeighbors = this.getNeighbors(reel, row);
4256
+ const nestedMatches = this.evaluateCluster(rootSymbol, nestedNeighbors);
4257
+ for (const [nkey, nsym] of nestedMatches.entries()) {
4171
4258
  matchingSymbols.set(nkey, nsym);
4172
- });
4259
+ }
4173
4260
  }
4174
- });
4261
+ }
4175
4262
  return matchingSymbols;
4176
4263
  }
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
4264
  };
4184
4265
 
4185
4266
  // src/win-types/ManywaysWinType.ts
@@ -4198,7 +4279,7 @@ var ManywaysWinType = class extends WinType {
4198
4279
  const { jumpGaps = false } = opts;
4199
4280
  const waysWins = [];
4200
4281
  const reels = board;
4201
- const possibleWaysWins = /* @__PURE__ */ new Map();
4282
+ const numReels = reels.length;
4202
4283
  const candidateSymbols = /* @__PURE__ */ new Map();
4203
4284
  if (jumpGaps) {
4204
4285
  for (const reel of reels) {
@@ -4209,7 +4290,7 @@ var ManywaysWinType = class extends WinType {
4209
4290
  } else {
4210
4291
  let searchReelIdx = 0;
4211
4292
  let searchActive = true;
4212
- while (searchActive && searchReelIdx < reels.length) {
4293
+ while (searchActive && searchReelIdx < numReels) {
4213
4294
  const reel = reels[searchReelIdx];
4214
4295
  let hasWild = false;
4215
4296
  for (const symbol of reel) {
@@ -4225,71 +4306,78 @@ var ManywaysWinType = class extends WinType {
4225
4306
  }
4226
4307
  }
4227
4308
  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()) {
4309
+ const symbolList = [];
4310
+ let wayLength = 0;
4311
+ let firstNonWildSymbol;
4312
+ let totalWays = 1;
4313
+ for (let ridx = 0; ridx < numReels; ridx++) {
4314
+ const reel = reels[ridx];
4315
+ let reelMatches;
4316
+ for (let sidx = 0; sidx < reel.length; sidx++) {
4317
+ const symbol = reel[sidx];
4233
4318
  const isMatch = baseSymbol.compare(symbol) || this.isWild(symbol);
4234
4319
  if (isMatch) {
4235
- if (!symbolList[ridx]) {
4236
- symbolList[ridx] = [];
4320
+ if (!reelMatches) {
4321
+ reelMatches = [];
4322
+ }
4323
+ reelMatches.push({ reel: ridx, row: sidx, symbol });
4324
+ if (!firstNonWildSymbol && !this.isWild(symbol)) {
4325
+ firstNonWildSymbol = symbol;
4237
4326
  }
4238
- symbolList[ridx].push({ reel: ridx, row: sidx, symbol });
4239
4327
  }
4240
4328
  }
4241
- if (!symbolList[ridx] && !jumpGaps) {
4242
- isInterrupted = true;
4329
+ if (reelMatches) {
4330
+ symbolList[wayLength++] = reelMatches;
4331
+ totalWays *= reelMatches.length;
4332
+ } else if (!jumpGaps) {
4243
4333
  break;
4244
4334
  }
4245
4335
  }
4246
- const minSymLine = Math.min(
4247
- ...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
4248
- );
4249
- const wayLength = this.getWayLength(symbolList);
4336
+ const pays = baseSymbol.pays || {};
4337
+ let minSymLine = Infinity;
4338
+ for (const key in pays) {
4339
+ const num = parseInt(key, 10);
4340
+ if (num < minSymLine) minSymLine = num;
4341
+ }
4250
4342
  if (wayLength >= minSymLine) {
4251
- possibleWaysWins.set(baseSymbol.id, symbolList);
4343
+ const winBaseSymbol = firstNonWildSymbol || symbolList[0][0].symbol;
4344
+ const singleWayPayout = this.getSymbolPayout(winBaseSymbol, wayLength);
4345
+ const totalPayout2 = singleWayPayout * totalWays;
4346
+ const symbols = [];
4347
+ for (let i = 0; i < wayLength; i++) {
4348
+ const reelSyms = symbolList[i];
4349
+ for (let j = 0; j < reelSyms.length; j++) {
4350
+ const s = reelSyms[j];
4351
+ symbols.push({
4352
+ symbol: s.symbol,
4353
+ isWild: this.isWild(s.symbol),
4354
+ reelIndex: s.reel,
4355
+ posIndex: s.row
4356
+ });
4357
+ }
4358
+ }
4359
+ waysWins.push({
4360
+ kind: wayLength,
4361
+ baseSymbol: winBaseSymbol,
4362
+ symbols,
4363
+ ways: totalWays,
4364
+ payout: totalPayout2
4365
+ });
4366
+ this.ctx.services.data.recordSymbolOccurrence({
4367
+ kind: wayLength,
4368
+ symbolId: winBaseSymbol.id,
4369
+ spinType: this.ctx.state.currentSpinType
4370
+ });
4252
4371
  }
4253
4372
  }
4254
- for (const [, 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
- });
4278
- }
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
- });
4373
+ let totalPayout = 0;
4374
+ for (let i = 0; i < waysWins.length; i++) {
4375
+ totalPayout += waysWins[i].payout;
4285
4376
  }
4286
- this.payout = waysWins.reduce((sum, l) => sum + l.payout, 0);
4377
+ this.payout = totalPayout;
4287
4378
  this.winCombinations = waysWins;
4288
4379
  return this;
4289
4380
  }
4290
- getWayLength(symbolList) {
4291
- return Object.keys(symbolList).length;
4292
- }
4293
4381
  };
4294
4382
 
4295
4383
  // src/reel-set/GeneratedReelSet.ts