@dicelette/core 1.25.1 → 1.26.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/README.md CHANGED
@@ -152,6 +152,25 @@ Note: when a parameter `engine` is shown it usually defaults to the `NumberGener
152
152
  #### Function: getEngine(engine: "nativeMath" | "browserCrypto" | "nodeCrypto"): Engine
153
153
  - Returns the engine instance (from `NumberGenerator.engines`) matching the provided name.
154
154
 
155
+ ### Similarity (`src/similarity.ts`)
156
+ #### Function: calculateSimilarity(str1: string, str2: string): number
157
+ - Calculate and return a similarity score between two strings using the levenshtein distance (value between 0 and 1).
158
+
159
+ #### Function: levenshteinDistance(str1: string, str2: string): number
160
+ - Calculate and return the Levenshtein distance between two strings.
161
+ - Used internally by `calculateSimilarity`.
162
+ - Returns an integer distance value.
163
+
164
+ #### Function : findBestStatMatch<T>(searchTerm: string, normalizedStat: Map<string, T>, similarityThreshold: number = 0.5, partialSearch: boolean = true): T | undefined`
165
+ - Find and return the best matching statistic from `normalizedStat` based on `searchTerm`.
166
+ - Normalized stat is a map of lowercased stat names to their original entries
167
+ - `partialSearch` allow to check if `normalizedStat` have a key that starts, ends or contains the `searchTerm` and return it directly.
168
+ - `similarityThreshold` is the minimum similarity score to consider a match. By default, it's set to 0.5.
169
+
170
+ #### Function: `findBestRecord(snippets: Record<string, string>, searchTerm: string, similarityThreshold: number = 0.5): string | undefined`
171
+ - Find and return the best matching string from `snippets` based on `searchTerm`.
172
+ - Just an alias for `findBestStatMatch` specialized for `Record<string, string>` and without partial search.
173
+
155
174
  ### Dice functions (`src/dice.ts`)
156
175
 
157
176
  #### Function: roll(dice: string, engine?: Engine | null, pity?: boolean): Resultat | undefined
package/dist/index.d.mts CHANGED
@@ -307,6 +307,26 @@ declare function replaceInFormula(element: string, diceResult: Resultat, compare
307
307
  compare: Compare | undefined;
308
308
  }, res: boolean, engine?: Engine | null, pity?: boolean): string;
309
309
 
310
+ /**
311
+ * Utility functions for string similarity and distance calculations.
312
+ */
313
+ /**
314
+ * Calculates the similarity between two strings as a value between 0 and 1.
315
+ */
316
+ declare function calculateSimilarity(str1: string, str2: string): number;
317
+ /**
318
+ * Calculates the Levenshtein distance between two strings.
319
+ */
320
+ declare function levenshteinDistance(str1: string, str2: string): number;
321
+ declare function findBestStatMatch<T>(searchTerm: string, normalizedStats: Map<string, T>, similarityThreshold?: number, partialSearch?: boolean): T | undefined;
322
+ /**
323
+ * Find the snippet name with the highest similarity to `macroName`.
324
+ * Single-pass O(n) algorithm: keeps the best (name, similarity) seen so far.
325
+ * Returns `null` if no snippets or if the best similarity is < `minSimilarity`.
326
+ * Tie-breaker: first encountered best similarity (deterministic).
327
+ */
328
+ declare function findBestRecord(record: Record<string, string>, searchTerm: string, similarityThreshold?: number): string | null;
329
+
310
330
  /**
311
331
  * Escape regex string
312
332
  * @param string {string}
@@ -323,9 +343,10 @@ declare function standardizeDice(dice: string): string;
323
343
  * and after evaluate any formula using `replaceFormulaInDice`
324
344
  * @param {string} originalDice
325
345
  * @param {Record<string,number>|undefined} stats
346
+ * @param {number} minThreshold Minimum similarity threshold to consider a stat name match
326
347
  * @param {string|undefined} dollarValue
327
348
  */
