@slot-engine/core 0.2.0 → 0.2.2

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
@@ -31,6 +31,7 @@ function createPermanentFilePaths(basePath) {
31
31
  `books_${mode}_chunk_${worker}-${chunk}.jsonl.zst`
32
32
  ),
33
33
  booksCompressed: (mode) => path.join(basePath, "publish_files", `books_${mode}.jsonl.zst`),
34
+ booksUncompressed: (mode) => path.join(basePath, `books_${mode}.jsonl`),
34
35
  lookupTable: (mode) => path.join(basePath, `lookUpTable_${mode}.csv`),
35
36
  lookupTableIndex: (mode) => path.join(basePath, `lookUpTable_${mode}.index`),
36
37
  lookupTableSegmented: (mode) => path.join(basePath, `lookUpTableSegmented_${mode}.csv`),
@@ -428,8 +429,18 @@ var Board = class {
428
429
  * Used for triggering anticipation effects.
429
430
  */
430
431
  anticipation;
432
+ /**
433
+ * The most recent stop positions for the reels.
434
+ */
431
435
  lastDrawnReelStops;
436
+ /**
437
+ * The reel set used in the most recent draw.
438
+ */
432
439
  lastUsedReels;
440
+ /**
441
+ * Indicates whether each reel is locked or not.
442
+ */
443
+ reelsLocked;
433
444
  constructor() {
434
445
  this.reels = [];
435
446
  this.paddingTop = [];
@@ -437,6 +448,7 @@ var Board = class {
437
448
  this.anticipation = [];
438
449
  this.lastDrawnReelStops = [];
439
450
  this.lastUsedReels = [];
451
+ this.reelsLocked = [];
440
452
  }
441
453
  getSymbol(reelIndex, rowIndex) {
442
454
  return this.reels[reelIndex]?.[rowIndex];
@@ -584,14 +596,19 @@ var Board = class {
584
596
  return reelSet;
585
597
  }
586
598
  resetReels(opts) {
587
- const length = opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount;
599
+ const { ctx, reelsAmount, reelsLocked } = opts;
600
+ const length = reelsAmount ?? ctx.services.game.getCurrentGameMode().reelsAmount;
588
601
  this.reels = this.makeEmptyReels(opts);
589
602
  this.anticipation = Array.from({ length }, () => false);
603
+ this.reelsLocked = reelsLocked ?? Array.from({ length }, () => false);
590
604
  this.paddingTop = this.makeEmptyReels(opts);
591
605
  this.paddingBottom = this.makeEmptyReels(opts);
592
606
  }
593
607
  drawBoardMixed(opts) {
594
- this.resetReels(opts);
608
+ this.resetReels({
609
+ ...opts,
610
+ ...this.reelsLocked.length && { reelsLocked: this.reelsLocked }
611
+ });
595
612
  const reelsAmount = opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount;
596
613
  const symbolsPerReel = opts.symbolsPerReel ?? opts.ctx.services.game.getCurrentGameMode().symbolsPerReel;
597
614
  const padSymbols = opts.padSymbols ?? opts.ctx.config.padSymbols;
@@ -620,6 +637,18 @@ var Board = class {
620
637
  );
621
638
  }
622
639
  }
640
+ if (this.reelsLocked.some((locked) => locked) && this.lastDrawnReelStops.length == 0) {
641
+ throw new Error(
642
+ "Cannot draw board with locked reels before drawing it at least once."
643
+ );
644
+ }
645
+ if (this.reelsLocked.some((locked) => locked)) {
646
+ for (let ridx = 0; ridx < reelsAmount; ridx++) {
647
+ if (this.reelsLocked[ridx]) {
648
+ finalReelStops[ridx] = this.lastDrawnReelStops[ridx];
649
+ }
650
+ }
651
+ }
623
652
  this.lastDrawnReelStops = finalReelStops.map((pos) => pos);
624
653
  this.lastUsedReels = opts.reels;
625
654
  for (let ridx = 0; ridx < reelsAmount; ridx++) {
@@ -781,6 +810,9 @@ var BoardService = class extends AbstractService {
781
810
  getAnticipation() {
782
811
  return this.board.anticipation;
783
812
  }
813
+ getLockedReels() {
814
+ return this.board.reelsLocked;
815
+ }
784
816
  /**
785
817
  * Gets the symbol at the specified reel and row index.
786
818
  */
@@ -810,6 +842,12 @@ var BoardService = class extends AbstractService {
810
842
  setAnticipationForReel(reelIndex, value) {
811
843
  this.board.anticipation[reelIndex] = value;
812
844
  }
845
+ /**
846
+ * Sets the locked state for a specific reel.
847
+ */
848
+ setReelLocked(reelIndex, value) {
849
+ this.board.reelsLocked[reelIndex] = value;
850
+ }
813
851
  /**
814
852
  * Counts how many symbols matching the criteria are on a specific reel.
815
853
  */
@@ -1751,6 +1789,8 @@ var TerminalUi = class {
1751
1789
  logs = [];
1752
1790
  logScrollOffset = 0;
1753
1791
  isScrolled = false;
1792
+ maxLogs = 500;
1793
+ totalLogs = 0;
1754
1794
  minWidth = 50;
1755
1795
  minHeight = 12;
1756
1796
  isRendering = false;
@@ -1776,12 +1816,13 @@ var TerminalUi = class {
1776
1816
  this.scrollDown();
1777
1817
  } else if (key === "l") {
1778
1818
  this.scrollToBottom();
1779
- } else if (key === "") {
1819
+ } else if (key === "q" || key === "") {
1780
1820
  this.stop();
1781
1821
  process.exit(0);
1782
1822
  }
1783
1823
  };
1784
1824
  process.stdout.on("resize", this.resizeHandler);
1825
+ process.on("SIGINT", this.sigintHandler);
1785
1826
  }
1786
1827
  get terminalWidth() {
1787
1828
  return process.stdout.columns || 80;
@@ -1803,7 +1844,6 @@ var TerminalUi = class {
1803
1844
  }
1804
1845
  this.render();
1805
1846
  this.renderInterval = setInterval(() => this.render(), 100);
1806
- process.on("SIGINT", this.sigintHandler);
1807
1847
  }
1808
1848
  stop() {
1809
1849
  if (this.renderInterval) {
@@ -1831,7 +1871,16 @@ var TerminalUi = class {
1831
1871
  this.totalSims = opts.totalSims;
1832
1872
  }
1833
1873
  log(message) {
1834
- this.logs.push({ i: this.logs.length, m: message });
1874
+ this.logs.push({ i: this.totalLogs, m: message });
1875
+ this.totalLogs++;
1876
+ if (this.logs.length > this.maxLogs) {
1877
+ const excess = this.logs.length - this.maxLogs;
1878
+ this.logs.splice(0, excess);
1879
+ this.logScrollOffset = Math.min(
1880
+ this.logScrollOffset,
1881
+ Math.max(0, this.logs.length - this.getLogAreaHeight())
1882
+ );
1883
+ }
1835
1884
  if (!this.isScrolled) this.scrollToBottom();
1836
1885
  }
1837
1886
  scrollUp(lines = 1) {
@@ -1996,6 +2045,7 @@ var Simulation = class {
1996
2045
  gameConfig;
1997
2046
  simRunsAmount;
1998
2047
  concurrency;
2048
+ makeUncompressedBooks;
1999
2049
  debug = false;
2000
2050
  actualSims = 0;
2001
2051
  wallet = new Wallet();
@@ -2024,6 +2074,7 @@ var Simulation = class {
2024
2074
  const { config, metadata } = createGameConfig(gameConfigOpts);
2025
2075
  this.gameConfig = { ...config, ...metadata };
2026
2076
  this.gameConfigOpts = gameConfigOpts;
2077
+ this.makeUncompressedBooks = opts.makeUncompressedBooks || false;
2027
2078
  this.simRunsAmount = opts.simRunsAmount || {};
2028
2079
  this.concurrency = (opts.concurrency || 6) >= 2 ? opts.concurrency || 6 : 2;
2029
2080
  this.maxPendingSims = opts.maxPendingSims ?? 25;
@@ -2101,9 +2152,7 @@ var Simulation = class {
2101
2152
  const startTime = Date.now();
2102
2153
  statusMessage = `Simulating mode "${mode}" with ${this.simRunsAmount[mode]} runs.`;
2103
2154
  this.tui?.log(statusMessage);
2104
- if (this.socket && this.panelActive) {
2105
- this.socket.emit("simulationStatus", statusMessage);
2106
- }
2155
+ this.sendSimulationStatus(statusMessage);
2107
2156
  const runs = this.simRunsAmount[mode] || 0;
2108
2157
  if (runs <= 0) continue;
2109
2158
  if (!configuredGameModes.includes(mode)) {
@@ -2140,9 +2189,7 @@ var Simulation = class {
2140
2189
  createDirIfNotExists(this.PATHS.publishFiles);
2141
2190
  statusMessage = `Writing final files for game mode "${mode}". This may take a while...`;
2142
2191
  this.tui?.log(statusMessage);
2143
- if (this.socket && this.panelActive) {
2144
- this.socket.emit("simulationStatus", statusMessage);
2145
- }
2192
+ this.sendSimulationStatus(statusMessage);
2146
2193
  writeFile(
2147
2194
  this.PATHS.booksIndexMeta(mode),
2148
2195
  JSON.stringify(
@@ -2197,13 +2244,43 @@ var Simulation = class {
2197
2244
  }
2198
2245
  await this.writeRecords(mode);
2199
2246
  this.writeIndexJson();
2247
+ if (this.makeUncompressedBooks) {
2248
+ statusMessage = `Creating decompressed book file for mode "${mode}". This may take a while...`;
2249
+ this.tui?.log(statusMessage);
2250
+ this.sendSimulationStatus(statusMessage);
2251
+ const uncompressedBooksPath = this.PATHS.booksUncompressed(mode);
2252
+ const outputStream = fs3.createWriteStream(uncompressedBooksPath, {
2253
+ highWaterMark: this.streamHighWaterMark
2254
+ });
2255
+ try {
2256
+ for (const { worker, chunks: chunks2 } of this.bookIndexMetas) {
2257
+ for (let chunk = 0; chunk < chunks2; chunk++) {
2258
+ const bookChunkPath = this.PATHS.booksChunk(mode, worker, chunk);
2259
+ if (!fs3.existsSync(bookChunkPath)) continue;
2260
+ const inputStream = fs3.createReadStream(bookChunkPath);
2261
+ const compress = zlib.createZstdDecompress();
2262
+ for await (const decompChunk of inputStream.pipe(compress)) {
2263
+ if (!outputStream.write(decompChunk)) {
2264
+ await new Promise((r) => outputStream.once("drain", () => r()));
2265
+ }
2266
+ }
2267
+ }
2268
+ }
2269
+ outputStream.end();
2270
+ await new Promise((r) => outputStream.on("finish", () => r()));
2271
+ } catch (error) {
2272
+ statusMessage = chalk2.yellow(
2273
+ `Error creating uncompressed book file: ${error.message}`
2274
+ );
2275
+ this.tui?.log(statusMessage);
2276
+ this.sendSimulationStatus(statusMessage);
2277
+ }
2278
+ }
2200
2279
  const endTime = Date.now();
2201
2280
  const prettyTime = new Date(endTime - startTime).toISOString().slice(11, -1);
2202
2281
  statusMessage = `Mode ${mode} done! Time taken: ${prettyTime}`;
2203
2282
  this.tui?.log(statusMessage);
2204
- if (this.socket && this.panelActive) {
2205
- this.socket.emit("simulationStatus", statusMessage);
2206
- }
2283
+ this.sendSimulationStatus(statusMessage);
2207
2284
  }
2208
2285
  this.tui?.stop();
2209
2286
  await this.printSimulationSummary();
@@ -2405,7 +2482,7 @@ var Simulation = class {
2405
2482
  `
2406
2483
  )
2407
2484
  ]);
2408
- if (this.bookBufferSizes.get(index) >= 12 * 1024 * 1024) {
2485
+ if (this.bookBufferSizes.get(index) >= 10 * 1024 * 1024) {
2409
2486
  await flushBookChunk();
2410
2487
  }
2411
2488
  if (this.recordsWriteStream) {
@@ -2851,6 +2928,11 @@ var Simulation = class {
2851
2928
  });
2852
2929
  }
2853
2930
  }
2931
+ sendSimulationStatus(message) {
2932
+ if (this.socket && this.panelActive) {
2933
+ this.socket.emit("simulationStatus", message);
2934
+ }
2935
+ }
2854
2936
  };
2855
2937
 
2856
2938
  // src/analysis/index.ts
@@ -4498,6 +4580,12 @@ var StandaloneBoard = class {
4498
4580
  getPaddingBottom() {
4499
4581
  return this.board.paddingBottom;
4500
4582
  }
4583
+ getAnticipation() {
4584
+ return this.board.anticipation;
4585
+ }
4586
+ getLockedReels() {
4587
+ return this.board.reelsLocked;
4588
+ }
4501
4589
  /**
4502
4590
  * Gets the symbol at the specified reel and row index.
4503
4591
  */
@@ -4527,6 +4615,12 @@ var StandaloneBoard = class {
4527
4615
  setAnticipationForReel(reelIndex, value) {
4528
4616
  this.board.anticipation[reelIndex] = value;
4529
4617
  }
4618
+ /**
4619
+ * Sets the locked state for a specific reel.
4620
+ */
4621
+ setReelLocked(reelIndex, value) {
4622
+ this.board.reelsLocked[reelIndex] = value;
4623
+ }
4530
4624
  /**
4531
4625
  * Counts how many symbols matching the criteria are on a specific reel.
4532
4626
  */