@slot-engine/core 0.1.2 → 0.1.4
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.d.mts +3 -4
- package/dist/index.d.ts +3 -4
- package/dist/index.js +176 -88
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +176 -88
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -89,8 +89,9 @@ declare class Simulation {
|
|
|
89
89
|
private debug;
|
|
90
90
|
private actualSims;
|
|
91
91
|
private library;
|
|
92
|
-
private recorder;
|
|
93
92
|
private wallet;
|
|
93
|
+
private recordsWriteStream;
|
|
94
|
+
private hasWrittenRecord;
|
|
94
95
|
constructor(opts: SimulationOptions, gameConfigOpts: GameConfigOptions);
|
|
95
96
|
runSimulation(opts: SimulationConfigOptions): Promise<void>;
|
|
96
97
|
/**
|
|
@@ -148,13 +149,11 @@ declare class Simulation {
|
|
|
148
149
|
private writeRecords;
|
|
149
150
|
private writeIndexJson;
|
|
150
151
|
private writeBooksJson;
|
|
151
|
-
private logSymbolOccurrences;
|
|
152
152
|
/**
|
|
153
153
|
* Compiles user configured game to JS for use in different Node processes
|
|
154
154
|
*/
|
|
155
155
|
private preprocessFiles;
|
|
156
156
|
private getSimRangesForChunks;
|
|
157
|
-
private mergeRecords;
|
|
158
157
|
/**
|
|
159
158
|
* Generates reelset CSV files for all game modes.
|
|
160
159
|
*/
|
|
@@ -1580,4 +1579,4 @@ interface StandaloneBoardOptions {
|
|
|
1580
1579
|
padSymbols: number;
|
|
1581
1580
|
}
|
|
1582
1581
|
|
|
1583
|
-
export { type AnyGameModes, type AnySymbols, type AnyUserData, ClusterWinType, type GameContext, type GameHooks, GameMode, GameSymbol, GeneratedReelSet, type InferGameType, LinesWinType, ManywaysWinType, OptimizationConditions, OptimizationParameters, OptimizationScaling, type Reels, ResultSet, SPIN_TYPE, type SpinType, StandaloneBoard, StaticReelSet, createSlotGame, defineGameModes, defineSymbols, defineUserState };
|
|
1582
|
+
export { type AnyGameModes, type AnySymbols, type AnyUserData, ClusterWinType, type GameContext, type GameHooks, GameMode, GameSymbol, GeneratedReelSet, type InferGameType, LinesWinType, ManywaysWinType, OptimizationConditions, OptimizationParameters, OptimizationScaling, type Reels, ResultSet, SPIN_TYPE, type SpinType, StandaloneBoard, StaticReelSet, type WinCombination, createSlotGame, defineGameModes, defineSymbols, defineUserState };
|
package/dist/index.d.ts
CHANGED
|
@@ -89,8 +89,9 @@ declare class Simulation {
|
|
|
89
89
|
private debug;
|
|
90
90
|
private actualSims;
|
|
91
91
|
private library;
|
|
92
|
-
private recorder;
|
|
93
92
|
private wallet;
|
|
93
|
+
private recordsWriteStream;
|
|
94
|
+
private hasWrittenRecord;
|
|
94
95
|
constructor(opts: SimulationOptions, gameConfigOpts: GameConfigOptions);
|
|
95
96
|
runSimulation(opts: SimulationConfigOptions): Promise<void>;
|
|
96
97
|
/**
|
|
@@ -148,13 +149,11 @@ declare class Simulation {
|
|
|
148
149
|
private writeRecords;
|
|
149
150
|
private writeIndexJson;
|
|
150
151
|
private writeBooksJson;
|
|
151
|
-
private logSymbolOccurrences;
|
|
152
152
|
/**
|
|
153
153
|
* Compiles user configured game to JS for use in different Node processes
|
|
154
154
|
*/
|
|
155
155
|
private preprocessFiles;
|
|
156
156
|
private getSimRangesForChunks;
|
|
157
|
-
private mergeRecords;
|
|
158
157
|
/**
|
|
159
158
|
* Generates reelset CSV files for all game modes.
|
|
160
159
|
*/
|
|
@@ -1580,4 +1579,4 @@ interface StandaloneBoardOptions {
|
|
|
1580
1579
|
padSymbols: number;
|
|
1581
1580
|
}
|
|
1582
1581
|
|
|
1583
|
-
export { type AnyGameModes, type AnySymbols, type AnyUserData, ClusterWinType, type GameContext, type GameHooks, GameMode, GameSymbol, GeneratedReelSet, type InferGameType, LinesWinType, ManywaysWinType, OptimizationConditions, OptimizationParameters, OptimizationScaling, type Reels, ResultSet, SPIN_TYPE, type SpinType, StandaloneBoard, StaticReelSet, createSlotGame, defineGameModes, defineSymbols, defineUserState };
|
|
1582
|
+
export { type AnyGameModes, type AnySymbols, type AnyUserData, ClusterWinType, type GameContext, type GameHooks, GameMode, GameSymbol, GeneratedReelSet, type InferGameType, LinesWinType, ManywaysWinType, OptimizationConditions, OptimizationParameters, OptimizationScaling, type Reels, ResultSet, SPIN_TYPE, type SpinType, StandaloneBoard, StaticReelSet, type WinCombination, createSlotGame, defineGameModes, defineSymbols, defineUserState };
|
package/dist/index.js
CHANGED
|
@@ -86,6 +86,7 @@ var import_fs2 = __toESM(require("fs"));
|
|
|
86
86
|
var import_path = __toESM(require("path"));
|
|
87
87
|
var import_assert6 = __toESM(require("assert"));
|
|
88
88
|
var import_zlib = __toESM(require("zlib"));
|
|
89
|
+
var import_readline2 = __toESM(require("readline"));
|
|
89
90
|
var import_esbuild = require("esbuild");
|
|
90
91
|
var import_worker_threads = require("worker_threads");
|
|
91
92
|
|
|
@@ -253,6 +254,7 @@ var RandomNumberGenerator = class {
|
|
|
253
254
|
|
|
254
255
|
// utils.ts
|
|
255
256
|
var import_fs = __toESM(require("fs"));
|
|
257
|
+
var import_readline = __toESM(require("readline"));
|
|
256
258
|
function createDirIfNotExists(dirPath) {
|
|
257
259
|
if (!import_fs.default.existsSync(dirPath)) {
|
|
258
260
|
import_fs.default.mkdirSync(dirPath, { recursive: true });
|
|
@@ -284,6 +286,29 @@ var JSONL = class {
|
|
|
284
286
|
static parse(jsonl) {
|
|
285
287
|
return jsonl.split("\n").filter((s) => s !== "").map((str) => JSON.parse(str));
|
|
286
288
|
}
|
|
289
|
+
static async convertToJson(inputPath, outputPath) {
|
|
290
|
+
const writeStream = import_fs.default.createWriteStream(outputPath, { encoding: "utf-8" });
|
|
291
|
+
writeStream.write("[\n");
|
|
292
|
+
const rl = import_readline.default.createInterface({
|
|
293
|
+
input: import_fs.default.createReadStream(inputPath),
|
|
294
|
+
crlfDelay: Infinity
|
|
295
|
+
});
|
|
296
|
+
let isFirst = true;
|
|
297
|
+
for await (const line of rl) {
|
|
298
|
+
if (line.trim() === "") continue;
|
|
299
|
+
if (!isFirst) {
|
|
300
|
+
writeStream.write(",\n");
|
|
301
|
+
}
|
|
302
|
+
writeStream.write(line);
|
|
303
|
+
isFirst = false;
|
|
304
|
+
}
|
|
305
|
+
writeStream.write("\n]");
|
|
306
|
+
writeStream.end();
|
|
307
|
+
return new Promise((resolve, reject) => {
|
|
308
|
+
writeStream.on("finish", () => resolve());
|
|
309
|
+
writeStream.on("error", reject);
|
|
310
|
+
});
|
|
311
|
+
}
|
|
287
312
|
};
|
|
288
313
|
|
|
289
314
|
// src/result-set/index.ts
|
|
@@ -1464,8 +1489,10 @@ var Wallet = class {
|
|
|
1464
1489
|
};
|
|
1465
1490
|
|
|
1466
1491
|
// src/simulation/index.ts
|
|
1492
|
+
var import_promises = require("stream/promises");
|
|
1467
1493
|
var completedSimulations = 0;
|
|
1468
1494
|
var TEMP_FILENAME = "__temp_compiled_src_IGNORE.js";
|
|
1495
|
+
var TEMP_FOLDER = "temp_files";
|
|
1469
1496
|
var Simulation = class {
|
|
1470
1497
|
gameConfigOpts;
|
|
1471
1498
|
gameConfig;
|
|
@@ -1474,15 +1501,15 @@ var Simulation = class {
|
|
|
1474
1501
|
debug = false;
|
|
1475
1502
|
actualSims = 0;
|
|
1476
1503
|
library;
|
|
1477
|
-
recorder;
|
|
1478
1504
|
wallet;
|
|
1505
|
+
recordsWriteStream;
|
|
1506
|
+
hasWrittenRecord = false;
|
|
1479
1507
|
constructor(opts, gameConfigOpts) {
|
|
1480
1508
|
this.gameConfig = createGameConfig(gameConfigOpts);
|
|
1481
1509
|
this.gameConfigOpts = gameConfigOpts;
|
|
1482
1510
|
this.simRunsAmount = opts.simRunsAmount || {};
|
|
1483
1511
|
this.concurrency = (opts.concurrency || 6) >= 2 ? opts.concurrency || 6 : 2;
|
|
1484
1512
|
this.library = /* @__PURE__ */ new Map();
|
|
1485
|
-
this.recorder = new Recorder();
|
|
1486
1513
|
this.wallet = new Wallet();
|
|
1487
1514
|
const gameModeKeys = Object.keys(this.gameConfig.gameModes);
|
|
1488
1515
|
(0, import_assert6.default)(
|
|
@@ -1508,7 +1535,7 @@ var Simulation = class {
|
|
|
1508
1535
|
completedSimulations = 0;
|
|
1509
1536
|
this.wallet = new Wallet();
|
|
1510
1537
|
this.library = /* @__PURE__ */ new Map();
|
|
1511
|
-
this.
|
|
1538
|
+
this.hasWrittenRecord = false;
|
|
1512
1539
|
debugDetails[mode] = {};
|
|
1513
1540
|
console.log(`
|
|
1514
1541
|
Simulating game mode: ${mode}`);
|
|
@@ -1520,8 +1547,59 @@ Simulating game mode: ${mode}`);
|
|
|
1520
1547
|
`Tried to simulate game mode "${mode}", but it's not configured in the game config.`
|
|
1521
1548
|
);
|
|
1522
1549
|
}
|
|
1550
|
+
const booksPath = import_path.default.join(
|
|
1551
|
+
this.gameConfig.rootDir,
|
|
1552
|
+
this.gameConfig.outputDir,
|
|
1553
|
+
`books_${mode}.jsonl`
|
|
1554
|
+
);
|
|
1555
|
+
const tempRecordsPath = import_path.default.join(
|
|
1556
|
+
this.gameConfig.rootDir,
|
|
1557
|
+
this.gameConfig.outputDir,
|
|
1558
|
+
TEMP_FOLDER,
|
|
1559
|
+
`temp_records_${mode}.jsonl`
|
|
1560
|
+
);
|
|
1561
|
+
createDirIfNotExists(
|
|
1562
|
+
import_path.default.join(this.gameConfig.rootDir, this.gameConfig.outputDir)
|
|
1563
|
+
);
|
|
1564
|
+
createDirIfNotExists(
|
|
1565
|
+
import_path.default.join(this.gameConfig.rootDir, this.gameConfig.outputDir, TEMP_FOLDER)
|
|
1566
|
+
);
|
|
1567
|
+
this.recordsWriteStream = import_fs2.default.createWriteStream(tempRecordsPath);
|
|
1523
1568
|
const simNumsToCriteria = ResultSet.assignCriteriaToSimulations(this, mode);
|
|
1524
1569
|
await this.spawnWorkersForGameMode({ mode, simNumsToCriteria });
|
|
1570
|
+
const finalBookStream = import_fs2.default.createWriteStream(booksPath);
|
|
1571
|
+
const numSims = Object.keys(simNumsToCriteria).length;
|
|
1572
|
+
const chunks = this.getSimRangesForChunks(numSims, this.concurrency);
|
|
1573
|
+
let isFirstChunk = true;
|
|
1574
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
1575
|
+
const tempBookPath = import_path.default.join(
|
|
1576
|
+
this.gameConfig.rootDir,
|
|
1577
|
+
this.gameConfig.outputDir,
|
|
1578
|
+
TEMP_FOLDER,
|
|
1579
|
+
`temp_books_${mode}_${i}.jsonl`
|
|
1580
|
+
);
|
|
1581
|
+
if (import_fs2.default.existsSync(tempBookPath)) {
|
|
1582
|
+
if (!isFirstChunk) {
|
|
1583
|
+
finalBookStream.write("\n");
|
|
1584
|
+
}
|
|
1585
|
+
const content = import_fs2.default.createReadStream(tempBookPath);
|
|
1586
|
+
for await (const chunk of content) {
|
|
1587
|
+
finalBookStream.write(chunk);
|
|
1588
|
+
}
|
|
1589
|
+
import_fs2.default.rmSync(tempBookPath);
|
|
1590
|
+
isFirstChunk = false;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
finalBookStream.end();
|
|
1594
|
+
await new Promise((resolve) => finalBookStream.on("finish", resolve));
|
|
1595
|
+
if (this.recordsWriteStream) {
|
|
1596
|
+
await new Promise((resolve) => {
|
|
1597
|
+
this.recordsWriteStream.end(() => {
|
|
1598
|
+
resolve();
|
|
1599
|
+
});
|
|
1600
|
+
});
|
|
1601
|
+
this.recordsWriteStream = void 0;
|
|
1602
|
+
}
|
|
1525
1603
|
createDirIfNotExists(
|
|
1526
1604
|
import_path.default.join(
|
|
1527
1605
|
this.gameConfig.rootDir,
|
|
@@ -1532,11 +1610,13 @@ Simulating game mode: ${mode}`);
|
|
|
1532
1610
|
createDirIfNotExists(
|
|
1533
1611
|
import_path.default.join(this.gameConfig.rootDir, this.gameConfig.outputDir, "publish_files")
|
|
1534
1612
|
);
|
|
1613
|
+
console.log(`Writing final files for game mode: ${mode} ...`);
|
|
1535
1614
|
this.writeLookupTableCSV(mode);
|
|
1536
1615
|
this.writeLookupTableSegmentedCSV(mode);
|
|
1537
1616
|
this.writeRecords(mode);
|
|
1538
1617
|
await this.writeBooksJson(mode);
|
|
1539
1618
|
this.writeIndexJson();
|
|
1619
|
+
console.log(`Mode ${mode} done!`);
|
|
1540
1620
|
debugDetails[mode].rtp = this.wallet.getCumulativeWins() / (runs * this.gameConfig.gameModes[mode].cost);
|
|
1541
1621
|
debugDetails[mode].wins = this.wallet.getCumulativeWins();
|
|
1542
1622
|
debugDetails[mode].winsPerSpinType = this.wallet.getCumulativeWinsPerSpinType();
|
|
@@ -1615,6 +1695,12 @@ Simulating game mode: ${mode}`);
|
|
|
1615
1695
|
index
|
|
1616
1696
|
}
|
|
1617
1697
|
});
|
|
1698
|
+
const tempBookPath = import_path.default.join(
|
|
1699
|
+
basePath,
|
|
1700
|
+
TEMP_FOLDER,
|
|
1701
|
+
`temp_books_${mode}_${index}.jsonl`
|
|
1702
|
+
);
|
|
1703
|
+
const bookStream = import_fs2.default.createWriteStream(tempBookPath);
|
|
1618
1704
|
worker.on("message", (msg) => {
|
|
1619
1705
|
if (msg.type === "log") {
|
|
1620
1706
|
} else if (msg.type === "complete") {
|
|
@@ -1623,9 +1709,23 @@ Simulating game mode: ${mode}`);
|
|
|
1623
1709
|
logArrowProgress(completedSimulations, totalSims);
|
|
1624
1710
|
}
|
|
1625
1711
|
const book = Book.fromSerialized(msg.book);
|
|
1712
|
+
const bookData = {
|
|
1713
|
+
id: book.id,
|
|
1714
|
+
payoutMultiplier: book.payout,
|
|
1715
|
+
events: book.events
|
|
1716
|
+
};
|
|
1717
|
+
const prefix = book.id === simStart ? "" : "\n";
|
|
1718
|
+
bookStream.write(prefix + JSONL.stringify([bookData]));
|
|
1719
|
+
book.events = [];
|
|
1626
1720
|
this.library.set(book.id, book);
|
|
1721
|
+
if (this.recordsWriteStream) {
|
|
1722
|
+
for (const record of msg.records) {
|
|
1723
|
+
const recordPrefix = this.hasWrittenRecord ? "\n" : "";
|
|
1724
|
+
this.recordsWriteStream.write(recordPrefix + JSONL.stringify([record]));
|
|
1725
|
+
this.hasWrittenRecord = true;
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1627
1728
|
this.wallet.mergeSerialized(msg.wallet);
|
|
1628
|
-
this.mergeRecords(msg.records);
|
|
1629
1729
|
} else if (msg.type === "done") {
|
|
1630
1730
|
resolve(true);
|
|
1631
1731
|
}
|
|
@@ -1768,16 +1868,61 @@ Simulating game mode: ${mode}`);
|
|
|
1768
1868
|
writeFile(outputFilePath, rows.join("\n"));
|
|
1769
1869
|
return outputFilePath;
|
|
1770
1870
|
}
|
|
1771
|
-
writeRecords(
|
|
1772
|
-
const
|
|
1773
|
-
const outputFilePath = import_path.default.join(
|
|
1871
|
+
async writeRecords(mode) {
|
|
1872
|
+
const tempRecordsPath = import_path.default.join(
|
|
1774
1873
|
this.gameConfig.rootDir,
|
|
1775
1874
|
this.gameConfig.outputDir,
|
|
1776
|
-
|
|
1875
|
+
TEMP_FOLDER,
|
|
1876
|
+
`temp_records_${mode}.jsonl`
|
|
1777
1877
|
);
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1878
|
+
const forceRecordsPath = import_path.default.join(
|
|
1879
|
+
this.gameConfig.rootDir,
|
|
1880
|
+
this.gameConfig.outputDir,
|
|
1881
|
+
`force_record_${mode}.json`
|
|
1882
|
+
);
|
|
1883
|
+
const aggregatedRecords = /* @__PURE__ */ new Map();
|
|
1884
|
+
if (import_fs2.default.existsSync(tempRecordsPath)) {
|
|
1885
|
+
const fileStream = import_fs2.default.createReadStream(tempRecordsPath);
|
|
1886
|
+
const rl = import_readline2.default.createInterface({
|
|
1887
|
+
input: fileStream,
|
|
1888
|
+
crlfDelay: Infinity
|
|
1889
|
+
});
|
|
1890
|
+
for await (const line of rl) {
|
|
1891
|
+
if (line.trim() === "") continue;
|
|
1892
|
+
const record = JSON.parse(line);
|
|
1893
|
+
const key = JSON.stringify(record.search);
|
|
1894
|
+
let existing = aggregatedRecords.get(key);
|
|
1895
|
+
if (!existing) {
|
|
1896
|
+
existing = {
|
|
1897
|
+
search: record.search,
|
|
1898
|
+
timesTriggered: 0,
|
|
1899
|
+
bookIds: []
|
|
1900
|
+
};
|
|
1901
|
+
aggregatedRecords.set(key, existing);
|
|
1902
|
+
}
|
|
1903
|
+
existing.timesTriggered += record.timesTriggered;
|
|
1904
|
+
for (const bookId of record.bookIds) {
|
|
1905
|
+
existing.bookIds.push(bookId);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
import_fs2.default.rmSync(forceRecordsPath, { force: true });
|
|
1910
|
+
const writeStream = import_fs2.default.createWriteStream(forceRecordsPath, { encoding: "utf-8" });
|
|
1911
|
+
writeStream.write("[\n");
|
|
1912
|
+
let isFirst = true;
|
|
1913
|
+
for (const record of aggregatedRecords.values()) {
|
|
1914
|
+
if (!isFirst) {
|
|
1915
|
+
writeStream.write(",\n");
|
|
1916
|
+
}
|
|
1917
|
+
writeStream.write(JSON.stringify(record));
|
|
1918
|
+
isFirst = false;
|
|
1919
|
+
}
|
|
1920
|
+
writeStream.write("\n]");
|
|
1921
|
+
writeStream.end();
|
|
1922
|
+
await new Promise((resolve) => {
|
|
1923
|
+
writeStream.on("finish", () => resolve());
|
|
1924
|
+
});
|
|
1925
|
+
import_fs2.default.rmSync(tempRecordsPath, { force: true });
|
|
1781
1926
|
}
|
|
1782
1927
|
writeIndexJson() {
|
|
1783
1928
|
const outputFilePath = import_path.default.join(
|
|
@@ -1799,54 +1944,25 @@ Simulating game mode: ${mode}`);
|
|
|
1799
1944
|
writeFile(outputFilePath, JSON.stringify({ modes }, null, 2));
|
|
1800
1945
|
}
|
|
1801
1946
|
async writeBooksJson(gameMode) {
|
|
1802
|
-
const outputFileName = `books_${gameMode}.jsonl`;
|
|
1803
1947
|
const outputFilePath = import_path.default.join(
|
|
1804
1948
|
this.gameConfig.rootDir,
|
|
1805
1949
|
this.gameConfig.outputDir,
|
|
1806
|
-
|
|
1950
|
+
`books_${gameMode}.jsonl`
|
|
1807
1951
|
);
|
|
1808
|
-
const books = Array.from(this.library.values()).map((b) => b.serialize()).map((b) => ({
|
|
1809
|
-
id: b.id,
|
|
1810
|
-
payoutMultiplier: b.payout,
|
|
1811
|
-
events: b.events
|
|
1812
|
-
})).sort((a, b) => a.id - b.id);
|
|
1813
|
-
const contents = JSONL.stringify(books);
|
|
1814
|
-
writeFile(outputFilePath, contents);
|
|
1815
|
-
const compressedFileName = `books_${gameMode}.jsonl.zst`;
|
|
1816
1952
|
const compressedFilePath = import_path.default.join(
|
|
1817
1953
|
this.gameConfig.rootDir,
|
|
1818
1954
|
this.gameConfig.outputDir,
|
|
1819
1955
|
"publish_files",
|
|
1820
|
-
|
|
1956
|
+
`books_${gameMode}.jsonl.zst`
|
|
1821
1957
|
);
|
|
1822
1958
|
import_fs2.default.rmSync(compressedFilePath, { force: true });
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
const structuredRecords = validRecords.map((r) => {
|
|
1831
|
-
const symbolEntry = r.search.find((s) => s.name === "symbolId");
|
|
1832
|
-
const kindEntry = r.search.find((s) => s.name === "kind");
|
|
1833
|
-
const spinTypeEntry = r.search.find((s) => s.name === "spinType");
|
|
1834
|
-
return {
|
|
1835
|
-
symbol: symbolEntry ? symbolEntry.value : "unknown",
|
|
1836
|
-
kind: kindEntry ? kindEntry.value : "unknown",
|
|
1837
|
-
spinType: spinTypeEntry ? spinTypeEntry.value : "unknown",
|
|
1838
|
-
timesTriggered: r.timesTriggered
|
|
1839
|
-
};
|
|
1840
|
-
}).sort((a, b) => {
|
|
1841
|
-
if (a.symbol < b.symbol) return -1;
|
|
1842
|
-
if (a.symbol > b.symbol) return 1;
|
|
1843
|
-
if (a.kind < b.kind) return -1;
|
|
1844
|
-
if (a.kind > b.kind) return 1;
|
|
1845
|
-
if (a.spinType < b.spinType) return -1;
|
|
1846
|
-
if (a.spinType > b.spinType) return 1;
|
|
1847
|
-
return 0;
|
|
1848
|
-
});
|
|
1849
|
-
console.table(structuredRecords);
|
|
1959
|
+
if (import_fs2.default.existsSync(outputFilePath)) {
|
|
1960
|
+
await (0, import_promises.pipeline)(
|
|
1961
|
+
import_fs2.default.createReadStream(outputFilePath),
|
|
1962
|
+
import_zlib.default.createZstdCompress(),
|
|
1963
|
+
import_fs2.default.createWriteStream(compressedFilePath)
|
|
1964
|
+
);
|
|
1965
|
+
}
|
|
1850
1966
|
}
|
|
1851
1967
|
/**
|
|
1852
1968
|
* Compiles user configured game to JS for use in different Node processes
|
|
@@ -1884,32 +2000,6 @@ Simulating game mode: ${mode}`);
|
|
|
1884
2000
|
}
|
|
1885
2001
|
return result;
|
|
1886
2002
|
}
|
|
1887
|
-
mergeRecords(otherRecords) {
|
|
1888
|
-
for (const otherRecord of otherRecords) {
|
|
1889
|
-
let record = this.recorder.records.find((r) => {
|
|
1890
|
-
if (r.search.length !== otherRecord.search.length) return false;
|
|
1891
|
-
for (let i = 0; i < r.search.length; i++) {
|
|
1892
|
-
if (r.search[i].name !== otherRecord.search[i].name) return false;
|
|
1893
|
-
if (r.search[i].value !== otherRecord.search[i].value) return false;
|
|
1894
|
-
}
|
|
1895
|
-
return true;
|
|
1896
|
-
});
|
|
1897
|
-
if (!record) {
|
|
1898
|
-
record = {
|
|
1899
|
-
search: otherRecord.search,
|
|
1900
|
-
timesTriggered: 0,
|
|
1901
|
-
bookIds: []
|
|
1902
|
-
};
|
|
1903
|
-
this.recorder.records.push(record);
|
|
1904
|
-
}
|
|
1905
|
-
record.timesTriggered += otherRecord.timesTriggered;
|
|
1906
|
-
for (const bookId of otherRecord.bookIds) {
|
|
1907
|
-
if (!record.bookIds.includes(bookId)) {
|
|
1908
|
-
record.bookIds.push(bookId);
|
|
1909
|
-
}
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
}
|
|
1913
2003
|
/**
|
|
1914
2004
|
* Generates reelset CSV files for all game modes.
|
|
1915
2005
|
*/
|
|
@@ -2516,18 +2606,13 @@ var Optimizer = class {
|
|
|
2516
2606
|
const conditions = Object.keys(mode.conditions);
|
|
2517
2607
|
const scalings = Object.keys(mode.scaling);
|
|
2518
2608
|
const parameters = Object.keys(mode.parameters);
|
|
2519
|
-
for (const
|
|
2520
|
-
if (!
|
|
2609
|
+
for (const rs of configMode.resultSets) {
|
|
2610
|
+
if (!conditions.includes(rs.criteria)) {
|
|
2521
2611
|
throw new Error(
|
|
2522
|
-
`
|
|
2612
|
+
`ResultSet criteria "${rs.criteria}" in game mode "${k}" does not have a corresponding optimization condition defined.`
|
|
2523
2613
|
);
|
|
2524
2614
|
}
|
|
2525
2615
|
}
|
|
2526
|
-
const criteria = configMode.resultSets.map((r) => r.criteria);
|
|
2527
|
-
(0, import_assert9.default)(
|
|
2528
|
-
conditions.every((c) => criteria.includes(c)),
|
|
2529
|
-
`Not all ResultSet criteria in game mode "${k}" are defined as optimization conditions.`
|
|
2530
|
-
);
|
|
2531
2616
|
let gameModeRtp = configMode.rtp;
|
|
2532
2617
|
let paramRtp = 0;
|
|
2533
2618
|
for (const cond of conditions) {
|
|
@@ -2580,6 +2665,7 @@ async function rustProgram(...args) {
|
|
|
2580
2665
|
}
|
|
2581
2666
|
|
|
2582
2667
|
// src/slot-game/index.ts
|
|
2668
|
+
var import_worker_threads4 = require("worker_threads");
|
|
2583
2669
|
var SlotGame = class {
|
|
2584
2670
|
configOpts;
|
|
2585
2671
|
simulation;
|
|
@@ -2655,6 +2741,7 @@ var SlotGame = class {
|
|
|
2655
2741
|
if (opts.doAnalysis) {
|
|
2656
2742
|
await this.runAnalysis(opts.analysisOpts || { gameModes: [] });
|
|
2657
2743
|
}
|
|
2744
|
+
if (import_worker_threads4.isMainThread) console.log("Finishing up...");
|
|
2658
2745
|
}
|
|
2659
2746
|
/**
|
|
2660
2747
|
* Gets the game configuration.
|
|
@@ -2936,13 +3023,14 @@ var ClusterWinType = class extends WinType {
|
|
|
2936
3023
|
}
|
|
2937
3024
|
}
|
|
2938
3025
|
}
|
|
2939
|
-
|
|
3026
|
+
for (const cluster of potentialClusters) {
|
|
2940
3027
|
const kind = cluster.length;
|
|
2941
3028
|
let baseSymbol = cluster.find((s) => !this.isWild(s.symbol))?.symbol;
|
|
2942
3029
|
if (!baseSymbol) baseSymbol = cluster[0].symbol;
|
|
2943
3030
|
const payout = this.getSymbolPayout(baseSymbol, kind);
|
|
3031
|
+
if (payout === 0) continue;
|
|
2944
3032
|
if (!baseSymbol.pays || Object.keys(baseSymbol.pays).length === 0) {
|
|
2945
|
-
|
|
3033
|
+
continue;
|
|
2946
3034
|
}
|
|
2947
3035
|
clusterWins.push({
|
|
2948
3036
|
payout,
|
|
@@ -2955,7 +3043,7 @@ var ClusterWinType = class extends WinType {
|
|
|
2955
3043
|
posIndex: s.row
|
|
2956
3044
|
}))
|
|
2957
3045
|
});
|
|
2958
|
-
}
|
|
3046
|
+
}
|
|
2959
3047
|
for (const win of clusterWins) {
|
|
2960
3048
|
this.ctx.services.data.recordSymbolOccurrence({
|
|
2961
3049
|
kind: win.kind,
|
|
@@ -3120,7 +3208,7 @@ var ManywaysWinType = class extends WinType {
|
|
|
3120
3208
|
// src/reel-set/GeneratedReelSet.ts
|
|
3121
3209
|
var import_fs5 = __toESM(require("fs"));
|
|
3122
3210
|
var import_path7 = __toESM(require("path"));
|
|
3123
|
-
var
|
|
3211
|
+
var import_worker_threads5 = require("worker_threads");
|
|
3124
3212
|
|
|
3125
3213
|
// src/reel-set/index.ts
|
|
3126
3214
|
var import_fs4 = __toESM(require("fs"));
|
|
@@ -3465,7 +3553,7 @@ var GeneratedReelSet = class extends ReelSet {
|
|
|
3465
3553
|
}
|
|
3466
3554
|
}
|
|
3467
3555
|
const csvString = csvRows.map((row) => row.join(",")).join("\n");
|
|
3468
|
-
if (
|
|
3556
|
+
if (import_worker_threads5.isMainThread) {
|
|
3469
3557
|
import_fs5.default.writeFileSync(filePath, csvString);
|
|
3470
3558
|
this.reels = this.parseReelsetCSV(filePath, config);
|
|
3471
3559
|
console.log(
|