328
- declare function generateStatsDice(originalDice: string, stats?: Record<string, number>, dollarValue?: string): string;
349
+ declare function generateStatsDice(originalDice: string, stats?: Record<string, number>, minThreshold?: number, dollarValue?: string): string;
329
350
  /**
330
351
  * Replace the {{}} in the dice string and evaluate the interior if any
331
352
  * @param dice {string}
@@ -430,4 +451,4 @@ declare function testStatCombinaison(template: StatisticalTemplate, engine?: Eng
430
451
  */
431
452
  declare function generateRandomStat(total?: number | undefined, max?: number, min?: number, engine?: Engine | null): number;
432
453
 
433
- export { COMMENT_REGEX, type Compare, type ComparedValue, type Critical, type CustomCritical, type CustomCriticalMap, DETECT_CRITICAL, DiceTypeError, EmptyObjectError, FormulaError, MaxGreater, type Modifier, NoStatisticsError, OPTIONAL_COMMENT, type Resultat, SIGN_REGEX, SIGN_REGEX_SPACE, SYMBOL_DICE, type Sign, SortOrder, type Statistic, type StatisticalSchema, type StatisticalTemplate, TooManyDice, TooManyStats, createCriticalCustom, diceRandomParse, diceTypeRandomParse, escapeRegex, evalCombinaison, evalOneCombinaison, evalStatsDice, generateRandomStat, generateStatsDice, getEngine, getEngineId, isNumber, randomInt, replaceExpByRandom, replaceFormulaInDice, replaceInFormula, roll, standardizeDice, templateSchema, testDiceRegistered, testStatCombinaison, verifyTemplateValue };
454
+ export { COMMENT_REGEX, type Compare, type ComparedValue, type Critical, type CustomCritical, type CustomCriticalMap, DETECT_CRITICAL, DiceTypeError, EmptyObjectError, FormulaError, MaxGreater, type Modifier, NoStatisticsError, OPTIONAL_COMMENT, type Resultat, SIGN_REGEX, SIGN_REGEX_SPACE, SYMBOL_DICE, type Sign, SortOrder, type Statistic, type StatisticalSchema, type StatisticalTemplate, TooManyDice, TooManyStats, calculateSimilarity, createCriticalCustom, diceRandomParse, diceTypeRandomParse, escapeRegex, evalCombinaison, evalOneCombinaison, evalStatsDice, findBestRecord, findBestStatMatch, generateRandomStat, generateStatsDice, getEngine, getEngineId, isNumber, levenshteinDistance, randomInt, replaceExpByRandom, replaceFormulaInDice, replaceInFormula, roll, standardizeDice, templateSchema, testDiceRegistered, testStatCombinaison, verifyTemplateValue };
package/dist/index.d.ts CHANGED
@@ -307,6 +307,26 @@ declare function replaceInFormula(element: string, diceResult: Resultat, compare
307
307
  compare: Compare | undefined;
308
308
  }, res: boolean, engine?: Engine | null, pity?: boolean): string;
309
309
 
310
+ /**
311
+ * Utility functions for string similarity and distance calculations.
312
+ */
313
+ /**
314
+ * Calculates the similarity between two strings as a value between 0 and 1.
315
+ */
316
+ declare function calculateSimilarity(str1: string, str2: string): number;
317
+ /**
318
+ * Calculates the Levenshtein distance between two strings.
319
+ */
320
+ declare function levenshteinDistance(str1: string, str2: string): number;
321
+ declare function findBestStatMatch<T>(searchTerm: string, normalizedStats: Map<string, T>, similarityThreshold?: number, partialSearch?: boolean): T | undefined;
322
+ /**
323
+ * Find the snippet name with the highest similarity to `macroName`.
324
+ * Single-pass O(n) algorithm: keeps the best (name, similarity) seen so far.
325
+ * Returns `null` if no snippets or if the best similarity is < `minSimilarity`.
326
+ * Tie-breaker: first encountered best similarity (deterministic).
327
+ */
328
+ declare function findBestRecord(record: Record<string, string>, searchTerm: string, similarityThreshold?: number): string | null;
329
+
310
330
  /**
311
331
  * Escape regex string
312
332
  * @param string {string}
@@ -323,9 +343,10 @@ declare function standardizeDice(dice: string): string;
323
343
  * and after evaluate any formula using `replaceFormulaInDice`
324
344
  * @param {string} originalDice
325
345
  * @param {Record<string,number>|undefined} stats
346
+ * @param {number} minThreshold Minimum similarity threshold to consider a stat name match
326
347
  * @param {string|undefined} dollarValue
327
348
  */
