@slot-engine/core 0.0.1 → 0.0.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/README.md +1 -1
- package/dist/index.d.mts +0 -7
- package/dist/index.d.ts +0 -7
- package/dist/index.js +9 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -1
- package/.turbo/turbo-build.log +0 -33
- package/.turbo/turbo-typecheck.log +0 -4
- package/CHANGELOG.md +0 -7
- package/index.ts +0 -205
- package/lib/zstd.exe +0 -0
- package/optimizer-rust/Cargo.toml +0 -19
- package/optimizer-rust/src/exes.rs +0 -154
- package/optimizer-rust/src/main.rs +0 -1659
- package/src/Board.ts +0 -527
- package/src/Book.ts +0 -83
- package/src/GameConfig.ts +0 -148
- package/src/GameMode.ts +0 -86
- package/src/GameState.ts +0 -272
- package/src/GameSymbol.ts +0 -61
- package/src/ReelGenerator.ts +0 -589
- package/src/ResultSet.ts +0 -207
- package/src/Simulation.ts +0 -625
- package/src/SlotGame.ts +0 -117
- package/src/Wallet.ts +0 -203
- package/src/WinType.ts +0 -102
- package/src/analysis/index.ts +0 -198
- package/src/analysis/utils.ts +0 -128
- package/src/optimizer/OptimizationConditions.ts +0 -99
- package/src/optimizer/OptimizationParameters.ts +0 -46
- package/src/optimizer/OptimizationScaling.ts +0 -18
- package/src/optimizer/index.ts +0 -142
- package/src/utils/math-config.ts +0 -109
- package/src/utils/setup-file.ts +0 -36
- package/src/utils/zstd.ts +0 -28
- package/src/winTypes/ClusterWinType.ts +0 -3
- package/src/winTypes/LinesWinType.ts +0 -208
- package/src/winTypes/ManywaysWinType.ts +0 -3
- package/tsconfig.json +0 -19
- package/utils.ts +0 -270
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import { GameSymbol } from "../GameSymbol"
|
|
2
|
-
import { AnySimulationContext } from "../Simulation"
|
|
3
|
-
import { WinCombination, WinType, WinTypeOpts } from "../WinType"
|
|
4
|
-
|
|
5
|
-
export class LinesWinType extends WinType {
|
|
6
|
-
protected lines: Record<number, number[]>
|
|
7
|
-
declare protected winCombinations: LineWinCombination[]
|
|
8
|
-
declare context: (ctx: AnySimulationContext) => LinesWinType
|
|
9
|
-
declare getWins: () => {
|
|
10
|
-
payout: number
|
|
11
|
-
winCombinations: LineWinCombination[]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
constructor(opts: LinesWinTypeOpts) {
|
|
15
|
-
super(opts)
|
|
16
|
-
this.lines = opts.lines
|
|
17
|
-
|
|
18
|
-
if (Object.keys(this.lines).length === 0) {
|
|
19
|
-
throw new Error("LinesWinType must have at least one line defined.")
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
private validateConfig() {
|
|
24
|
-
const reelsAmount = this.ctx.getCurrentGameMode().reelsAmount
|
|
25
|
-
const symsPerReel = this.ctx.getCurrentGameMode().symbolsPerReel
|
|
26
|
-
|
|
27
|
-
for (const [lineNum, positions] of Object.entries(this.lines)) {
|
|
28
|
-
if (positions.length !== reelsAmount) {
|
|
29
|
-
throw new Error(
|
|
30
|
-
`Line ${lineNum} has ${positions.length} positions, but the current game mode has ${reelsAmount} reels.`,
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
for (let i = 0; i < positions.length; i++) {
|
|
34
|
-
if (positions[i]! < 0 || positions[i]! >= symsPerReel[i]!) {
|
|
35
|
-
throw new Error(
|
|
36
|
-
`Line ${lineNum} has an invalid position ${positions[i]} on reel ${i}. Valid range is 0 to ${
|
|
37
|
-
symsPerReel[i]! - 1
|
|
38
|
-
}.`,
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const firstLine = Math.min(...Object.keys(this.lines).map(Number))
|
|
45
|
-
if (firstLine !== 1) {
|
|
46
|
-
throw new Error(
|
|
47
|
-
`Lines must start from 1. Found line ${firstLine} as the first line.`,
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private isWild(symbol: GameSymbol) {
|
|
53
|
-
return !!this.wildSymbol && symbol.compare(this.wildSymbol)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
evaluateWins() {
|
|
57
|
-
this.ensureContext()
|
|
58
|
-
this.validateConfig()
|
|
59
|
-
|
|
60
|
-
const lineWins: LineWinCombination[] = []
|
|
61
|
-
let payout = 0
|
|
62
|
-
|
|
63
|
-
const reels = this.ctx.board.reels
|
|
64
|
-
const reelsAmount = this.ctx.getCurrentGameMode().reelsAmount
|
|
65
|
-
|
|
66
|
-
for (const [lineNumStr, lineDef] of Object.entries(this.lines)) {
|
|
67
|
-
const lineNum = Number(lineNumStr)
|
|
68
|
-
|
|
69
|
-
let baseSymbol: GameSymbol | null = null
|
|
70
|
-
let leadingWilds = 0
|
|
71
|
-
const chain: GameSymbol[] = []
|
|
72
|
-
const details: LineWinCombination["symbols"] = []
|
|
73
|
-
|
|
74
|
-
for (let ridx = 0; ridx < reelsAmount; ridx++) {
|
|
75
|
-
const rowIdx = lineDef[ridx]!
|
|
76
|
-
const sym = reels[ridx]![rowIdx]
|
|
77
|
-
if (!sym) throw new Error("Encountered an invalid symbol while evaluating wins.")
|
|
78
|
-
|
|
79
|
-
const wild = this.isWild(sym)
|
|
80
|
-
|
|
81
|
-
if (ridx === 0) {
|
|
82
|
-
chain.push(sym)
|
|
83
|
-
details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: wild })
|
|
84
|
-
if (wild) leadingWilds++
|
|
85
|
-
else baseSymbol = sym
|
|
86
|
-
continue
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (wild) {
|
|
90
|
-
chain.push(sym)
|
|
91
|
-
details.push({
|
|
92
|
-
reelIndex: ridx,
|
|
93
|
-
posIndex: rowIdx,
|
|
94
|
-
symbol: sym,
|
|
95
|
-
isWild: true,
|
|
96
|
-
substitutedFor: baseSymbol || undefined,
|
|
97
|
-
})
|
|
98
|
-
continue
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (!baseSymbol) {
|
|
102
|
-
baseSymbol = sym
|
|
103
|
-
chain.push(sym)
|
|
104
|
-
details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: false })
|
|
105
|
-
continue
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (sym.id === baseSymbol.id) {
|
|
109
|
-
chain.push(sym)
|
|
110
|
-
details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: false })
|
|
111
|
-
continue
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
break
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (chain.length === 0) continue
|
|
118
|
-
|
|
119
|
-
const allWild = chain.every((s) => this.isWild(s))
|
|
120
|
-
const wildRepresentative =
|
|
121
|
-
this.wildSymbol instanceof GameSymbol ? this.wildSymbol : null
|
|
122
|
-
|
|
123
|
-
const len = chain.length
|
|
124
|
-
let bestPayout = 0
|
|
125
|
-
let bestType: LineWinCombination["winType"] | null = null
|
|
126
|
-
let payingSymbol: GameSymbol | null = null
|
|
127
|
-
|
|
128
|
-
if (baseSymbol?.pays && baseSymbol.pays[len]) {
|
|
129
|
-
bestPayout = baseSymbol.pays[len]!
|
|
130
|
-
bestType = "substituted"
|
|
131
|
-
payingSymbol = baseSymbol
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (allWild && wildRepresentative?.pays && wildRepresentative.pays[len]) {
|
|
135
|
-
const wildPay = wildRepresentative.pays[len]!
|
|
136
|
-
if (wildPay > bestPayout) {
|
|
137
|
-
bestPayout = wildPay
|
|
138
|
-
bestType = "pure-wild"
|
|
139
|
-
payingSymbol = wildRepresentative
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (!bestPayout || !bestType || !payingSymbol) continue
|
|
144
|
-
|
|
145
|
-
const minLen = payingSymbol.pays
|
|
146
|
-
? Math.min(...Object.keys(payingSymbol.pays).map(Number))
|
|
147
|
-
: Infinity
|
|
148
|
-
|
|
149
|
-
if (len < minLen) continue
|
|
150
|
-
|
|
151
|
-
const wildCount = details.filter((d) => d.isWild).length
|
|
152
|
-
const nonWildCount = len - wildCount
|
|
153
|
-
|
|
154
|
-
lineWins.push({
|
|
155
|
-
lineNumber: lineNum,
|
|
156
|
-
kind: len,
|
|
157
|
-
payout: bestPayout,
|
|
158
|
-
symbol: payingSymbol,
|
|
159
|
-
winType: bestType,
|
|
160
|
-
substitutedBaseSymbol: bestType === "pure-wild" ? null : baseSymbol,
|
|
161
|
-
symbols: details,
|
|
162
|
-
stats: { wildCount, nonWildCount, leadingWilds },
|
|
163
|
-
})
|
|
164
|
-
payout += bestPayout
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
for (const win of lineWins) {
|
|
168
|
-
this.ctx.recordSymbolOccurrence({
|
|
169
|
-
kind: win.kind,
|
|
170
|
-
symbolId: win.symbol.id,
|
|
171
|
-
spinType: this.ctx.state.currentSpinType,
|
|
172
|
-
})
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
this.payout = payout
|
|
176
|
-
this.winCombinations = lineWins
|
|
177
|
-
|
|
178
|
-
return this
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
interface LinesWinTypeOpts extends WinTypeOpts {
|
|
183
|
-
/**
|
|
184
|
-
* Defines the paylines for the slot game.
|
|
185
|
-
*
|
|
186
|
-
* @example
|
|
187
|
-
* ```ts
|
|
188
|
-
* lines: {
|
|
189
|
-
* 1: [0, 0, 0, 0, 0],
|
|
190
|
-
* 2: [1, 1, 1, 1, 1],
|
|
191
|
-
* 3: [2, 2, 2, 2, 2],
|
|
192
|
-
* }
|
|
193
|
-
* ```
|
|
194
|
-
*/
|
|
195
|
-
lines: Record<number, number[]>
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export interface LineWinCombination extends WinCombination {
|
|
199
|
-
lineNumber: number
|
|
200
|
-
symbol: GameSymbol
|
|
201
|
-
winType: "pure-wild" | "substituted"
|
|
202
|
-
substitutedBaseSymbol: GameSymbol | null
|
|
203
|
-
stats: {
|
|
204
|
-
wildCount: number
|
|
205
|
-
nonWildCount: number
|
|
206
|
-
leadingWilds: number
|
|
207
|
-
}
|
|
208
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"declaration": true,
|
|
5
|
-
"declarationMap": true,
|
|
6
|
-
"esModuleInterop": true,
|
|
7
|
-
"incremental": false,
|
|
8
|
-
"isolatedModules": true,
|
|
9
|
-
"lib": ["es2022", "DOM", "DOM.Iterable"],
|
|
10
|
-
"module": "NodeNext",
|
|
11
|
-
"moduleDetection": "force",
|
|
12
|
-
"moduleResolution": "NodeNext",
|
|
13
|
-
"noUncheckedIndexedAccess": true,
|
|
14
|
-
"resolveJsonModule": true,
|
|
15
|
-
"skipLibCheck": true,
|
|
16
|
-
"strict": true,
|
|
17
|
-
"target": "ES2022"
|
|
18
|
-
}
|
|
19
|
-
}
|
package/utils.ts
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import fs from "fs"
|
|
2
|
-
import { Board } from "./src/Board"
|
|
3
|
-
|
|
4
|
-
export function weightedRandom<T extends Record<string, number>>(
|
|
5
|
-
weights: T,
|
|
6
|
-
rng: RandomNumberGenerator,
|
|
7
|
-
) {
|
|
8
|
-
const totalWeight = Object.values(weights).reduce(
|
|
9
|
-
(sum: number, weight) => sum + (weight as number),
|
|
10
|
-
0,
|
|
11
|
-
)
|
|
12
|
-
const randomValue = rng.randomFloat(0, 1) * totalWeight
|
|
13
|
-
|
|
14
|
-
let cumulativeWeight = 0
|
|
15
|
-
for (const [key, weight] of Object.entries(weights)) {
|
|
16
|
-
cumulativeWeight += weight as number
|
|
17
|
-
if (randomValue < cumulativeWeight) {
|
|
18
|
-
return key
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
throw new Error("No item selected in weighted random selection.")
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function randomItem<T>(array: T[], rng: RandomNumberGenerator) {
|
|
26
|
-
if (array.length === 0) {
|
|
27
|
-
throw new Error("Cannot select a random item from an empty array.")
|
|
28
|
-
}
|
|
29
|
-
const randomIndex = Math.floor(rng.randomFloat(0, 1) * array.length)
|
|
30
|
-
return array[randomIndex]!
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function createDirIfNotExists(dirPath: string): void {
|
|
34
|
-
if (!fs.existsSync(dirPath)) {
|
|
35
|
-
fs.mkdirSync(dirPath, { recursive: true })
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function shuffle<T>(array: T[], rng: RandomNumberGenerator): T[] {
|
|
40
|
-
const newArray = [...array]
|
|
41
|
-
let currentIndex = newArray.length,
|
|
42
|
-
randomIndex
|
|
43
|
-
|
|
44
|
-
while (currentIndex != 0) {
|
|
45
|
-
randomIndex = Math.floor(rng.randomFloat(0, 1) * currentIndex)
|
|
46
|
-
currentIndex--
|
|
47
|
-
;[newArray[currentIndex] as any, newArray[randomIndex] as any] = [
|
|
48
|
-
newArray[randomIndex],
|
|
49
|
-
newArray[currentIndex],
|
|
50
|
-
]
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return newArray
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function writeJsonFile(filePath: string, data: object | any[]) {
|
|
57
|
-
try {
|
|
58
|
-
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), {
|
|
59
|
-
encoding: "utf8",
|
|
60
|
-
})
|
|
61
|
-
} catch (error) {
|
|
62
|
-
throw new Error(`Failed to write JSON file at ${filePath}: ${error}`)
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function writeFile(filePath: string, data: string) {
|
|
67
|
-
try {
|
|
68
|
-
fs.writeFileSync(filePath, data, { encoding: "utf8" })
|
|
69
|
-
} catch (error) {
|
|
70
|
-
throw new Error(`Failed to write file at ${filePath}: ${error}`)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export class RandomNumberGenerator {
|
|
75
|
-
mIdum: number
|
|
76
|
-
mIy: number
|
|
77
|
-
mIv: Array<number>
|
|
78
|
-
NTAB: number
|
|
79
|
-
IA: number
|
|
80
|
-
IM: number
|
|
81
|
-
IQ: number
|
|
82
|
-
IR: number
|
|
83
|
-
NDIV: number
|
|
84
|
-
AM: number
|
|
85
|
-
RNMX: number
|
|
86
|
-
|
|
87
|
-
protected _currentSeed: number = 0
|
|
88
|
-
|
|
89
|
-
constructor() {
|
|
90
|
-
this.mIdum = 0
|
|
91
|
-
this.mIy = 0
|
|
92
|
-
this.mIv = []
|
|
93
|
-
|
|
94
|
-
this.NTAB = 32
|
|
95
|
-
this.IA = 16807
|
|
96
|
-
this.IM = 2147483647
|
|
97
|
-
this.IQ = 127773
|
|
98
|
-
this.IR = 2836
|
|
99
|
-
this.NDIV = 1 + (this.IM - 1) / this.NTAB
|
|
100
|
-
this.AM = 1.0 / this.IM
|
|
101
|
-
this.RNMX = 1.0 - 1.2e-7
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
getCurrentSeed() {
|
|
105
|
-
return this._currentSeed
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
protected setCurrentSeed(seed: number) {
|
|
109
|
-
this._currentSeed = seed
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
setSeed(seed: number): void {
|
|
113
|
-
this.mIdum = seed
|
|
114
|
-
this.setCurrentSeed(seed)
|
|
115
|
-
|
|
116
|
-
if (seed >= 0) {
|
|
117
|
-
this.mIdum = -seed
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
this.mIy = 0
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
setSeedIfDifferent(seed: number) {
|
|
124
|
-
if (this.getCurrentSeed() !== seed) {
|
|
125
|
-
this.setSeed(seed)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
generateRandomNumber(): number {
|
|
130
|
-
let k: number
|
|
131
|
-
let j: number
|
|
132
|
-
|
|
133
|
-
if (this.mIdum <= 0 || this.mIy === 0) {
|
|
134
|
-
if (-this.mIdum < 1) {
|
|
135
|
-
this.mIdum = 1
|
|
136
|
-
} else {
|
|
137
|
-
this.mIdum = -this.mIdum
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
for (j = this.NTAB + 7; j >= 0; j -= 1) {
|
|
141
|
-
k = Math.floor(this.mIdum / this.IQ)
|
|
142
|
-
this.mIdum = Math.floor(this.IA * (this.mIdum - k * this.IQ) - this.IR * k)
|
|
143
|
-
|
|
144
|
-
if (this.mIdum < 0) {
|
|
145
|
-
this.mIdum += this.IM
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (j < this.NTAB) {
|
|
149
|
-
this.mIv[j] = this.mIdum
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
;[this.mIy as any] = this.mIv
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
k = Math.floor(this.mIdum / this.IQ)
|
|
157
|
-
this.mIdum = Math.floor(this.IA * (this.mIdum - k * this.IQ) - this.IR * k)
|
|
158
|
-
|
|
159
|
-
if (this.mIdum < 0) {
|
|
160
|
-
this.mIdum += this.IM
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
j = Math.floor(this.mIy / this.NDIV)
|
|
164
|
-
|
|
165
|
-
this.mIy = Math.floor(this.mIv[j] as any)
|
|
166
|
-
this.mIv[j] = this.mIdum
|
|
167
|
-
|
|
168
|
-
return this.mIy
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
randomFloat(low: number, high: number): number {
|
|
172
|
-
let float: number = this.AM * this.generateRandomNumber()
|
|
173
|
-
|
|
174
|
-
if (float > this.RNMX) {
|
|
175
|
-
float = this.RNMX
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return float * (high - low) + low
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Creates a deep copy of an object or array.
|
|
184
|
-
*/
|
|
185
|
-
export function copy<T>(obj: T): T {
|
|
186
|
-
return JSON.parse(JSON.stringify(obj))
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Prints the board to the console in a readable format.
|
|
191
|
-
*/
|
|
192
|
-
export function printBoard({ board, config }: Board<any, any, any>) {
|
|
193
|
-
const fullBoard = board.reels.map((reel, ridx) => {
|
|
194
|
-
if (config.padSymbols && config.padSymbols > 0) {
|
|
195
|
-
return [...board.paddingTop[ridx]!, ...reel, ...board.paddingBottom[ridx]!]
|
|
196
|
-
}
|
|
197
|
-
return reel
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
const rows = Math.max(...fullBoard.map((reel) => reel.length))
|
|
201
|
-
const cellWidth = 4 // inner width of symbol area
|
|
202
|
-
|
|
203
|
-
const padSymbol = (sym: string) => {
|
|
204
|
-
if (sym.length > cellWidth) sym = sym.slice(0, cellWidth)
|
|
205
|
-
const left = Math.floor((cellWidth - sym.length) / 2)
|
|
206
|
-
const right = cellWidth - sym.length - left
|
|
207
|
-
return " ".repeat(left) + sym + " ".repeat(right)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const maxTop = Math.max(...board.paddingTop.map((p) => p?.length ?? 0))
|
|
211
|
-
const maxBottom = Math.max(...board.paddingBottom.map((p) => p?.length ?? 0))
|
|
212
|
-
const boardStart = maxTop
|
|
213
|
-
const boardEnd = rows - maxBottom - 1
|
|
214
|
-
|
|
215
|
-
const makeSeparator = () => {
|
|
216
|
-
return fullBoard.map(() => `═${"═".repeat(cellWidth)}═ `).join("")
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
for (let row = 0; row < rows; row++) {
|
|
220
|
-
if (row === boardStart) {
|
|
221
|
-
console.log(makeSeparator()) // top border of board
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
let top = ""
|
|
225
|
-
let mid = ""
|
|
226
|
-
let bot = ""
|
|
227
|
-
for (let col = 0; col < fullBoard.length; col++) {
|
|
228
|
-
const sym = fullBoard[col]![row]?.id ?? " "
|
|
229
|
-
const padded = padSymbol(sym)
|
|
230
|
-
top += `┌${"─".repeat(cellWidth)}┐ `
|
|
231
|
-
mid += `│${padded}│ `
|
|
232
|
-
bot += `└${"─".repeat(cellWidth)}┘ `
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
console.log(top)
|
|
236
|
-
console.log(mid)
|
|
237
|
-
console.log(bot)
|
|
238
|
-
|
|
239
|
-
if (row === boardEnd) {
|
|
240
|
-
console.log(makeSeparator()) // bottom border of board
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
export function weightedAverage(dist: Record<number, number>) {
|
|
246
|
-
const keys = Object.keys(dist).map(Number)
|
|
247
|
-
const values = Object.values(dist)
|
|
248
|
-
|
|
249
|
-
const totalWeight = round(values.reduce((a, b) => a + b, 0), 6)
|
|
250
|
-
const weightedSum = keys.reduce((sum, key, i) => sum + key * values[i]!, 0)
|
|
251
|
-
|
|
252
|
-
return weightedSum / totalWeight
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
export class JSONL {
|
|
256
|
-
public static stringify(array: object[]): string {
|
|
257
|
-
return array.map((object) => JSON.stringify(object)).join("\n")
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
public static parse<T>(jsonl: string): Array<T> {
|
|
261
|
-
return jsonl
|
|
262
|
-
.split("\n")
|
|
263
|
-
.filter((s) => s !== "")
|
|
264
|
-
.map((str) => JSON.parse(str))
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export function round(value: number, decimals: number) {
|
|
269
|
-
return Number(Math.round(Number(value + "e" + decimals)) + "e-" + decimals)
|
|
270
|
-
}
|