@slot-engine/core 0.1.3 → 0.1.5
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 +79 -68
- package/dist/index.d.ts +79 -68
- package/dist/index.js +203 -86
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +203 -86
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -34,6 +34,7 @@ import fs2 from "fs";
|
|
|
34
34
|
import path from "path";
|
|
35
35
|
import assert6 from "assert";
|
|
36
36
|
import zlib from "zlib";
|
|
37
|
+
import readline2 from "readline";
|
|
37
38
|
import { buildSync } from "esbuild";
|
|
38
39
|
import { Worker, isMainThread, parentPort, workerData } from "worker_threads";
|
|
39
40
|
|
|
@@ -201,6 +202,7 @@ var RandomNumberGenerator = class {
|
|
|
201
202
|
|
|
202
203
|
// utils.ts
|
|
203
204
|
import fs from "fs";
|
|
205
|
+
import readline from "readline";
|
|
204
206
|
function createDirIfNotExists(dirPath) {
|
|
205
207
|
if (!fs.existsSync(dirPath)) {
|
|
206
208
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
@@ -232,6 +234,29 @@ var JSONL = class {
|
|
|
232
234
|
static parse(jsonl) {
|
|
233
235
|
return jsonl.split("\n").filter((s) => s !== "").map((str) => JSON.parse(str));
|
|
234
236
|
}
|
|
237
|
+
static async convertToJson(inputPath, outputPath) {
|
|
238
|
+
const writeStream = fs.createWriteStream(outputPath, { encoding: "utf-8" });
|
|
239
|
+
writeStream.write("[\n");
|
|
240
|
+
const rl = readline.createInterface({
|
|
241
|
+
input: fs.createReadStream(inputPath),
|
|
242
|
+
crlfDelay: Infinity
|
|
243
|
+
});
|
|
244
|
+
let isFirst = true;
|
|
245
|
+
for await (const line of rl) {
|
|
246
|
+
if (line.trim() === "") continue;
|
|
247
|
+
if (!isFirst) {
|
|
248
|
+
writeStream.write(",\n");
|
|
249
|
+
}
|
|
250
|
+
writeStream.write(line);
|
|
251
|
+
isFirst = false;
|
|
252
|
+
}
|
|
253
|
+
writeStream.write("\n]");
|
|
254
|
+
writeStream.end();
|
|
255
|
+
return new Promise((resolve, reject) => {
|
|
256
|
+
writeStream.on("finish", () => resolve());
|
|
257
|
+
writeStream.on("error", reject);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
235
260
|
};
|
|
236
261
|
|
|
237
262
|
// src/result-set/index.ts
|
|
@@ -695,6 +720,9 @@ var Board = class {
|
|
|
695
720
|
newPaddingTopSymbols[ridx].unshift(padSymbol);
|
|
696
721
|
}
|
|
697
722
|
}
|
|
723
|
+
this.lastDrawnReelStops = this.lastDrawnReelStops.map((stop, ridx) => {
|
|
724
|
+
return newFirstSymbolPositions[ridx] ?? stop;
|
|
725
|
+
});
|
|
698
726
|
return {
|
|
699
727
|
newBoardSymbols,
|
|
700
728
|
newPaddingTopSymbols
|
|
@@ -1040,6 +1068,27 @@ var GameService = class extends AbstractService {
|
|
|
1040
1068
|
this.ctx().state.totalFreespinAmount += amount;
|
|
1041
1069
|
this.ctx().state.triggeredFreespins = true;
|
|
1042
1070
|
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Dedupes win symbols.
|
|
1073
|
+
*
|
|
1074
|
+
* Since it may be possible that multiple win combinations include the same symbol (e.g. Wilds),\
|
|
1075
|
+
* this method ensures that each symbol is only listed once.
|
|
1076
|
+
*
|
|
1077
|
+
* If you want to tumble based on winning symbols, run them through this method first.
|
|
1078
|
+
*/
|
|
1079
|
+
dedupeWinSymbols(winCombinations) {
|
|
1080
|
+
const symbolsMap = /* @__PURE__ */ new Map();
|
|
1081
|
+
winCombinations.forEach((wc) => {
|
|
1082
|
+
wc.symbols.forEach((s) => {
|
|
1083
|
+
symbolsMap.set(`${s.reelIndex},${s.posIndex}`, {
|
|
1084
|
+
reelIdx: s.reelIndex,
|
|
1085
|
+
rowIdx: s.posIndex
|
|
1086
|
+
});
|
|
1087
|
+
});
|
|
1088
|
+
});
|
|
1089
|
+
const symbolsToRemove = Array.from(symbolsMap.values());
|
|
1090
|
+
return symbolsToRemove;
|
|
1091
|
+
}
|
|
1043
1092
|
};
|
|
1044
1093
|
|
|
1045
1094
|
// src/service/wallet.ts
|
|
@@ -1412,8 +1461,10 @@ var Wallet = class {
|
|
|
1412
1461
|
};
|
|
1413
1462
|
|
|
1414
1463
|
// src/simulation/index.ts
|
|
1464
|
+
import { pipeline } from "stream/promises";
|
|
1415
1465
|
var completedSimulations = 0;
|
|
1416
1466
|
var TEMP_FILENAME = "__temp_compiled_src_IGNORE.js";
|
|
1467
|
+
var TEMP_FOLDER = "temp_files";
|
|
1417
1468
|
var Simulation = class {
|
|
1418
1469
|
gameConfigOpts;
|
|
1419
1470
|
gameConfig;
|
|
@@ -1422,15 +1473,15 @@ var Simulation = class {
|
|
|
1422
1473
|
debug = false;
|
|
1423
1474
|
actualSims = 0;
|
|
1424
1475
|
library;
|
|
1425
|
-
recorder;
|
|
1426
1476
|
wallet;
|
|
1477
|
+
recordsWriteStream;
|
|
1478
|
+
hasWrittenRecord = false;
|
|
1427
1479
|
constructor(opts, gameConfigOpts) {
|
|
1428
1480
|
this.gameConfig = createGameConfig(gameConfigOpts);
|
|
1429
1481
|
this.gameConfigOpts = gameConfigOpts;
|
|
1430
1482
|
this.simRunsAmount = opts.simRunsAmount || {};
|
|
1431
1483
|
this.concurrency = (opts.concurrency || 6) >= 2 ? opts.concurrency || 6 : 2;
|
|
1432
1484
|
this.library = /* @__PURE__ */ new Map();
|
|
1433
|
-
this.recorder = new Recorder();
|
|
1434
1485
|
this.wallet = new Wallet();
|
|
1435
1486
|
const gameModeKeys = Object.keys(this.gameConfig.gameModes);
|
|
1436
1487
|
assert6(
|
|
@@ -1456,7 +1507,7 @@ var Simulation = class {
|
|
|
1456
1507
|
completedSimulations = 0;
|
|
1457
1508
|
this.wallet = new Wallet();
|
|
1458
1509
|
this.library = /* @__PURE__ */ new Map();
|
|
1459
|
-
this.
|
|
1510
|
+
this.hasWrittenRecord = false;
|
|
1460
1511
|
debugDetails[mode] = {};
|
|
1461
1512
|
console.log(`
|
|
1462
1513
|
Simulating game mode: ${mode}`);
|
|
@@ -1468,8 +1519,59 @@ Simulating game mode: ${mode}`);
|
|
|
1468
1519
|
`Tried to simulate game mode "${mode}", but it's not configured in the game config.`
|
|
1469
1520
|
);
|
|
1470
1521
|
}
|
|
1522
|
+
const booksPath = path.join(
|
|
1523
|
+
this.gameConfig.rootDir,
|
|
1524
|
+
this.gameConfig.outputDir,
|
|
1525
|
+
`books_${mode}.jsonl`
|
|
1526
|
+
);
|
|
1527
|
+
const tempRecordsPath = path.join(
|
|
1528
|
+
this.gameConfig.rootDir,
|
|
1529
|
+
this.gameConfig.outputDir,
|
|
1530
|
+
TEMP_FOLDER,
|
|
1531
|
+
`temp_records_${mode}.jsonl`
|
|
1532
|
+
);
|
|
1533
|
+
createDirIfNotExists(
|
|
1534
|
+
path.join(this.gameConfig.rootDir, this.gameConfig.outputDir)
|
|
1535
|
+
);
|
|
1536
|
+
createDirIfNotExists(
|
|
1537
|
+
path.join(this.gameConfig.rootDir, this.gameConfig.outputDir, TEMP_FOLDER)
|
|
1538
|
+
);
|
|
1539
|
+
this.recordsWriteStream = fs2.createWriteStream(tempRecordsPath);
|
|
1471
1540
|
const simNumsToCriteria = ResultSet.assignCriteriaToSimulations(this, mode);
|
|
1472
1541
|
await this.spawnWorkersForGameMode({ mode, simNumsToCriteria });
|
|
1542
|
+
const finalBookStream = fs2.createWriteStream(booksPath);
|
|
1543
|
+
const numSims = Object.keys(simNumsToCriteria).length;
|
|
1544
|
+
const chunks = this.getSimRangesForChunks(numSims, this.concurrency);
|
|
1545
|
+
let isFirstChunk = true;
|
|
1546
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
1547
|
+
const tempBookPath = path.join(
|
|
1548
|
+
this.gameConfig.rootDir,
|
|
1549
|
+
this.gameConfig.outputDir,
|
|
1550
|
+
TEMP_FOLDER,
|
|
1551
|
+
`temp_books_${mode}_${i}.jsonl`
|
|
1552
|
+
);
|
|
1553
|
+
if (fs2.existsSync(tempBookPath)) {
|
|
1554
|
+
if (!isFirstChunk) {
|
|
1555
|
+
finalBookStream.write("\n");
|
|
1556
|
+
}
|
|
1557
|
+
const content = fs2.createReadStream(tempBookPath);
|
|
1558
|
+
for await (const chunk of content) {
|
|
1559
|
+
finalBookStream.write(chunk);
|
|
1560
|
+
}
|
|
1561
|
+
fs2.rmSync(tempBookPath);
|
|
1562
|
+
isFirstChunk = false;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
finalBookStream.end();
|
|
1566
|
+
await new Promise((resolve) => finalBookStream.on("finish", resolve));
|
|
1567
|
+
if (this.recordsWriteStream) {
|
|
1568
|
+
await new Promise((resolve) => {
|
|
1569
|
+
this.recordsWriteStream.end(() => {
|
|
1570
|
+
resolve();
|
|
1571
|
+
});
|
|
1572
|
+
});
|
|
1573
|
+
this.recordsWriteStream = void 0;
|
|
1574
|
+
}
|
|
1473
1575
|
createDirIfNotExists(
|
|
1474
1576
|
path.join(
|
|
1475
1577
|
this.gameConfig.rootDir,
|
|
@@ -1480,11 +1582,13 @@ Simulating game mode: ${mode}`);
|
|
|
1480
1582
|
createDirIfNotExists(
|
|
1481
1583
|
path.join(this.gameConfig.rootDir, this.gameConfig.outputDir, "publish_files")
|
|
1482
1584
|
);
|
|
1585
|
+
console.log(`Writing final files for game mode: ${mode} ...`);
|
|
1483
1586
|
this.writeLookupTableCSV(mode);
|
|
1484
1587
|
this.writeLookupTableSegmentedCSV(mode);
|
|
1485
1588
|
this.writeRecords(mode);
|
|
1486
1589
|
await this.writeBooksJson(mode);
|
|
1487
1590
|
this.writeIndexJson();
|
|
1591
|
+
console.log(`Mode ${mode} done!`);
|
|
1488
1592
|
debugDetails[mode].rtp = this.wallet.getCumulativeWins() / (runs * this.gameConfig.gameModes[mode].cost);
|
|
1489
1593
|
debugDetails[mode].wins = this.wallet.getCumulativeWins();
|
|
1490
1594
|
debugDetails[mode].winsPerSpinType = this.wallet.getCumulativeWinsPerSpinType();
|
|
@@ -1563,6 +1667,12 @@ Simulating game mode: ${mode}`);
|
|
|
1563
1667
|
index
|
|
1564
1668
|
}
|
|
1565
1669
|
});
|
|
1670
|
+
const tempBookPath = path.join(
|
|
1671
|
+
basePath,
|
|
1672
|
+
TEMP_FOLDER,
|
|
1673
|
+
`temp_books_${mode}_${index}.jsonl`
|
|
1674
|
+
);
|
|
1675
|
+
const bookStream = fs2.createWriteStream(tempBookPath);
|
|
1566
1676
|
worker.on("message", (msg) => {
|
|
1567
1677
|
if (msg.type === "log") {
|
|
1568
1678
|
} else if (msg.type === "complete") {
|
|
@@ -1571,15 +1681,34 @@ Simulating game mode: ${mode}`);
|
|
|
1571
1681
|
logArrowProgress(completedSimulations, totalSims);
|
|
1572
1682
|
}
|
|
1573
1683
|
const book = Book.fromSerialized(msg.book);
|
|
1684
|
+
const bookData = {
|
|
1685
|
+
id: book.id,
|
|
1686
|
+
payoutMultiplier: book.payout,
|
|
1687
|
+
events: book.events
|
|
1688
|
+
};
|
|
1689
|
+
const prefix = book.id === simStart ? "" : "\n";
|
|
1690
|
+
bookStream.write(prefix + JSONL.stringify([bookData]));
|
|
1691
|
+
book.events = [];
|
|
1574
1692
|
this.library.set(book.id, book);
|
|
1693
|
+
if (this.recordsWriteStream) {
|
|
1694
|
+
for (const record of msg.records) {
|
|
1695
|
+
const recordPrefix = this.hasWrittenRecord ? "\n" : "";
|
|
1696
|
+
this.recordsWriteStream.write(recordPrefix + JSONL.stringify([record]));
|
|
1697
|
+
this.hasWrittenRecord = true;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1575
1700
|
this.wallet.mergeSerialized(msg.wallet);
|
|
1576
|
-
this.mergeRecords(msg.records);
|
|
1577
1701
|
} else if (msg.type === "done") {
|
|
1578
1702
|
resolve(true);
|
|
1579
1703
|
}
|
|
1580
1704
|
});
|
|
1581
1705
|
worker.on("error", (error) => {
|
|
1582
|
-
|
|
1706
|
+
process.stdout.write(`
|
|
1707
|
+
${error.message}
|
|
1708
|
+
`);
|
|
1709
|
+
process.stdout.write(`
|
|
1710
|
+
${error.stack}
|
|
1711
|
+
`);
|
|
1583
1712
|
reject(error);
|
|
1584
1713
|
});
|
|
1585
1714
|
worker.on("exit", (code) => {
|
|
@@ -1716,16 +1845,61 @@ Simulating game mode: ${mode}`);
|
|
|
1716
1845
|
writeFile(outputFilePath, rows.join("\n"));
|
|
1717
1846
|
return outputFilePath;
|
|
1718
1847
|
}
|
|
1719
|
-
writeRecords(
|
|
1720
|
-
const
|
|
1721
|
-
const outputFilePath = path.join(
|
|
1848
|
+
async writeRecords(mode) {
|
|
1849
|
+
const tempRecordsPath = path.join(
|
|
1722
1850
|
this.gameConfig.rootDir,
|
|
1723
1851
|
this.gameConfig.outputDir,
|
|
1724
|
-
|
|
1852
|
+
TEMP_FOLDER,
|
|
1853
|
+
`temp_records_${mode}.jsonl`
|
|
1725
1854
|
);
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1855
|
+
const forceRecordsPath = path.join(
|
|
1856
|
+
this.gameConfig.rootDir,
|
|
1857
|
+
this.gameConfig.outputDir,
|
|
1858
|
+
`force_record_${mode}.json`
|
|
1859
|
+
);
|
|
1860
|
+
const aggregatedRecords = /* @__PURE__ */ new Map();
|
|
1861
|
+
if (fs2.existsSync(tempRecordsPath)) {
|
|
1862
|
+
const fileStream = fs2.createReadStream(tempRecordsPath);
|
|
1863
|
+
const rl = readline2.createInterface({
|
|
1864
|
+
input: fileStream,
|
|
1865
|
+
crlfDelay: Infinity
|
|
1866
|
+
});
|
|
1867
|
+
for await (const line of rl) {
|
|
1868
|
+
if (line.trim() === "") continue;
|
|
1869
|
+
const record = JSON.parse(line);
|
|
1870
|
+
const key = JSON.stringify(record.search);
|
|
1871
|
+
let existing = aggregatedRecords.get(key);
|
|
1872
|
+
if (!existing) {
|
|
1873
|
+
existing = {
|
|
1874
|
+
search: record.search,
|
|
1875
|
+
timesTriggered: 0,
|
|
1876
|
+
bookIds: []
|
|
1877
|
+
};
|
|
1878
|
+
aggregatedRecords.set(key, existing);
|
|
1879
|
+
}
|
|
1880
|
+
existing.timesTriggered += record.timesTriggered;
|
|
1881
|
+
for (const bookId of record.bookIds) {
|
|
1882
|
+
existing.bookIds.push(bookId);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
fs2.rmSync(forceRecordsPath, { force: true });
|
|
1887
|
+
const writeStream = fs2.createWriteStream(forceRecordsPath, { encoding: "utf-8" });
|
|
1888
|
+
writeStream.write("[\n");
|
|
1889
|
+
let isFirst = true;
|
|
1890
|
+
for (const record of aggregatedRecords.values()) {
|
|
1891
|
+
if (!isFirst) {
|
|
1892
|
+
writeStream.write(",\n");
|
|
1893
|
+
}
|
|
1894
|
+
writeStream.write(JSON.stringify(record));
|
|
1895
|
+
isFirst = false;
|
|
1896
|
+
}
|
|
1897
|
+
writeStream.write("\n]");
|
|
1898
|
+
writeStream.end();
|
|
1899
|
+
await new Promise((resolve) => {
|
|
1900
|
+
writeStream.on("finish", () => resolve());
|
|
1901
|
+
});
|
|
1902
|
+
fs2.rmSync(tempRecordsPath, { force: true });
|
|
1729
1903
|
}
|
|
1730
1904
|
writeIndexJson() {
|
|
1731
1905
|
const outputFilePath = path.join(
|
|
@@ -1747,54 +1921,25 @@ Simulating game mode: ${mode}`);
|
|
|
1747
1921
|
writeFile(outputFilePath, JSON.stringify({ modes }, null, 2));
|
|
1748
1922
|
}
|
|
1749
1923
|
async writeBooksJson(gameMode) {
|
|
1750
|
-
const outputFileName = `books_${gameMode}.jsonl`;
|
|
1751
1924
|
const outputFilePath = path.join(
|
|
1752
1925
|
this.gameConfig.rootDir,
|
|
1753
1926
|
this.gameConfig.outputDir,
|
|
1754
|
-
|
|
1927
|
+
`books_${gameMode}.jsonl`
|
|
1755
1928
|
);
|
|
1756
|
-
const books = Array.from(this.library.values()).map((b) => b.serialize()).map((b) => ({
|
|
1757
|
-
id: b.id,
|
|
1758
|
-
payoutMultiplier: b.payout,
|
|
1759
|
-
events: b.events
|
|
1760
|
-
})).sort((a, b) => a.id - b.id);
|
|
1761
|
-
const contents = JSONL.stringify(books);
|
|
1762
|
-
writeFile(outputFilePath, contents);
|
|
1763
|
-
const compressedFileName = `books_${gameMode}.jsonl.zst`;
|
|
1764
1929
|
const compressedFilePath = path.join(
|
|
1765
1930
|
this.gameConfig.rootDir,
|
|
1766
1931
|
this.gameConfig.outputDir,
|
|
1767
1932
|
"publish_files",
|
|
1768
|
-
|
|
1933
|
+
`books_${gameMode}.jsonl.zst`
|
|
1769
1934
|
);
|
|
1770
1935
|
fs2.rmSync(compressedFilePath, { force: true });
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
const structuredRecords = validRecords.map((r) => {
|
|
1779
|
-
const symbolEntry = r.search.find((s) => s.name === "symbolId");
|
|
1780
|
-
const kindEntry = r.search.find((s) => s.name === "kind");
|
|
1781
|
-
const spinTypeEntry = r.search.find((s) => s.name === "spinType");
|
|
1782
|
-
return {
|
|
1783
|
-
symbol: symbolEntry ? symbolEntry.value : "unknown",
|
|
1784
|
-
kind: kindEntry ? kindEntry.value : "unknown",
|
|
1785
|
-
spinType: spinTypeEntry ? spinTypeEntry.value : "unknown",
|
|
1786
|
-
timesTriggered: r.timesTriggered
|
|
1787
|
-
};
|
|
1788
|
-
}).sort((a, b) => {
|
|
1789
|
-
if (a.symbol < b.symbol) return -1;
|
|
1790
|
-
if (a.symbol > b.symbol) return 1;
|
|
1791
|
-
if (a.kind < b.kind) return -1;
|
|
1792
|
-
if (a.kind > b.kind) return 1;
|
|
1793
|
-
if (a.spinType < b.spinType) return -1;
|
|
1794
|
-
if (a.spinType > b.spinType) return 1;
|
|
1795
|
-
return 0;
|
|
1796
|
-
});
|
|
1797
|
-
console.table(structuredRecords);
|
|
1936
|
+
if (fs2.existsSync(outputFilePath)) {
|
|
1937
|
+
await pipeline(
|
|
1938
|
+
fs2.createReadStream(outputFilePath),
|
|
1939
|
+
zlib.createZstdCompress(),
|
|
1940
|
+
fs2.createWriteStream(compressedFilePath)
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1798
1943
|
}
|
|
1799
1944
|
/**
|
|
1800
1945
|
* Compiles user configured game to JS for use in different Node processes
|
|
@@ -1832,32 +1977,6 @@ Simulating game mode: ${mode}`);
|
|
|
1832
1977
|
}
|
|
1833
1978
|
return result;
|
|
1834
1979
|
}
|
|
1835
|
-
mergeRecords(otherRecords) {
|
|
1836
|
-
for (const otherRecord of otherRecords) {
|
|
1837
|
-
let record = this.recorder.records.find((r) => {
|
|
1838
|
-
if (r.search.length !== otherRecord.search.length) return false;
|
|
1839
|
-
for (let i = 0; i < r.search.length; i++) {
|
|
1840
|
-
if (r.search[i].name !== otherRecord.search[i].name) return false;
|
|
1841
|
-
if (r.search[i].value !== otherRecord.search[i].value) return false;
|
|
1842
|
-
}
|
|
1843
|
-
return true;
|
|
1844
|
-
});
|
|
1845
|
-
if (!record) {
|
|
1846
|
-
record = {
|
|
1847
|
-
search: otherRecord.search,
|
|
1848
|
-
timesTriggered: 0,
|
|
1849
|
-
bookIds: []
|
|
1850
|
-
};
|
|
1851
|
-
this.recorder.records.push(record);
|
|
1852
|
-
}
|
|
1853
|
-
record.timesTriggered += otherRecord.timesTriggered;
|
|
1854
|
-
for (const bookId of otherRecord.bookIds) {
|
|
1855
|
-
if (!record.bookIds.includes(bookId)) {
|
|
1856
|
-
record.bookIds.push(bookId);
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
1980
|
/**
|
|
1862
1981
|
* Generates reelset CSV files for all game modes.
|
|
1863
1982
|
*/
|
|
@@ -2471,11 +2590,6 @@ var Optimizer = class {
|
|
|
2471
2590
|
);
|
|
2472
2591
|
}
|
|
2473
2592
|
}
|
|
2474
|
-
const criteria = configMode.resultSets.map((r) => r.criteria);
|
|
2475
|
-
assert9(
|
|
2476
|
-
conditions.every((c) => criteria.includes(c)),
|
|
2477
|
-
`Not all ResultSet criteria in game mode "${k}" are defined as optimization conditions.`
|
|
2478
|
-
);
|
|
2479
2593
|
let gameModeRtp = configMode.rtp;
|
|
2480
2594
|
let paramRtp = 0;
|
|
2481
2595
|
for (const cond of conditions) {
|
|
@@ -2528,6 +2642,7 @@ async function rustProgram(...args) {
|
|
|
2528
2642
|
}
|
|
2529
2643
|
|
|
2530
2644
|
// src/slot-game/index.ts
|
|
2645
|
+
import { isMainThread as isMainThread4 } from "worker_threads";
|
|
2531
2646
|
var SlotGame = class {
|
|
2532
2647
|
configOpts;
|
|
2533
2648
|
simulation;
|
|
@@ -2603,6 +2718,7 @@ var SlotGame = class {
|
|
|
2603
2718
|
if (opts.doAnalysis) {
|
|
2604
2719
|
await this.runAnalysis(opts.analysisOpts || { gameModes: [] });
|
|
2605
2720
|
}
|
|
2721
|
+
if (isMainThread4) console.log("Finishing up...");
|
|
2606
2722
|
}
|
|
2607
2723
|
/**
|
|
2608
2724
|
* Gets the game configuration.
|
|
@@ -2884,13 +3000,14 @@ var ClusterWinType = class extends WinType {
|
|
|
2884
3000
|
}
|
|
2885
3001
|
}
|
|
2886
3002
|
}
|
|
2887
|
-
|
|
3003
|
+
for (const cluster of potentialClusters) {
|
|
2888
3004
|
const kind = cluster.length;
|
|
2889
3005
|
let baseSymbol = cluster.find((s) => !this.isWild(s.symbol))?.symbol;
|
|
2890
3006
|
if (!baseSymbol) baseSymbol = cluster[0].symbol;
|
|
2891
3007
|
const payout = this.getSymbolPayout(baseSymbol, kind);
|
|
3008
|
+
if (payout === 0) continue;
|
|
2892
3009
|
if (!baseSymbol.pays || Object.keys(baseSymbol.pays).length === 0) {
|
|
2893
|
-
|
|
3010
|
+
continue;
|
|
2894
3011
|
}
|
|
2895
3012
|
clusterWins.push({
|
|
2896
3013
|
payout,
|
|
@@ -2903,7 +3020,7 @@ var ClusterWinType = class extends WinType {
|
|
|
2903
3020
|
posIndex: s.row
|
|
2904
3021
|
}))
|
|
2905
3022
|
});
|
|
2906
|
-
}
|
|
3023
|
+
}
|
|
2907
3024
|
for (const win of clusterWins) {
|
|
2908
3025
|
this.ctx.services.data.recordSymbolOccurrence({
|
|
2909
3026
|
kind: win.kind,
|
|
@@ -3068,7 +3185,7 @@ var ManywaysWinType = class extends WinType {
|
|
|
3068
3185
|
// src/reel-set/GeneratedReelSet.ts
|
|
3069
3186
|
import fs5 from "fs";
|
|
3070
3187
|
import path7 from "path";
|
|
3071
|
-
import { isMainThread as
|
|
3188
|
+
import { isMainThread as isMainThread5 } from "worker_threads";
|
|
3072
3189
|
|
|
3073
3190
|
// src/reel-set/index.ts
|
|
3074
3191
|
import fs4 from "fs";
|
|
@@ -3413,7 +3530,7 @@ var GeneratedReelSet = class extends ReelSet {
|
|
|
3413
3530
|
}
|
|
3414
3531
|
}
|
|
3415
3532
|
const csvString = csvRows.map((row) => row.join(",")).join("\n");
|
|
3416
|
-
if (
|
|
3533
|
+
if (isMainThread5) {
|
|
3417
3534
|
fs5.writeFileSync(filePath, csvString);
|
|
3418
3535
|
this.reels = this.parseReelsetCSV(filePath, config);
|
|
3419
3536
|
console.log(
|