328
- declare function generateStatsDice(originalDice: string, stats?: Record<string, number>, dollarValue?: string): string;
349
+ declare function generateStatsDice(originalDice: string, stats?: Record<string, number>, minThreshold?: number, dollarValue?: string): string;
329
350
  /**
330
351
  * Replace the {{}} in the dice string and evaluate the interior if any
331
352
  * @param dice {string}
@@ -430,4 +451,4 @@ declare function testStatCombinaison(template: StatisticalTemplate, engine?: Eng
430
451
  */
431
452
  declare function generateRandomStat(total?: number | undefined, max?: number, min?: number, engine?: Engine | null): number;
432
453
 
433
- export { COMMENT_REGEX, type Compare, type ComparedValue, type Critical, type CustomCritical, type CustomCriticalMap, DETECT_CRITICAL, DiceTypeError, EmptyObjectError, FormulaError, MaxGreater, type Modifier, NoStatisticsError, OPTIONAL_COMMENT, type Resultat, SIGN_REGEX, SIGN_REGEX_SPACE, SYMBOL_DICE, type Sign, SortOrder, type Statistic, type StatisticalSchema, type StatisticalTemplate, TooManyDice, TooManyStats, createCriticalCustom, diceRandomParse, diceTypeRandomParse, escapeRegex, evalCombinaison, evalOneCombinaison, evalStatsDice, generateRandomStat, generateStatsDice, getEngine, getEngineId, isNumber, randomInt, replaceExpByRandom, replaceFormulaInDice, replaceInFormula, roll, standardizeDice, templateSchema, testDiceRegistered, testStatCombinaison, verifyTemplateValue };
454
+ export { COMMENT_REGEX, type Compare, type ComparedValue, type Critical, type CustomCritical, type CustomCriticalMap, DETECT_CRITICAL, DiceTypeError, EmptyObjectError, FormulaError, MaxGreater, type Modifier, NoStatisticsError, OPTIONAL_COMMENT, type Resultat, SIGN_REGEX, SIGN_REGEX_SPACE, SYMBOL_DICE, type Sign, SortOrder, type Statistic, type StatisticalSchema, type StatisticalTemplate, TooManyDice, TooManyStats, calculateSimilarity, createCriticalCustom, diceRandomParse, diceTypeRandomParse, escapeRegex, evalCombinaison, evalOneCombinaison, evalStatsDice, findBestRecord, findBestStatMatch, generateRandomStat, generateStatsDice, getEngine, getEngineId, isNumber, levenshteinDistance, randomInt, replaceExpByRandom, replaceFormulaInDice, replaceInFormula, roll, standardizeDice, templateSchema, testDiceRegistered, testStatCombinaison, verifyTemplateValue };
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  SortOrder: () => SortOrder,
35
35
  TooManyDice: () => TooManyDice,
36
36
  TooManyStats: () => TooManyStats,
37
+ calculateSimilarity: () => calculateSimilarity,
37
38
  createCriticalCustom: () => createCriticalCustom,
38
39
  diceRandomParse: () => diceRandomParse,
39
40
  diceTypeRandomParse: () => diceTypeRandomParse,
@@ -41,11 +42,14 @@ __export(index_exports, {
41
42
  evalCombinaison: () => evalCombinaison,
42
43
  evalOneCombinaison: () => evalOneCombinaison,
43
44
  evalStatsDice: () => evalStatsDice,
45
+ findBestRecord: () => findBestRecord,
46
+ findBestStatMatch: () => findBestStatMatch,
44
47
  generateRandomStat: () => generateRandomStat,
45
48
  generateStatsDice: () => generateStatsDice,
46
49
  getEngine: () => getEngine,
47
50
  getEngineId: () => getEngineId,
48
51
  isNumber: () => isNumber,
52
+ levenshteinDistance: () => levenshteinDistance,
49
53
  randomInt: () => randomInt,
50
54
  replaceExpByRandom: () => replaceExpByRandom,
51
55
  replaceFormulaInDice: () => replaceFormulaInDice,
@@ -154,11 +158,11 @@ var NoStatisticsError = class extends Error {
154
158
  };
155
159
 
156
160
  // src/interfaces/index.ts
157
- var SortOrder = /* @__PURE__ */ ((SortOrder3) => {
158
- SortOrder3["Ascending"] = "sa";
159
- SortOrder3["Descending"] = "sd";
160
- SortOrder3["None"] = "none";
161
- return SortOrder3;
161
+ var SortOrder = /* @__PURE__ */ ((SortOrder2) => {
162
+ SortOrder2["Ascending"] = "sa";
163
+ SortOrder2["Descending"] = "sd";
164
+ SortOrder2["None"] = "none";
165
+ return SortOrder2;
162
166
  })(SortOrder || {});
163
167
 
164
168
  // src/interfaces/constant.ts
@@ -242,6 +246,79 @@ var import_mathjs = require("mathjs");
242
246
  var import_uniformize = require("uniformize");
243
247
  var import_rpg_dice_roller2 = require("@dice-roller/rpg-dice-roller");
244
248
  var import_random_js = require("random-js");
249
+
250
+ // src/similarity.ts
251
+ var MIN_THRESHOLD_MATCH = 0.5;
252
+ function calculateSimilarity(str1, str2) {
253
+ const longer = str1.length > str2.length ? str1 : str2;
254
+ const shorter = str1.length > str2.length ? str2 : str1;
255
+ if (longer.length === 0) return 1;
256
+ const distance = levenshteinDistance(longer, shorter);
257
+ return (longer.length - distance) / longer.length;
258
+ }
259
+ function levenshteinDistance(str1, str2) {
260
+ const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));
261
+ for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;
262
+ for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;
263
+ for (let j = 1; j <= str2.length; j++) {
264
+ for (let i = 1; i <= str1.length; i++) {
265
+ const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
266
+ matrix[j][i] = Math.min(
267
+ matrix[j][i - 1] + 1,
268
+ // insertion
269
+ matrix[j - 1][i] + 1,
270
+ // deletion
271
+ matrix[j - 1][i - 1] + cost
272
+ // substitution
273
+ );
274
+ }
275
+ }
276
+ return matrix[str2.length][str1.length];
277
+ }
278
+ function findBestStatMatch(searchTerm, normalizedStats, similarityThreshold = MIN_THRESHOLD_MATCH, partialSearch = true) {
279
+ const exact = normalizedStats.get(searchTerm);
280
+ if (exact) return exact;
281
+ if (partialSearch) {
282
+ const candidates = [];
283
+ for (const [normalizedKey, original] of normalizedStats) {
284
+ if (normalizedKey.startsWith(searchTerm))
285
+ candidates.push([original, normalizedKey.length]);
286
+ else if (normalizedKey.endsWith(searchTerm))
287
+ candidates.push([original, normalizedKey.length]);
288
+ else if (normalizedKey.includes(searchTerm))
289
+ candidates.push([original, normalizedKey.length]);
290
+ }
291
+ if (candidates.length > 0) {
292
+ candidates.sort((a, b) => a[1] - b[1]);
293
+ return candidates[0][0];
294
+ }
295
+ }
296
+ let bestMatch;
297
+ let bestSimilarity = 0;
298
+ for (const [normalizedKey, original] of normalizedStats) {
299
+ const similarity = calculateSimilarity(searchTerm, normalizedKey);
300
+ if (similarity === 1) return original;
301
+ if (similarity > bestSimilarity && similarity >= similarityThreshold) {
302
+ bestSimilarity = similarity;
303
+ bestMatch = original;
304
+ }
305
+ }
306
+ return bestMatch;
307
+ }
308
+ function findBestRecord(record, searchTerm, similarityThreshold = MIN_THRESHOLD_MATCH) {
309
+ const normalizeRecord = /* @__PURE__ */ new Map();
310
+ for (const key of Object.keys(record)) {
311
+ normalizeRecord.set(key.standardize(), key);
312
+ }
313
+ return findBestStatMatch(
314
+ searchTerm.standardize(),
315
+ normalizeRecord,
316
+ similarityThreshold,
317
+ false
318
+ ) || null;
319
+ }
320
+
321
+ // src/utils.ts
245
322
  function escapeRegex(string) {
246
323
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
247
324
  }
@@ -251,10 +328,34 @@ function standardizeDice(dice) {
251
328
  (_match, insideBrackets, outsideText) => insideBrackets ? insideBrackets : outsideText.standardize().replaceAll("df", "dF")
252
329
  );
253
330
  }
254
- function generateStatsDice(originalDice, stats, dollarValue) {
331
+ function handleDiceAfterD(tokenStd, normalizedStats) {
332
+ const diceMatch = /^(\d*)d(.+)$/i.exec(tokenStd);
333
+ if (!diceMatch) return null;
334
+ const diceCount = diceMatch[1] || "";
335
+ const afterD = diceMatch[2];
336
+ const bestMatch = findBestStatMatch(afterD, normalizedStats, 1, false);
337
+ if (bestMatch) {
338
+ const [, value] = bestMatch;
339
+ return `${diceCount}d${value.toString()}`;
340
+ }
341
+ return null;
342
+ }
343
+ function handleSimpleToken(tokenStd, token, normalizedStats, minThreshold) {
344
+ const bestMatch = findBestStatMatch(tokenStd, normalizedStats, minThreshold, false);
345
+ if (bestMatch) {
346
+ const [, value] = bestMatch;
347
+ return value.toString();
348
+ }
349
+ return token;
350
+ }
351
+ function generateStatsDice(originalDice, stats, minThreshold = 0.6, dollarValue) {
255
352
  let dice = originalDice.standardize();
256
353
  if (stats && Object.keys(stats).length > 0) {
257
- const statKeys = Object.keys(stats);
354
+ const normalizedStats = /* @__PURE__ */ new Map();
355
+ for (const [key, value] of Object.entries(stats)) {
356
+ const normalized = key.standardize();
357
+ normalizedStats.set(normalized, [key, value]);
358
+ }
258
359
  const partsRegex = /(\[[^\]]+])|([^[]+)/g;
259
360
  let result = "";
260
361
  let match;
@@ -268,7 +369,7 @@ function generateStatsDice(originalDice, stats, dollarValue) {
268
369
  if (!outsideText) {
269
370
  continue;
270
371
  }
271
- const tokenRegex = /(\$?[\p{L}\p{N}_]+)/gu;
372
+ const tokenRegex = /(\$?[\p{L}\p{N}_.]+)/gu;
272
373
  let lastIndex = 0;
273
374
  let tokenMatch;
274
375
  while ((tokenMatch = tokenRegex.exec(outsideText)) !== null) {
@@ -277,45 +378,13 @@ function generateStatsDice(originalDice, stats, dollarValue) {
277
378
  const tokenHasDollar = token.startsWith("$");
278
379
  const tokenForCompare = tokenHasDollar ? token.slice(1) : token;
279
380
  const tokenStd = tokenForCompare.standardize();
280
- const diceMatch = /^(\d*)d(.+)$/i.exec(tokenStd);
281
- if (diceMatch) {
282
- const diceCount = diceMatch[1] || "";
283
- const afterD = diceMatch[2];
284
- let foundStatAfterD = false;
285
- for (const key of statKeys) {
286
- const keyStd = key.standardize();
287
- if (afterD === keyStd) {
288
- result += `${diceCount}d${stats[key].toString()}`;
289
- foundStatAfterD = true;
290
- break;
291
- }
292
- }
293
- if (foundStatAfterD) {
294
- lastIndex = tokenRegex.lastIndex;
295
- continue;
296
- }
297
- }
298
- let bestKey = null;
299
- let bestScore = 0;
300
- for (const key of statKeys) {
301
- const keyStd = key.standardize();
302
- if (tokenStd === keyStd) {
303
- bestKey = key;
304
- bestScore = 1;
305
- break;
306
- }
307
- const score = similarityScore(tokenStd, keyStd);
308
- if (score > bestScore) {
309
- bestScore = score;
310
- bestKey = key;
311
- }
312
- }
313
- if (bestKey && bestScore >= 0.6) {
314
- const statValue = stats[bestKey];
315
- result += statValue.toString();
316
- } else {
317
- result += token;
381
+ const diceReplacement = handleDiceAfterD(tokenStd, normalizedStats);
382
+ if (diceReplacement) {
383
+ result += diceReplacement;
384
+ lastIndex = tokenRegex.lastIndex;
385
+ continue;
318
386
  }
387
+ result += handleSimpleToken(tokenStd, token, normalizedStats, minThreshold);
319
388
  lastIndex = tokenRegex.lastIndex;
320
389
  }
321
390
  result += outsideText.slice(lastIndex);
@@ -359,33 +428,6 @@ function randomInt(min, max, engine = import_rpg_dice_roller2.NumberGenerator.en
359
428
  if (!rng) rng = new import_random_js.Random(engine || void 0);
360
429
  return rng.integer(min, max);
361
430
  }
362
- function levenshteinDistance(a, b) {
363
- if (a === b) return 0;
364
- const al = a.length;
365
- const bl = b.length;
366
- if (al === 0) return bl;
367
- if (bl === 0) return al;
368
- const v0 = new Array(bl + 1);
369
- const v1 = new Array(bl + 1);
370
- for (let i = 0; i <= bl; i++) v0[i] = i;
371
- for (let i = 0; i < al; i++) {
372
- v1[0] = i + 1;
373
- for (let j = 0; j < bl; j++) {
374
- const cost = a[i] === b[j] ? 0 : 1;
375
- v1[j + 1] = Math.min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost);
376
- }
377
- for (let j = 0; j <= bl; j++) v0[j] = v1[j];
378
- }
379
- return v1[bl];
380
- }
381
- function similarityScore(a, b) {
382
- const la = a.length;
383
- const lb = b.length;
384
- if (la === 0 && lb === 0) return 1;
385
- const dist = levenshteinDistance(a, b);
386
- const max = Math.max(la, lb);
387
- return 1 - dist / max;
388
- }
389
431
  function createCriticalCustom(dice, customCritical, template, engine = import_rpg_dice_roller2.NumberGenerator.engines.nodeCrypto) {
390
432
  const compareRegex = dice.match(SIGN_REGEX_SPACE);
391
433
  let customDice = dice;
@@ -1505,6 +1547,7 @@ function generateRandomStat(total = 100, max, min, engine = import_rpg_dice_roll
1505
1547
  SortOrder,
1506
1548
  TooManyDice,
1507
1549
  TooManyStats,
1550
+ calculateSimilarity,
1508
1551
  createCriticalCustom,
1509
1552
  diceRandomParse,
1510
1553
  diceTypeRandomParse,
@@ -1512,11 +1555,14 @@ function generateRandomStat(total = 100, max, min, engine = import_rpg_dice_roll
1512
1555
  evalCombinaison,
1513
1556
  evalOneCombinaison,
1514
1557
  evalStatsDice,
1558
+ findBestRecord,
1559
+ findBestStatMatch,
1515
1560
  generateRandomStat,
1516
1561
  generateStatsDice,
1517
1562
  getEngine,
1518
1563
  getEngineId,
1519
1564
  isNumber,
1565
+ levenshteinDistance,
1520
1566
  randomInt,
1521
1567
  replaceExpByRandom,
1522
1568
  replaceFormulaInDice,