@yipe/dice 0.8.1 → 0.9.0
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/CHANGELOG.md +456 -0
- package/README.md +169 -19
- package/dist/builder/index.cjs +668 -6
- package/dist/builder/index.cjs.map +1 -1
- package/dist/builder/index.d.ts +1 -0
- package/dist/builder/index.d.ts.map +1 -1
- package/dist/builder/index.js +665 -7
- package/dist/builder/index.js.map +1 -1
- package/dist/index.cjs +114 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +113 -1
- package/dist/index.js.map +1 -1
- package/dist/parser/rollType.d.ts +58 -0
- package/dist/parser/rollType.d.ts.map +1 -0
- package/dist/pmf/query.d.ts +26 -0
- package/dist/pmf/query.d.ts.map +1 -1
- package/dist/turn/index.d.ts +3 -0
- package/dist/turn/index.d.ts.map +1 -0
- package/dist/turn/plan.d.ts +55 -0
- package/dist/turn/plan.d.ts.map +1 -0
- package/dist/turn/state.d.ts +23 -0
- package/dist/turn/state.d.ts.map +1 -0
- package/dist/turn/turn.d.ts +149 -0
- package/dist/turn/turn.d.ts.map +1 -0
- package/dist/turn/types.d.ts +99 -0
- package/dist/turn/types.d.ts.map +1 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./common/errors.js";
|
|
|
3
3
|
export * from "./common/lru-cache.js";
|
|
4
4
|
export * from "./common/types.js";
|
|
5
5
|
export * from "./parser/parser.js";
|
|
6
|
+
export * from "./parser/rollType.js";
|
|
6
7
|
export * from "./pmf/mixture.js";
|
|
7
8
|
export * from "./pmf/pmf.js";
|
|
8
9
|
export * from "./pmf/query.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -207,6 +207,10 @@ var _DiceQuery = class _DiceQuery {
|
|
|
207
207
|
if (this._combinedWithAttr) {
|
|
208
208
|
return this._combinedWithAttr;
|
|
209
209
|
}
|
|
210
|
+
if (this._combinedProvided) {
|
|
211
|
+
this._combinedWithAttr = this.combined.withAttribution();
|
|
212
|
+
return this._combinedWithAttr;
|
|
213
|
+
}
|
|
210
214
|
if (this.singles.every((pmf) => pmf.hasAttribution())) {
|
|
211
215
|
this._combinedWithAttr = this.combined;
|
|
212
216
|
return this._combinedWithAttr;
|
|
@@ -977,6 +981,55 @@ var _DiceQuery = class _DiceQuery {
|
|
|
977
981
|
}
|
|
978
982
|
return out;
|
|
979
983
|
}
|
|
984
|
+
/**
|
|
985
|
+
* Per-outcome probabilities and damage ranges, aggregated over the individual
|
|
986
|
+
* singles rather than read off the combined distribution.
|
|
987
|
+
*
|
|
988
|
+
* `damageRange` is the sum, over every single that can produce the outcome, of
|
|
989
|
+
* that single's own conditional damage range: "what this outcome contributes
|
|
990
|
+
* across the whole turn when every attack that can produce it does". Linear in
|
|
991
|
+
* the number of attacks by construction.
|
|
992
|
+
*
|
|
993
|
+
* Prefer this over {@link DiceQuery.snapshot} for a multi-attack query.
|
|
994
|
+
* `snapshot` reads `damageRange` off the combined PMF's `count`, which the
|
|
995
|
+
* convolution accumulates as an expected count, so its `avg` is size-biased
|
|
996
|
+
* for N≥2 (its own doc comment says so). The two agree for a single attack.
|
|
997
|
+
*
|
|
998
|
+
* Only outcomes that actually occur appear in the result.
|
|
999
|
+
*
|
|
1000
|
+
* Like every `singles`-based helper on this class, it describes the singles
|
|
1001
|
+
* and not an explicitly provided `combined`. `Turn.toQuery()` supplies one whose
|
|
1002
|
+
* distribution also contains rider attacks that are absent from `singles`
|
|
1003
|
+
* (an `otherwise([unarmed, unarmed])` flurry, say), so those attacks do not
|
|
1004
|
+
* appear here. For rider-inclusive figures read the combined distribution
|
|
1005
|
+
* directly: {@link DiceQuery.outcomeTotals}, {@link DiceQuery.outcomeDamageRanges}.
|
|
1006
|
+
*
|
|
1007
|
+
* @param outcomes Which outcomes to consider; defaults to every canonical one.
|
|
1008
|
+
*/
|
|
1009
|
+
outcomeStats(outcomes = ALL_OUTCOME_TYPES) {
|
|
1010
|
+
const stats = /* @__PURE__ */ new Map();
|
|
1011
|
+
const perSingle = this.singles.map((pmf) => new _DiceQuery([pmf], void 0, this._eps));
|
|
1012
|
+
for (const outcome of outcomes) {
|
|
1013
|
+
const atLeastOneProbability = this.probAtLeastOne(outcome);
|
|
1014
|
+
if (atLeastOneProbability <= 0) continue;
|
|
1015
|
+
const damageRange = { min: 0, avg: 0, max: 0 };
|
|
1016
|
+
let contributors = 0;
|
|
1017
|
+
for (const single of perSingle) {
|
|
1018
|
+
if (single.probAtLeastOne(outcome) <= 0) continue;
|
|
1019
|
+
contributors++;
|
|
1020
|
+
const stat = single.damageStatsFrom(outcome);
|
|
1021
|
+
damageRange.min += stat.min;
|
|
1022
|
+
damageRange.avg += stat.avg;
|
|
1023
|
+
damageRange.max += stat.max;
|
|
1024
|
+
}
|
|
1025
|
+
stats.set(outcome, {
|
|
1026
|
+
atLeastOneProbability,
|
|
1027
|
+
allProbability: contributors > 0 ? this.probExactlyK(outcome, contributors) : 0,
|
|
1028
|
+
damageRange
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
return stats;
|
|
1032
|
+
}
|
|
980
1033
|
/**
|
|
981
1034
|
* Snapshot of the distribution in the exact shape the UI consumes.
|
|
982
1035
|
* - outcome probabilities are "at least one" (and equal to "all" for a single PMF)
|
|
@@ -3345,6 +3398,65 @@ function parseOperation(s) {
|
|
|
3345
3398
|
return;
|
|
3346
3399
|
}
|
|
3347
3400
|
|
|
3401
|
+
// src/parser/rollType.ts
|
|
3402
|
+
var DECIMAL_INTEGER = /^\s*[+-]?\d+\s*$/;
|
|
3403
|
+
function tryParse(expression) {
|
|
3404
|
+
if (DECIMAL_INTEGER.test(expression)) {
|
|
3405
|
+
const value = Number(expression);
|
|
3406
|
+
return Number.isSafeInteger(value) ? PMF.delta(value) : PMF.empty();
|
|
3407
|
+
}
|
|
3408
|
+
try {
|
|
3409
|
+
return parse(expression);
|
|
3410
|
+
} catch {
|
|
3411
|
+
return PMF.empty();
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
var CHECK_TOKEN = /\b(AC|DC)\b/i;
|
|
3415
|
+
var D20_RUN = /\bh?d20(?:\s*[><]\s*h?d20)*/i;
|
|
3416
|
+
var RUN_FOR_ROLL_TYPE = {
|
|
3417
|
+
flat: "d20",
|
|
3418
|
+
advantage: "d20 > d20",
|
|
3419
|
+
disadvantage: "d20 < d20",
|
|
3420
|
+
"elven accuracy": "d20 > d20 > d20"
|
|
3421
|
+
};
|
|
3422
|
+
function withRollType(expression, rollType) {
|
|
3423
|
+
const scopes = enclosingScopes(expression);
|
|
3424
|
+
let rewritten = "";
|
|
3425
|
+
let copiedUpTo = 0;
|
|
3426
|
+
for (const run of expression.matchAll(new RegExp(D20_RUN, "gi"))) {
|
|
3427
|
+
const at = run.index;
|
|
3428
|
+
if (!isAttackRoll(expression, scopes, at)) continue;
|
|
3429
|
+
const replacement = run[0].toLowerCase().startsWith("h") ? `h${RUN_FOR_ROLL_TYPE[rollType]}` : RUN_FOR_ROLL_TYPE[rollType];
|
|
3430
|
+
rewritten += expression.slice(copiedUpTo, at) + replacement;
|
|
3431
|
+
copiedUpTo = at + run[0].length;
|
|
3432
|
+
}
|
|
3433
|
+
return rewritten + expression.slice(copiedUpTo);
|
|
3434
|
+
}
|
|
3435
|
+
function enclosingScopes(expression) {
|
|
3436
|
+
const open = [];
|
|
3437
|
+
const scopes = [];
|
|
3438
|
+
for (let i = 0; i < expression.length; i++) {
|
|
3439
|
+
if (expression[i] === "(") open.push(i);
|
|
3440
|
+
else if (expression[i] === ")") {
|
|
3441
|
+
const start = open.pop();
|
|
3442
|
+
if (start !== void 0) scopes.push({ start, end: i + 1 });
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
return scopes.sort((a, b) => a.end - a.start - (b.end - b.start));
|
|
3446
|
+
}
|
|
3447
|
+
function isAttackRoll(expression, scopes, at) {
|
|
3448
|
+
let scoped = false;
|
|
3449
|
+
for (const scope of scopes) {
|
|
3450
|
+
if (at < scope.start || at >= scope.end) continue;
|
|
3451
|
+
scoped = true;
|
|
3452
|
+
const check2 = CHECK_TOKEN.exec(expression.slice(scope.start, scope.end));
|
|
3453
|
+
if (check2) return check2[1].toUpperCase() === "AC";
|
|
3454
|
+
}
|
|
3455
|
+
if (scoped) return false;
|
|
3456
|
+
const check = CHECK_TOKEN.exec(expression);
|
|
3457
|
+
return check !== null && check[1].toUpperCase() === "AC";
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3348
3460
|
// src/pmf/mixture.ts
|
|
3349
3461
|
var Mixture = class _Mixture {
|
|
3350
3462
|
constructor(eps = EPS) {
|
|
@@ -3463,6 +3575,6 @@ var Mixture = class _Mixture {
|
|
|
3463
3575
|
}
|
|
3464
3576
|
};
|
|
3465
3577
|
|
|
3466
|
-
export { ALL_OUTCOME_TYPES, DiceParseError, DiceQuery, EPS, LRUCache, MISS_NONE_OUTCOME, Mixture, OUTCOME_DISPLAY_ORDER, PMF, calculateBounceOdds, clearParserCache, critProbability, getCachingEnabled, onAnyHit, onCritOnly, onHitOnly, onMissDamageOnly, onMissOnly, onPotentCantripOnly, onSaveFailOnly, onSaveHalfOnly, parse, pmfCache, setCachingEnabled, sortOutcomes };
|
|
3578
|
+
export { ALL_OUTCOME_TYPES, DiceParseError, DiceQuery, EPS, LRUCache, MISS_NONE_OUTCOME, Mixture, OUTCOME_DISPLAY_ORDER, PMF, calculateBounceOdds, clearParserCache, critProbability, getCachingEnabled, onAnyHit, onCritOnly, onHitOnly, onMissDamageOnly, onMissOnly, onPotentCantripOnly, onSaveFailOnly, onSaveHalfOnly, parse, pmfCache, setCachingEnabled, sortOutcomes, tryParse, withRollType };
|
|
3467
3579
|
//# sourceMappingURL=index.js.map
|
|
3468
3580
|
//# sourceMappingURL=index.js.map
|