✨ 農夫過河問題 (HTML + JavaScript 版本) ✨
大家好, 這裡用「前端網頁」的方式分享「農夫過河問題」的解法. 核心概念與經典的農夫過河題相同: 運用角色的「位置狀態」與「安全判斷」機制, 來找出可行的過河策略.
🌊 問題描述
問題中的角色有 4 種:
- 👨🌾 農夫 Farmer (F)
- 🐺 狼 Wolf (W)
- 🐑 羊 Sheep (S)
- 🥬 白菜 Cabbage (C)
農夫需要把狼, 羊, 白菜帶到對岸, 但是:
- 一次只能帶一樣物品過河 (農夫自己一定要在船上).
- 狼和羊留在一起 (農夫不在) 會出事: 🐺 吃 🐑.
- 羊和白菜留在一起 (農夫不在) 會出事: 🐑 吃 🥬.
目標: 要想辦法從初始狀態(都在左岸)成功移動到都在右岸.
🧮 角色狀態與邏輯
在此版本, 我們使用 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>
💡 運作原理簡述
- 利用
characters陣列紀錄「角色id, 表情符號, 所在位置(left / boat / right)」. - 點擊角色圖示時, 檢查能否「上下船」, 避免「船上人數超限(農夫+1)」.
- 按下「過河」按鈕時, 檢查農夫是否在船上, 若是, 連同船上角色一起換岸; 若否, 提示錯誤.
- 開船後, 檢查是否有「狼 & 羊但無農夫」或「羊 & 白菜但無農夫」的情形產生, 如果有則失敗.
- 最後, 若全部角色皆在右岸, 即視為通關.
🎉 結語
以上為「農夫過河問題」以 HTML + JavaScript 實作的簡單教學示範. 透過基本的角色狀態管理, 邏輯判斷與網頁互動, 就能體驗經典的農夫過河遊戲.
希望這篇教學能幫助初學前端或對此問題有興趣的朋友, 了解如何在瀏覽器中完成一個小型互動式專案.
祝大家學習順利, 一起加油!