@slot-engine/core 0.0.2 → 0.0.3

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
@@ -1,4 +1,5 @@
1
1
  // src/GameConfig.ts
2
+ import assert from "assert";
2
3
  var GameConfig = class _GameConfig {
3
4
  config;
4
5
  constructor(opts) {
@@ -22,14 +23,12 @@ var GameConfig = class _GameConfig {
22
23
  userState: opts.userState,
23
24
  outputDir: "__build__"
24
25
  };
25
- for (const symbol of opts.symbols) {
26
- if (!this.config.symbols.has(symbol.id)) {
27
- this.config.symbols.set(symbol.id, symbol);
28
- } else {
29
- console.warn(
30
- `Symbol with id "${symbol.id}" already exists in the game config. Skipping duplicate. This is probably not intentional.`
31
- );
32
- }
26
+ for (const [key, value] of Object.entries(opts.symbols)) {
27
+ assert(
28
+ value.id === key,
29
+ `Symbol key "${key}" does not match symbol id "${value.id}"`
30
+ );
31
+ this.config.symbols.set(key, value);
33
32
  }
34
33
  function getAnticipationTrigger(spinType) {
35
34
  return Math.min(...Object.keys(opts.scatterToFreespins[spinType]).map(Number)) - 1;
@@ -1773,7 +1772,7 @@ var ManywaysWinType = class extends WinType {
1773
1772
  };
1774
1773
 
1775
1774
  // src/optimizer/OptimizationConditions.ts
1776
- import assert from "assert";
1775
+ import assert2 from "assert";
1777
1776
  var OptimizationConditions = class {
1778
1777
  rtp;
1779
1778
  avgWin;
@@ -1784,14 +1783,14 @@ var OptimizationConditions = class {
1784
1783
  constructor(opts) {
1785
1784
  let { rtp, avgWin, hitRate, searchConditions, priority } = opts;
1786
1785
  if (rtp == void 0 || rtp === "x") {
1787
- assert(avgWin !== void 0 && hitRate !== void 0, "If RTP is not specified, hit-rate (hr) and average win amount (av_win) must be given.");
1786
+ assert2(avgWin !== void 0 && hitRate !== void 0, "If RTP is not specified, hit-rate (hr) and average win amount (av_win) must be given.");
1788
1787
  rtp = Math.round(avgWin / Number(hitRate) * 1e5) / 1e5;
1789
1788
  }
1790
1789
  let noneCount = 0;
1791
1790
  for (const val of [rtp, avgWin, hitRate]) {
1792
1791
  if (val === void 0) noneCount++;
1793
1792
  }
1794
- assert(noneCount <= 1, "Invalid combination of optimization conditions.");
1793
+ assert2(noneCount <= 1, "Invalid combination of optimization conditions.");
1795
1794
  this.searchRange = [-1, -1];
1796
1795
  this.forceSearch = {};
1797
1796
  if (typeof searchConditions === "number") {
@@ -1871,41 +1870,11 @@ var OptimizationParameters = class _OptimizationParameters {
1871
1870
 
1872
1871
  // src/Simulation.ts
1873
1872
  import fs3 from "fs";
1874
- import path3 from "path";
1875
- import assert2 from "assert";
1873
+ import path2 from "path";
1874
+ import assert3 from "assert";
1875
+ import zlib from "zlib";
1876
1876
  import { buildSync } from "esbuild";
1877
1877
  import { Worker, isMainThread as isMainThread2, parentPort, workerData } from "worker_threads";
1878
-
1879
- // src/utils/zstd.ts
1880
- import path2 from "path";
1881
- import { spawn } from "child_process";
1882
- async function zstd(...args) {
1883
- return new Promise((resolve, reject) => {
1884
- const task = spawn(path2.join(__dirname, "./lib/zstd.exe"), args);
1885
- task.on("error", (error) => {
1886
- console.error("Error:", error);
1887
- reject(error);
1888
- });
1889
- task.on("exit", () => {
1890
- resolve(true);
1891
- });
1892
- task.on("close", () => {
1893
- resolve(true);
1894
- });
1895
- task.stdout.on("data", (data) => {
1896
- console.log(data.toString());
1897
- });
1898
- task.stderr.on("data", (data) => {
1899
- console.log(data.toString());
1900
- });
1901
- task.stdout.on("error", (data) => {
1902
- console.log(data.toString());
1903
- reject(data.toString());
1904
- });
1905
- });
1906
- }
1907
-
1908
- // src/Simulation.ts
1909
1878
  var completedSimulations = 0;
1910
1879
  var TEMP_FILENAME = "__temp_compiled_src_IGNORE.js";
1911
1880
  var Simulation = class _Simulation {
@@ -1926,7 +1895,7 @@ var Simulation = class _Simulation {
1926
1895
  this.library = /* @__PURE__ */ new Map();
1927
1896
  this.records = [];
1928
1897
  const gameModeKeys = Object.keys(this.gameConfig.config.gameModes);
1929
- assert2(
1898
+ assert3(
1930
1899
  Object.values(this.gameConfig.config.gameModes).map((m) => gameModeKeys.includes(m.name)).every((v) => v === true),
1931
1900
  "Game mode name must match its key in the gameModes object."
1932
1901
  );
@@ -1963,14 +1932,14 @@ Simulating game mode: ${mode}`);
1963
1932
  const simNumsToCriteria = ResultSet.assignCriteriaToSimulations(this, mode);
1964
1933
  await this.spawnWorkersForGameMode({ mode, simNumsToCriteria });
1965
1934
  createDirIfNotExists(
1966
- path3.join(
1935
+ path2.join(
1967
1936
  process.cwd(),
1968
1937
  this.gameConfig.config.outputDir,
1969
1938
  "optimization_files"
1970
1939
  )
1971
1940
  );
1972
1941
  createDirIfNotExists(
1973
- path3.join(process.cwd(), this.gameConfig.config.outputDir, "publish_files")
1942
+ path2.join(process.cwd(), this.gameConfig.config.outputDir, "publish_files")
1974
1943
  );
1975
1944
  _Simulation.writeLookupTableCSV({
1976
1945
  gameMode: mode,
@@ -2066,7 +2035,7 @@ Simulating game mode: ${mode}`);
2066
2035
  }
2067
2036
  }
2068
2037
  return new Promise((resolve, reject) => {
2069
- const scriptPath = path3.join(process.cwd(), basePath, TEMP_FILENAME);
2038
+ const scriptPath = path2.join(process.cwd(), basePath, TEMP_FILENAME);
2070
2039
  const worker = new Worker(scriptPath, {
2071
2040
  workerData: {
2072
2041
  mode,
@@ -2114,10 +2083,10 @@ Simulating game mode: ${mode}`);
2114
2083
  }
2115
2084
  rows.sort((a, b) => Number(a.split(",")[0]) - Number(b.split(",")[0]));
2116
2085
  let outputFileName = `lookUpTable_${gameMode}.csv`;
2117
- let outputFilePath = path3.join(gameConfig.outputDir, outputFileName);
2086
+ let outputFilePath = path2.join(gameConfig.outputDir, outputFileName);
2118
2087
  writeFile(outputFilePath, rows.join("\n"));
2119
2088
  outputFileName = `lookUpTable_${gameMode}_0.csv`;
2120
- outputFilePath = path3.join(gameConfig.outputDir, outputFileName);
2089
+ outputFilePath = path2.join(gameConfig.outputDir, outputFileName);
2121
2090
  writeFile(outputFilePath, rows.join("\n"));
2122
2091
  return outputFilePath;
2123
2092
  }
@@ -2134,21 +2103,21 @@ Simulating game mode: ${mode}`);
2134
2103
  }
2135
2104
  rows.sort((a, b) => Number(a.split(",")[0]) - Number(b.split(",")[0]));
2136
2105
  const outputFileName = `lookUpTableSegmented_${gameMode}.csv`;
2137
- const outputFilePath = path3.join(gameConfig.outputDir, outputFileName);
2106
+ const outputFilePath = path2.join(gameConfig.outputDir, outputFileName);
2138
2107
  writeFile(outputFilePath, rows.join("\n"));
2139
2108
  return outputFilePath;
2140
2109
  }
2141
2110
  static writeRecords(opts) {
2142
2111
  const { gameMode, fileNameWithoutExtension, records, gameConfig, debug } = opts;
2143
2112
  const outputFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.json` : `force_record_${gameMode}.json`;
2144
- const outputFilePath = path3.join(gameConfig.outputDir, outputFileName);
2113
+ const outputFilePath = path2.join(gameConfig.outputDir, outputFileName);
2145
2114
  writeFile(outputFilePath, JSON.stringify(records, null, 2));
2146
2115
  if (debug) _Simulation.logSymbolOccurrences(records);
2147
2116
  return outputFilePath;
2148
2117
  }
2149
2118
  static writeIndexJson(opts) {
2150
2119
  const { gameConfig } = opts;
2151
- const outputFilePath = path3.join(
2120
+ const outputFilePath = path2.join(
2152
2121
  process.cwd(),
2153
2122
  gameConfig.outputDir,
2154
2123
  "publish_files",
@@ -2165,7 +2134,7 @@ Simulating game mode: ${mode}`);
2165
2134
  static async writeBooksJson(opts) {
2166
2135
  const { gameMode, fileNameWithoutExtension, library, gameConfig } = opts;
2167
2136
  const outputFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.jsonl` : `books_${gameMode}.jsonl`;
2168
- const outputFilePath = path3.join(gameConfig.outputDir, outputFileName);
2137
+ const outputFilePath = path2.join(gameConfig.outputDir, outputFileName);
2169
2138
  const books = Array.from(library.values()).map((b) => b.serialize()).map((b) => ({
2170
2139
  id: b.id,
2171
2140
  payoutMultiplier: b.payout,
@@ -2174,14 +2143,15 @@ Simulating game mode: ${mode}`);
2174
2143
  const contents = JSONL.stringify(books);
2175
2144
  writeFile(outputFilePath, contents);
2176
2145
  const compressedFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.jsonl.zst` : `books_${gameMode}.jsonl.zst`;
2177
- const compressedFilePath = path3.join(
2146
+ const compressedFilePath = path2.join(
2178
2147
  process.cwd(),
2179
2148
  gameConfig.outputDir,
2180
2149
  "publish_files",
2181
2150
  compressedFileName
2182
2151
  );
2183
2152
  fs3.rmSync(compressedFilePath, { force: true });
2184
- await zstd("-f", outputFilePath, "-o", compressedFilePath);
2153
+ const compressed = zlib.zstdCompressSync(Buffer.from(contents));
2154
+ fs3.writeFileSync(compressedFilePath, compressed);
2185
2155
  }
2186
2156
  static logSymbolOccurrences(records) {
2187
2157
  const validRecords = records.filter(
@@ -2212,14 +2182,14 @@ Simulating game mode: ${mode}`);
2212
2182
  * Compiles user configured game to JS for use in different Node processes
2213
2183
  */
2214
2184
  preprocessFiles() {
2215
- const builtFilePath = path3.join(this.gameConfig.config.outputDir, TEMP_FILENAME);
2185
+ const builtFilePath = path2.join(this.gameConfig.config.outputDir, TEMP_FILENAME);
2216
2186
  fs3.rmSync(builtFilePath, { force: true });
2217
2187
  buildSync({
2218
2188
  entryPoints: [process.cwd()],
2219
2189
  bundle: true,
2220
2190
  platform: "node",
2221
- outfile: path3.join(this.gameConfig.config.outputDir, TEMP_FILENAME),
2222
- external: ["esbuild", "@mongodb-js/zstd"]
2191
+ outfile: path2.join(this.gameConfig.config.outputDir, TEMP_FILENAME),
2192
+ external: ["esbuild"]
2223
2193
  });
2224
2194
  }
2225
2195
  getSimRangesForChunks(total, chunks) {
@@ -2327,8 +2297,8 @@ var SimulationContext = class extends Board {
2327
2297
 
2328
2298
  // src/analysis/index.ts
2329
2299
  import fs4 from "fs";
2330
- import path4 from "path";
2331
- import assert3 from "assert";
2300
+ import path3 from "path";
2301
+ import assert4 from "assert";
2332
2302
 
2333
2303
  // src/analysis/utils.ts
2334
2304
  function parseLookupTable(content) {
@@ -2372,8 +2342,11 @@ function getPayoutWeights(lut, opts = {}) {
2372
2342
  }
2373
2343
  function getNonZeroHitrate(payoutWeights) {
2374
2344
  const totalWeight = getTotalWeight(payoutWeights);
2375
- const nonZeroWeight = totalWeight - (payoutWeights[0] ?? 0) * totalWeight;
2376
- return nonZeroWeight / totalWeight;
2345
+ if (Math.min(...Object.keys(payoutWeights).map(Number)) == 0) {
2346
+ return totalWeight / (totalWeight - (payoutWeights[0] ?? 0) / totalWeight);
2347
+ } else {
2348
+ return 1;
2349
+ }
2377
2350
  }
2378
2351
  function getNullHitrate(payoutWeights) {
2379
2352
  return payoutWeights[0] ?? 0;
@@ -2434,6 +2407,7 @@ function getLessBetHitrate(payoutWeights, cost) {
2434
2407
  }
2435
2408
 
2436
2409
  // src/analysis/index.ts
2410
+ import { isMainThread as isMainThread3 } from "worker_threads";
2437
2411
  var Analysis = class {
2438
2412
  gameConfig;
2439
2413
  optimizerConfig;
@@ -2444,6 +2418,7 @@ var Analysis = class {
2444
2418
  this.filePaths = {};
2445
2419
  }
2446
2420
  async runAnalysis(gameModes) {
2421
+ if (!isMainThread3) return;
2447
2422
  this.filePaths = this.getPathsForModes(gameModes);
2448
2423
  this.getNumberStats(gameModes);
2449
2424
  this.getWinRanges(gameModes);
@@ -2453,28 +2428,28 @@ var Analysis = class {
2453
2428
  const rootPath = process.cwd();
2454
2429
  const paths = {};
2455
2430
  for (const modeStr of gameModes) {
2456
- const lut = path4.join(
2431
+ const lut = path3.join(
2457
2432
  rootPath,
2458
2433
  this.gameConfig.outputDir,
2459
2434
  `lookUpTable_${modeStr}.csv`
2460
2435
  );
2461
- const lutSegmented = path4.join(
2436
+ const lutSegmented = path3.join(
2462
2437
  rootPath,
2463
2438
  this.gameConfig.outputDir,
2464
2439
  `lookUpTableSegmented_${modeStr}.csv`
2465
2440
  );
2466
- const lutOptimized = path4.join(
2441
+ const lutOptimized = path3.join(
2467
2442
  rootPath,
2468
2443
  this.gameConfig.outputDir,
2469
2444
  "publish_files",
2470
2445
  `lookUpTable_${modeStr}_0.csv`
2471
2446
  );
2472
- const booksJsonl = path4.join(
2447
+ const booksJsonl = path3.join(
2473
2448
  rootPath,
2474
2449
  this.gameConfig.outputDir,
2475
2450
  `books_${modeStr}.jsonl`
2476
2451
  );
2477
- const booksJsonlCompressed = path4.join(
2452
+ const booksJsonlCompressed = path3.join(
2478
2453
  rootPath,
2479
2454
  this.gameConfig.outputDir,
2480
2455
  "publish_files",
@@ -2488,7 +2463,7 @@ var Analysis = class {
2488
2463
  booksJsonlCompressed
2489
2464
  };
2490
2465
  for (const p of Object.values(paths[modeStr])) {
2491
- assert3(
2466
+ assert4(
2492
2467
  fs4.existsSync(p),
2493
2468
  `File "${p}" does not exist. Run optimization to auto-create it.`
2494
2469
  );
@@ -2522,7 +2497,7 @@ var Analysis = class {
2522
2497
  });
2523
2498
  }
2524
2499
  writeJsonFile(
2525
- path4.join(process.cwd(), this.gameConfig.outputDir, "stats_summary.json"),
2500
+ path3.join(process.cwd(), this.gameConfig.outputDir, "stats_summary.json"),
2526
2501
  stats
2527
2502
  );
2528
2503
  }
@@ -2560,13 +2535,13 @@ var Analysis = class {
2560
2535
  }
2561
2536
  getGameModeConfig(mode) {
2562
2537
  const config = this.gameConfig.gameModes[mode];
2563
- assert3(config, `Game mode "${mode}" not found in game config`);
2538
+ assert4(config, `Game mode "${mode}" not found in game config`);
2564
2539
  return config;
2565
2540
  }
2566
2541
  };
2567
2542
 
2568
2543
  // src/utils/math-config.ts
2569
- import path5 from "path";
2544
+ import path4 from "path";
2570
2545
  function makeMathConfig(optimizer, opts = {}) {
2571
2546
  const game = optimizer.getGameConfig();
2572
2547
  const gameModesCfg = optimizer.getOptimizerGameModes();
@@ -2610,14 +2585,14 @@ function makeMathConfig(optimizer, opts = {}) {
2610
2585
  }))
2611
2586
  };
2612
2587
  if (writeToFile) {
2613
- const outPath = path5.join(process.cwd(), game.outputDir, "math_config.json");
2588
+ const outPath = path4.join(process.cwd(), game.outputDir, "math_config.json");
2614
2589
  writeJsonFile(outPath, config);
2615
2590
  }
2616
2591
  return config;
2617
2592
  }
2618
2593
 
2619
2594
  // src/utils/setup-file.ts
2620
- import path6 from "path";
2595
+ import path5 from "path";
2621
2596
  function makeSetupFile(optimizer, gameMode) {
2622
2597
  const gameConfig = optimizer.getGameConfig();
2623
2598
  const optimizerGameModes = optimizer.getOptimizerGameModes();
@@ -2653,19 +2628,19 @@ function makeSetupFile(optimizer, gameMode) {
2653
2628
  `;
2654
2629
  content += `simulation_trials;${params.simulationTrials}
2655
2630
  `;
2656
- content += `user_game_build_path;${path6.join(process.cwd(), gameConfig.outputDir)}
2631
+ content += `user_game_build_path;${path5.join(process.cwd(), gameConfig.outputDir)}
2657
2632
  `;
2658
2633
  content += `pmb_rtp;${params.pmbRtp}
2659
2634
  `;
2660
- const outPath = path6.join(__dirname, "./optimizer-rust/src", "setup.txt");
2635
+ const outPath = path5.join(__dirname, "./optimizer-rust/src", "setup.txt");
2661
2636
  writeFile(outPath, content);
2662
2637
  }
2663
2638
 
2664
2639
  // src/optimizer/index.ts
2665
- import { spawn as spawn2 } from "child_process";
2666
- import path7 from "path";
2667
- import assert4 from "assert";
2668
- import { isMainThread as isMainThread3 } from "worker_threads";
2640
+ import { spawn } from "child_process";
2641
+ import path6 from "path";
2642
+ import assert5 from "assert";
2643
+ import { isMainThread as isMainThread4 } from "worker_threads";
2669
2644
  var Optimizer = class {
2670
2645
  gameConfig;
2671
2646
  gameModes;
@@ -2678,12 +2653,13 @@ var Optimizer = class {
2678
2653
  * Runs the optimization process, and runs analysis after.
2679
2654
  */
2680
2655
  async runOptimization({ gameModes }) {
2681
- if (!isMainThread3) return;
2656
+ if (!isMainThread4) return;
2682
2657
  const mathConfig = makeMathConfig(this, { writeToFile: true });
2683
2658
  for (const mode of gameModes) {
2684
2659
  const setupFile = makeSetupFile(this, mode);
2685
2660
  await this.runSingleOptimization();
2686
2661
  }
2662
+ console.log("Optimization complete. Files written to build directory.");
2687
2663
  }
2688
2664
  async runSingleOptimization() {
2689
2665
  return await rustProgram();
@@ -2707,7 +2683,7 @@ var Optimizer = class {
2707
2683
  }
2708
2684
  }
2709
2685
  const criteria = configMode.resultSets.map((r) => r.criteria);
2710
- assert4(
2686
+ assert5(
2711
2687
  conditions.every((c) => criteria.includes(c)),
2712
2688
  `Not all ResultSet criteria in game mode "${k}" are defined as optimization conditions.`
2713
2689
  );
@@ -2719,7 +2695,7 @@ var Optimizer = class {
2719
2695
  }
2720
2696
  gameModeRtp = Math.round(gameModeRtp * 1e3) / 1e3;
2721
2697
  paramRtp = Math.round(paramRtp * 1e3) / 1e3;
2722
- assert4(
2698
+ assert5(
2723
2699
  gameModeRtp === paramRtp,
2724
2700
  `Sum of all RTP conditions (${paramRtp}) does not match the game mode RTP (${gameModeRtp}) in game mode "${k}".`
2725
2701
  );
@@ -2734,9 +2710,9 @@ var Optimizer = class {
2734
2710
  };
2735
2711
  async function rustProgram(...args) {
2736
2712
  return new Promise((resolve, reject) => {
2737
- const task = spawn2("cargo", ["run", "--release", ...args], {
2713
+ const task = spawn("cargo", ["run", "--release", ...args], {
2738
2714
  shell: true,
2739
- cwd: path7.join(__dirname, "./optimizer-rust"),
2715
+ cwd: path6.join(__dirname, "./optimizer-rust"),
2740
2716
  stdio: "pipe"
2741
2717
  });
2742
2718
  task.on("error", (error) => {