diff --git a/assets/whack-a-mole.png b/assets/whack-a-mole.png new file mode 100644 index 00000000..9383d567 Binary files /dev/null and b/assets/whack-a-mole.png differ diff --git a/en/beginner-projects/whack-a-mole.md b/en/beginner-projects/whack-a-mole.md new file mode 100644 index 00000000..f4aa0a27 --- /dev/null +++ b/en/beginner-projects/whack-a-mole.md @@ -0,0 +1,42 @@ +# 🦫 Whack-a-Mole Game + +![Whack-a-Mole Preview](../../assets/whack-a-mole.png) + +## Description + +A fun and fast-paced reflex game where players “whack” moles as they randomly pop out of holes. Each successful hit earns you points — but be quick, because the mole disappears in a second! The game runs for a limited time, making it both exciting and challenging. + +## Features + +- Start button to begin the game +- Random mole appearances in 9 different holes +- Real-time score tracking +- Automatic 30-second game duration +- Alert message showing final score when time’s up + +## Concepts Practiced + +- DOM manipulation and dynamic element creation +- Event listeners for interactive clicks +- Randomization and timing using `Math.random()` and `setTimeout()` +- Basic game loop logic +- CSS animations for mole movement + +## Bonus Challenge + +- Add **levels** (Easy, Medium, Hard) that increase mole speed +- Add a **visible countdown timer** +- Store and display **high scores** using `localStorage` +- Add **sound effects** when the mole is clicked +- Use **images or emojis** to customize the mole design + +## Live Demo + +
+ +
diff --git a/examples/Whack-A-Mole/README.md b/examples/Whack-A-Mole/README.md new file mode 100644 index 00000000..bccaaa2d --- /dev/null +++ b/examples/Whack-A-Mole/README.md @@ -0,0 +1,37 @@ +# Whack-a-Mole — Reflex Game 🦫 + +This is a fun and interactive **Whack-a-Mole** reflex game built using **HTML, CSS, and modern JavaScript (ES Modules)**. +Players must click (or “whack”) the mole as it randomly pops out of one of the holes before it disappears — testing their speed and reaction time! + +## Features + +- 🎯 **Fast-paced gameplay** — moles appear randomly across the board. +- ⏱️ **30-second timer** — challenge yourself to score as high as possible within the time limit. +- 🧠 **Live score tracking** — updates instantly with each successful hit. +- 💥 **Smooth animations** — CSS-based mole pop-up effect for a polished experience. +- ⚡ Built using **pure HTML, CSS, and JavaScript (no libraries or frameworks)**. + +## Files + +- `index.html` — main structure and layout of the game board. +- `styles.css` — visual design, layout grid, and animations. +- `index.mjs` — game logic for mole appearance, scoring, and timer handling. + +## How to Play + +1. Open `index.html` in your browser (Chrome, Edge, or Firefox recommended). +2. Click the **“Start Game”** button to begin. +3. Moles will pop up randomly in different holes — **click them as fast as you can!** +4. Each successful hit gives you **+1 point**. +5. After 30 seconds, the game ends and your **final score** is displayed. +6. Click **“Start Game”** again to play another round. + +## Notes + +- Uses **JavaScript’s `setTimeout()`** for mole timing and random placement. +- A great beginner project to understand **event handling, DOM updates, and randomization**. +- Fully responsive — works on both **desktop and mobile** devices. + +--- + +✨ **Test your reflexes and see how many moles you can whack!** diff --git a/examples/Whack-A-Mole/index.html b/examples/Whack-A-Mole/index.html new file mode 100644 index 00000000..4988d451 --- /dev/null +++ b/examples/Whack-A-Mole/index.html @@ -0,0 +1,41 @@ + + + + + + Whack-a-Mole Game 🦫 + + + +

Whack-a-Mole 🦫

+ +
+

Score: 0

+ + +

Time: 30s

