feat: dice roller improved
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import type { DicePool, DicePoolResult, DieResult, RollResult } from "./types";
|
||||
import { tokenize, parse } from "./parser";
|
||||
|
||||
/**
|
||||
* 掷骰子
|
||||
*/
|
||||
export function rollDie(sides: number): number {
|
||||
if (sides === 0) return 0; // 固定数字
|
||||
return Math.floor(Math.random() * sides) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 掷骰池
|
||||
*/
|
||||
export function rollDicePool(pool: DicePool): DicePoolResult {
|
||||
// 掷所有骰子
|
||||
const rolls: DieResult[] = pool.dice.map((die) => ({
|
||||
sides: die.sides,
|
||||
value: rollDie(die.sides),
|
||||
isNegative: die.isNegative,
|
||||
}));
|
||||
|
||||
// 应用修饰符
|
||||
const { keptRolls, droppedRolls } = applyModifier(rolls, pool.modifier);
|
||||
|
||||
// 计算小计
|
||||
const subtotal = keptRolls.reduce((sum, roll) => {
|
||||
return sum + (roll.isNegative ? -roll.value : roll.value);
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
pool,
|
||||
rolls,
|
||||
keptRolls,
|
||||
droppedRolls,
|
||||
subtotal,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用修饰符
|
||||
*/
|
||||
export function applyModifier(
|
||||
rolls: DieResult[],
|
||||
modifier: DicePool["modifier"]
|
||||
): { keptRolls: DieResult[]; droppedRolls: DieResult[] } {
|
||||
if (!modifier) {
|
||||
return { keptRolls: rolls, droppedRolls: [] };
|
||||
}
|
||||
|
||||
// 按值排序
|
||||
const sorted = [...rolls].sort((a, b) => a.value - b.value);
|
||||
|
||||
switch (modifier.type) {
|
||||
case "kh": // Keep Highest - 保留最大的 N 个
|
||||
case "dh": { // Drop Highest - 丢弃最大的 N 个
|
||||
const count = modifier.count;
|
||||
if (modifier.type === "kh") {
|
||||
const kept = sorted.slice(-count);
|
||||
const dropped = sorted.slice(0, -count);
|
||||
return { keptRolls: kept, droppedRolls: dropped };
|
||||
} else {
|
||||
const kept = sorted.slice(0, -count);
|
||||
const dropped = sorted.slice(-count);
|
||||
return { keptRolls: kept, droppedRolls: dropped };
|
||||
}
|
||||
}
|
||||
case "kl": // Keep Lowest - 保留最小的 N 个
|
||||
case "dl": { // Drop Lowest - 丢弃最小的 N 个
|
||||
const count = modifier.count;
|
||||
if (modifier.type === "kl") {
|
||||
const kept = sorted.slice(0, count);
|
||||
const dropped = sorted.slice(count);
|
||||
return { keptRolls: kept, droppedRolls: dropped };
|
||||
} else {
|
||||
const kept = sorted.slice(count);
|
||||
const dropped = sorted.slice(0, count);
|
||||
return { keptRolls: kept, droppedRolls: dropped };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行完整的掷骰
|
||||
*/
|
||||
export function roll(formula: string): RollResult {
|
||||
const tokens = tokenize(formula);
|
||||
const pools = parse(tokens);
|
||||
|
||||
const poolResults = pools.map((pool) => rollDicePool(pool));
|
||||
|
||||
const total = poolResults.reduce((sum, result) => sum + result.subtotal, 0);
|
||||
|
||||
// 生成详细表达式 - 使用 HTML 格式
|
||||
const parts: string[] = [];
|
||||
for (const result of poolResults) {
|
||||
const diceCount = result.pool.dice.filter((d) => d.sides > 0).length;
|
||||
const fixedValue = result.pool.dice.find((d) => d.sides === 0)?.value || 0;
|
||||
const modStr = result.pool.modifier
|
||||
? `${result.pool.modifier.type}${result.pool.modifier.count}`
|
||||
: "";
|
||||
|
||||
// 骰子部分
|
||||
let dicePart: string;
|
||||
if (diceCount > 0) {
|
||||
const sides = result.pool.dice[0]?.sides || 0;
|
||||
dicePart = `${diceCount}d${sides}${modStr}`;
|
||||
} else {
|
||||
dicePart = `${fixedValue}`;
|
||||
}
|
||||
|
||||
// 构建骰子结果 HTML
|
||||
const rollSpans = result.rolls.map((roll) => {
|
||||
const isKept = result.keptRolls.some(
|
||||
(kept) => kept === roll
|
||||
);
|
||||
if (isKept) {
|
||||
return `<strong>[${roll.value}]</strong>`;
|
||||
} else {
|
||||
return `<span class="text-gray-400">[${roll.value}]</span>`;
|
||||
}
|
||||
});
|
||||
|
||||
const rollsStr = rollSpans.join(" ");
|
||||
parts.push(`${dicePart} ${rollsStr} = <strong>${result.subtotal}</strong>`);
|
||||
}
|
||||
|
||||
const detail = parts.join(" + ");
|
||||
|
||||
return {
|
||||
pools: poolResults,
|
||||
total,
|
||||
detail,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user