@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/dist/index.mjs CHANGED
@@ -93,11 +93,11 @@ var NoStatisticsError = class extends Error {
93
93
  };
94
94
 
95
95
  // src/interfaces/index.ts
96
- var SortOrder = /* @__PURE__ */ ((SortOrder3) => {
97
- SortOrder3["Ascending"] = "sa";
98
- SortOrder3["Descending"] = "sd";
99
- SortOrder3["None"] = "none";
100
- return SortOrder3;
96
+ var SortOrder = /* @__PURE__ */ ((SortOrder2) => {
97
+ SortOrder2["Ascending"] = "sa";
98
+ SortOrder2["Descending"] = "sd";
99
+ SortOrder2["None"] = "none";
100
+ return SortOrder2;
101
101
  })(SortOrder || {});
102
102
 
103
103
  // src/interfaces/constant.ts
@@ -181,6 +181,79 @@ import { evaluate } from "mathjs";
181
181
  import "uniformize";
182
182
  import { NumberGenerator as NumberGenerator2 } from "@dice-roller/rpg-dice-roller";
183
183
  import { Random } from "random-js";
184
+
185
+ // src/similarity.ts
186
+ var MIN_THRESHOLD_MATCH = 0.5;
187
+ function calculateSimilarity(str1, str2) {
188
+ const longer = str1.length > str2.length ? str1 : str2;
189
+ const shorter = str1.length > str2.length ? str2 : str1;
190
+ if (longer.length === 0) return 1;
191
+ const distance = levenshteinDistance(longer, shorter);
192
+ return (longer.length - distance) / longer.length;
193
+ }
194
+ function levenshteinDistance(str1, str2) {
195
+ const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));
196
+ for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;
197
+ for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;
198
+ for (let j = 1; j <= str2.length; j++) {
199
+ for (let i = 1; i <= str1.length; i++) {
200
+ const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
201
+ matrix[j][i] = Math.min(
202
+ matrix[j][i - 1] + 1,
203
+ // insertion
204
+ matrix[j - 1][i] + 1,
205
+ // deletion
206
+ matrix[j - 1][i - 1] + cost
207
+ // substitution
208
+ );
209
+ }
210
+ }
211
+ return matrix[str2.length][str1.length];
212
+ }
213
+ function findBestStatMatch(searchTerm, normalizedStats, similarityThreshold = MIN_THRESHOLD_MATCH, partialSearch = true) {
214
+ const exact = normalizedStats.get(searchTerm);
215
+ if (exact) return exact;
216
+ if (partialSearch) {
217
+ const candidates = [];
218
+ for (const [normalizedKey, original] of normalizedStats) {
219
+ if (normalizedKey.startsWith(searchTerm))
220
+ candidates.push([original, normalizedKey.length]);
221
+ else if (normalizedKey.endsWith(searchTerm))
222
+ candidates.push([original, normalizedKey.length]);
223
+ else if (normalizedKey.includes(searchTerm))
224
+ candidates.push([original, normalizedKey.length]);
225
+ }
226
+ if (candidates.length > 0) {
227
+ candidates.sort((a, b) => a[1] - b[1]);
228
+ return candidates[0][0];
229
+ }
230
+ }
231
+ let bestMatch;
232
+ let bestSimilarity = 0;
233
+ for (const [normalizedKey, original] of normalizedStats) {
234
+ const similarity = calculateSimilarity(searchTerm, normalizedKey);
235
+ if (similarity === 1) return original;
236
+ if (similarity > bestSimilarity && similarity >= similarityThreshold) {
237
+ bestSimilarity = similarity;
238
+ bestMatch = original;
239
+ }
240
+ }
241
+ return bestMatch;
242
+ }
243
+ function findBestRecord(record, searchTerm, similarityThreshold = MIN_THRESHOLD_MATCH) {
244
+ const normalizeRecord = /* @__PURE__ */ new Map();
245
+ for (const key of Object.keys(record)) {
246
+ normalizeRecord.set(key.standardize(), key);
247
+ }
248
+ return findBestStatMatch(
249
+ searchTerm.standardize(),
250
+ normalizeRecord,
251
+ similarityThreshold,
252
+ false
253
+ ) || null;
254
+ }
255
+
256
+ // src/utils.ts
184
257
  function escapeRegex(string) {
185
258
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
186
259
  }
@@ -190,10 +263,34 @@ function standardizeDice(dice) {
190
263
  (_match, insideBrackets, outsideText) => insideBrackets ? insideBrackets : outsideText.standardize().replaceAll("df", "dF")
191
264
  );
192
265
  }
193
- function generateStatsDice(originalDice, stats, dollarValue) {
266
+ function handleDiceAfterD(tokenStd, normalizedStats) {
267
+ const diceMatch = /^(\d*)d(.+)$/i.exec(tokenStd);
268
+ if (!diceMatch) return null;
269
+ const diceCount = diceMatch[1] || "";
270
+ const afterD = diceMatch[2];
271
+ const bestMatch = findBestStatMatch(afterD, normalizedStats, 1, false);
272
+ if (bestMatch) {
273
+ const [, value] = bestMatch;
274
+ return `${diceCount}d${value.toString()}`;
275
+ }
276
+ return null;
277
+ }
278
+ function handleSimpleToken(tokenStd, token, normalizedStats, minThreshold) {
279
+ const bestMatch = findBestStatMatch(tokenStd, normalizedStats, minThreshold, false);
280
+ if (bestMatch) {
281
+ const [, value] = bestMatch;
282
+ return value.toString();
283
+ }
284
+ return token;
285
+ }
286
+ function generateStatsDice(originalDice, stats, minThreshold = 0.6, dollarValue) {
194
287
  let dice = originalDice.standardize();
195
288
  if (stats && Object.keys(stats).length > 0) {
196
- const statKeys = Object.keys(stats);
289
+ const normalizedStats = /* @__PURE__ */ new Map();
290
+ for (const [key, value] of Object.entries(stats)) {
291
+ const normalized = key.standardize();
292
+ normalizedStats.set(normalized, [key, value]);
293
+ }
197
294
  const partsRegex = /(\[[^\]]+])|([^[]+)/g;
198
295
  let result = "";
199
296
  let match;
@@ -207,7 +304,7 @@ function generateStatsDice(originalDice, stats, dollarValue) {
207
304
  if (!outsideText) {
208
305
  continue;
209
306
  }
210
- const tokenRegex = /(\$?[\p{L}\p{N}_]+)/gu;
307
+ const tokenRegex = /(\$?[\p{L}\p{N}_.]+)/gu;
211
308
  let lastIndex = 0;
212
309
  let tokenMatch;
213
310
  while ((tokenMatch = tokenRegex.exec(outsideText)) !== null) {
@@ -216,45 +313,13 @@ function generateStatsDice(originalDice, stats, dollarValue) {
216
313
  const tokenHasDollar = token.startsWith("$");
217
314
  const tokenForCompare = tokenHasDollar ? token.slice(1) : token;
218
315
  const tokenStd = tokenForCompare.standardize();
219
- const diceMatch = /^(\d*)d(.+)$/i.exec(tokenStd);
220
- if (diceMatch) {
221
- const diceCount = diceMatch[1] || "";
222
- const afterD = diceMatch[2];
223
- let foundStatAfterD = false;
224
- for (const key of statKeys) {
225
- const keyStd = key.standardize();
226
- if (afterD === keyStd) {
227
- result += `${diceCount}d${stats[key].toString()}`;
228
- foundStatAfterD = true;
229
- break;
230
- }
231
- }
232
- if (foundStatAfterD) {
233
- lastIndex = tokenRegex.lastIndex;
234
- continue;
235
- }
236
- }
237
- let bestKey = null;
238
- let bestScore = 0;
239
- for (const key of statKeys) {
240
- const keyStd = key.standardize();
241
- if (tokenStd === keyStd) {
242
- bestKey = key;
243
- bestScore = 1;
244
- break;
245
- }
246
- const score = similarityScore(tokenStd, keyStd);
247
- if (score > bestScore) {
248
- bestScore = score;
249
- bestKey = key;
250
- }
251
- }
252
- if (bestKey && bestScore >= 0.6) {
253
- const statValue = stats[bestKey];
254
- result += statValue.toString();
255
- } else {
256
- result += token;
316
+ const diceReplacement = handleDiceAfterD(tokenStd, normalizedStats);
317
+ if (diceReplacement) {
318
+ result += diceReplacement;
319
+ lastIndex = tokenRegex.lastIndex;
320
+ continue;
257
321
  }
322
+ result += handleSimpleToken(tokenStd, token, normalizedStats, minThreshold);
258
323
  lastIndex = tokenRegex.lastIndex;
259
324
  }
260
325
  result += outsideText.slice(lastIndex);
@@ -298,33 +363,6 @@ function randomInt(min, max, engine = NumberGenerator2.engines.nodeCrypto, rng)
298
363
  if (!rng) rng = new Random(engine || void 0);
299
364
  return rng.integer(min, max);
300
365
  }
301
- function levenshteinDistance(a, b) {
302
- if (a === b) return 0;
303
- const al = a.length;
304
- const bl = b.length;
305
- if (al === 0) return bl;
306
- if (bl === 0) return al;
307
- const v0 = new Array(bl + 1);
308
- const v1 = new Array(bl + 1);
309
- for (let i = 0; i <= bl; i++) v0[i] = i;
310
- for (let i = 0; i < al; i++) {
311
- v1[0] = i + 1;
312
- for (let j = 0; j < bl; j++) {
313
- const cost = a[i] === b[j] ? 0 : 1;
314
- v1[j + 1] = Math.min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost);
315
- }
316
- for (let j = 0; j <= bl; j++) v0[j] = v1[j];
317
- }
318
- return v1[bl];
319
- }
320
- function similarityScore(a, b) {
321
- const la = a.length;
322
- const lb = b.length;
323
- if (la === 0 && lb === 0) return 1;
324
- const dist = levenshteinDistance(a, b);
325
- const max = Math.max(la, lb);
326
- return 1 - dist / max;
327
- }
328
366
  function createCriticalCustom(dice, customCritical, template, engine = NumberGenerator2.engines.nodeCrypto) {
329
367
  const compareRegex = dice.match(SIGN_REGEX_SPACE);
330
368
  let customDice = dice;
@@ -1443,6 +1481,7 @@ export {
1443
1481
  SortOrder,
1444
1482
  TooManyDice,
1445
1483
  TooManyStats,
1484
+ calculateSimilarity,
1446
1485
  createCriticalCustom,
1447
1486
  diceRandomParse,
1448
1487
  diceTypeRandomParse,
@@ -1450,11 +1489,14 @@ export {
1450
1489
  evalCombinaison,
1451
1490
  evalOneCombinaison,
1452
1491
  evalStatsDice,
1492
+ findBestRecord,
1493
+ findBestStatMatch,
1453
1494
  generateRandomStat,
1454
1495
  generateStatsDice,
1455
1496
  getEngine,
1456
1497
  getEngineId,
1457
1498
  isNumber,
1499
+ levenshteinDistance,
1458
1500
  randomInt,
1459
1501
  replaceExpByRandom,
1460
1502
  replaceFormulaInDice,