tiny-essentials 1.24.3 → 1.24.5

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.
@@ -105,9 +105,111 @@ console.log(fruits[index]); // Random fruit
105
105
 
106
106
  ---
107
107
 
108
+ #### 🧮 `static _safeEvaluate(expression)`
109
+
110
+ A strict mathematical parser supporting:
111
+
112
+ * `+ - * / %`
113
+ * Parentheses
114
+ * Decimals & fractions
115
+ * Exponentiation (`^` or `**`)
116
+
117
+ Validates every character before evaluating.
118
+
119
+ ```js
120
+ /**
121
+ * Safely evaluates a mathematical expression...
122
+ */
123
+ ```
124
+
125
+ ##### Throws
126
+
127
+ * `Error` on invalid characters
128
+ * `Error` on malformed math expressions
129
+
130
+ ---
131
+
132
+ #### 🔄 `static replaceValues(input, values)`
133
+
134
+ Replaces occurrences of dice tokens (`d6`, `d20`, etc.) with corresponding numbers.
135
+
136
+ Useful internally when reconstructing expressions.
137
+
138
+ ---
139
+
140
+ #### 🔄 `static tokenizeValues(input, values)`
141
+
142
+ Tokenizes an expression while replacing dice tokens (`d6`, `3d6`, etc.) with numeric results.
143
+
144
+ Useful internally when rebuilding or evaluating expressions, since dice rolls are converted into numeric tokens and the rest of the expression is split into operator/number tokens.
145
+
146
+ ---
147
+
148
+ #### 🔍 `static parseString(input)`
149
+
150
+ Parses comma-separated dice expressions such as:
151
+
152
+ * `"d6"`
153
+ * `"3d12"`
154
+ * `"8d100"`
155
+ * `"d6 + 3"` (modifier support)
156
+ * `"2d6 * (1 + d4)"`
157
+ * `(0 | 1 | d4)` → randomly selects one option
158
+
159
+ ##### Returns:
160
+
161
+ ```ts
162
+ {
163
+ sides: { count: number; sides: number }[],
164
+ modifiers: { index: number; original: string; expression: string }[]
165
+ }
166
+ ```
167
+
168
+ ##### Features:
169
+
170
+ * Supports multi-dice groups
171
+ * Recognizes and resolves random-choice groups like `(1 | d6 | 3)`
172
+ * Preserves original and final expression
173
+
174
+ ---
175
+
176
+ #### 🎛️ `static applyModifiers(values, modifiers)`
177
+
178
+ Applies modifier expressions to a sequence of dice results.
179
+
180
+ ##### Values Format
181
+
182
+ ```ts
183
+ (number | { value: number, sides: number })[]
184
+ ```
185
+
186
+ You can provide:
187
+
188
+ * Raw numbers
189
+ * Structured dice results for validation
190
+
191
+ ##### Returns
192
+
193
+ A full `ApplyDiceModifiersResult` with:
194
+
195
+ * `final`
196
+ * `steps[]`
197
+
198
+ ##### Validates:
199
+
200
+ * Dice values cannot exceed their maximum sides
201
+ * Modifier expressions must be math-safe
202
+ * Dice tokens are mapped correctly to their evaluated slots
203
+
204
+ ---
205
+
108
206
  ## 📝 Examples
109
207
 
