@yipe/dice 0.2.16 → 0.2.19

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
@@ -36,7 +36,9 @@ console.log("DPR:", attack.mean());
36
36
  - **Composable API**: Build dice expressions, run queries, and analyze results in just a few lines.
37
37
  - **TypeScript First**: Full type safety and developer experience.
38
38
 
39
- ## 🚀 Installation
39
+ ## 🚀 Quick Start
40
+
41
+ ### Installation
40
42
 
41
43
  ```bash
42
44
  # Install with npm or yarn
@@ -45,6 +47,236 @@ npm install @yipe/dice
45
47
  yarn add @yipe/dice
46
48
  ```
47
49
 
50
+ ### Basic Usage
51
+
52
+ ```ts
53
+ import { parse, DiceQuery } from "@yipe/dice";
54
+
55
+ const query = d20.plus(8).ac(16).onHit(d4.plus(4)).toQuery();
56
+
57
+ console.log("Hit chance:", query.probAtLeastOne(["hit", "crit"]));
58
+ console.log("Crit chance:", query.probAtLeastOne(["crit"]));
59
+ console.log("DPR:", query.mean());
60
+ ```
61
+
62
+ **Output:**
63
+
64
+ ```
65
+ Hit chance: 0.65
66
+ Crit chance: 0.05
67
+ DPR: 4.35
68
+ ```
69
+
70
+ ## 🛠 Development Setup
71
+
72
+ ### Prerequisites
73
+
74
+ - **Node.js**: >= 18.17
75
+ - **Yarn**: 4.9.4 (specified in `packageManager`)
76
+
77
+ ### Initial Setup
78
+
79
+ ```bash
80
+ # Clone the repository
81
+ git clone https://github.com/yipe/dice.git
82
+ cd dice
83
+
84
+ # Install dependencies
85
+ yarn install
86
+
87
+ # Build the project
88
+ yarn build
89
+
90
+ # Run tests
91
+ yarn test
92
+
93
+ # Run examples
94
+ yarn example
95
+ ```
96
+
97
+ ### Available Scripts
98
+
99
+ | Command | Purpose |
100
+ |---------|---------|
101
+ | `yarn build` | Compile TypeScript to JavaScript (outputs to `dist/`) |
102
+ | `yarn test` | Run test suite once |
103
+ | `yarn test:watch` | Run tests in watch mode |
104
+ | `yarn typecheck` | Type-check without emitting files |
105
+ | `yarn lint` | Run ESLint |
106
+ | `yarn example` | Run example scripts |
107
+
108
+ ### Project Structure
109
+
110
+ ```
111
+ src/
112
+ ├── builder/ # Fluent API for building dice expressions
113
+ │ ├── factory.ts # Factory functions (d20, d6, roll, etc.)
114
+ │ ├── roll.ts # RollBuilder - core builder class
115
+ │ ├── ac.ts # ACBuilder - attack roll builder
116
+ │ ├── attack.ts # AttackBuilder - attack with damage
117
+ │ ├── save.ts # SaveBuilder - saving throw builder
118
+ │ ├── dc.ts # DCBuilder - difficulty check builder
119
+ │ ├── ast.ts # AST generation and PMF conversion
120
+ │ └── nodes.ts # AST node type definitions
121
+ ├── parser/ # String-based dice expression parser
122
+ │ ├── parser.ts # Main parser implementation
123
+ │ └── dice.ts # Dice class (legacy parser representation)
124
+ ├── pmf/ # Probability Mass Function core
125
+ │ ├── pmf.ts # PMF class - core data structure
126
+ │ ├── query.ts # DiceQuery - analysis interface
127
+ │ └── mixture.ts # Mixture operations
128
+ └── common/ # Shared utilities
129
+ ├── types.ts # Type definitions
130
+ └── lru-cache.ts # LRU cache implementation
131
+ ```
132
+
133
+ ## 🏗 Architecture Overview
134
+
135
+ The library provides two parallel entry points for creating dice expressions:
136
+
137
+ ### Entry Point 1: String Parser
138
+
139
+ Parses text expressions like `"(d20 + 8 AC 16) * (1d8 + 4) crit (2d8 + 4)"`:
140
+
141
+ ```
142
+ String Expression
143
+ │
144
+ ├─ parse() ──────────────┐
145
+ │ │
146
+ │ ▼
147
+ │ parseExpression()
148
+ │ │
149
+ │ ├─ parseArgument() ──► Dice objects
150
+ │ │
151
+ │ └─ parseOperation() ──► Dice operations
152
+ │ │
153
+ │ ▼
154
+ │ Dice.toPMF() ──► PMF
155
+ │ │
156
+ └────────────────────────┘
157
+ ```
158
+
159
+ ### Entry Point 2: Fluent Builder API
160
+
161
+ Type-safe builder pattern:
162
+
163
+ ```
164
+ RollBuilder (d20, d6, roll(), etc.)
165
+ │
166
+ ├─ .plus() ──► RollBuilder
167
+ ├─ .ac() ────► ACBuilder
168
+ │ │
169
+ │ └─ .onHit() ──► AttackBuilder
170
+ │ │
171
+ │ ├─ .toQuery() ──► DiceQuery
172
+ │ └─ .pmf ─────────► PMF
173
+ │
174
+ └─ .toPMF() ──► PMF
175
+ ```
176
+
177
+ ### Core Class Flow
178
+
179
+ ```
180
+ ┌─────────────────────────────────────────────────────────────┐
181
+ │ User Input Layer │
182
+ ├─────────────────────────────────────────────────────────────┤
183
+ │ String Parser │ Fluent Builder │
184
+ │ parse("...") │ d20.plus(8).ac(16) │
185
+ └────────────┬────────────┴────────────┬──────────────────────┘
186
+ │ │
187
+ ▼ ▼
188
+ ┌─────────────────────────────────────────────────────────────┐
189
+ │ Builder Layer │
190
+ ├─────────────────────────────────────────────────────────────┤
191
+ │ RollBuilder ──► ACBuilder ──► AttackBuilder │
192
+ │ │ │ │ │
193
+ │ │ │ │ │
194
+ │ └──────────────┼──────────────┘ │
195
+ │ │ │
196
+ │ ▼ │
197
+ │ astFromRollConfigs() │
198
+ │ │ │
199
+ │ ▼ │
200
+ │ ExpressionNode (AST) │
201
+ └──────────────────────┬──────────────────────────────────────┘
202
+ │
203
+ ▼
204
+ ┌─────────────────────────────────────────────────────────────┐
205
+ │ PMF Generation │
206
+ ├─────────────────────────────────────────────────────────────┤
207
+ │ pmfFromRollBuilder() │
208
+ │ │ │
209
+ │ ├─ d20RollPMF() ──► PMF (for d20 rolls) │
210
+ │ ├─ diePMF() ──────► PMF (for regular dice) │
211
+ │ └─ combinePMFs() ─► PMF (convolve multiple PMFs) │
212
+ └──────────────────────┬──────────────────────────────────────┘
213
+ │
214
+ ▼
215
+ ┌─────────────────────────────────────────────────────────────┐
216
+ │ Query & Analysis │
217
+ ├─────────────────────────────────────────────────────────────┤
218
+ │ DiceQuery │
219
+ │ │ │
220
+ │ ├─ .mean() ────────────► Expected damage │
221
+ │ ├─ .variance() ────────► Damage variance │
222
+ │ ├─ .probAtLeastOne() ──► Hit/crit probabilities │
223
+ │ ├─ .toChartSeries() ────► Chart data │
224
+ │ └─ .combined ───────────► Final PMF │
225
+ └─────────────────────────────────────────────────────────────┘
226
+ ```
227
+
228
+ ### PMF Data Structure
229
+
230
+ The `PMF` class is the core mathematical representation:
231
+
232
+ ```
233
+ PMF
234
+ ├── map: Map<number, Bin>
235
+ │ └── Bin
236
+ │ ├── p: number (probability)
237
+ │ ├── count: {...} (outcome counts: hit, crit, miss)
238
+ │ └── attr: {...} (damage attribution)
239
+ ├── epsilon: number (probability threshold)
240
+ ├── normalized: boolean (whether PMF sums to 1.0)
241
+ └── identifier: string (cache key / debug name)
242
+ ```
243
+
244
+ ### Main Flow Example
245
+
246
+ Here's how a simple attack flows through the system:
247
+
248
+ ```
249
+ 1. User creates: d20.plus(5).ac(15).onHit(d6.plus(2))
250
+
251
+ 2. Builder chain:
252
+ RollBuilder(d20)
253
+ → plus(5) → RollBuilder(d20 + 5)
254
+ → ac(15) → ACBuilder(d20 + 5 AC 15)
255
+ → onHit(...) → AttackBuilder
256
+
257
+ 3. AST generation:
258
+ RollConfig[] → ExpressionNode
259
+ - DieNode (d20)
260
+ - ConstantNode (+5)
261
+ - D20RollNode (AC check)
262
+ - ConditionalNode (on hit)
263
+
264
+ 4. PMF generation:
265
+ AST → PMF operations
266
+ - d20RollPMF(rollType, rerollOne) → PMF
267
+ - Conditional application → PMF.branch()
268
+ - Damage PMF → PMF
269
+ - Combine → PMF (final result)
270
+
271
+ 5. Query creation:
272
+ AttackBuilder.toQuery() → DiceQuery
273
+ - singles: [PMF]
274
+ - combined: PMF (convolved)
275
+
276
+ 6. Analysis:
277
+ DiceQuery.mean() → 3.20 DPR
278
+ ```
279
+
48
280
  ## 📦 Core Concepts
