蒙提霍尔问题

1,516 阅读3分钟

image.png 最近看韩国电视剧【D.P逃兵追缉令】里面提到一个有趣的数学概率游戏 -> 蒙提霍尔问题

意思是:参赛者会看见三扇门,其中一扇门的里面有一辆汽车,选中里面是汽车的那扇门,就可以赢得该辆汽车,另外两扇门里面则都是一只山羊,让你任意选择其中一个,然后打开其余两个门中的一个并且是山羊(去掉一个错误答案),这时,让你重新选择。那么你是会坚持原来的选择,还是换选另外一个未被打开过的门呢?

大家可以想一想如果是自己,我们是会换还是不会换?

好了,我当时看到后感觉很有意思,所以我简单写了一套代码,源码贴在下面,大家可以验证一下,先告诉大家,换:赢得汽车的概率是2/3,不换:赢得汽车的概率是1/3

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>蒙提霍尔问题模拟器</title>

  <style>
    * {
      box-sizing: border-box;
    }

    body {
      margin: 0;
      font-family: "Microsoft YaHei", sans-serif;
      background: #f4f6f9;
      color: #333;
      display: flex;
      justify-content: center;
      padding: 30px;
    }

    .container {
      width: 900px;
      max-width: 100%;
      background: white;
      border-radius: 20px;
      padding: 30px;
      box-shadow: 0 10px 30px rgba(0,0,0,0.1);
    }

    h1 {
      text-align: center;
      margin-bottom: 10px;
    }

    .desc {
      text-align: center;
      color: #666;
      margin-bottom: 30px;
      line-height: 1.6;
    }

    .doors {
      display: flex;
      gap: 20px;
      justify-content: center;
      margin-bottom: 30px;
      flex-wrap: wrap;
    }

    .door {
      width: 180px;
      height: 260px;
      background: linear-gradient(145deg, #c98b2b, #a86b14);
      border-radius: 12px;
      position: relative;
      cursor: pointer;
      transition: all 0.25s ease;
      display: flex;
      justify-content: center;
      align-items: center;
      color: white;
      font-size: 28px;
      font-weight: bold;
      user-select: none;
    }

    .door:hover {
      transform: translateY(-5px);
    }

    .door.open {
      background: #ddd;
      color: #222;
    }

    .door.selected {
      border: 6px solid #4caf50;
    }

    .door.revealed-goat {
      background: #999;
    }

    .door.win {
      background: gold;
      color: #222;
    }

    .controls {
      text-align: center;
      margin-top: 20px;
    }

    button {
      border: none;
      background: #1677ff;
      color: white;
      padding: 12px 20px;
      border-radius: 10px;
      font-size: 16px;
      cursor: pointer;
      margin: 8px;
      transition: 0.2s;
    }

    button:hover {
      opacity: 0.9;
    }

    button:disabled {
      background: #999;
      cursor: not-allowed;
    }

    .message {
      text-align: center;
      margin-top: 20px;
      font-size: 20px;
      font-weight: bold;
      min-height: 30px;
    }

    .stats {
      margin-top: 30px;
      background: #f8f8f8;
      padding: 20px;
      border-radius: 12px;
      line-height: 1.8;
    }

    .highlight {
      color: #1677ff;
      font-weight: bold;
    }

    .footer {
      margin-top: 20px;
      color: #666;
      font-size: 14px;
      line-height: 1.7;
    }
  </style>
</head>

<body>
  <div class="container">
    <h1>🎲 蒙提霍尔问题模拟器</h1>

    <div class="desc">
      三扇门后面:<br>
      一扇是 🚗 汽车,两扇是 🐐 山羊。<br><br>

      你先选一扇门,主持人会帮你打开一扇有山羊的门。<br>
      然后你可以选择:<br>
      <span class="highlight">坚持原选择</span><span class="highlight">换门</span>
    </div>

    <div class="doors" id="doors"></div>

    <div class="controls">
      <button id="stayBtn" disabled>坚持不换</button>
      <button id="switchBtn" disabled>换门</button>
      <button onclick="resetGame()">重新开始</button>
    </div>

    <div class="message" id="message"></div>

    <div class="stats">
      <h3>📊 统计数据</h3>

      <div>总局数:<span id="total">0</span></div>

      <hr>

      <div>
        坚持不换:
        胜利 <span id="stayWin">0</span> 次 /
        尝试 <span id="stayTotal">0</span></div>

      <div>
        胜率:
        <span class="highlight" id="stayRate">0%</span>
      </div>

      <br>

      <div>
        换门:
        胜利 <span id="switchWin">0</span> 次 /
        尝试 <span id="switchTotal">0</span></div>

      <div>
        胜率:
        <span class="highlight" id="switchRate">0%</span>
      </div>
    </div>

    <div class="footer">
      💡 理论上:<br>
      - 坚持不换胜率 ≈ 33%<br>
      - 换门胜率 ≈ 66%<br><br>

      多玩几次你就会发现:<br>
      “换门”真的更容易赢 🚀
    </div>
  </div>

  <script>
    const doorsContainer = document.getElementById("doors");
    const message = document.getElementById("message");

    const stayBtn = document.getElementById("stayBtn");
    const switchBtn = document.getElementById("switchBtn");

    let prizeDoor;
    let selectedDoor = null;
    let revealedDoor = null;
    let gameFinished = false;

    const stats = {
      total: 0,

      stayWin: 0,
      stayTotal: 0,

      switchWin: 0,
      switchTotal: 0
    };

    function createDoors() {
      doorsContainer.innerHTML = "";

      for (let i = 0; i < 3; i++) {
        const door = document.createElement("div");

        door.className = "door";
        door.dataset.index = i;
        door.innerText = "🚪";

        door.onclick = () => selectDoor(i);

        doorsContainer.appendChild(door);
      }
    }

    function resetGame() {
      prizeDoor = Math.floor(Math.random() * 3);

      selectedDoor = null;
      revealedDoor = null;
      gameFinished = false;

      stayBtn.disabled = true;
      switchBtn.disabled = true;

      message.innerText = "";

      createDoors();
    }

    function selectDoor(index) {
      if (selectedDoor !== null) return;

      selectedDoor = index;

      const doors = document.querySelectorAll(".door");

      doors[index].classList.add("selected");

      const possibleReveal = [];

      for (let i = 0; i < 3; i++) {
        if (i !== selectedDoor && i !== prizeDoor) {
          possibleReveal.push(i);
        }
      }

      revealedDoor =
        possibleReveal[Math.floor(Math.random() * possibleReveal.length)];

      doors[revealedDoor].classList.add("open", "revealed-goat");
      doors[revealedDoor].innerText = "🐐";

      stayBtn.disabled = false;
      switchBtn.disabled = false;

      message.innerText =
        "主持人帮你打开了一扇山羊门!你要换门吗?";
    }

    stayBtn.onclick = () => finishGame(false);
    switchBtn.onclick = () => finishGame(true);

    function finishGame(shouldSwitch) {
      if (gameFinished) return;

      gameFinished = true;

      let finalChoice = selectedDoor;

      if (shouldSwitch) {
        for (let i = 0; i < 3; i++) {
          if (i !== selectedDoor && i !== revealedDoor) {
            finalChoice = i;
          }
        }
      }

      const doors = document.querySelectorAll(".door");

      for (let i = 0; i < 3; i++) {
        doors[i].classList.add("open");

        if (i === prizeDoor) {
          doors[i].innerText = "🚗";
          doors[i].classList.add("win");
        } else {
          doors[i].innerText = "🐐";
        }
      }

      const win = finalChoice === prizeDoor;

      stats.total++;

      if (shouldSwitch) {
        stats.switchTotal++;

        if (win) stats.switchWin++;
      } else {
        stats.stayTotal++;

        if (win) stats.stayWin++;
      }

      updateStats();

      if (win) {
        message.innerText = shouldSwitch
          ? "🎉 你换门后赢得了汽车!"
          : "🎉 你坚持选择,赢得了汽车!";
      } else {
        message.innerText = shouldSwitch
          ? "😢 你换门后输了..."
          : "😢 你坚持后输了...";
      }

      stayBtn.disabled = true;
      switchBtn.disabled = true;
    }

    function updateStats() {
      document.getElementById("total").innerText = stats.total;

      document.getElementById("stayWin").innerText = stats.stayWin;
      document.getElementById("stayTotal").innerText = stats.stayTotal;

      document.getElementById("switchWin").innerText = stats.switchWin;
      document.getElementById("switchTotal").innerText = stats.switchTotal;

      const stayRate =
        stats.stayTotal === 0
          ? 0
          : ((stats.stayWin / stats.stayTotal) * 100).toFixed(1);

      const switchRate =
        stats.switchTotal === 0
          ? 0
          : ((stats.switchWin / stats.switchTotal) * 100).toFixed(1);

      document.getElementById("stayRate").innerText = stayRate + "%";
      document.getElementById("switchRate").innerText = switchRate + "%";
    }

    resetGame();
  </script>
</body>
</html>