ttrpg-tools/src/components/utils/weighted-random.ts

89 lines
2.4 KiB
TypeScript
Raw Normal View History

2026-03-21 18:42:11 +08:00
/**
*
* @param label "1" "1-3"
* @returns [min, max] null
*/
export function parseIntegerRange(label: string): [number, number] | null {
const trimmed = label.trim();
// 尝试匹配整数范围格式 (如 "1-3")
const rangeMatch = trimmed.match(/^(\d+)\s*-\s*(\d+)$/);
if (rangeMatch) {
const min = parseInt(rangeMatch[1]!, 10);
const max = parseInt(rangeMatch[2]!, 10);
if (min <= max) {
return [min, max];
}
}
// 尝试匹配单个整数格式 (如 "5")
const intMatch = trimmed.match(/^(\d+)$/);
if (intMatch) {
const num = parseInt(intMatch[1]!, 10);
return [num, num];
}
return null;
}
/**
*
* @param labels
* @returns
*/
export function calculateTotalWeight(labels: string[]): number {
let total = 0;
for (const label of labels) {
const range = parseIntegerRange(label);
if (range) {
const [min, max] = range;
total += max - min + 1;
}
}
return total;
}
/**
*
* @param labels
* @param totalWeight
* @returns -1
*/
export function weightedRandomIndex(labels: string[], totalWeight?: number): number {
const weight = totalWeight ?? calculateTotalWeight(labels);
if (weight === 0) {
return -1;
}
// 生成一个随机权重值 (1 到 totalWeight)
const roll = Math.floor(Math.random() * weight) + 1;
let cumulative = 0;
for (let i = 0; i < labels.length; i++) {
const range = parseIntegerRange(labels[i]!);
if (range) {
const [min, max] = range;
const labelWeight = max - min + 1;
cumulative += labelWeight;
if (roll <= cumulative) {
return i;
}
}
}
// 理论上不会到这里,但为了安全返回最后一个
return labels.length - 1;
}
/**
* label
* @param labels
* @returns label / true
*/
export function areAllLabelsNumeric(labels: string[]): boolean {
if (labels.length === 0) return false;
return labels.every(label => parseIntegerRange(label) !== null);
}