Implement the 021-bestiary-statblock feature that adds a searchable D&D 2024 Monster Manual creature library with inline autocomplete suggestions, full stat block display in a fixed side panel, auto-numbering of duplicate creature names, HP/AC pre-fill from bestiary data, and automatic stat block display on turn change for wide viewports
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
54
packages/domain/src/auto-number.ts
Normal file
54
packages/domain/src/auto-number.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Resolves a creature name against existing combatant names,
|
||||
* handling auto-numbering for duplicates.
|
||||
*
|
||||
* - No conflict: returns name as-is, no renames.
|
||||
* - First conflict (one existing match): renames existing to "Name 1",
|
||||
* new becomes "Name 2".
|
||||
* - Subsequent conflicts: new gets next number suffix.
|
||||
*/
|
||||
export function resolveCreatureName(
|
||||
baseName: string,
|
||||
existingNames: readonly string[],
|
||||
): {
|
||||
newName: string;
|
||||
renames: ReadonlyArray<{ from: string; to: string }>;
|
||||
} {
|
||||
// Find exact matches and numbered matches (e.g., "Goblin 1", "Goblin 2")
|
||||
const exactMatches: number[] = [];
|
||||
let maxNumber = 0;
|
||||
|
||||
for (let i = 0; i < existingNames.length; i++) {
|
||||
const name = existingNames[i];
|
||||
if (name === baseName) {
|
||||
exactMatches.push(i);
|
||||
} else {
|
||||
const match = new RegExp(`^${escapeRegExp(baseName)} (\\d+)$`).exec(name);
|
||||
if (match) {
|
||||
const num = Number.parseInt(match[1], 10);
|
||||
if (num > maxNumber) maxNumber = num;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No conflict at all
|
||||
if (exactMatches.length === 0 && maxNumber === 0) {
|
||||
return { newName: baseName, renames: [] };
|
||||
}
|
||||
|
||||
// First conflict: one exact match, no numbered ones yet
|
||||
if (exactMatches.length === 1 && maxNumber === 0) {
|
||||
return {
|
||||
newName: `${baseName} 2`,
|
||||
renames: [{ from: baseName, to: `${baseName} 1` }],
|
||||
};
|
||||
}
|
||||
|
||||
// Subsequent conflicts: append next number
|
||||
const nextNumber = Math.max(maxNumber, exactMatches.length) + 1;
|
||||
return { newName: `${baseName} ${nextNumber}`, renames: [] };
|
||||
}
|
||||
|
||||
function escapeRegExp(s: string): string {
|
||||
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
Reference in New Issue
Block a user