const CHAINS = [
{
id: "linear",
title: "方法一:线性除法",
tag: "常见直觉",
steps: [
"把 48 天看成「均匀增长」。",
"一半时间 ≈ 一半面积。",
"计算:48 ÷ 2 = 24。",
],
answer: 24,
correct: false,
note: "误把指数增长当作线性增长。",
},
{
id: "reverse",
title: "方法二:逆向递推",
tag: "逆向推理",
steps: [
"第 48 天面积 = 100%(满池)。",
"每天翻倍 ⇒ 第 47 天 = 48 天的一半。",
"所以第 47 天刚好覆盖 50%。",
],
answer: 47,
correct: true,
note: "从终态往回推,符合「翻倍」定义。",
},
{
id: "simulate",
title: "方法三:正向模拟",
tag: "枚举验证",
steps: [
"设 f(n) 为第 n 天覆盖比例,f(n) = 2 × f(n-1)。",
"已知 f(48) = 1(满池)。",
"则 f(47) = 1/2,第 47 天刚好一半。",
],
answer: 47,
correct: true,
note: "用递推关系验证,与逆向结论一致。",
},
];
const SINGLE_CHAIN = CHAINS[0];
const runBtn = document.getElementById("run-btn");
const resetBtn = document.getElementById("reset-btn");
const singleMode = document.getElementById("single-mode");
const statusEl = document.getElementById("status");
const chainsEl = document.getElementById("chains");
const votePanel = document.getElementById("vote-panel");
const voteBars = document.getElementById("vote-bars");
const finalAnswer = document.getElementById("final-answer");
let running = false;
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function setStatus(text, tone = "info") {
statusEl.textContent = text;
statusEl.style.borderColor =
tone === "success"
? "rgba(52, 211, 153, 0.35)"
: tone === "warn"
? "rgba(251, 191, 36, 0.35)"
: "rgba(96, 165, 250, 0.18)";
statusEl.style.background =
tone === "success"
? "rgba(52, 211, 153, 0.08)"
: tone === "warn"
? "rgba(251, 191, 36, 0.08)"
: "rgba(96, 165, 250, 0.08)";
}
function createChainCard(chain) {
const card = document.createElement("article");
card.className = "chain-card";
card.dataset.id = chain.id;
card.innerHTML = `
<div class="chain-head">
<div class="chain-title">${chain.title}</div>
<span class="badge pending">等待中</span>
</div>
<div class="chain-steps"></div>
`;
return card;
}
function resetDemo() {
chainsEl.innerHTML = "";
votePanel.classList.add("hidden");
voteBars.innerHTML = "";
finalAnswer.innerHTML = "";
setStatus("点击按钮,观察三条推理链如何产生答案并投票。");
}
async function animateChain(card, chain) {
const badge = card.querySelector(".badge");
const stepsWrap = card.querySelector(".chain-steps");
card.classList.add("visible");
badge.textContent = "推理中…";
badge.className = "badge thinking";
for (const text of chain.steps) {
const step = document.createElement("div");
step.className = "step";
step.textContent = text;
stepsWrap.appendChild(step);
await sleep(80);
step.classList.add("show");
await sleep(420);
}
const answerStep = document.createElement("div");
answerStep.className = `step answer ${chain.correct ? "correct" : "wrong"} show`;
answerStep.textContent = `最终答案:第 ${chain.answer} 天`;
stepsWrap.appendChild(answerStep);
badge.textContent = chain.correct ? "推理完成 ✓" : "推理完成 ✗";
badge.className = `badge ${chain.correct ? "correct" : "wrong"}`;
if (!chain.correct) {
card.classList.add("wrong");
}
await sleep(300);
return chain.answer;
}
function renderVote(answers, winner) {
votePanel.classList.remove("hidden");
const counts = answers.reduce((acc, value) => {
acc[value] = (acc[value] || 0) + 1;
return acc;
}, {});
const sorted = Object.entries(counts).sort((a, b) => b[1] - a[1]);
const maxCount = sorted[0][1];
voteBars.innerHTML = sorted
.map(([value, count]) => {
const pct = (count / answers.length) * 100;
const cls = Number(value) === 47 ? "a47" : "a24";
return `
<div class="vote-row">
<div class="vote-label">${value} 天</div>
<div class="bar-track">
<div class="bar-fill ${cls}" style="width: ${pct}%"></div>
</div>
<div class="vote-count">${count} 票</div>
</div>
`;
})
.join("");
CHAINS.forEach((chain) => {
const card = chainsEl.querySelector(`[data-id="${chain.id}"]`);
if (card && chain.answer === winner) {
card.classList.add("winner");
}
});
finalAnswer.innerHTML = `
<div>出现次数最多的答案:<strong>第 ${winner} 天</strong>(${maxCount}/${answers.length} 票)</div>
<div style="margin-top:8px;color:#cbd5e1;">
${winner === 47
? "多数表决成功纠正了「24 天」的线性直觉错误。这就是 Self-Consistency 的价值。"
: "仅单条链运行时没有投票纠错,容易停留在直觉错误上。"}
</div>
`;
}
async function runDemo() {
if (running) return;
running = true;
runBtn.disabled = true;
resetDemo();
const useSingle = singleMode.checked;
const chains = useSingle ? [SINGLE_CHAIN] : CHAINS;
if (useSingle) {
setStatus("单链模式:只运行「线性除法」这一条推理链……", "warn");
} else {
setStatus("正在并行启动 3 条不同推理链……");
}
chains.forEach((chain) => {
chainsEl.appendChild(createChainCard(chain));
});
await sleep(350);
const answers = [];
for (const chain of chains) {
const card = chainsEl.querySelector(`[data-id="${chain.id}"]`);
const answer = await animateChain(card, chain);
answers.push(answer);
}
if (useSingle) {
setStatus(`单链输出:第 ${answers[0]} 天。没有多数表决,无法纠正潜在错误。`, "warn");
finalAnswer.innerHTML = `
<div>单链最终答案:<strong style="color:#fca5a5;">第 ${answers[0]} 天</strong>(错误)</div>
<div style="margin-top:8px;color:#cbd5e1;">取消勾选「对比模式」,运行 3 条链并观察多数表决如何得到 47 天。</div>
`;
votePanel.classList.remove("hidden");
voteBars.innerHTML = `
<div class="vote-row">
<div class="vote-label">${answers[0]} 天</div>
<div class="bar-track"><div class="bar-fill a24" style="width:100%"></div></div>
<div class="vote-count">1 票</div>
</div>
`;
} else {
setStatus("3 条推理链已完成,正在进行多数表决……", "success");
const counts = answers.reduce((acc, value) => {
acc[value] = (acc[value] || 0) + 1;
return acc;
}, {});
const winner = Number(
Object.entries(counts).sort((a, b) => b[1] - a[1])[0][0]
);
await sleep(500);
renderVote(answers, winner);
setStatus(`多数表决完成:最终正确答案为第 ${winner} 天。`, "success");
}
running = false;
runBtn.disabled = false;
}
runBtn.addEventListener("click", runDemo);
resetBtn.addEventListener("click", resetDemo);
resetDemo();