@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.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
|
|
@@ -747,6 +772,9 @@ var Board = class {
|
|
|
747
772
|
newPaddingTopSymbols[ridx].unshift(padSymbol);
|
|
748
773
|
}
|
|
749
774
|
}
|
|
775
|
+
this.lastDrawnReelStops = this.lastDrawnReelStops.map((stop, ridx) => {
|
|
776
|
+
return newFirstSymbolPositions[ridx] ?? stop;
|
|
777
|
+
});
|
|
750
778
|
return {
|
|
751
779
|
newBoardSymbols,
|
|
752
780
|
newPaddingTopSymbols
|
|
@@ -1092,6 +1120,27 @@ var GameService = class extends AbstractService {
|
|
|
1092
1120
|
this.ctx().state.totalFreespinAmount += amount;
|
|
1093
1121
|
this.ctx().state.triggeredFreespins = true;
|
|
1094
1122
|
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Dedupes win symbols.
|
|
1125
|
+
*
|
|
1126
|
+
* Since it may be possible that multiple win combinations include the same symbol (e.g. Wilds),\
|
|
1127
|
+
* this method ensures that each symbol is only listed once.
|
|
1128
|
+
*
|
|
1129
|
+
* If you want to tumble based on winning symbols, run them through this method first.
|
|
1130
|
+
*/
|
|
1131
|
+
dedupeWinSymbols(winCombinations) {
|
|
1132
|
+
const symbolsMap = /* @__PURE__ */ new Map();
|
|
1133
|
+
winCombinations.forEach((wc) => {
|
|
1134
|
+
wc.symbols.forEach((s) => {
|
|
1135
|
+
symbolsMap.set(`${s.reelIndex},${s.posIndex}`, {
|
|
1136
|
+
reelIdx: s.reelIndex,
|
|
1137
|
+
rowIdx: s.posIndex
|
|
1138
|
+
});
|
|
1139
|
+
});
|
|
1140
|
+
});
|
|
1141
|
+
const symbolsToRemove = Array.from(symbolsMap.values());
|
|
1142
|
+
return symbolsToRemove;
|
|
1143
|
+
}
|
|
1095
1144
|
};
|
|
1096
1145
|
|
|
1097
1146
|
// src/service/wallet.ts
|
|
@@ -1464,8 +1513,10 @@ var Wallet = class {
|
|
|
1464
1513
|
};
|
|
1465
1514
|
|
|
1466
1515
|
// src/simulation/index.ts
|
|
1516
|
+
var import_promises = require("stream/promises");
|
|
1467
1517
|
var completedSimulations = 0;
|
|
1468
1518
|
var TEMP_FILENAME = "__temp_compiled_src_IGNORE.js";
|
|
1519
|
+
var TEMP_FOLDER = "temp_files";
|
|
1469
1520
|
var Simulation = class {
|
|
1470
1521
|
gameConfigOpts;
|
|
1471
1522
|
gameConfig;
|
|
@@ -1474,15 +1525,15 @@ var Simulation = class {
|
|
|
1474
1525
|
debug = false;
|
|
1475
1526
|
actualSims = 0;
|
|
1476
1527
|
library;
|
|
1477
|
-
recorder;
|
|
1478
1528
|
wallet;
|
|
1529
|
+
recordsWriteStream;
|
|
1530
|
+
hasWrittenRecord = false;
|
|
1479
1531
|
constructor(opts, gameConfigOpts) {
|
|
1480
1532
|
this.gameConfig = createGameConfig(gameConfigOpts);
|
|
1481
1533
|
this.gameConfigOpts = gameConfigOpts;
|
|
1482
1534
|
this.simRunsAmount = opts.simRunsAmount || {};
|
|
1483
1535
|
this.concurrency = (opts.concurrency || 6) >= 2 ? opts.concurrency || 6 : 2;
|
|
1484
1536
|
this.library = /* @__PURE__ */ new Map();
|
|
1485
|
-
this.recorder = new Recorder();
|
|
1486
1537
|
this.wallet = new Wallet();
|
|
1487
1538
|
const gameModeKeys = Object.keys(this.gameConfig.gameModes);
|
|
1488
1539
|
(0, import_assert6.default)(
|
|
@@ -1508,7 +1559,7 @@ var Simulation = class {
|
|
|
1508
1559
|
completedSimulations = 0;
|
|
1509
1560
|
this.wallet = new Wallet();
|
|
1510
1561
|
this.library = /* @__PURE__ */ new Map();
|
|
1511
|
-
this.
|
|
1562
|
+
this.hasWrittenRecord = false;
|
|
1512
1563
|
debugDetails[mode] = {};
|
|
1513
1564
|
console.log(`
|
|
1514
1565
|
Simulating game mode: ${mode}`);
|
|
@@ -1520,8 +1571,59 @@ Simulating game mode: ${mode}`);
|
|
|
1520
1571
|
`Tried to simulate game mode "${mode}", but it's not configured in the game config.`
|
|
1521
1572
|
);
|
|
1522
1573
|
}
|
|
1574
|
+
const booksPath = import_path.default.join(
|
|
1575
|
+
this.gameConfig.rootDir,
|
|
1576
|
+
this.gameConfig.outputDir,
|
|
1577
|
+
`books_${mode}.jsonl`
|
|
1578
|
+
);
|
|
1579
|
+
const tempRecordsPath = import_path.default.join(
|
|
1580
|
+
this.gameConfig.rootDir,
|
|
1581
|
+
this.gameConfig.outputDir,
|
|
1582
|
+
TEMP_FOLDER,
|
|
1583
|
+
`temp_records_${mode}.jsonl`
|
|
1584
|
+
);
|
|
1585
|
+
createDirIfNotExists(
|
|
1586
|
+
import_path.default.join(this.gameConfig.rootDir, this.gameConfig.outputDir)
|
|
1587
|
+
);
|
|
1588
|
+
createDirIfNotExists(
|
|
1589
|
+
import_path.default.join(this.gameConfig.rootDir, this.gameConfig.outputDir, TEMP_FOLDER)
|
|
1590
|
+
);
|
|
1591
|
+
this.recordsWriteStream = import_fs2.default.createWriteStream(tempRecordsPath);
|
|
1523
1592
|
const simNumsToCriteria = ResultSet.assignCriteriaToSimulations(this, mode);
|
|
1524
1593
|
await this.spawnWorkersForGameMode({ mode, simNumsToCriteria });
|
|
1594
|
+
const finalBookStream = import_fs2.default.createWriteStream(booksPath);
|
|
1595
|
+
const numSims = Object.keys(simNumsToCriteria).length;
|
|
1596
|
+
const chunks = this.getSimRangesForChunks(numSims, this.concurrency);
|
|
1597
|
+
let isFirstChunk = true;
|
|
1598
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
1599
|
+
const tempBookPath = import_path.default.join(
|
|
1600
|
+
this.gameConfig.rootDir,
|
|
1601
|
+
this.gameConfig.outputDir,
|
|
1602
|
+
TEMP_FOLDER,
|
|
1603
|
+
`temp_books_${mode}_${i}.jsonl`
|
|
1604
|
+
);
|
|
1605
|
+
if (import_fs2.default.existsSync(tempBookPath)) {
|
|
1606
|
+
if (!isFirstChunk) {
|
|
1607
|
+
finalBookStream.write("\n");
|
|
1608
|
+
}
|
|
1609
|
+
const content = import_fs2.default.createReadStream(tempBookPath);
|
|
1610
|
+
for await (const chunk of content) {
|
|
1611
|
+
finalBookStream.write(chunk);
|
|
1612
|
+
}
|
|
1613
|
+
import_fs2.default.rmSync(tempBookPath);
|
|
1614
|
+
isFirstChunk = false;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
finalBookStream.end();
|
|
1618
|
+
await new Promise((resolve) => finalBookStream.on("finish", resolve));
|
|
1619
|
+
if (this.recordsWriteStream) {
|
|
1620
|
+
await new Promise((resolve) => {
|
|
1621
|
+
this.recordsWriteStream.end(() => {
|
|
1622
|
+
resolve();
|
|
1623
|
+
});
|
|
1624
|
+
});
|
|
1625
|
+
this.recordsWriteStream = void 0;
|
|
1626
|
+
}
|
|
1525
1627
|
createDirIfNotExists(
|
|
1526
1628
|
import_path.default.join(
|
|
1527
1629
|
this.gameConfig.rootDir,
|
|
@@ -1532,11 +1634,13 @@ Simulating game mode: ${mode}`);
|
|
|
1532
1634
|
createDirIfNotExists(
|
|
1533
1635
|
import_path.default.join(this.gameConfig.rootDir, this.gameConfig.outputDir, "publish_files")
|
|
1534
1636
|
);
|
|
1637
|
+
console.log(`Writing final files for game mode: ${mode} ...`);
|
|
1535
1638
|
this.writeLookupTableCSV(mode);
|
|
1536
1639
|
this.writeLookupTableSegmentedCSV(mode);
|
|
1537
1640
|
this.writeRecords(mode);
|
|
1538
1641
|
await this.writeBooksJson(mode);
|
|
1539
1642
|
this.writeIndexJson();
|
|
1643
|
+
console.log(`Mode ${mode} done!`);
|
|
1540
1644
|
debugDetails[mode].rtp = this.wallet.getCumulativeWins() / (runs * this.gameConfig.gameModes[mode].cost);
|
|
1541
1645
|
debugDetails[mode].wins = this.wallet.getCumulativeWins();
|
|
1542
1646
|
debugDetails[mode].winsPerSpinType = this.wallet.getCumulativeWinsPerSpinType();
|
|
@@ -1615,6 +1719,12 @@ Simulating game mode: ${mode}`);
|
|
|
1615
1719
|
index
|
|
1616
1720
|
}
|
|
1617
1721
|
});
|
|
1722
|
+
const tempBookPath = import_path.default.join(
|
|
1723
|
+
basePath,
|
|
1724
|
+
TEMP_FOLDER,
|
|
1725
|
+
`temp_books_${mode}_${index}.jsonl`
|
|
1726
|
+
);
|
|
1727
|
+
const bookStream = import_fs2.default.createWriteStream(tempBookPath);
|
|
1618
1728
|
worker.on("message", (msg) => {
|
|
1619
1729
|
if (msg.type === "log") {
|
|
1620
1730
|
} else if (msg.type === "complete") {
|
|
@@ -1623,15 +1733,34 @@ Simulating game mode: ${mode}`);
|
|
|
1623
1733
|
logArrowProgress(completedSimulations, totalSims);
|
|
1624
1734
|
}
|
|
1625
1735
|
const book = Book.fromSerialized(msg.book);
|
|
1736
|
+
const bookData = {
|
|
1737
|
+
id: book.id,
|
|
1738
|
+
payoutMultiplier: book.payout,
|
|
1739
|
+
events: book.events
|
|
1740
|
+
};
|
|
1741
|
+
const prefix = book.id === simStart ? "" : "\n";
|
|
1742
|
+
bookStream.write(prefix + JSONL.stringify([bookData]));
|
|
1743
|
+
book.events = [];
|
|
1626
1744
|
this.library.set(book.id, book);
|
|
1745
|
+
if (this.recordsWriteStream) {
|
|
1746
|
+
for (const record of msg.records) {
|
|
1747
|
+
const recordPrefix = this.hasWrittenRecord ? "\n" : "";
|
|
1748
|
+
this.recordsWriteStream.write(recordPrefix + JSONL.stringify([record]));
|
|
1749
|
+
this.hasWrittenRecord = true;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1627
1752
|
this.wallet.mergeSerialized(msg.wallet);
|
|
1628
|
-
this.mergeRecords(msg.records);
|
|
1629
1753
|
} else if (msg.type === "done") {
|
|
1630
1754
|
resolve(true);
|
|
1631
1755
|
}
|
|
1632
1756
|
});
|
|
1633
1757
|
worker.on("error", (error) => {
|
|
1634
|
-
|
|
1758
|
+
process.stdout.write(`
|
|
1759
|
+
${error.message}
|
|
1760
|
+
`);
|
|
1761
|
+
process.stdout.write(`
|
|
1762
|
+
${error.stack}
|
|
1763
|
+
`);
|
|
1635
1764
|
reject(error);
|
|
1636
1765
|
});
|
|
1637
1766
|
worker.on("exit", (code) => {
|
|
@@ -1768,16 +1897,61 @@ Simulating game mode: ${mode}`);
|
|
|
1768
1897
|
writeFile(outputFilePath, rows.join("\n"));
|
|
1769
1898
|
return outputFilePath;
|
|
1770
1899
|
}
|
|
1771
|
-
writeRecords(
|
|
1772
|
-
const
|
|
1773
|
-
const outputFilePath = import_path.default.join(
|
|
1900
|
+
async writeRecords(mode) {
|
|
1901
|
+
const tempRecordsPath = import_path.default.join(
|
|
1774
1902
|
this.gameConfig.rootDir,
|
|
1775
1903
|
this.gameConfig.outputDir,
|
|
1776
|
-
|
|
1904
|
+
TEMP_FOLDER,
|
|
1905
|
+
`temp_records_${mode}.jsonl`
|
|
1777
1906
|
);
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1907
|
+
const forceRecordsPath = import_path.default.join(
|
|
1908
|
+
this.gameConfig.rootDir,
|
|
1909
|
+
this.gameConfig.outputDir,
|
|
1910
|
+
`force_record_${mode}.json`
|
|
1911
|
+
);
|
|
1912
|
+
const aggregatedRecords = /* @__PURE__ */ new Map();
|
|
1913
|
+
if (import_fs2.default.existsSync(tempRecordsPath)) {
|
|
1914
|
+
const fileStream = import_fs2.default.createReadStream(tempRecordsPath);
|
|
1915
|
+
const rl = import_readline2.default.createInterface({
|
|
1916
|
+
input: fileStream,
|
|
1917
|
+
crlfDelay: Infinity
|
|
1918
|
+
});
|
|
1919
|
+
for await (const line of rl) {
|
|
1920
|
+
if (line.trim() === "") continue;
|
|
1921
|
+
const record = JSON.parse(line);
|
|
1922
|
+
const key = JSON.stringify(record.search);
|
|
1923
|
+
let existing = aggregatedRecords.get(key);
|
|
1924
|
+
if (!existing) {
|
|
1925
|
+
existing = {
|
|
1926
|
+
search: record.search,
|
|
1927
|
+
timesTriggered: 0,
|
|
1928
|
+
bookIds: []
|
|
1929
|
+
};
|
|
1930
|
+
aggregatedRecords.set(key, existing);
|
|
1931
|
+
}
|
|
1932
|
+
existing.timesTriggered += record.timesTriggered;
|
|
1933
|
+
for (const bookId of record.bookIds) {
|
|
1934
|
+
existing.bookIds.push(bookId);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
import_fs2.default.rmSync(forceRecordsPath, { force: true });
|
|
1939
|
+
const writeStream = import_fs2.default.createWriteStream(forceRecordsPath, { encoding: "utf-8" });
|
|
1940
|
+
writeStream.write("[\n");
|
|
1941
|
+
let isFirst = true;
|
|
1942
|
+
for (const record of aggregatedRecords.values()) {
|
|
1943
|
+
if (!isFirst) {
|
|
1944
|
+
writeStream.write(",\n");
|
|
1945
|
+
}
|
|
1946
|
+
writeStream.write(JSON.stringify(record));
|
|
1947
|
+
isFirst = false;
|
|
1948
|
+
}
|
|
1949
|
+
writeStream.write("\n]");
|
|
1950
|
+
writeStream.end();
|
|
1951
|
+
await new Promise((resolve) => {
|
|
1952
|
+
writeStream.on("finish", () => resolve());
|
|
1953
|
+
});
|
|
1954
|
+
import_fs2.default.rmSync(tempRecordsPath, { force: true });
|
|
1781
1955
|
}
|
|
1782
1956
|
writeIndexJson() {
|
|
1783
1957
|
const outputFilePath = import_path.default.join(
|
|
@@ -1799,54 +1973,25 @@ Simulating game mode: ${mode}`);
|
|
|
1799
1973
|
writeFile(outputFilePath, JSON.stringify({ modes }, null, 2));
|
|
1800
1974
|
}
|
|
1801
1975
|
async writeBooksJson(gameMode) {
|
|
1802
|
-
const outputFileName = `books_${gameMode}.jsonl`;
|
|
1803
1976
|
const outputFilePath = import_path.default.join(
|
|
1804
1977
|
this.gameConfig.rootDir,
|
|
1805
1978
|
this.gameConfig.outputDir,
|
|
1806
|
-
|
|
1979
|
+
`books_${gameMode}.jsonl`
|
|
1807
1980
|
);
|
|
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
1981
|
const compressedFilePath = import_path.default.join(
|
|
1817
1982
|
this.gameConfig.rootDir,
|
|
1818
1983
|
this.gameConfig.outputDir,
|
|
1819
1984
|
"publish_files",
|
|
1820
|
-
|
|
1985
|
+
`books_${gameMode}.jsonl.zst`
|
|
1821
1986
|
);
|
|
1822
1987
|
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);
|
|
1988
|
+
if (import_fs2.default.existsSync(outputFilePath)) {
|
|
1989
|
+
await (0, import_promises.pipeline)(
|
|
1990
|
+
import_fs2.default.createReadStream(outputFilePath),
|
|
1991
|
+
import_zlib.default.createZstdCompress(),
|
|
1992
|
+
import_fs2.default.createWriteStream(compressedFilePath)
|
|
1993
|
+
);
|
|
1994
|
+
}
|
|
1850
1995
|
}
|
|
1851
1996
|
/**
|
|
1852
1997
|
* Compiles user configured game to JS for use in different Node processes
|
|
@@ -1884,32 +2029,6 @@ Simulating game mode: ${mode}`);
|
|
|
1884
2029
|
}
|
|
1885
2030
|
return result;
|
|
1886
2031
|
}
|
|
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
2032
|
/**
|
|
1914
2033
|
* Generates reelset CSV files for all game modes.
|
|
1915
2034
|
*/
|
|
@@ -2523,11 +2642,6 @@ var Optimizer = class {
|
|
|
2523
2642
|
);
|
|
2524
2643
|
}
|
|
2525
2644
|
}
|
|
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
2645
|
let gameModeRtp = configMode.rtp;
|
|
2532
2646
|
let paramRtp = 0;
|
|
2533
2647
|
for (const cond of conditions) {
|
|
@@ -2580,6 +2694,7 @@ async function rustProgram(...args) {
|
|
|
2580
2694
|
}
|
|
2581
2695
|
|
|
2582
2696
|
// src/slot-game/index.ts
|
|
2697
|
+
var import_worker_threads4 = require("worker_threads");
|
|
2583
2698
|
var SlotGame = class {
|
|
2584
2699
|
configOpts;
|
|
2585
2700
|
simulation;
|
|
@@ -2655,6 +2770,7 @@ var SlotGame = class {
|
|
|
2655
2770
|
if (opts.doAnalysis) {
|
|
2656
2771
|
await this.runAnalysis(opts.analysisOpts || { gameModes: [] });
|
|
2657
2772
|
}
|
|
2773
|
+
if (import_worker_threads4.isMainThread) console.log("Finishing up...");
|
|
2658
2774
|
}
|
|
2659
2775
|
/**
|
|
2660
2776
|
* Gets the game configuration.
|
|
@@ -2936,13 +3052,14 @@ var ClusterWinType = class extends WinType {
|
|
|
2936
3052
|
}
|
|
2937
3053
|
}
|
|
2938
3054
|
}
|
|
2939
|
-
|
|
3055
|
+
for (const cluster of potentialClusters) {
|
|
2940
3056
|
const kind = cluster.length;
|
|
2941
3057
|
let baseSymbol = cluster.find((s) => !this.isWild(s.symbol))?.symbol;
|
|
2942
3058
|
if (!baseSymbol) baseSymbol = cluster[0].symbol;
|
|
2943
3059
|
const payout = this.getSymbolPayout(baseSymbol, kind);
|
|
3060
|
+
if (payout === 0) continue;
|
|
2944
3061
|
if (!baseSymbol.pays || Object.keys(baseSymbol.pays).length === 0) {
|
|
2945
|
-
|
|
3062
|
+
continue;
|
|
2946
3063
|
}
|
|
2947
3064
|
clusterWins.push({
|
|
2948
3065
|
payout,
|
|
@@ -2955,7 +3072,7 @@ var ClusterWinType = class extends WinType {
|
|
|
2955
3072
|
posIndex: s.row
|
|
2956
3073
|
}))
|
|
2957
3074
|
});
|
|
2958
|
-
}
|
|
3075
|
+
}
|
|
2959
3076
|
for (const win of clusterWins) {
|
|
2960
3077
|
this.ctx.services.data.recordSymbolOccurrence({
|
|
2961
3078
|
kind: win.kind,
|
|
@@ -3120,7 +3237,7 @@ var ManywaysWinType = class extends WinType {
|
|
|
3120
3237
|
// src/reel-set/GeneratedReelSet.ts
|
|
3121
3238
|
var import_fs5 = __toESM(require("fs"));
|
|
3122
3239
|
var import_path7 = __toESM(require("path"));
|
|
3123
|
-
var
|
|
3240
|
+
var import_worker_threads5 = require("worker_threads");
|
|
3124
3241
|
|
|
3125
3242
|
// src/reel-set/index.ts
|
|
3126
3243
|
var import_fs4 = __toESM(require("fs"));
|
|
@@ -3465,7 +3582,7 @@ var GeneratedReelSet = class extends ReelSet {
|
|
|
3465
3582
|
}
|
|
3466
3583
|
}
|
|
3467
3584
|
const csvString = csvRows.map((row) => row.join(",")).join("\n");
|
|
3468
|
-
if (
|
|
3585
|
+
if (import_worker_threads5.isMainThread) {
|
|
3469
3586
|
import_fs5.default.writeFileSync(filePath, csvString);
|
|
3470
3587
|
this.reels = this.parseReelsetCSV(filePath, config);
|
|
3471
3588
|
console.log(
|