🚣‍♂️農夫過河問題 (HTML + JavaScript 教學)

農夫過河問題 (HTML + JavaScript 版本)

✨ 農夫過河問題 (HTML + JavaScript 版本) ✨

大家好, 這裡用「前端網頁」的方式分享「農夫過河問題」的解法. 核心概念與經典的農夫過河題相同: 運用角色的「位置狀態」與「安全判斷」機制, 來找出可行的過河策略.


🌊 問題描述

問題中的角色有 4 種:

  • 👨‍🌾 農夫 Farmer (F)
  • 🐺 狼 Wolf (W)
  • 🐑 羊 Sheep (S)
  • 🥬 白菜 Cabbage (C)

農夫需要把狼, 羊, 白菜帶到對岸, 但是:

  1. 一次只能帶一樣物品過河 (農夫自己一定要在船上).
  2. 狼和羊留在一起 (農夫不在) 會出事: 🐺🐑.
  3. 羊和白菜留在一起 (農夫不在) 會出事: 🐑🥬.

目標: 要想辦法從初始狀態(都在左岸)成功移動到都在右岸.


🧮 角色狀態與邏輯

在此版本, 我們使用 JavaScript 物件陣列紀錄每個角色的位置 (左岸/船上/右岸): { id: 'wolf', name: '🐺', position: 'left' } 並且通過篩選這些角色來檢查是否存在不安全的情況, 例如「狼, 羊都在同一岸卻沒有農夫」等.

為方便管理, 我們定義 characters 陣列來保存角色, 並用布林值 boatOnLeft 代表船當前在哪一岸 (true: 左岸; false: 右岸). 每次點擊角色可讓該角色上/下船; 若按下「過河」按鈕, 則檢查農夫是否在船上, 再一起移動到對岸.


🚦 判斷安全狀態

當船開往另一岸後, 我們檢查留下的那一岸 (以及對岸) 是否同時擁有「狼與羊」且無農夫, 或者同時擁有「羊與白菜」且無農夫. 若出現這種情況就表示失敗.


🔁 實作與程式碼

以下程式碼即為一個完整可執行的範例:

<!DOCTYPE html>
<html lang="zh-Hant">
<head>
  <meta charset="UTF-8" />
  <title>農夫過河問題</title>
  <style>
    /* (省略部分 CSS, 與上文相同, 這裡可自行調整版面) */
  </style>
