Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions project/classic-games/20260718-0024-water-jug-lite.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mini Water Jug</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="min-h-screen bg-slate-950 text-slate-100">
<main class="mx-auto flex min-h-screen w-full max-w-5xl flex-col gap-5 px-4 py-6 sm:py-8">
<header class="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
<div>
<p class="text-sm font-semibold uppercase tracking-wide text-cyan-300">Classic Logic Puzzle</p>
<h1 class="text-3xl font-bold text-white sm:text-4xl">Mini Water Jug</h1>
</div>
<button id="resetBtn" class="rounded-md bg-cyan-400 px-4 py-2 text-sm font-bold text-slate-950 transition hover:bg-cyan-300">
Reset
</button>
</header>

<section class="grid gap-4 lg:grid-cols-[1fr_18rem]">
<div class="rounded-lg border border-slate-800 bg-slate-900 p-4 shadow-2xl shadow-cyan-950/20">
<div class="grid gap-4 sm:grid-cols-2">
<article class="rounded-lg border border-slate-800 bg-slate-950 p-4">
<div class="mb-3 flex items-center justify-between">
<h2 class="text-lg font-bold text-white">3 Liter Jug</h2>
<span id="smallLabel" class="text-sm font-semibold text-cyan-300">0 / 3</span>
</div>
<div class="flex h-64 items-end overflow-hidden rounded-md border border-slate-700 bg-slate-900">
<div id="smallWater" class="w-full bg-cyan-400 transition-all duration-300" style="height: 0%"></div>
</div>
</article>

<article class="rounded-lg border border-slate-800 bg-slate-950 p-4">
<div class="mb-3 flex items-center justify-between">
<h2 class="text-lg font-bold text-white">5 Liter Jug</h2>
<span id="largeLabel" class="text-sm font-semibold text-cyan-300">0 / 5</span>
</div>
<div class="flex h-64 items-end overflow-hidden rounded-md border border-slate-700 bg-slate-900">
<div id="largeWater" class="w-full bg-blue-500 transition-all duration-300" style="height: 0%"></div>
</div>
</article>
</div>

<div id="controls" class="mt-4 grid gap-2 sm:grid-cols-3"></div>
</div>

<aside class="flex flex-col gap-4">
<div class="rounded-lg border border-slate-800 bg-slate-900 p-4">
<h2 class="text-lg font-semibold text-white">Goal</h2>
<p id="message" class="mt-2 min-h-16 text-sm leading-6 text-slate-300"></p>
</div>

<div class="grid grid-cols-2 gap-2 text-center lg:grid-cols-1">
<div class="rounded-lg border border-slate-800 bg-slate-900 p-3">
<p class="text-xs uppercase tracking-wide text-slate-500">Moves</p>
<p id="moveCount" class="mt-1 text-2xl font-bold text-emerald-300">0</p>
</div>
<div class="rounded-lg border border-slate-800 bg-slate-900 p-3">
<p class="text-xs uppercase tracking-wide text-slate-500">Target</p>
<p class="mt-1 text-2xl font-bold text-cyan-300">4L</p>
</div>
</div>

<div class="rounded-lg border border-slate-800 bg-slate-900 p-4 text-sm leading-6 text-slate-300">
<p>Use fill, empty, and pour actions to leave exactly 4 liters in either jug. The classic solution uses the 3L and 5L jugs together.</p>
</div>
</aside>
</section>
</main>

<script>
const capacities = { small: 3, large: 5 };
const target = 4;
const smallWater = document.getElementById("smallWater");
const largeWater = document.getElementById("largeWater");
const smallLabel = document.getElementById("smallLabel");
const largeLabel = document.getElementById("largeLabel");
const controls = document.getElementById("controls");
const message = document.getElementById("message");
const moveCount = document.getElementById("moveCount");
const resetBtn = document.getElementById("resetBtn");

let state = { small: 0, large: 0, moves: 0, won: false };

const actions = [
["Fill 3L", () => fillJug("small")],
["Fill 5L", () => fillJug("large")],
["Empty 3L", () => emptyJug("small")],
["Empty 5L", () => emptyJug("large")],
["Pour 3L → 5L", () => pourJug("small", "large")],
["Pour 5L → 3L", () => pourJug("large", "small")],
];

function startGame() {
state = { small: 0, large: 0, moves: 0, won: false };
setMessage("Measure exactly 4 liters in either jug.");
render();
}

function fillJug(jug) {
if (state.won || state[jug] === capacities[jug]) return;
state[jug] = capacities[jug];
finishAction();
}

function emptyJug(jug) {
if (state.won || state[jug] === 0) return;
state[jug] = 0;
finishAction();
}

function pourJug(from, to) {
if (state.won || state[from] === 0 || state[to] === capacities[to]) return;
const room = capacities[to] - state[to];
const moved = Math.min(state[from], room);
state[from] -= moved;
state[to] += moved;
finishAction();
}

function finishAction() {
state.moves += 1;
checkWin();
render();
}

function checkWin() {
if (state.small === target || state.large === target) {
state.won = true;
setMessage(`Solved in ${state.moves} moves. Exactly 4 liters measured.`);
} else {
setMessage("Keep pouring. One jug needs to show exactly 4 liters.");
}
}

function setMessage(text) {
message.textContent = text;
}

function render() {
smallWater.style.height = `${(state.small / capacities.small) * 100}%`;
largeWater.style.height = `${(state.large / capacities.large) * 100}%`;
smallLabel.textContent = `${state.small} / ${capacities.small}`;
largeLabel.textContent = `${state.large} / ${capacities.large}`;
moveCount.textContent = state.moves;
controls.innerHTML = "";
actions.forEach(([label, handler]) => {
const button = document.createElement("button");
button.type = "button";
button.textContent = label;
button.className = "rounded-md border border-slate-700 bg-slate-800 px-3 py-2 text-sm font-semibold text-slate-100 transition hover:border-cyan-300 hover:bg-slate-700 disabled:cursor-not-allowed disabled:opacity-40";
button.disabled = state.won;
button.addEventListener("click", handler);
controls.appendChild(button);
});
}

resetBtn.addEventListener("click", startGame);
startGame();
</script>
</body>
</html>