@yipe/dice 0.1.0 → 0.1.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 CHANGED
@@ -1,15 +1,23 @@
1
- <!-- [![npm version](https://img.shields.io/npm/v/@dpr/dice.svg)](https://www.npmjs.com/package/@dpr/dice)
2
- [![Build Status](https://github.com/yipe/dpr/actions/workflows/ci.yml/badge.svg)](https://github.com/yipe/dpr/actions)
3
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -->
1
+ [![npm version](https://img.shields.io/npm/v/@yipe/dice.svg)](https://www.npmjs.com/package/@yipe/dice)
2
+ [![License: MIT](https://img.shields.io/badge/License-MIT-007ec6.svg)](LICENSE)
3
+ ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-3178c6?logo=typescript)
4
+ ![Size](https://img.shields.io/bundlephobia/minzip/@yipe/dice?logo=npm&logoColor=fff&label=Size)
5
+ ![Dice](https://dprcalc.com/api/roll.svg)
4
6
 
5
- # 🎲 @dpr/dice
7
+ ![Last Commit](https://img.shields.io/github/last-commit/yipe/dice?logo=github)
8
+ ![Dependencies](https://img.shields.io/librariesio/release/npm/@yipe/dice?logo=npm)
9
+ [![GitHub issues](https://img.shields.io/github/issues/yipe/dice.svg?logo=github)](https://github.com/yipe/dice/issues)
10
+ [![Build Status](https://github.com/yipe/dice/actions/workflows/ci.yml/badge.svg)](https://github.com/yipe/dice/actions)
11
+ [![Tests](https://img.shields.io/badge/tests-passing-4c1.svg?logo=vitest&logoColor=white)](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 "@dpr/dice";
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 @dpr/dice
40
+ npm install @yipe/dice
33
41
  # or
34
- yarn add @dpr/dice
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 "@dpr/dice";
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 "@dpr/dice";
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 "@dpr/dice";
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.cache = /* @__PURE__ */ new Map();
11
+ __publicField(this, "cache", /* @__PURE__ */ new Map());
8
12
  }
9
13
  get(key) {
10
14
  const value = this.cache.get(key);
@@ -43,12 +47,13 @@ var LRUCache = class {
43
47
  };
44
48
 
45
49
  // src/types.ts
46
- var COMPUTATIONAL_EPS = 1e-40;
47
- var TEST_EPS = 1e-10;
50
+ var EPS = 1e-12;
48
51
 
49
52
  // src/query.ts
50
53
  var _DiceQuery = class _DiceQuery {
51
54
  constructor(singles, combined) {
55
+ __publicField(this, "singles");
56
+ __publicField(this, "combined");
52
57
  this.singles = Array.isArray(singles) ? singles : [singles];
53
58
  const c = combined ?? PMF.convolveMany(this.singles);
54
59
  this.combined = Math.abs(c.mass() - 1) <= 1e-12 ? c : c.normalize();
@@ -995,7 +1000,7 @@ var _DiceQuery = class _DiceQuery {
995
1000
  outcomeTotals(outcomes = this.outcomeKeys()) {
996
1001
  const totals = /* @__PURE__ */ new Map();
997
1002
  outcomes.forEach((o) => totals.set(o, 0));
998
- for (const [x, row] of this.combined.map) {
1003
+ for (const [, row] of this.combined.map) {
999
1004
  for (const o of outcomes) {
1000
1005
  const p = row.count[o] || 0;
1001
1006
  totals.set(o, (totals.get(o) || 0) + p);
@@ -1210,7 +1215,7 @@ var _DiceQuery = class _DiceQuery {
1210
1215
  * const bonusAttack = parse("(d20 + 3 AC 15) * (1d6 + 1)");
1211
1216
  * const bothAttacks = mainAttack.convolve(bonusAttack);
1212
1217
  */
1213
- convolve(other, eps) {
1218
+ convolve(other) {
1214
1219
  const singles = [...this.singles, ...other.singles];
1215
1220
  return new _DiceQuery(singles);
1216
1221
  }
@@ -1223,7 +1228,7 @@ var _DiceQuery = class _DiceQuery {
1223
1228
  *
1224
1229
  * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone]
1225
1230
  */
1226
- firstSuccessSplit(successOutcome, subsetOutcome, eps = COMPUTATIONAL_EPS) {
1231
+ firstSuccessSplit(successOutcome, subsetOutcome, eps = EPS) {
1227
1232
  const pmfs = this.singles;
1228
1233
  if (!pmfs.length) {
1229
1234
  throw new Error("firstSuccessSplitFromPMFs: pmfs must be non-empty");
@@ -1266,33 +1271,40 @@ var _DiceQuery = class _DiceQuery {
1266
1271
  return [a, b, any, none];
1267
1272
  }
1268
1273
  };
1269
- _DiceQuery.DEFAULT_OUTCOMES = [
1274
+ __publicField(_DiceQuery, "DEFAULT_OUTCOMES", [
1270
1275
  "hit",
1271
1276
  "crit",
1272
1277
  "missNone"
1273
- ];
1278
+ ]);
1274
1279
  var DiceQuery = _DiceQuery;
1275
- var EPSILON = COMPUTATIONAL_EPS;
1276
1280
  var pmfCache = new LRUCache(1e3);
1277
1281
  var _PMF = class _PMF {
1278
- constructor(map = /* @__PURE__ */ new Map(), epsilon = EPSILON, normalized = false, identifier = `anon#${_PMF.__anonIdCounter++}`, _preservedProvidence = true) {
1282
+ constructor(map = /* @__PURE__ */ new Map(), epsilon = EPS, normalized = false, identifier = `anon#${_PMF.__anonIdCounter++}`, _preservedProvidence = true) {
1279
1283
  this.map = map;
1280
1284
  this.epsilon = epsilon;
1281
1285
  this.normalized = normalized;
1282
1286
  this.identifier = identifier;
1283
1287
  this._preservedProvidence = _preservedProvidence;
1284
- }
1285
- static empty(epsilon = EPSILON, identifier = "empty") {
1288
+ // Cached computed values
1289
+ __publicField(this, "_support");
1290
+ __publicField(this, "_min");
1291
+ __publicField(this, "_max");
1292
+ __publicField(this, "_totalMass");
1293
+ __publicField(this, "_mean");
1294
+ __publicField(this, "_variance");
1295
+ __publicField(this, "_stdev");
1296
+ }
1297
+ static empty(epsilon = EPS, identifier = "empty") {
1286
1298
  return new _PMF(/* @__PURE__ */ new Map(), epsilon, false, identifier);
1287
1299
  }
1288
1300
  // This has a single bin at value 0, mass of 1
1289
- static zero(epsilon = EPSILON) {
1301
+ static zero(epsilon = EPS) {
1290
1302
  const m = /* @__PURE__ */ new Map();
1291
1303
  m.set(0, { p: 1, count: { miss: 1 }, attr: {} });
1292
1304
  return new _PMF(m, epsilon, false, "zero");
1293
1305
  }
1294
1306
  // This creates a single bin at value 0, but with weight 0.
1295
- static emptyMass(epsilon = EPSILON) {
1307
+ static emptyMass() {
1296
1308
  return _PMF.zero().scaleMass(0);
1297
1309
  }
1298
1310
  // Makes PMF iterable over [damage, bin] pairs.
@@ -1458,6 +1470,12 @@ var _PMF = class _PMF {
1458
1470
  preservedProvidence() {
1459
1471
  return this._preservedProvidence;
1460
1472
  }
1473
+ getPowerCacheKey(n, eps) {
1474
+ const id = this.identifier;
1475
+ let key = `${id}`;
1476
+ for (let i = 1; i < n; i++) key += `+${id}`;
1477
+ return `${key}@${eps}`;
1478
+ }
1461
1479
  /**
1462
1480
  * Efficiently computes this PMF convolved with itself `n` times.
1463
1481
  * Uses exponentiation by squaring to reduce total convolutions.
@@ -1474,7 +1492,7 @@ var _PMF = class _PMF {
1474
1492
  }
1475
1493
  if (n === 1) return this;
1476
1494
  const epsilon = eps ?? this.epsilon;
1477
- const key = getPowerCacheKey(this, n, epsilon);
1495
+ const key = this.getPowerCacheKey(n, epsilon);
1478
1496
  {
1479
1497
  const cached = pmfCache?.get(key);
1480
1498
  if (cached) return cached;
@@ -1500,7 +1518,7 @@ var _PMF = class _PMF {
1500
1518
  /*
1501
1519
  * Helper for chaining multiple identical attacks
1502
1520
  */
1503
- replicate(n, eps = this.epsilon) {
1521
+ replicate(n) {
1504
1522
  if (!Number.isInteger(n) || n <= 0) {
1505
1523
  throw new Error("combineN(n): n must be a positive integer");
1506
1524
  }
@@ -1873,7 +1891,7 @@ var _PMF = class _PMF {
1873
1891
  * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.)
1874
1892
  * - Order-independent cache keys work better with consistent build patterns
1875
1893
  */
1876
- static convolveMany(pmfList, eps = EPSILON) {
1894
+ static convolveMany(pmfList, eps = EPS) {
1877
1895
  if (pmfList.length === 0) return _PMF.empty(eps);
1878
1896
  if (pmfList.length === 1) return pmfList[0];
1879
1897
  return _PMF.reduceConvolveLeft(pmfList, eps);
@@ -1888,7 +1906,7 @@ var _PMF = class _PMF {
1888
1906
  static fromJSON(jsonData) {
1889
1907
  return new _PMF(
1890
1908
  new Map(jsonData.bins),
1891
- EPSILON,
1909
+ EPS,
1892
1910
  !!jsonData.normalized,
1893
1911
  jsonData.identifier || "fromJSON"
1894
1912
  );
@@ -2098,22 +2116,17 @@ var _PMF = class _PMF {
2098
2116
  }
2099
2117
  };
2100
2118
  // Unique ID generator for anonymous PMFs to avoid cache key collisions
2101
- _PMF.__anonIdCounter = 1;
2119
+ __publicField(_PMF, "__anonIdCounter", 1);
2102
2120
  var PMF = _PMF;
2103
- function getPowerCacheKey(base, n, eps) {
2104
- const id = base.identifier;
2105
- let key = `${id}`;
2106
- for (let i = 1; i < n; i++) key += `+${id}`;
2107
- return `${key}@${eps}`;
2108
- }
2109
2121
 
2110
2122
  // src/dice.ts
2111
2123
  var Dice = class _Dice {
2112
2124
  constructor(x = 0) {
2113
- this.faces = {};
2114
- this.privateData = {};
2115
- this.outcomeData = {};
2116
- this.hasHitDistributionCalculated = false;
2125
+ __publicField(this, "faces", {});
2126
+ __publicField(this, "privateData", {});
2127
+ __publicField(this, "outcomeData", {});
2128
+ __publicField(this, "hasHitDistributionCalculated", false);
2129
+ __publicField(this, "identifier");
2117
2130
  if (x <= 0) return;
2118
2131
  for (let i = 1; i <= x; i++) {
2119
2132
  this.faces[i] = 1;
@@ -2420,7 +2433,7 @@ var Dice = class _Dice {
2420
2433
  * Convert dice to PMF using OutcomeType labels directly from damage distribution.
2421
2434
  * This is much cleaner than the original complex distribution conversion.
2422
2435
  */
2423
- toPMF(numEpsilon = COMPUTATIONAL_EPS) {
2436
+ toPMF(numEpsilon = EPS) {
2424
2437
  const total = this.total();
2425
2438
  if (total === 0) return PMF.empty(numEpsilon);
2426
2439
  this.ensureHitDistribution();
@@ -2782,20 +2795,12 @@ function parseArgumentInternal(s, n) {
2782
2795
  case "k":
2783
2796
  assertToken(s, "k");
2784
2797
  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
2798
  case "n":
2796
2799
  return parseNumber(s, n);
2800
+ default:
2801
+ if (isDigit(c)) return parseNumber(s, n);
2802
+ return;
2797
2803
  }
2798
- return;
2799
2804
  }
2800
2805
  function parseBinaryArgument(arg, arr, n) {
2801
2806
  if (arr.length >= 4 && arr[0] === "h" && peek(arr, "half")) {
@@ -2941,16 +2946,15 @@ function parseOperation(s) {
2941
2946
  return;
2942
2947
  }
2943
2948
 
2944
- exports.COMPUTATIONAL_EPS = COMPUTATIONAL_EPS;
2945
2949
  exports.Dice = Dice;
2946
2950
  exports.DiceQuery = DiceQuery;
2951
+ exports.EPS = EPS;
2947
2952
  exports.LRUCache = LRUCache;
2948
2953
  exports.PMF = PMF;
2949
- exports.TEST_EPS = TEST_EPS;
2950
2954
  exports.clearParserCache = clearParserCache;
2951
2955
  exports.getCachingEnabled = getCachingEnabled;
2952
- exports.getPowerCacheKey = getPowerCacheKey;
2953
2956
  exports.parse = parse;
2957
+ exports.pmfCache = pmfCache;
2954
2958
  exports.setCachingEnabled = setCachingEnabled;
2955
2959
  //# sourceMappingURL=index.cjs.map
2956
2960
  //# sourceMappingURL=index.cjs.map