fix: dice issue
This commit is contained in:
parent
cc10cea31d
commit
91d46dea75
|
|
@ -11,6 +11,48 @@ export function tokenize(input: string): Token[] {
|
||||||
while (i < str.length) {
|
while (i < str.length) {
|
||||||
const char = str[i];
|
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)) {
|
if (/\d/.test(char)) {
|
||||||
let numStr = "";
|
let numStr = "";
|
||||||
|
|
@ -86,45 +128,6 @@ export function tokenize(input: string): Token[] {
|
||||||
continue;
|
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++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
@ -194,20 +197,21 @@ function parsePool(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokens[i].type === "dice") {
|
const token = tokens[i];
|
||||||
for (let j = 0; j < tokens[i].count; j++) {
|
if (token.type === "dice") {
|
||||||
|
for (let j = 0; j < token.count; j++) {
|
||||||
dice.push({
|
dice.push({
|
||||||
sides: tokens[i].sides,
|
sides: token.sides,
|
||||||
value: 0,
|
value: 0,
|
||||||
isNegative: sign < 0,
|
isNegative: sign < 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
} else if (tokens[i].type === "number") {
|
} else if (token.type === "number") {
|
||||||
// 数字作为 0 面骰子(固定值)
|
// 数字作为 0 面骰子(固定值)
|
||||||
dice.push({
|
dice.push({
|
||||||
sides: 0,
|
sides: 0,
|
||||||
value: tokens[i].value,
|
value: token.value,
|
||||||
isNegative: sign < 0,
|
isNegative: sign < 0,
|
||||||
});
|
});
|
||||||
i++;
|
i++;
|
||||||
|
|
|
||||||
|
|
@ -13,25 +13,42 @@ export function rollDie(sides: number): number {
|
||||||
* 掷骰池
|
* 掷骰池
|
||||||
*/
|
*/
|
||||||
export function rollDicePool(pool: DicePool): DicePoolResult {
|
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,
|
sides: die.sides,
|
||||||
value: rollDie(die.sides),
|
value: rollDie(die.sides),
|
||||||
isNegative: die.isNegative,
|
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 { 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);
|
return sum + (roll.isNegative ? -roll.value : roll.value);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pool,
|
pool,
|
||||||
rolls,
|
rolls: allRolls,
|
||||||
keptRolls,
|
keptRolls: finalKeptRolls,
|
||||||
droppedRolls,
|
droppedRolls,
|
||||||
subtotal,
|
subtotal,
|
||||||
};
|
};
|
||||||
|
|
@ -55,27 +72,23 @@ export function applyModifier(
|
||||||
case "kh": // Keep Highest - 保留最大的 N 个
|
case "kh": // Keep Highest - 保留最大的 N 个
|
||||||
case "dh": { // Drop Highest - 丢弃最大的 N 个
|
case "dh": { // Drop Highest - 丢弃最大的 N 个
|
||||||
const count = modifier.count;
|
const count = modifier.count;
|
||||||
|
const higher = sorted.slice(-count);
|
||||||
|
const rest = sorted.slice(0, -count);
|
||||||
if (modifier.type === "kh") {
|
if (modifier.type === "kh") {
|
||||||
const kept = sorted.slice(-count);
|
return { keptRolls: higher, droppedRolls: rest };
|
||||||
const dropped = sorted.slice(0, -count);
|
|
||||||
return { keptRolls: kept, droppedRolls: dropped };
|
|
||||||
} else {
|
} else {
|
||||||
const kept = sorted.slice(0, -count);
|
return { keptRolls: rest, droppedRolls: higher };
|
||||||
const dropped = sorted.slice(-count);
|
|
||||||
return { keptRolls: kept, droppedRolls: dropped };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "kl": // Keep Lowest - 保留最小的 N 个
|
case "kl": // Keep Lowest - 保留最小的 N 个
|
||||||
case "dl": { // Drop Lowest - 丢弃最小的 N 个
|
case "dl": { // Drop Lowest - 丢弃最小的 N 个
|
||||||
const count = modifier.count;
|
const count = modifier.count;
|
||||||
|
const lower = sorted.slice(0, count);
|
||||||
|
const rest = sorted.slice(count);
|
||||||
if (modifier.type === "kl") {
|
if (modifier.type === "kl") {
|
||||||
const kept = sorted.slice(0, count);
|
return { keptRolls: lower, droppedRolls: rest };
|
||||||
const dropped = sorted.slice(count);
|
|
||||||
return { keptRolls: kept, droppedRolls: dropped };
|
|
||||||
} else {
|
} else {
|
||||||
const kept = sorted.slice(count);
|
return { keptRolls: rest, droppedRolls: lower };
|
||||||
const dropped = sorted.slice(0, count);
|
|
||||||
return { keptRolls: kept, droppedRolls: dropped };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -93,40 +106,24 @@ export function roll(formula: string): RollResult {
|
||||||
const total = poolResults.reduce((sum, result) => sum + result.subtotal, 0);
|
const total = poolResults.reduce((sum, result) => sum + result.subtotal, 0);
|
||||||
|
|
||||||
// 生成详细表达式 - 使用 HTML 格式
|
// 生成详细表达式 - 使用 HTML 格式
|
||||||
const parts: string[] = [];
|
const rollSpans: string[] = [];
|
||||||
for (const result of poolResults) {
|
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
|
// 构建骰子结果 HTML
|
||||||
const rollSpans = result.rolls.map((roll) => {
|
for (const roll of result.rolls) {
|
||||||
const isKept = result.keptRolls.some(
|
const isKept = result.keptRolls.some((kept) => kept === roll);
|
||||||
(kept) => kept === roll
|
if (roll.sides === 0) {
|
||||||
);
|
// 固定数字,不显示括号
|
||||||
if (isKept) {
|
rollSpans.push(`<strong>${roll.isNegative ? '-' : ''}${roll.value}</strong>`);
|
||||||
return `<strong>[${roll.value}]</strong>`;
|
} else if (isKept) {
|
||||||
|
rollSpans.push(`<strong>[${roll.value}]</strong>`);
|
||||||
} else {
|
} else {
|
||||||
return `<span class="text-gray-400">[${roll.value}]</span>`;
|
rollSpans.push(`<span class="text-gray-400">[${roll.value}]</span>`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const rollsStr = rollSpans.join(" ");
|
|
||||||
parts.push(`${dicePart} ${rollsStr} = <strong>${result.subtotal}</strong>`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const detail = parts.join(" + ");
|
const rollsStr = rollSpans.join(" + ");
|
||||||
|
const detail = `<strong>${total}</strong> = ${rollsStr}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pools: poolResults,
|
pools: poolResults,
|
||||||
|
|
|
||||||
|
|
@ -63,12 +63,13 @@ export function useDiceRoller() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 简化版掷骰,返回 HTML 格式结果
|
* 简化版掷骰,返回 HTML 格式结果
|
||||||
|
* 格式:[1] [2] [3] = <strong>6</strong>
|
||||||
*/
|
*/
|
||||||
function rollSimple(formula: string): { text: string; isHtml?: boolean } {
|
function rollSimple(formula: string): { text: string; isHtml?: boolean } {
|
||||||
try {
|
try {
|
||||||
const result = rollDice(formula);
|
const result = rollDice(formula);
|
||||||
return {
|
return {
|
||||||
text: `${result.formula} = ${result.total} (${result.detail})`,
|
text: result.detail,
|
||||||
isHtml: true, // detail 包含 HTML
|
isHtml: true, // detail 包含 HTML
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue