import { customElement, noShadowDOM } from "solid-element"; import { createSignal, onMount } from "solid-js"; interface RollResult { total: number; rolls: number[]; detail: string; } function rollDice(formula: string): RollResult { const allRolls: number[] = []; let total = 0; const details: string[] = []; // 解析骰子公式,支持 + 和 - 运算符 // 先按 + 和 - 分割,但保留运算符 const tokens = formula .split(/([+-])/) .map((t) => t.trim()) .filter((t) => t.length > 0); let sign = 1; for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; if (token === "+") { sign = 1; continue; } if (token === "-") { sign = -1; continue; } // 处理 kh/kl/dh/dl 运算符 let processedToken = token; let keepHigh = false; let keepLow = false; let dropHigh = false; let dropLow = false; let keepCount = 0; let dropCount = 0; const khMatch = token.match(/^(.+?)kh(\d+)$/i); const klMatch = token.match(/^(.+?)kl(\d+)$/i); const dhMatch = token.match(/^(.+?)dh(\d+)$/i); const dlMatch = token.match(/^(.+?)dl(\d+)$/i); if (khMatch) { processedToken = khMatch[1]; keepHigh = true; keepCount = parseInt(khMatch[2]); } else if (klMatch) { processedToken = klMatch[1]; keepLow = true; keepCount = parseInt(klMatch[2]); } else if (dhMatch) { processedToken = dhMatch[1]; dropHigh = true; dropCount = parseInt(dhMatch[2]); } else if (dlMatch) { processedToken = dlMatch[1]; dropLow = true; dropCount = parseInt(dlMatch[2]); } const match = processedToken.match(/^(\d+)?d(\d+)$/i); if (match) { const count = parseInt(match[1] || "1"); const sides = parseInt(match[2]); const rolls: number[] = []; for (let j = 0; j < count; j++) { rolls.push(Math.floor(Math.random() * sides) + 1); } let processedRolls = [...rolls]; let detail = `${count}d${sides}`; // 处理 keep/drop 运算符 if (keepHigh || keepLow || dropHigh || dropLow) { const sorted = [...processedRolls].sort((a, b) => a - b); if (dropHigh && dropCount > 0) { processedRolls = sorted.slice(0, sorted.length - dropCount); detail += `d${dropCount}h`; } else if (dropLow && dropCount > 0) { processedRolls = sorted.slice(dropCount); detail += `d${dropCount}l`; } else if (keepHigh && keepCount > 0) { processedRolls = sorted.slice(-keepCount); detail += `kh${keepCount}`; } else if (keepLow && keepCount > 0) { processedRolls = sorted.slice(0, keepCount); detail += `kl${keepCount}`; } } const partTotal = processedRolls.reduce((a, b) => a + b, 0); total += sign * partTotal; allRolls.push(...processedRolls); details.push( `${detail}=[${processedRolls.join(",")}]${sign < 0 ? " (负)" : ""}`, ); } else { // 处理固定数字 const num = parseInt(token); if (!isNaN(num)) { total += sign * num; details.push(`${num}${sign < 0 ? " (负)" : ""}`); } } } return { total, rolls: allRolls, detail: details.join(" + "), }; } // 从 URL 参数获取骰子结果 function getDiceResultFromUrl(key: string): number | null { if (typeof window === "undefined") return null; const params = new URLSearchParams(window.location.search); const value = params.get(`dice-${key}`); return value ? parseInt(value) : null; } function setDiceResultToUrl(key: string, value: number | null) { if (typeof window === "undefined") return; const params = new URLSearchParams(window.location.search); if (value === null) { params.delete(`dice-${key}`); } else { params.set(`dice-${key}`, value.toString()); } const newUrl = `${window.location.pathname}${params.toString() ? `?${params.toString()}` : ""}${window.location.hash}`; window.history.pushState({}, "", newUrl); } customElement("md-dice", { key: "" }, (props, { element }) => { noShadowDOM(); const [result, setResult] = createSignal(null); const [isRolled, setIsRolled] = createSignal(false); const [rollDetail, setRollDetail] = createSignal(""); // 从 element 的 textContent 获取骰子公式 const formula = element?.textContent?.trim() || ""; // 使用的 key(如果没有提供则使用生成的 ID) const effectiveKey = () => props.key; onMount(() => { // 初始化时检查 URL 参数 if (effectiveKey()) { const urlResult = getDiceResultFromUrl(effectiveKey()); if (urlResult !== null) { setResult(urlResult); setIsRolled(true); } } }); const handleRoll = () => { // 点击骰子图标:总是重 roll const rollResult = rollDice(formula); setResult(rollResult.total); setRollDetail(rollResult.detail); setIsRolled(true); if (effectiveKey()) { setDiceResultToUrl(effectiveKey(), rollResult.total); } }; const handleTextClick = () => { // 点击文本:总是重置为公式 setResult(null); setIsRolled(false); setRollDetail(""); if (effectiveKey()) { setDiceResultToUrl(effectiveKey(), null); } }; const displayText = () => (isRolled() ? `${result()}` : formula); return ( { e.preventDefault(); handleRoll(); }} class="text-blue-600 hover:text-blue-800 cursor-pointer" title={rollDetail() || "掷骰子"} > 🎲 {displayText()} ); });