49
281
 
50
282
  | Concept | Description |
@@ -52,11 +284,12 @@ yarn add @yipe/dice
52
284
  | **PMF** | Probability Mass Function. The core mathematical representation of outcomes. |
53
285
  | **Query** | Runs calculations and scenarios over one or more PMFs. |
54
286
  | **Parser** | Parses text-based dice expressions like `(d20 + 8 AC 16) * (1d4 + 4)`. |
55
- | **Dice** | Represents the outcome of a single dice expression |
287
+ | **Builder**| Fluent TypeScript API for building dice expressions. |
288
+ | **AST** | Abstract Syntax Tree representing dice operations. |
56
289
 
57
- ## 🧙 Basic Usage
290
+ ## 🧙 Usage Examples
58
291
 
59
- Here's a simple example of calculating damage for a basic attack:
292
+ ### Basic Attack
60
293
 
61
294
  ```ts
62
295
  import { parse, DiceQuery } from "@yipe/dice";
@@ -68,20 +301,23 @@ console.log("Crit chance:", query.probAtLeastOne(["crit"]));
68
301
  console.log("DPR:", query.mean());
69
302
  ```
70
303
 
71
- **Output:**
304
+ ### String Parser
72
305
 
73
- ```
74
- Hit chance: 0.65
75
- Crit chance: 0.05
76
- DPR: 4.35
306
+ ```ts
307
+ import { parse } from "@yipe/dice";
308
+
309
+ const pmf = parse("(d20 + 8 AC 16) * (1d8 + 4) crit (2d8 + 4)");
310
+ const query = new DiceQuery(pmf);
311
+
312
+ console.log("DPR:", query.mean());
77
313
  ```
78
314
 
79
- ## ⚔️ Sneak Attack Example
315
+ ### Sneak Attack (Conditional Damage)
80
316
 
81
317
  Conditional damage ("once-per-turn damage riders") like Sneak Attack can be modeled easily:
82
318
 
83
319
  ```ts
84
- import { DiceQuery } from "@yipe/dice";
320
+ import { DiceQuery, PMF, roll } from "@yipe/dice";
85
321
 
86
322
  function sneakAttack() {
87
323
  const attackPMF = d20.plus(8).ac(16).onHit(d4.plus(4)).pmf;
@@ -99,15 +335,13 @@ function sneakAttack() {
99
335
  console.log("DPR with once-per-turn sneak attack: ", sneakAttack().mean());
100
336
  ```
101
337
 
102
- ## 📊 Statistics and Charts
103
-
104
- You can generate full statistical distributions for visualization or reporting.
338
+ ### Statistics and Charts
105
339
 
106
340
  ```ts
107
341
  import { parse, DiceQuery } from "@yipe/dice";
108
342
 
109
- const query2 = parse("(d20 + 8 AC 16) * (1d4 + 4) crit (2d4 + 4)").toQuery();
110
- console.table(query2.toChartSeries());
343
+ const query = parse("(d20 + 8 AC 16) * (1d4 + 4) crit (2d4 + 4)").toQuery();
344
+ console.table(query.toChartSeries());
111
345
  ```
112
346
 
113
347
  **Output:**
@@ -128,17 +362,9 @@ console.table(query2.toChartSeries());
128
362
  └─────────┴────┴──────────┘
129
363
  ```
130
364
 
131
- ## 🗂 Project Structure
132
-
133
- | File | Purpose |
134
- | ----------- | ------------------------------------- |
135
- | `src/` | Core Dice class and logic |
136
- | `examples/` | Example scripts showing library usage |
137
- | `tests/` | Comprehensive library tests |
138
-
139
365
  ## 🧪 Running Examples
140
366
 
141
- This repository includes example scripts.
367
+ This repository includes example scripts:
142
368
 
143
369
  ```bash
144
370
  yarn example basic
@@ -147,7 +373,7 @@ yarn example sneakattack
147
373
  yarn example misc
148
374
  ```
149
375
 
150
- Here is the basic example:
376
+ Here is the basic example output:
151
377
 
152
378
  ```
153
379
  % yarn example basic
@@ -217,10 +443,9 @@ Here is the basic example:
217
443
  │ 13 │ 0.278% │ 0.278% │ 0.000% │ 0.000% │
218
444
  │ 14 │ 0.139% │ 0.139% │ 0.000% │ 0.000% │
219
445
  └────────┴─────────┴────────┴────────┴─────────┘
220
-
221
446
  ```
222
447
 
223
- ## 🎲 Sample Dice Expression Grammar
448
+ This enables rich statistics like "how much damage comes from crits vs hits".
224
449
 
225
450
  ## 🧱 Roadmap
226
451
 
@@ -247,13 +472,13 @@ cd dice
247
472
  yarn install
248
473
  ```
249
474
 
250
- Run tests
475
+ Run tests:
251
476
 
252
477
  ```bash
253
478
  yarn test
254
479
  ```
255
480
 
256
- Run examples
481
+ Run examples:
257
482
 
258
483
  ```bash
259
484
  yarn example
@@ -273,8 +498,4 @@ Wizards of the Coast, Dungeons & Dragons, and their logos are trademarks of Wiza
273
498
 
274
499
  Portions of this code are inspired by [dice.clockworkmod.com](https://github.com/koush/dice.clockworkmod.com) by Koushik Dutta (2013), licensed under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0).
275
500
 
276
- Initial [TypeScript port](https://github.com/loginName1/dice-calculator-ts) expertly created by [loginName1](https://github.com/loginName1).
277
-
278
- ```
279
-
280
- ```
501
+ Initial [TypeScript port](https://github.com/loginName1/dice-calculator-ts) expertly created by [loginName1](https://github.com/loginName1).