From 91d46dea7514f0e37aa1b29719d64489815aa8b3 Mon Sep 17 00:00:00 2001 From: hypercross Date: Sat, 28 Feb 2026 16:59:51 +0800 Subject: [PATCH] fix: dice issue --- .../md-commander/hooks/dice-engine/parser.ts | 92 ++++++++++--------- .../md-commander/hooks/dice-engine/roller.ts | 89 +++++++++--------- .../md-commander/hooks/useDiceRoller.ts | 3 +- 3 files changed, 93 insertions(+), 91 deletions(-) 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) {