</head>
<body>
  <h1>🚣‍♂️ 農夫過河遊戲 🚣‍♂️</h1>

  <p>點擊角色把它放上船 (或從船上移除), 再按「過河」移動到對岸. 若船離開某岸時, 該岸同時存在「狼 & 羊」或「羊 & 白菜」且沒有農夫, 就會失敗.</p>

  <div class="river-container">
    <div id="leftBank" class="bank"></div>
    <div id="boat" class="boat"></div>
    <div id="rightBank" class="bank"></div>
  </div>

  <div class="button-row">
    <button id="moveBtn">⬅️ / ➡️ 過河</button>
    <button id="resetBtn">🔄 重新開始</button>
  </div>

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

  <script>
  // 角色資料
  let characters = [
    { id: 'farmer',  name: '👨‍🌾', position: 'left' },
    { id: 'wolf',    name: '🐺',   position: 'left' },
    { id: 'sheep',   name: '🐑',   position: 'left' },
    { id: 'cabbage', name: '🥬',   position: 'left' },
  ];
  // 船是否在左岸
  let boatOnLeft = true;

  // DOM 物件
  const leftBankDiv  = document.getElementById('leftBank');
  const rightBankDiv = document.getElementById('rightBank');
  const boatDiv      = document.getElementById('boat');
  const messageDiv   = document.getElementById('message');
  const moveBtn      = document.getElementById('moveBtn');
  const resetBtn     = document.getElementById('resetBtn');

  // 初始化渲染
  render();

  // 綁定事件
  moveBtn.addEventListener('click', moveBoat);
  resetBtn.addEventListener('click', resetGame);

  function render() {
    leftBankDiv.innerHTML  = '';
    rightBankDiv.innerHTML = '';
    boatDiv.innerHTML      = '';
    messageDiv.textContent = '';

    characters.forEach(ch => {
      const span = document.createElement('span');
      span.className = 'character';
      span.textContent = ch.name;
      span.addEventListener('click', () => handleClickCharacter(ch.id));

      if (ch.position === 'left') {
        leftBankDiv.appendChild(span);
      } else if (ch.position === 'boat') {
        boatDiv.appendChild(span);
      } else {
        rightBankDiv.appendChild(span);
      }
    });
  }

  function handleClickCharacter(charId) {
    const c = characters.find(ch => ch.id === charId);
    const farmerOnBoat = characters.find(ch => ch.id === 'farmer').position === 'boat';

    if (c.id === 'farmer') {
      // 農夫自己上下船
      if (c.position === 'boat') {
        c.position = boatOnLeft ? 'left' : 'right';
      } else {
        // 檢查船上是否已經有 2 人 (農夫+1角色)
        const boatCount = characters.filter(ch => ch.position === 'boat').length;
        if (boatCount < 2) {
          c.position = 'boat';
        } else {
          showMessage('船上已滿, 農夫無法上船!');
        }
      }
    } else {
      // 其他角色, 必須農夫已在船上才能帶它
      if (!farmerOnBoat) {
        showMessage('請先讓農夫上船, 再帶其他角色!');
        return;
      }
      if (c.position === 'boat') {
        c.position = boatOnLeft ? 'left' : 'right';
      } else {
        const sameSide = (boatOnLeft && c.position === 'left')
                      || (!boatOnLeft && c.position === 'right');
        if (!sameSide) {
          showMessage('角色與農夫不在同一側, 無法上船!');
          return;
        }
        const boatCount = characters.filter(ch => ch.position === 'boat').length;
        if (boatCount < 2) {
          c.position = 'boat';
        } else {
          showMessage('船上已滿(農夫+1角色), 無法再上船!');
        }
      }
    }
    render();
  }

  function moveBoat() {
    const farmer = characters.find(ch => ch.id === 'farmer');
    if (farmer.position !== 'boat') {
      showMessage('農夫不在船上, 無法開船!');
      return;
    }

    // 切換船的位置
    boatOnLeft = !boatOnLeft;

    // 船上所有角色一起換岸
    characters.forEach(ch => {
      if (ch.position === 'boat') {
        ch.position = boatOnLeft ? 'left' : 'right';
      }
    });

    // 檢查安全
    if (!checkSafe()) {
      showMessage('💥 失敗! 狼吃羊或羊吃白菜了! 請重新開始.');
      moveBtn.disabled = true; 
      return;
    }

    // 檢查是否全部都在右岸 (通關)
    if (checkWin()) {
      showMessage('🎉 恭喜通關! 你成功帶大家過河了!', true);
      moveBtn.disabled = true;
      return;
    }
    render();
  }

  function checkSafe() {
    return isBankSafe('left') && isBankSafe('right');
  }
  function isBankSafe(side) {
    const sideChars = characters.filter(ch => ch.position === side).map(ch => ch.id);
    if (sideChars.includes('wolf') && sideChars.includes('sheep') && !sideChars.includes('farmer')) {
      return false;
    }
    if (sideChars.includes('sheep') && sideChars.includes('cabbage') && !sideChars.includes('farmer')) {
      return false;
    }
    return true;
  }
  function checkWin() {
    return characters.every(ch => ch.position === 'right');
  }
  function showMessage(msg, isSuccess = false) {
    messageDiv.textContent = msg;
    if (isSuccess) {
      messageDiv.classList.add('success');
    } else {
      messageDiv.classList.remove('success');
    }
  }
  function resetGame() {
    characters = [
      { id: 'farmer',  name: '👨‍🌾', position: 'left' },
      { id: 'wolf',    name: '🐺',   position: 'left' },
      { id: 'sheep',   name: '🐑',   position: 'left' },
      { id: 'cabbage', name: '🥬',   position: 'left' },
    ];
    boatOnLeft = true;
    moveBtn.disabled = false;
    showMessage('');
    render();
  }
  </script>

</body>
</html>

💡 運作原理簡述

  1. 利用 characters 陣列紀錄「角色id, 表情符號, 所在位置(left / boat / right)」.
  2. 點擊角色圖示時, 檢查能否「上下船」, 避免「船上人數超限(農夫+1)」.
  3. 按下「過河」按鈕時, 檢查農夫是否在船上, 若是, 連同船上角色一起換岸; 若否, 提示錯誤.
  4. 開船後, 檢查是否有「狼 & 羊但無農夫」或「羊 & 白菜但無農夫」的情形產生, 如果有則失敗.
  5. 最後, 若全部角色皆在右岸, 即視為通關.

🎉 結語

以上為「農夫過河問題」以 HTML + JavaScript 實作的簡單教學示範. 透過基本的角色狀態管理, 邏輯判斷與網頁互動, 就能體驗經典的農夫過河遊戲.

希望這篇教學能幫助初學前端或對此問題有興趣的朋友, 了解如何在瀏覽器中完成一個小型互動式專案.
祝大家學習順利, 一起加油!