+ + +
+ +
+ +
+
+
+
+
+
+
+
+
+
+ + + + diff --git a/examples/Whack-A-Mole/index.mjs b/examples/Whack-A-Mole/index.mjs new file mode 100644 index 00000000..281ce869 --- /dev/null +++ b/examples/Whack-A-Mole/index.mjs @@ -0,0 +1,163 @@ +const holes = document.querySelectorAll(".hole"); +const scoreDisplay = document.getElementById("score"); +const timeLeftDisplay = document.getElementById("time-left"); +const difficultySelect = document.getElementById("difficulty"); +const startBtn = document.getElementById("start-btn"); +const resetBtn = document.getElementById("reset-btn"); + +let score = 0; +let gameActive = false; +let moleTimer; // controls despawn/next-spawn timing +let endTimer; // controls game end +let tickTimer; // countdown display timer +let currentMole = null; + +const LEVELS = { + beginner: { + moleVisibleMs: 3000, + respawnDelayMs: 1200, + totalTimeSec: 30, + animationSec: 1.8, + }, + easy: { + moleVisibleMs: 2200, + respawnDelayMs: 900, + totalTimeSec: 30, + animationSec: 1.3, + }, + medium: { + moleVisibleMs: 1500, + respawnDelayMs: 500, + totalTimeSec: 30, + animationSec: 1.0, + }, + hard: { + moleVisibleMs: 1000, + respawnDelayMs: 300, + totalTimeSec: 30, + animationSec: 0.7, + }, +}; + +// Spawn a mole in a random hole +function showMole() { + if (!gameActive) return; + + // Cleanup any existing mole + if (currentMole && currentMole.isConnected) { + currentMole.remove(); + } + + const randomIndex = Math.floor(Math.random() * holes.length); + const hole = holes[randomIndex]; + + const mole = document.createElement("div"); + mole.classList.add("mole"); + hole.appendChild(mole); + currentMole = mole; + + // Tune the animation speed to the selected difficulty + const { moleVisibleMs, respawnDelayMs, animationSec } = LEVELS[difficultySelect.value] || LEVELS.easy; + mole.style.animationDuration = `${animationSec}s`; + + // Despawn after a short time and spawn the next one with a brief delay + clearTimeout(moleTimer); + moleTimer = setTimeout(() => { + if (mole.isConnected) mole.remove(); + if (gameActive) { + setTimeout(() => showMole(), respawnDelayMs); + } + }, moleVisibleMs); +} + +// Handle hits (click or touch/pointer) +holes.forEach((hole) => { + // Use pointerdown for better responsiveness across mouse/touch + hole.addEventListener("pointerdown", (e) => { + const target = e.target; + if (target && target.classList && target.classList.contains("mole")) { + // Register hit + score++; + scoreDisplay.textContent = score; + + // Remove current mole and spawn the next immediately + clearTimeout(moleTimer); + target.remove(); + if (gameActive) { + const { respawnDelayMs } = LEVELS[difficultySelect.value] || LEVELS.easy; + // Add a smooth delay after whack before next mole + setTimeout(() => showMole(), respawnDelayMs); + } + } + }); +}); + +// Start game logic +startBtn.addEventListener("click", () => { + if (gameActive) return; + + // Reset state + score = 0; + scoreDisplay.textContent = score; + gameActive = true; + startBtn.textContent = "Playing..."; + startBtn.disabled = true; + resetBtn.disabled = false; + difficultySelect.disabled = true; // Lock difficulty during game + + const { totalTimeSec } = LEVELS[difficultySelect.value] || LEVELS.beginner; + timeLeftDisplay.textContent = totalTimeSec; + + // Kick off the loop + showMole(); + + // Stop after specified duration + clearTimeout(endTimer); + clearInterval(tickTimer); + + const start = Date.now(); + const totalMs = totalTimeSec * 1000; + + // Countdown display (per second) + tickTimer = setInterval(() => { + const elapsed = Date.now() - start; + const remaining = Math.max(0, Math.ceil((totalMs - elapsed) / 1000)); + timeLeftDisplay.textContent = remaining; + }, 200); + + endTimer = setTimeout(() => { + gameActive = false; + clearTimeout(moleTimer); + clearInterval(tickTimer); + timeLeftDisplay.textContent = 0; + if (currentMole && currentMole.isConnected) currentMole.remove(); + startBtn.textContent = "Start Game"; + startBtn.disabled = false; + difficultySelect.disabled = false; // Re-enable difficulty selection + alert(`⏱️ Time's up! Your score: ${score}`); + }, totalMs); +}); + +// Reset game logic +resetBtn.addEventListener("click", () => { + if (!gameActive) return; + + // Stop the game + gameActive = false; + clearTimeout(moleTimer); + clearTimeout(endTimer); + clearInterval(tickTimer); + + // Clean up mole + if (currentMole && currentMole.isConnected) currentMole.remove(); + + // Reset UI + score = 0; + scoreDisplay.textContent = score; + const { totalTimeSec } = LEVELS[difficultySelect.value] || LEVELS.beginner; + timeLeftDisplay.textContent = totalTimeSec; + startBtn.textContent = "Start Game"; + startBtn.disabled = false; + resetBtn.disabled = true; + difficultySelect.disabled = false; // Allow difficulty change after reset +}); diff --git a/examples/Whack-A-Mole/styles.css b/examples/Whack-A-Mole/styles.css new file mode 100644 index 00000000..80bcfbd1 --- /dev/null +++ b/examples/Whack-A-Mole/styles.css @@ -0,0 +1,204 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: "Poppins", sans-serif; +} + +body { + background: linear-gradient(135deg, #3b1c32, #1b1a55); + color: #fff; + text-align: center; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 20px; +} + +h1 { + font-size: clamp(1.8rem, 5vw, 2.5rem); + margin-bottom: 20px; +} + +.scoreboard { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 0.8rem; + margin-bottom: 25px; + max-width: 100%; +} + +.scoreboard p, +.scoreboard label { + font-size: clamp(0.9rem, 2vw, 1rem); + margin: 0; +} + +#difficulty { + padding: 6px 10px; + border-radius: 5px; + border: 1px solid #fdbb2d; + background: #1b1a55; + color: #fff; + font-weight: bold; + outline: none; + font-size: clamp(0.85rem, 2vw, 1rem); + min-width: 100px; +} + +#difficulty option { + background: #1b1a55; + color: #fff; + font-weight: bold; +} + +#start-btn, +#reset-btn { + padding: 8px 16px; + background: #fdbb2d; + border: none; + border-radius: 5px; + font-weight: bold; + cursor: pointer; + transition: transform 0.2s, background 0.3s; + font-size: clamp(0.85rem, 2vw, 1rem); + white-space: nowrap; +} + +#start-btn:hover:not(:disabled), +#reset-btn:hover:not(:disabled) { + transform: scale(1.05); + background: #ffc94d; +} + +#start-btn:disabled, +#reset-btn:disabled { + background: #555; + cursor: not-allowed; + opacity: 0.6; +} + +#reset-btn { + background: #e74c3c; +} + +#reset-btn:hover:not(:disabled) { + background: #c0392b; +} + +.grid { + display: grid; + grid-template-columns: repeat(3, minmax(80px, 120px)); + grid-gap: clamp(10px, 2vw, 15px); + justify-content: center; + max-width: 100%; + padding: 0 10px; +} + +.hole { + width: 100%; + aspect-ratio: 1; + max-width: 120px; + background: #333; + border-radius: 50%; + position: relative; + overflow: hidden; + box-shadow: inset 0 5px 10px rgba(0, 0, 0, 0.5); +} + +.mole { + width: 70%; + height: 70%; + background: url("https://cdn-icons-png.flaticon.com/512/616/616408.png") + no-repeat center/contain; + position: absolute; + bottom: -70%; + left: 50%; + transform: translateX(-50%); + animation: pop 0.9s ease-out forwards; + cursor: pointer; + touch-action: manipulation; +} + +@keyframes pop { + 0% { + bottom: -70%; + } + 50% { + bottom: 10%; + } + 100% { + bottom: -70%; + } +} + +/* Mobile responsiveness */ +@media (max-width: 768px) { + h1 { + font-size: 1.8rem; + margin-bottom: 15px; + } + + .scoreboard { + gap: 0.6rem; + margin-bottom: 20px; + } + + .grid { + grid-template-columns: repeat(3, minmax(70px, 100px)); + grid-gap: 10px; + } + + #start-btn, + #reset-btn { + padding: 8px 12px; + font-size: 0.9rem; + } +} + +@media (max-width: 480px) { + body { + padding: 10px; + } + + h1 { + font-size: 1.5rem; + margin-bottom: 10px; + } + + .scoreboard { + gap: 0.5rem; + margin-bottom: 15px; + } + + .grid { + grid-template-columns: repeat(3, minmax(60px, 80px)); + grid-gap: 8px; + } + + #start-btn, + #reset-btn { + padding: 6px 10px; + font-size: 0.85rem; + } + + #difficulty { + font-size: 0.85rem; + padding: 5px 8px; + } +} + +@media (max-width: 360px) { + .grid { + grid-template-columns: repeat(3, 55px); + grid-gap: 6px; + } + + h1 { + font-size: 1.3rem; + } +}