diff --git a/src/components/md-commander/hooks/dice-engine/parser.ts b/src/components/md-commander/hooks/dice-engine/parser.ts
index 239380d..873759f 100644
--- a/src/components/md-commander/hooks/dice-engine/parser.ts
+++ b/src/components/md-commander/hooks/dice-engine/parser.ts
@@ -11,6 +11,48 @@ export function tokenize(input: string): Token[] {
while (i < str.length) {
const char = str[i];
+ // 修饰符 kh/kl/dh/dl - 必须在检查 'd' 骰子之前检查
+ if (char === "k" || char === "d") {
+ const rest = str.slice(i).toLowerCase();
+ if (rest.startsWith("kh")) {
+ i += 2;
+ let countStr = "";
+ while (i < str.length && /\d/.test(str[i])) {
+ countStr += str[i];
+ i++;
+ }
+ tokens.push({ type: "modifier", modType: "kh", count: parseInt(countStr) || 1 });
+ continue;
+ } else if (rest.startsWith("kl")) {
+ i += 2;
+ let countStr = "";
+ while (i < str.length && /\d/.test(str[i])) {
+ countStr += str[i];
+ i++;
+ }
+ tokens.push({ type: "modifier", modType: "kl", count: parseInt(countStr) || 1 });
+ continue;
+ } else if (rest.startsWith("dh")) {
+ i += 2;
+ let countStr = "";
+ while (i < str.length && /\d/.test(str[i])) {
+ countStr += str[i];
+ i++;
+ }
+ tokens.push({ type: "modifier", modType: "dh", count: parseInt(countStr) || 1 });
+ continue;
+ } else if (rest.startsWith("dl")) {
+ i += 2;
+ let countStr = "";
+ while (i < str.length && /\d/.test(str[i])) {
+ countStr += str[i];
+ i++;
+ }
+ tokens.push({ type: "modifier", modType: "dl", count: parseInt(countStr) || 1 });
+ continue;
+ }
+ }
+
// 数字字面量
if (/\d/.test(char)) {
let numStr = "";
@@ -86,45 +128,6 @@ export function tokenize(input: string): Token[] {
continue;
}
- // 修饰符 kh/kl/dh/dl
- if (char === "k" || char === "d") {
- const rest = str.slice(i).toLowerCase();
- if (rest.startsWith("kh")) {
- i += 2;
- let countStr = "";
- while (i < str.length && /\d/.test(str[i])) {
- countStr += str[i];
- i++;
- }
- tokens.push({ type: "modifier", modType: "kh", count: parseInt(countStr) || 1 });
- } else if (rest.startsWith("kl")) {
- i += 2;
- let countStr = "";
- while (i < str.length && /\d/.test(str[i])) {
- countStr += str[i];
- i++;
- }
- tokens.push({ type: "modifier", modType: "kl", count: parseInt(countStr) || 1 });
- } else if (rest.startsWith("dh")) {
- i += 2;
- let countStr = "";
- while (i < str.length && /\d/.test(str[i])) {
- countStr += str[i];
- i++;
- }
- tokens.push({ type: "modifier", modType: "dh", count: parseInt(countStr) || 1 });
- } else if (rest.startsWith("dl")) {
- i += 2;
- let countStr = "";
- while (i < str.length && /\d/.test(str[i])) {
- countStr += str[i];
- i++;
- }
- tokens.push({ type: "modifier", modType: "dl", count: parseInt(countStr) || 1 });
- }
- continue;
- }
-
// 未知字符,跳过
i++;
}
@@ -194,20 +197,21 @@ function parsePool(
continue;
}
- if (tokens[i].type === "dice") {
- for (let j = 0; j < tokens[i].count; j++) {
+ const token = tokens[i];
+ if (token.type === "dice") {
+ for (let j = 0; j < token.count; j++) {
dice.push({
- sides: tokens[i].sides,
+ sides: token.sides,
value: 0,
isNegative: sign < 0,
});
}
i++;
- } else if (tokens[i].type === "number") {
+ } else if (token.type === "number") {
// 数字作为 0 面骰子(固定值)
dice.push({
sides: 0,
- value: tokens[i].value,
+ value: token.value,
isNegative: sign < 0,
});
i++;
diff --git a/src/components/md-commander/hooks/dice-engine/roller.ts b/src/components/md-commander/hooks/dice-engine/roller.ts
index f11a1c1..d0b8e66 100644
--- a/src/components/md-commander/hooks/dice-engine/roller.ts
+++ b/src/components/md-commander/hooks/dice-engine/roller.ts
@@ -13,25 +13,42 @@ export function rollDie(sides: number): number {
* 掷骰池
*/
export function rollDicePool(pool: DicePool): DicePoolResult {
- // 掷所有骰子
- const rolls: DieResult[] = pool.dice.map((die) => ({
+ // 分离骰子和固定数字
+ const diceToRoll = pool.dice.filter((die) => die.sides > 0);
+ const fixedNumbers = pool.dice.filter((die) => die.sides === 0);
+
+ // 只掷真正的骰子
+ const rolls: DieResult[] = diceToRoll.map((die) => ({
sides: die.sides,
value: rollDie(die.sides),
isNegative: die.isNegative,
}));
- // 应用修饰符
+ // 添加固定数字
+ const allRolls: DieResult[] = [
+ ...rolls,
+ ...fixedNumbers.map((die) => ({
+ sides: 0,
+ value: die.value,
+ isNegative: die.isNegative,
+ })),
+ ];
+
+ // 应用修饰符(只对有面数的骰子生效)
const { keptRolls, droppedRolls } = applyModifier(rolls, pool.modifier);
+ // 固定数字总是保留
+ const finalKeptRolls = [...keptRolls, ...allRolls.filter((r) => r.sides === 0)];
+
// 计算小计
- const subtotal = keptRolls.reduce((sum, roll) => {
+ const subtotal = finalKeptRolls.reduce((sum, roll) => {
return sum + (roll.isNegative ? -roll.value : roll.value);
}, 0);
return {
pool,
- rolls,
- keptRolls,
+ rolls: allRolls,
+ keptRolls: finalKeptRolls,
droppedRolls,
subtotal,
};
@@ -55,27 +72,23 @@ export function applyModifier(
case "kh": // Keep Highest - 保留最大的 N 个
case "dh": { // Drop Highest - 丢弃最大的 N 个
const count = modifier.count;
+ const higher = sorted.slice(-count);
+ const rest = sorted.slice(0, -count);
if (modifier.type === "kh") {
- const kept = sorted.slice(-count);
- const dropped = sorted.slice(0, -count);
- return { keptRolls: kept, droppedRolls: dropped };
+ return { keptRolls: higher, droppedRolls: rest };
} else {
- const kept = sorted.slice(0, -count);
- const dropped = sorted.slice(-count);
- return { keptRolls: kept, droppedRolls: dropped };
+ return { keptRolls: rest, droppedRolls: higher };
}
}
case "kl": // Keep Lowest - 保留最小的 N 个
case "dl": { // Drop Lowest - 丢弃最小的 N 个
const count = modifier.count;
+ const lower = sorted.slice(0, count);
+ const rest = sorted.slice(count);
if (modifier.type === "kl") {
- const kept = sorted.slice(0, count);
- const dropped = sorted.slice(count);
- return { keptRolls: kept, droppedRolls: dropped };
+ return { keptRolls: lower, droppedRolls: rest };
} else {
- const kept = sorted.slice(count);
- const dropped = sorted.slice(0, count);
- return { keptRolls: kept, droppedRolls: dropped };
+ return { keptRolls: rest, droppedRolls: lower };
}
}
}
@@ -93,40 +106,24 @@ export function roll(formula: string): RollResult {
const total = poolResults.reduce((sum, result) => sum + result.subtotal, 0);
// 生成详细表达式 - 使用 HTML 格式
- const parts: string[] = [];
+ const rollSpans: 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 `[${roll.value}]`;
+ for (const roll of result.rolls) {
+ const isKept = result.keptRolls.some((kept) => kept === roll);
+ if (roll.sides === 0) {
+ // 固定数字,不显示括号
+ rollSpans.push(`${roll.isNegative ? '-' : ''}${roll.value}`);
+ } else if (isKept) {
+ rollSpans.push(`[${roll.value}]`);
} else {
- return `[${roll.value}]`;
+ rollSpans.push(`[${roll.value}]`);
}
- });
-
- const rollsStr = rollSpans.join(" ");
- parts.push(`${dicePart} ${rollsStr} = ${result.subtotal}`);
+ }
}
- const detail = parts.join(" + ");
+ const rollsStr = rollSpans.join(" + ");
+ const detail = `${total} = ${rollsStr}`;
return {
pools: poolResults,
diff --git a/src/components/md-commander/hooks/useDiceRoller.ts b/src/components/md-commander/hooks/useDiceRoller.ts
index 6b7ff2a..fd7110c 100644
--- a/src/components/md-commander/hooks/useDiceRoller.ts
+++ b/src/components/md-commander/hooks/useDiceRoller.ts
@@ -63,12 +63,13 @@ export function useDiceRoller() {
/**
* 简化版掷骰,返回 HTML 格式结果
+ * 格式:[1] [2] [3] = 6
*/
function rollSimple(formula: string): { text: string; isHtml?: boolean } {
try {
const result = rollDice(formula);
return {
- text: `${result.formula} = ${result.total} (${result.detail})`,
+ text: result.detail,
isHtml: true, // detail 包含 HTML
};
} catch (e) {