@yipe/dice 0.1.0 → 0.1.1
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 +18 -11
- package/dist/index.cjs +30 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +30 -23
- package/dist/index.js.map +1 -1
- package/package.json +19 -13
package/README.md
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
[](https://www.npmjs.com/package/@yipe/dice)
|
|
2
|
+
[](LICENSE)
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
4
6
|
|
|
5
|
-
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
[](https://github.com/yipe/dice/issues)
|
|
10
|
+
[](https://github.com/yipe/dice/actions)
|
|
11
|
+
[](https://github.com/yipe/dice/actions/workflows/ci.yml)
|
|
12
|
+
|
|
13
|
+
# 🎲 @yipe/dice
|
|
6
14
|
|
|
7
15
|
A TypeScript library for **D&D 5e damage-per-round (DPR) calculations**, designed for players, Dungeon Masters, and developers who want to analyze combat mathematically.
|
|
8
16
|
|
|
9
17
|
This library powers [dprcalc.com](https://dprcalc.com) and provides a precise, composable way to model dice rolls, attacks, and outcomes with probability mass functions (PMFs) — not just averages. This allows for rich charting and statistics with full outcome attribution.
|
|
10
18
|
|
|
11
19
|
```ts
|
|
12
|
-
import { parse } from "@
|
|
20
|
+
import { parse } from "@yipe/dice";
|
|
13
21
|
|
|
14
22
|
const attack = parse("(d20 + 8 AC 16) * (1d8 + 4) crit (2d8 + 4)");
|
|
15
23
|
console.log("DPR:", attack.mean());
|
|
@@ -29,9 +37,9 @@ console.log("DPR:", attack.mean());
|
|
|
29
37
|
|
|
30
38
|
```bash
|
|
31
39
|
# Install with npm or yarn
|
|
32
|
-
npm install @
|
|
40
|
+
npm install @yipe/dice
|
|
33
41
|
# or
|
|
34
|
-
yarn add @
|
|
42
|
+
yarn add @yipe/dice
|
|
35
43
|
```
|
|
36
44
|
|
|
37
45
|
## 📦 Core Concepts
|
|
@@ -48,7 +56,7 @@ yarn add @dpr/dice
|
|
|
48
56
|
Here's a simple example of calculating damage for a basic attack:
|
|
49
57
|
|
|
50
58
|
```ts
|
|
51
|
-
import { parse, DiceQuery } from "@
|
|
59
|
+
import { parse, DiceQuery } from "@yipe/dice";
|
|
52
60
|
|
|
53
61
|
const query = parse("(d20 + 8 AC 16) * (1d4 + 4) crit (2d4 + 4)").toQuery();
|
|
54
62
|
|
|
@@ -70,7 +78,7 @@ DPR: 4.35
|
|
|
70
78
|
Conditional damage ("once-per-turn damage riders") like Sneak Attack can be modeled easily:
|
|
71
79
|
|
|
72
80
|
```ts
|
|
73
|
-
import { parse, DiceQuery } from "@
|
|
81
|
+
import { parse, DiceQuery } from "@yipe/dice";
|
|
74
82
|
|
|
75
83
|
function damageRiderExample() {
|
|
76
84
|
const attack = parse("(d20 + 8 AC 16) * (1d4 + 4) crit (2d4 + 4)");
|
|
@@ -107,7 +115,7 @@ function damageRiderExample() {
|
|
|
107
115
|
You can generate full statistical distributions for visualization or reporting.
|
|
108
116
|
|
|
109
117
|
```ts
|
|
110
|
-
import { parse, DiceQuery } from "@
|
|
118
|
+
import { parse, DiceQuery } from "@yipe/dice";
|
|
111
119
|
|
|
112
120
|
const query2 = parse("(d20 + 8 AC 16) * (1d4 + 4) crit (2d4 + 4)").toQuery();
|
|
113
121
|
console.table(query2.toChartSeries());
|
|
@@ -227,7 +235,6 @@ Here is the basic example:
|
|
|
227
235
|
|
|
228
236
|
## 🧱 Roadmap
|
|
229
237
|
|
|
230
|
-
- [ ] **Publish** to npm as `@dpr/dice`
|
|
231
238
|
- [ ] Create a **web playground** with live examples
|
|
232
239
|
- [ ] Consider creating higher-level APIs: `Turn`, `Attack`, `DamageRider`
|
|
233
240
|
- [ ] Simplify and improve PMF and Query interface ergonomics
|
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
5
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
6
|
+
|
|
3
7
|
// src/lru-cache.ts
|
|
4
8
|
var LRUCache = class {
|
|
5
9
|
constructor(maxSize = 1e3) {
|
|
6
10
|
this.maxSize = maxSize;
|
|
7
|
-
this
|
|
11
|
+
__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
8
12
|
}
|
|
9
13
|
get(key) {
|
|
10
14
|
const value = this.cache.get(key);
|
|
@@ -49,6 +53,8 @@ var TEST_EPS = 1e-10;
|
|
|
49
53
|
// src/query.ts
|
|
50
54
|
var _DiceQuery = class _DiceQuery {
|
|
51
55
|
constructor(singles, combined) {
|
|
56
|
+
__publicField(this, "singles");
|
|
57
|
+
__publicField(this, "combined");
|
|
52
58
|
this.singles = Array.isArray(singles) ? singles : [singles];
|
|
53
59
|
const c = combined ?? PMF.convolveMany(this.singles);
|
|
54
60
|
this.combined = Math.abs(c.mass() - 1) <= 1e-12 ? c : c.normalize();
|
|
@@ -995,7 +1001,7 @@ var _DiceQuery = class _DiceQuery {
|
|
|
995
1001
|
outcomeTotals(outcomes = this.outcomeKeys()) {
|
|
996
1002
|
const totals = /* @__PURE__ */ new Map();
|
|
997
1003
|
outcomes.forEach((o) => totals.set(o, 0));
|
|
998
|
-
for (const [
|
|
1004
|
+
for (const [, row] of this.combined.map) {
|
|
999
1005
|
for (const o of outcomes) {
|
|
1000
1006
|
const p = row.count[o] || 0;
|
|
1001
1007
|
totals.set(o, (totals.get(o) || 0) + p);
|
|
@@ -1210,7 +1216,7 @@ var _DiceQuery = class _DiceQuery {
|
|
|
1210
1216
|
* const bonusAttack = parse("(d20 + 3 AC 15) * (1d6 + 1)");
|
|
1211
1217
|
* const bothAttacks = mainAttack.convolve(bonusAttack);
|
|
1212
1218
|
*/
|
|
1213
|
-
convolve(other
|
|
1219
|
+
convolve(other) {
|
|
1214
1220
|
const singles = [...this.singles, ...other.singles];
|
|
1215
1221
|
return new _DiceQuery(singles);
|
|
1216
1222
|
}
|
|
@@ -1266,11 +1272,11 @@ var _DiceQuery = class _DiceQuery {
|
|
|
1266
1272
|
return [a, b, any, none];
|
|
1267
1273
|
}
|
|
1268
1274
|
};
|
|
1269
|
-
_DiceQuery
|
|
1275
|
+
__publicField(_DiceQuery, "DEFAULT_OUTCOMES", [
|
|
1270
1276
|
"hit",
|
|
1271
1277
|
"crit",
|
|
1272
1278
|
"missNone"
|
|
1273
|
-
];
|
|
1279
|
+
]);
|
|
1274
1280
|
var DiceQuery = _DiceQuery;
|
|
1275
1281
|
var EPSILON = COMPUTATIONAL_EPS;
|
|
1276
1282
|
var pmfCache = new LRUCache(1e3);
|
|
@@ -1281,6 +1287,14 @@ var _PMF = class _PMF {
|
|
|
1281
1287
|
this.normalized = normalized;
|
|
1282
1288
|
this.identifier = identifier;
|
|
1283
1289
|
this._preservedProvidence = _preservedProvidence;
|
|
1290
|
+
// Cached computed values
|
|
1291
|
+
__publicField(this, "_support");
|
|
1292
|
+
__publicField(this, "_min");
|
|
1293
|
+
__publicField(this, "_max");
|
|
1294
|
+
__publicField(this, "_totalMass");
|
|
1295
|
+
__publicField(this, "_mean");
|
|
1296
|
+
__publicField(this, "_variance");
|
|
1297
|
+
__publicField(this, "_stdev");
|
|
1284
1298
|
}
|
|
1285
1299
|
static empty(epsilon = EPSILON, identifier = "empty") {
|
|
1286
1300
|
return new _PMF(/* @__PURE__ */ new Map(), epsilon, false, identifier);
|
|
@@ -1292,7 +1306,7 @@ var _PMF = class _PMF {
|
|
|
1292
1306
|
return new _PMF(m, epsilon, false, "zero");
|
|
1293
1307
|
}
|
|
1294
1308
|
// This creates a single bin at value 0, but with weight 0.
|
|
1295
|
-
static emptyMass(
|
|
1309
|
+
static emptyMass() {
|
|
1296
1310
|
return _PMF.zero().scaleMass(0);
|
|
1297
1311
|
}
|
|
1298
1312
|
// Makes PMF iterable over [damage, bin] pairs.
|
|
@@ -1500,7 +1514,7 @@ var _PMF = class _PMF {
|
|
|
1500
1514
|
/*
|
|
1501
1515
|
* Helper for chaining multiple identical attacks
|
|
1502
1516
|
*/
|
|
1503
|
-
replicate(n
|
|
1517
|
+
replicate(n) {
|
|
1504
1518
|
if (!Number.isInteger(n) || n <= 0) {
|
|
1505
1519
|
throw new Error("combineN(n): n must be a positive integer");
|
|
1506
1520
|
}
|
|
@@ -2098,7 +2112,7 @@ var _PMF = class _PMF {
|
|
|
2098
2112
|
}
|
|
2099
2113
|
};
|
|
2100
2114
|
// Unique ID generator for anonymous PMFs to avoid cache key collisions
|
|
2101
|
-
_PMF
|
|
2115
|
+
__publicField(_PMF, "__anonIdCounter", 1);
|
|
2102
2116
|
var PMF = _PMF;
|
|
2103
2117
|
function getPowerCacheKey(base, n, eps) {
|
|
2104
2118
|
const id = base.identifier;
|
|
@@ -2110,10 +2124,11 @@ function getPowerCacheKey(base, n, eps) {
|
|
|
2110
2124
|
// src/dice.ts
|
|
2111
2125
|
var Dice = class _Dice {
|
|
2112
2126
|
constructor(x = 0) {
|
|
2113
|
-
this
|
|
2114
|
-
this
|
|
2115
|
-
this
|
|
2116
|
-
this
|
|
2127
|
+
__publicField(this, "faces", {});
|
|
2128
|
+
__publicField(this, "privateData", {});
|
|
2129
|
+
__publicField(this, "outcomeData", {});
|
|
2130
|
+
__publicField(this, "hasHitDistributionCalculated", false);
|
|
2131
|
+
__publicField(this, "identifier");
|
|
2117
2132
|
if (x <= 0) return;
|
|
2118
2133
|
for (let i = 1; i <= x; i++) {
|
|
2119
2134
|
this.faces[i] = 1;
|
|
@@ -2782,20 +2797,12 @@ function parseArgumentInternal(s, n) {
|
|
|
2782
2797
|
case "k":
|
|
2783
2798
|
assertToken(s, "k");
|
|
2784
2799
|
return parseKeep(s, n);
|
|
2785
|
-
case "0":
|
|
2786
|
-
case "1":
|
|
2787
|
-
case "2":
|
|
2788
|
-
case "3":
|
|
2789
|
-
case "4":
|
|
2790
|
-
case "5":
|
|
2791
|
-
case "6":
|
|
2792
|
-
case "7":
|
|
2793
|
-
case "8":
|
|
2794
|
-
case "9":
|
|
2795
2800
|
case "n":
|
|
2796
2801
|
return parseNumber(s, n);
|
|
2802
|
+
default:
|
|
2803
|
+
if (isDigit(c)) return parseNumber(s, n);
|
|
2804
|
+
return;
|
|
2797
2805
|
}
|
|
2798
|
-
return;
|
|
2799
2806
|
}
|
|
2800
2807
|
function parseBinaryArgument(arg, arr, n) {
|
|
2801
2808
|
if (arr.length >= 4 && arr[0] === "h" && peek(arr, "half")) {
|