@yipe/dice 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/README.md +0 -2
- package/dist/index.cjs +232 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -76
- package/dist/index.d.ts +52 -76
- package/dist/index.js +232 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -51,15 +51,15 @@ var EPS = 1e-12;
|
|
|
51
51
|
|
|
52
52
|
// src/query.ts
|
|
53
53
|
var _DiceQuery = class _DiceQuery {
|
|
54
|
-
constructor(singles, combined) {
|
|
54
|
+
constructor(singles, combined, eps = EPS) {
|
|
55
55
|
__publicField(this, "singles");
|
|
56
56
|
__publicField(this, "combined");
|
|
57
57
|
this.singles = Array.isArray(singles) ? singles : [singles];
|
|
58
|
-
const c = combined ?? PMF.convolveMany(this.singles);
|
|
59
|
-
this.combined = Math.abs(c.mass() - 1) <= 1e-12 ? c : c.normalize();
|
|
60
58
|
if (this.singles.some((s) => s === void 0)) {
|
|
61
59
|
throw new Error("DiceQuery contains undefined singles");
|
|
62
60
|
}
|
|
61
|
+
const c = combined ?? PMF.convolveMany(this.singles);
|
|
62
|
+
this.combined = Math.abs(c.mass() - 1) <= eps ? c : c.normalize();
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
65
|
* Returns the expected damage across all possible outcomes.
|
|
@@ -357,23 +357,16 @@ var _DiceQuery = class _DiceQuery {
|
|
|
357
357
|
* - "How much damage comes from miss effects (like save-for-half spells)?"
|
|
358
358
|
*/
|
|
359
359
|
expectedDamageFrom(labels) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
360
|
+
const wanted = Array.isArray(labels) ? labels : [labels];
|
|
361
|
+
let total = 0;
|
|
362
|
+
for (const single of this.singles) {
|
|
363
|
+
for (const [dmg, bin] of single) {
|
|
364
|
+
let p = 0;
|
|
365
|
+
for (const label of wanted) p += bin.count[label] ?? 0;
|
|
366
|
+
total += dmg * p;
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
|
-
return
|
|
370
|
-
}
|
|
371
|
-
_expectedDamageFromSingle(label) {
|
|
372
|
-
let totalExpectedDamage = 0;
|
|
373
|
-
for (const [, probabilityBin] of this.combined) {
|
|
374
|
-
totalExpectedDamage += probabilityBin.attr?.[label] || 0;
|
|
375
|
-
}
|
|
376
|
-
return totalExpectedDamage;
|
|
369
|
+
return total;
|
|
377
370
|
}
|
|
378
371
|
/**
|
|
379
372
|
* Returns damage statistics for scenarios where AT LEAST ONE attack results in
|
|
@@ -596,7 +589,7 @@ var _DiceQuery = class _DiceQuery {
|
|
|
596
589
|
* query.toStackedChartData(['hit', 'crit'])
|
|
597
590
|
* // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]}
|
|
598
591
|
*/
|
|
599
|
-
toStackedChartData(labels = [], epsilon =
|
|
592
|
+
toStackedChartData(labels = [], epsilon = EPS) {
|
|
600
593
|
const damageValues = this.combined.support();
|
|
601
594
|
damageValues.map((dmg) => {
|
|
602
595
|
const bin = this.combined.map.get(dmg);
|
|
@@ -1279,12 +1272,12 @@ __publicField(_DiceQuery, "DEFAULT_OUTCOMES", [
|
|
|
1279
1272
|
var DiceQuery = _DiceQuery;
|
|
1280
1273
|
var pmfCache = new LRUCache(1e3);
|
|
1281
1274
|
var _PMF = class _PMF {
|
|
1282
|
-
constructor(map = /* @__PURE__ */ new Map(), epsilon = EPS, normalized = false, identifier = `anon#${_PMF.__anonIdCounter++}`,
|
|
1275
|
+
constructor(map = /* @__PURE__ */ new Map(), epsilon = EPS, normalized = false, identifier = `anon#${_PMF.__anonIdCounter++}`, _preservedProvenance = true) {
|
|
1283
1276
|
this.map = map;
|
|
1284
1277
|
this.epsilon = epsilon;
|
|
1285
1278
|
this.normalized = normalized;
|
|
1286
1279
|
this.identifier = identifier;
|
|
1287
|
-
this.
|
|
1280
|
+
this._preservedProvenance = _preservedProvenance;
|
|
1288
1281
|
// Cached computed values
|
|
1289
1282
|
__publicField(this, "_support");
|
|
1290
1283
|
__publicField(this, "_min");
|
|
@@ -1303,6 +1296,9 @@ var _PMF = class _PMF {
|
|
|
1303
1296
|
m.set(0, { p: 1, count: { miss: 1 }, attr: {} });
|
|
1304
1297
|
return new _PMF(m, epsilon, false, "zero");
|
|
1305
1298
|
}
|
|
1299
|
+
static delta(value, epsilon = EPS) {
|
|
1300
|
+
return _PMF.fromMap(/* @__PURE__ */ new Map([[value, 1]]), epsilon);
|
|
1301
|
+
}
|
|
1306
1302
|
// This creates a single bin at value 0, but with weight 0.
|
|
1307
1303
|
static emptyMass() {
|
|
1308
1304
|
return _PMF.zero().scaleMass(0);
|
|
@@ -1405,7 +1401,7 @@ var _PMF = class _PMF {
|
|
|
1405
1401
|
* @param eps Optional tolerance for floating point rounding
|
|
1406
1402
|
* @returns A single PMF representing the exclusive mixture of all options.
|
|
1407
1403
|
*/
|
|
1408
|
-
static exclusive(options, eps =
|
|
1404
|
+
static exclusive(options, eps = EPS) {
|
|
1409
1405
|
const items = options.map(
|
|
1410
1406
|
(o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o
|
|
1411
1407
|
);
|
|
@@ -1435,8 +1431,7 @@ var _PMF = class _PMF {
|
|
|
1435
1431
|
* [pCrit, critPMF],
|
|
1436
1432
|
* ]);
|
|
1437
1433
|
*/
|
|
1438
|
-
static mixN(weights) {
|
|
1439
|
-
const eps = 1e-12;
|
|
1434
|
+
static mixN(weights, eps = EPS) {
|
|
1440
1435
|
const filtered = weights.filter(([w]) => w > eps);
|
|
1441
1436
|
if (filtered.length === 0) {
|
|
1442
1437
|
return _PMF.emptyMass();
|
|
@@ -1458,17 +1453,17 @@ var _PMF = class _PMF {
|
|
|
1458
1453
|
// This is a convenience method for when we use power
|
|
1459
1454
|
// TODO: It can be smarter in the future, and we can also add it to query
|
|
1460
1455
|
// That way statistics operations on invalid PMFs can throw an error
|
|
1461
|
-
// TODO… how can we detect if manually merging two queries' combined PMFs, as that loses
|
|
1462
|
-
|
|
1463
|
-
if (!this.
|
|
1456
|
+
// TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance?
|
|
1457
|
+
setPreservedProvenance(preserved) {
|
|
1458
|
+
if (!this._preservedProvenance && preserved) {
|
|
1464
1459
|
throw new Error(
|
|
1465
|
-
"Preserved
|
|
1460
|
+
"Preserved provenance is already set to false, cannot fix that"
|
|
1466
1461
|
);
|
|
1467
1462
|
}
|
|
1468
|
-
this.
|
|
1463
|
+
this._preservedProvenance = preserved;
|
|
1469
1464
|
}
|
|
1470
|
-
|
|
1471
|
-
return this.
|
|
1465
|
+
preservedProvenance() {
|
|
1466
|
+
return this._preservedProvenance;
|
|
1472
1467
|
}
|
|
1473
1468
|
getPowerCacheKey(n, eps) {
|
|
1474
1469
|
const id = this.identifier;
|
|
@@ -1509,7 +1504,7 @@ var _PMF = class _PMF {
|
|
|
1509
1504
|
base = base.convolve(base, epsilon);
|
|
1510
1505
|
}
|
|
1511
1506
|
}
|
|
1512
|
-
result.
|
|
1507
|
+
result.setPreservedProvenance(false);
|
|
1513
1508
|
{
|
|
1514
1509
|
pmfCache?.set(key, result);
|
|
1515
1510
|
}
|
|
@@ -1538,7 +1533,7 @@ var _PMF = class _PMF {
|
|
|
1538
1533
|
outcomeMass(outcome) {
|
|
1539
1534
|
let totalProbabilityMass = 0;
|
|
1540
1535
|
for (const { p, count } of this.map.values()) {
|
|
1541
|
-
totalProbabilityMass += p * count[outcome];
|
|
1536
|
+
totalProbabilityMass += p * (count[outcome] ?? 0);
|
|
1542
1537
|
}
|
|
1543
1538
|
return totalProbabilityMass;
|
|
1544
1539
|
}
|
|
@@ -1801,7 +1796,7 @@ var _PMF = class _PMF {
|
|
|
1801
1796
|
}
|
|
1802
1797
|
convolve(other, eps, raw = false) {
|
|
1803
1798
|
const epsilon = eps ?? this.epsilon;
|
|
1804
|
-
const norm = (x) => raw ? x : Math.abs(x.mass() - 1) <=
|
|
1799
|
+
const norm = (x) => raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize();
|
|
1805
1800
|
const A0 = norm(this);
|
|
1806
1801
|
const B0 = norm(other);
|
|
1807
1802
|
const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0];
|
|
@@ -1836,14 +1831,13 @@ var _PMF = class _PMF {
|
|
|
1836
1831
|
epsilon,
|
|
1837
1832
|
!raw,
|
|
1838
1833
|
`${A.identifier}${raw ? "*" : "+"}${B.identifier}`
|
|
1839
|
-
// ← canonical order
|
|
1840
1834
|
);
|
|
1841
1835
|
const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1);
|
|
1842
1836
|
const mGot = result.mass();
|
|
1843
|
-
if (mExp !== 0 && Math.abs(mGot - mExp) >
|
|
1837
|
+
if (mExp !== 0 && Math.abs(mGot - mExp) > epsilon) {
|
|
1844
1838
|
result = result.scaleMass(mExp / mGot);
|
|
1845
1839
|
}
|
|
1846
|
-
if (!raw && Math.abs(result.mass() - 1) >
|
|
1840
|
+
if (!raw && Math.abs(result.mass() - 1) > epsilon)
|
|
1847
1841
|
result = result.normalize();
|
|
1848
1842
|
pmfCache?.set(cacheKey, result);
|
|
1849
1843
|
return result;
|
|
@@ -2052,6 +2046,21 @@ var _PMF = class _PMF {
|
|
|
2052
2046
|
}
|
|
2053
2047
|
return false;
|
|
2054
2048
|
}
|
|
2049
|
+
tailProbGE(t) {
|
|
2050
|
+
let s = 0;
|
|
2051
|
+
for (const [x, rec] of this) {
|
|
2052
|
+
const p = typeof rec === "number" ? rec : rec.p;
|
|
2053
|
+
if (p > 0 && x >= t) s += p;
|
|
2054
|
+
}
|
|
2055
|
+
return s;
|
|
2056
|
+
}
|
|
2057
|
+
tailProbGT(t) {
|
|
2058
|
+
let s = 0;
|
|
2059
|
+
for (const [x, rec] of this) {
|
|
2060
|
+
if (x > t) s += rec.p;
|
|
2061
|
+
}
|
|
2062
|
+
return s;
|
|
2063
|
+
}
|
|
2055
2064
|
/**
|
|
2056
2065
|
* Returns a new PMF containing only bins where the specified outcome has non-zero probability.
|
|
2057
2066
|
* This creates a marginal distribution for the given outcome type, with probabilities
|
|
@@ -2111,6 +2120,66 @@ var _PMF = class _PMF {
|
|
|
2111
2120
|
const pNone = 1 - pSpecificSuccess - pGeneralSuccess;
|
|
2112
2121
|
return { pSpecificSuccess, pGeneralSuccess, pNone, pAny };
|
|
2113
2122
|
}
|
|
2123
|
+
mapValues(f, eps = EPS, opts) {
|
|
2124
|
+
const rounding = opts?.rounding ?? "none";
|
|
2125
|
+
const preserveCounts = opts?.preserveCounts ?? true;
|
|
2126
|
+
const round = (x) => rounding === "floor" ? Math.floor(x) : rounding === "ceil" ? Math.ceil(x) : rounding === "round" ? Math.round(x) : x;
|
|
2127
|
+
const probs = /* @__PURE__ */ new Map();
|
|
2128
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2129
|
+
for (const [v, rec] of this) {
|
|
2130
|
+
if (Math.abs(rec.p) < eps) continue;
|
|
2131
|
+
const u = round(f(v));
|
|
2132
|
+
probs.set(u, (probs.get(u) ?? 0) + rec.p);
|
|
2133
|
+
if (preserveCounts) {
|
|
2134
|
+
const rec2 = this.map.get(v);
|
|
2135
|
+
const src = typeof rec2 === "number" ? void 0 : rec2?.count;
|
|
2136
|
+
if (src) {
|
|
2137
|
+
const dest = counts.get(u) ?? {};
|
|
2138
|
+
for (const k in src) {
|
|
2139
|
+
dest[k] = (dest[k] ?? 0) + src[k];
|
|
2140
|
+
}
|
|
2141
|
+
counts.set(u, dest);
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
const internal = /* @__PURE__ */ new Map();
|
|
2146
|
+
for (const [u, p] of probs) {
|
|
2147
|
+
internal.set(u, { p, count: counts.get(u) ?? {} });
|
|
2148
|
+
}
|
|
2149
|
+
return _PMF.fromMap(
|
|
2150
|
+
new Map(Array.from(internal, ([u, b]) => [u, b.p])),
|
|
2151
|
+
eps
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
static fromMap(m, eps = EPS, { requireIntegerValues = true } = {}) {
|
|
2155
|
+
const filtered = [];
|
|
2156
|
+
for (const [v, p] of m) {
|
|
2157
|
+
if (!Number.isFinite(v) || !Number.isFinite(p)) continue;
|
|
2158
|
+
if (p <= 0 || Math.abs(p) < eps) continue;
|
|
2159
|
+
if (requireIntegerValues && !Number.isInteger(v)) {
|
|
2160
|
+
throw new Error(`fromMap: non-integer outcome ${v}`);
|
|
2161
|
+
}
|
|
2162
|
+
filtered.push([v, p]);
|
|
2163
|
+
}
|
|
2164
|
+
if (filtered.length === 0) {
|
|
2165
|
+
throw new Error("fromMap: empty or invalid input map");
|
|
2166
|
+
}
|
|
2167
|
+
let sum = 0;
|
|
2168
|
+
let c = 0;
|
|
2169
|
+
for (const [, p] of filtered) {
|
|
2170
|
+
const y = p - c;
|
|
2171
|
+
const t = sum + y;
|
|
2172
|
+
c = t - sum - y;
|
|
2173
|
+
sum = t;
|
|
2174
|
+
}
|
|
2175
|
+
if (sum <= 0) throw new Error("pmfFromMap: probabilities sum to 0");
|
|
2176
|
+
filtered.sort((a, b) => a[0] - b[0]);
|
|
2177
|
+
const internal = /* @__PURE__ */ new Map();
|
|
2178
|
+
for (const [v, p] of filtered) {
|
|
2179
|
+
internal.set(v, { p: p / sum, count: {} });
|
|
2180
|
+
}
|
|
2181
|
+
return new _PMF(internal, eps);
|
|
2182
|
+
}
|
|
2114
2183
|
query() {
|
|
2115
2184
|
return new DiceQuery(this);
|
|
2116
2185
|
}
|
|
@@ -2119,6 +2188,127 @@ var _PMF = class _PMF {
|
|
|
2119
2188
|
__publicField(_PMF, "__anonIdCounter", 1);
|
|
2120
2189
|
var PMF = _PMF;
|
|
2121
2190
|
|
|
2191
|
+
// src/mixture.ts
|
|
2192
|
+
var Mixture = class _Mixture {
|
|
2193
|
+
constructor(eps = EPS) {
|
|
2194
|
+
__publicField(this, "totals", /* @__PURE__ */ new Map());
|
|
2195
|
+
// raw mass per outcome (pre-normalization)
|
|
2196
|
+
__publicField(this, "labelMass", /* @__PURE__ */ new Map());
|
|
2197
|
+
// raw mass per outcome per label
|
|
2198
|
+
__publicField(this, "eps");
|
|
2199
|
+
this.eps = Number.isFinite(eps) ? eps : EPS;
|
|
2200
|
+
}
|
|
2201
|
+
/** Remove all accumulated state. */
|
|
2202
|
+
clear() {
|
|
2203
|
+
this.totals.clear();
|
|
2204
|
+
this.labelMass.clear();
|
|
2205
|
+
return this;
|
|
2206
|
+
}
|
|
2207
|
+
/** Number of distinct outcome values currently accumulated. */
|
|
2208
|
+
size() {
|
|
2209
|
+
return this.totals.size;
|
|
2210
|
+
}
|
|
2211
|
+
/** Whether a label was ever added. */
|
|
2212
|
+
hasLabel(label) {
|
|
2213
|
+
for (const bag of this.labelMass.values()) if (bag[label]) return true;
|
|
2214
|
+
return false;
|
|
2215
|
+
}
|
|
2216
|
+
/**
|
|
2217
|
+
* Add a labeled component with a mixture weight.
|
|
2218
|
+
* Weight can be any positive finite number. Very small contributions are pruned by eps.
|
|
2219
|
+
*/
|
|
2220
|
+
add(label, pmf, weight = 1) {
|
|
2221
|
+
if (!Number.isFinite(weight) || weight <= 0) return this;
|
|
2222
|
+
for (const [v, binOrNumber] of pmf) {
|
|
2223
|
+
const isNumber = typeof binOrNumber === "number";
|
|
2224
|
+
const p = isNumber ? binOrNumber : binOrNumber?.p ?? 0;
|
|
2225
|
+
if (p <= 0) continue;
|
|
2226
|
+
const add = weight * p;
|
|
2227
|
+
if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue;
|
|
2228
|
+
this.totals.set(v, (this.totals.get(v) ?? 0) + add);
|
|
2229
|
+
const bag = this.labelMass.get(v) ?? {};
|
|
2230
|
+
bag[label] = (bag[label] ?? 0) + add;
|
|
2231
|
+
this.labelMass.set(v, bag);
|
|
2232
|
+
}
|
|
2233
|
+
return this;
|
|
2234
|
+
}
|
|
2235
|
+
buildPMF(eps = EPS) {
|
|
2236
|
+
let grand = 0;
|
|
2237
|
+
let c = 0;
|
|
2238
|
+
for (const m of this.totals.values()) {
|
|
2239
|
+
const y = m - c;
|
|
2240
|
+
const t = grand + y;
|
|
2241
|
+
c = t - grand - y;
|
|
2242
|
+
grand = t;
|
|
2243
|
+
}
|
|
2244
|
+
if (!(grand > 0)) throw new Error("Mixture: zero total mass");
|
|
2245
|
+
const internal = /* @__PURE__ */ new Map();
|
|
2246
|
+
for (const [v, m] of this.totals) {
|
|
2247
|
+
if (m <= 0 || Math.abs(m) < this.eps) continue;
|
|
2248
|
+
const count = this.labelMass.get(v) ?? {};
|
|
2249
|
+
internal.set(v, { p: m / grand, count });
|
|
2250
|
+
}
|
|
2251
|
+
return new PMF(internal, eps);
|
|
2252
|
+
}
|
|
2253
|
+
/**
|
|
2254
|
+
* Produce normalized *per-label* PMFs (labels independent).
|
|
2255
|
+
* These are unlabeled PMFs built from the raw mass of that label alone.
|
|
2256
|
+
*/
|
|
2257
|
+
byOutcome() {
|
|
2258
|
+
const labels = /* @__PURE__ */ new Set();
|
|
2259
|
+
for (const bag of this.labelMass.values()) {
|
|
2260
|
+
for (const k of Object.keys(bag)) labels.add(k);
|
|
2261
|
+
}
|
|
2262
|
+
const out = {};
|
|
2263
|
+
for (const label of labels) {
|
|
2264
|
+
const m = /* @__PURE__ */ new Map();
|
|
2265
|
+
for (const [v, bag] of this.labelMass) {
|
|
2266
|
+
const w = bag[label];
|
|
2267
|
+
if (w && Math.abs(w) >= this.eps) m.set(v, w);
|
|
2268
|
+
}
|
|
2269
|
+
if (m.size > 0) out[label] = PMF.fromMap(m, this.eps);
|
|
2270
|
+
}
|
|
2271
|
+
return out;
|
|
2272
|
+
}
|
|
2273
|
+
/**
|
|
2274
|
+
* Mixture weights per label, normalized to sum to 1 over labels that appeared.
|
|
2275
|
+
* Uses raw mass before per-outcome normalization.
|
|
2276
|
+
*/
|
|
2277
|
+
weights() {
|
|
2278
|
+
const res = {};
|
|
2279
|
+
for (const [, bag] of this.labelMass) {
|
|
2280
|
+
for (const [lab, w] of Object.entries(bag)) {
|
|
2281
|
+
if (!Number.isFinite(w) || w <= 0) continue;
|
|
2282
|
+
res[lab] = (res[lab] ?? 0) + w;
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
let total = 0;
|
|
2286
|
+
let c = 0;
|
|
2287
|
+
for (const v of Object.values(res)) {
|
|
2288
|
+
const y = v - c;
|
|
2289
|
+
const t = total + y;
|
|
2290
|
+
c = t - total - y;
|
|
2291
|
+
total = t;
|
|
2292
|
+
}
|
|
2293
|
+
if (total > 0) {
|
|
2294
|
+
for (const k in res) res[k] = res[k] / total;
|
|
2295
|
+
}
|
|
2296
|
+
return res;
|
|
2297
|
+
}
|
|
2298
|
+
toJSON() {
|
|
2299
|
+
return {
|
|
2300
|
+
totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]),
|
|
2301
|
+
labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]),
|
|
2302
|
+
eps: this.eps
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
static mix(items, eps = EPS) {
|
|
2306
|
+
const mix = new _Mixture(eps);
|
|
2307
|
+
for (const [lab, pmf, w] of items) mix.add(lab, pmf, w);
|
|
2308
|
+
return mix.buildPMF();
|
|
2309
|
+
}
|
|
2310
|
+
};
|
|
2311
|
+
|
|
2122
2312
|
// src/dice.ts
|
|
2123
2313
|
var Dice = class _Dice {
|
|
2124
2314
|
constructor(x = 0) {
|
|
@@ -2612,7 +2802,7 @@ function parseExpression(arr, n) {
|
|
|
2612
2802
|
}
|
|
2613
2803
|
critNorm = crit.total();
|
|
2614
2804
|
crit = op.call(crit, parseBinaryArgument(arg, arr, n));
|
|
2615
|
-
critNorm = critNorm ? crit.total() / critNorm : 1;
|
|
2805
|
+
critNorm = crit && critNorm ? crit.total() / critNorm : 1;
|
|
2616
2806
|
}
|
|
2617
2807
|
let save;
|
|
2618
2808
|
let saveNorm = 1;
|
|
@@ -2627,7 +2817,7 @@ function parseExpression(arr, n) {
|
|
|
2627
2817
|
saveNorm = save.total();
|
|
2628
2818
|
finalResult = finalResult.deleteFace(min);
|
|
2629
2819
|
save = op.call(save, parseBinaryArgument(arg, arr, n));
|
|
2630
|
-
saveNorm = saveNorm ? save.total() / saveNorm : 1;
|
|
2820
|
+
saveNorm = save && saveNorm ? save.total() / saveNorm : 1;
|
|
2631
2821
|
}
|
|
2632
2822
|
let pc;
|
|
2633
2823
|
let pcNorm = 1;
|
|
@@ -2640,7 +2830,7 @@ function parseExpression(arr, n) {
|
|
|
2640
2830
|
const missBefore = pc.total();
|
|
2641
2831
|
finalResult = finalResult.deleteFace(min);
|
|
2642
2832
|
pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2);
|
|
2643
|
-
const missAfter = pc.total();
|
|
2833
|
+
const missAfter = pc ? pc.total() : 0;
|
|
2644
2834
|
pcNorm = missBefore ? missAfter / missBefore : 1;
|
|
2645
2835
|
}
|
|
2646
2836
|
let miss;
|
|
@@ -2656,7 +2846,7 @@ function parseExpression(arr, n) {
|
|
|
2656
2846
|
missNorm = miss.total();
|
|
2657
2847
|
finalResult = finalResult.deleteFace(min);
|
|
2658
2848
|
miss = op.call(miss, parseBinaryArgument(arg, arr, n));
|
|
2659
|
-
missNorm = missNorm ? miss.total() / missNorm : 1;
|
|
2849
|
+
missNorm = miss && missNorm ? miss.total() / missNorm : 1;
|
|
2660
2850
|
}
|
|
2661
2851
|
let norm = finalResult.total();
|
|
2662
2852
|
finalResult = op.call(finalResult, arg);
|
|
@@ -2946,10 +3136,10 @@ function parseOperation(s) {
|
|
|
2946
3136
|
return;
|
|
2947
3137
|
}
|
|
2948
3138
|
|
|
2949
|
-
exports.Dice = Dice;
|
|
2950
3139
|
exports.DiceQuery = DiceQuery;
|
|
2951
3140
|
exports.EPS = EPS;
|
|
2952
3141
|
exports.LRUCache = LRUCache;
|
|
3142
|
+
exports.Mixture = Mixture;
|
|
2953
3143
|
exports.PMF = PMF;
|
|
2954
3144
|
exports.clearParserCache = clearParserCache;
|
|
2955
3145
|
exports.getCachingEnabled = getCachingEnabled;
|