110
208
  ```js
209
+ // -----------------------------------------------------------
210
+ // Basic Dice Usage
211
+ // -----------------------------------------------------------
212
+
111
213
  // Create dice
112
214
  const dice = new TinySimpleDice({ maxValue: 12, allowZero: true });
113
215
 
@@ -118,6 +220,12 @@ console.log(dice.roll()); // e.g., 0–12
118
220
  dice.maxValue = 20;
119
221
  dice.allowZero = false;
120
222
 
223
+ console.log(dice.roll()); // e.g., 1–20
224
+
225
+ // -----------------------------------------------------------
226
+ // Rolling Random Array / Set Index
227
+ // -----------------------------------------------------------
228
+
121
229
  // Roll for an array index
122
230
  const colors = ['red', 'green', 'blue', 'yellow'];
123
231
  const idx = TinySimpleDice.rollArrayIndex(colors);
@@ -126,6 +234,164 @@ console.log(colors[idx]); // Random color
126
234
  // Roll for a Set
127
235
  const mySet = new Set([10, 20, 30]);
128
236
  console.log(TinySimpleDice.rollArrayIndex(mySet)); // 0–2 index
237
+
238
+ // -----------------------------------------------------------
239
+ // Parsing Dice Strings
240
+ // -----------------------------------------------------------
241
+
242
+ const parsed = TinySimpleDice.parseString("3d6, d8, (0 | 1 | d4)");
243
+
244
+ console.log(parsed.sides);
245
+ // [
246
+ // { count: 3, sides: 6 },
247
+ // { count: 1, sides: 8 },
248
+ // { count: 1, sides: 4 } // only if d4 was chosen
249
+ // ]
250
+
251
+ console.log(parsed.modifiers);
252
+ // [
253
+ // { index: 0, original: "3d6", expression: "3d6" },
254
+ // { index: 1, original: "d8", expression: "d8" },
255
+ // { index: 2, original: "(0 | 1 | d4)", expression: "1" } // example chosen value
256
+ // ]
257
+
258
+
259
+ // -----------------------------------------------------------
260
+ // Using replaceValues
261
+ // -----------------------------------------------------------
262
+
263
+ const replaced = TinySimpleDice.replaceValues("d6 + d6 + d6", [4, 2, 1]);
264
+ console.log(replaced); // "4 + 2 + 1"
265
+
266
+
267
+ // -----------------------------------------------------------
268
+ // Applying Modifiers (Simple Example)
269
+ // -----------------------------------------------------------
270
+
271
+ const bases = [4]; // example: rolled a d6 and got 4
272
+
273
+ const modifiersSimple = [
274
+ { expression: "x + 2", original: "x + 2" } // demonstration of using constants
275
+ ];
276
+
277
+ modifiersSimple[0].expression = "4 + 2"; // normally you'd build dynamically
278
+
279
+ const resultSimple = TinySimpleDice.applyModifiers(bases, modifiersSimple);
280
+
281
+ console.log(resultSimple);
282
+ // {
283
+ // final: 6,
284
+ // steps: [
285
+ // { tokens, rawTokensP, rawTokens, total: 6, ... }
286
+ // ]
287
+ // }
288
+
289
+
290
+ // -----------------------------------------------------------
291
+ // Applying Modifiers (Full Dice Expressions)
292
+ // -----------------------------------------------------------
293
+
294
+ // Step 1: Parse user input
295
+ const parsed2 = TinySimpleDice.parseString("2d6, d8 + 2, (1 | d4) * 3");
296
+
297
+ // Step 2: Roll all dice
298
+ // Assume the parsed result gave us: 2d6, 1d8, 1d4
299
+ const allValues = [
300
+ { value: 5, sides: 6 }, // first d6
301
+ { value: 2, sides: 6 }, // second d6
302
+ { value: 7, sides: 8 }, // d8
303
+ { value: 3, sides: 4 } // d4 chosen in (1 | d4)
304
+ ];
305
+
306
+ // Step 3: Apply modifiers
307
+ const resultAdv = TinySimpleDice.applyModifiers(
308
+ allValues,
309
+ parsed2.modifiers
310
+ );
311
+
312
+ console.log(resultAdv.final); // e.g., 5+2 + (7+2) + (3*3) = 5+2 + 9 + 9 = 25
313
+
314
+
315
+ // -----------------------------------------------------------
316
+ // Inspecting Modifier Steps (Debugging)
317
+ // -----------------------------------------------------------
318
+
319
+ resultAdv.steps.forEach((step, i) => {
320
+ console.log(`--- Step #${i + 1} ---`);
321
+ console.log("Tokens:", step.tokens);
322
+ console.log("Raw tokens:", step.rawTokens);
323
+ console.log("Raw dice slots:", step.rawDiceTokenSlots);
324
+ console.log("Normalized dice slots:", step.diceTokenSlots);
325
+ console.log("Dice results:", step.dicesResult);
326
+ console.log("Subtotal:", step.total);
327
+ });
328
+
329
+
330
+ // -----------------------------------------------------------
331
+ // Example: Evaluating a Complex RPG-Style Expression
332
+ // -----------------------------------------------------------
333
+
334
+ const parsed3 = TinySimpleDice.parseString("3d6 + (d4 * 2), d20, 2d8 + (0 | 1 | d6)");
335
+
336
+ const allValues3 = [
337
+ { value: 6, sides: 6 }, // 3d6
338
+ { value: 3, sides: 6 },
339
+ { value: 2, sides: 6 },
340
+
341
+ { value: 3, sides: 4 }, // d4 for the multiplier
342
+
343
+ { value: 17, sides: 20 }, // d20
344
+
345
+ { value: 7, sides: 8 }, // 2d8
346
+ { value: 5, sides: 8 },
347
+
348
+ { value: 4, sides: 6 } // chosen from (0 | 1 | d6)
349
+ ];
350
+
351
+ const resultRPG = TinySimpleDice.applyModifiers(allValues3, parsed3.modifiers);
352
+
353
+ console.log("Final total:", resultRPG.final);
354
+
355
+ console.log("Detailed steps:");
356
+ console.log(JSON.stringify(resultRPG.steps, null, 2));
357
+
358
+
359
+ // -----------------------------------------------------------
360
+ // Example: Building a Log String for UI / Chat
361
+ // -----------------------------------------------------------
362
+
363
+ function buildDiceLog(result) {
364
+ let out = "Dice Roll Summary:\n";
365
+
366
+ result.steps.forEach((s, i) => {
367
+ const rolled = s.dicesResult
368
+ .map((arr) => `[${arr.join(", ")}]`)
369
+ .join(", ");
370
+
371
+ out += `Step ${i + 1}: rolled ${rolled}, subtotal = ${s.total}\n`;
372
+ });
373
+
374
+ out += `Final total: ${result.final}`;
375
+ return out;
376
+ }
377
+
378
+ console.log(buildDiceLog(resultRPG));
379
+
380
+
381
+ // -----------------------------------------------------------
382
+ // Example: Validating Dice Before Use
383
+ // -----------------------------------------------------------
384
+
385
+ try {
386
+ TinySimpleDice.applyModifiers(
387
+ [
388
+ { value: 12, sides: 6 } // invalid: value 12 cannot be from a d6
389
+ ],
390
+ [{ expression: "d6", original: "d6" }]
391
+ );
392
+ } catch (err) {
393
+ console.error("Validation error:", err.message);
394
+ }
129
395
  ```
130
396
 
131
397
  ---
@@ -139,6 +405,27 @@ console.log(TinySimpleDice.rollArrayIndex(mySet)); // 0–2 index
139
405
 
140
406
  ---
141
407
 
408
+ ## 🧠 **How the Modifier Engine Works (High-Level Overview)**
409
+
410
+ 1. **parseString** extracts dice groups & modifiers.
411
+
412
+ 2. You roll all dice using your preferred method.
413
+
414
+ 3. **applyModifiers** uses the rolled values and processes each modifier:
415
+
416
+ * Finds dice tokens (`d6`, `3d8`, etc.)
417
+ * Replaces them with corresponding rolled numbers
418
+ * Tokenizes the expression
419
+ * Tracks dice slot positions
420
+ * Evaluates math
421
+ * Collects totals and metadata into a step object
422
+
423
+ 4. All step totals are summed into `final`.
424
+
425
+ This enables a robust system capable of advanced RPG-style calculations.
426
+
427
+ ---
428
+
142
429
  ## 💡 Notes
143
430
 
144
431
  * The `rollArrayIndex` method always returns a **valid index**, regardless of the collection type.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiny-essentials",
3
- "version": "1.24.3",
3
+ "version": "1.24.5",
4
4
  "description": "Collection of small, essential scripts designed to be used across various projects. These simple utilities are crafted for speed, ease of use, and versatility.",
5
5
  "scripts": {
6
6
  "test": "npm run test:mjs && npm run test:cjs && npm run test:js",