|
| 1 | +const holes = document.querySelectorAll(".hole"); |
| 2 | +const scoreDisplay = document.getElementById("score"); |
| 3 | +const timeLeftDisplay = document.getElementById("time-left"); |
| 4 | +const difficultySelect = document.getElementById("difficulty"); |
| 5 | +const startBtn = document.getElementById("start-btn"); |
| 6 | + |
| 7 | +let score = 0; |
| 8 | +let gameActive = false; |
| 9 | +let moleTimer; // controls despawn/next-spawn timing |
| 10 | +let endTimer; // controls game end |
| 11 | +let tickTimer; // countdown display timer |
| 12 | +let currentMole = null; |
| 13 | + |
| 14 | +const LEVELS = { |
| 15 | + beginner: { |
| 16 | + moleVisibleMs: 3000, |
| 17 | + respawnDelayMs: 1200, |
| 18 | + totalTimeSec: 30, |
| 19 | + animationSec: 1.8, |
| 20 | + }, |
| 21 | + easy: { |
| 22 | + moleVisibleMs: 2200, |
| 23 | + respawnDelayMs: 900, |
| 24 | + totalTimeSec: 30, |
| 25 | + animationSec: 1.3, |
| 26 | + }, |
| 27 | + medium: { |
| 28 | + moleVisibleMs: 1500, |
| 29 | + respawnDelayMs: 500, |
| 30 | + totalTimeSec: 30, |
| 31 | + animationSec: 1.0, |
| 32 | + }, |
| 33 | + hard: { |
| 34 | + moleVisibleMs: 1000, |
| 35 | + respawnDelayMs: 300, |
| 36 | + totalTimeSec: 30, |
| 37 | + animationSec: 0.7, |
| 38 | + }, |
| 39 | +}; |
| 40 | + |
| 41 | +// Spawn a mole in a random hole |
| 42 | +function showMole() { |
| 43 | + if (!gameActive) return; |
| 44 | + |
| 45 | + // Cleanup any existing mole |
| 46 | + if (currentMole && currentMole.isConnected) { |
| 47 | + currentMole.remove(); |
| 48 | + } |
| 49 | + |
| 50 | + const randomIndex = Math.floor(Math.random() * holes.length); |
| 51 | + const hole = holes[randomIndex]; |
| 52 | + |
| 53 | + const mole = document.createElement("div"); |
| 54 | + mole.classList.add("mole"); |
| 55 | + hole.appendChild(mole); |
| 56 | + currentMole = mole; |
| 57 | + |
| 58 | + // Tune the animation speed to the selected difficulty |
| 59 | + const { moleVisibleMs, respawnDelayMs, animationSec } = LEVELS[difficultySelect.value] || LEVELS.easy; |
| 60 | + mole.style.animationDuration = `${animationSec}s`; |
| 61 | + |
| 62 | + // Despawn after a short time and spawn the next one with a brief delay |
| 63 | + clearTimeout(moleTimer); |
| 64 | + moleTimer = setTimeout(() => { |
| 65 | + if (mole.isConnected) mole.remove(); |
| 66 | + if (gameActive) { |
| 67 | + setTimeout(() => showMole(), respawnDelayMs); |
| 68 | + } |
| 69 | + }, moleVisibleMs); |
| 70 | +} |
| 71 | + |
| 72 | +// Handle hits (click or touch/pointer) |
| 73 | +holes.forEach((hole) => { |
| 74 | + // Use pointerdown for better responsiveness across mouse/touch |
| 75 | + hole.addEventListener("pointerdown", (e) => { |
| 76 | + const target = e.target; |
| 77 | + if (target && target.classList && target.classList.contains("mole")) { |
| 78 | + // Register hit |
| 79 | + score++; |
| 80 | + scoreDisplay.textContent = score; |
| 81 | + |
| 82 | + // Remove current mole and spawn the next immediately |
| 83 | + clearTimeout(moleTimer); |
| 84 | + target.remove(); |
| 85 | + if (gameActive) { |
| 86 | + const { respawnDelayMs } = LEVELS[difficultySelect.value] || LEVELS.easy; |
| 87 | + // Add a smooth delay after whack before next mole |
| 88 | + setTimeout(() => showMole(), respawnDelayMs); |
| 89 | + } |
| 90 | + } |
| 91 | + }); |
| 92 | +}); |
| 93 | + |
| 94 | +// Start game logic |
| 95 | +startBtn.addEventListener("click", () => { |
| 96 | + if (gameActive) return; |
| 97 | + |
| 98 | + // Reset state |
| 99 | + score = 0; |
| 100 | + scoreDisplay.textContent = score; |
| 101 | + gameActive = true; |
| 102 | + startBtn.textContent = "Playing..."; |
| 103 | + const { totalTimeSec } = LEVELS[difficultySelect.value] || LEVELS.easy; |
| 104 | + timeLeftDisplay.textContent = totalTimeSec; |
| 105 | + |
| 106 | + // Kick off the loop |
| 107 | + showMole(); |
| 108 | + |
| 109 | + // Stop after 30 seconds |
| 110 | + clearTimeout(endTimer); |
| 111 | + clearInterval(tickTimer); |
| 112 | + |
| 113 | + const start = Date.now(); |
| 114 | + const totalMs = totalTimeSec * 1000; |
| 115 | + |
| 116 | + // Countdown display (per second) |
| 117 | + tickTimer = setInterval(() => { |
| 118 | + const elapsed = Date.now() - start; |
| 119 | + const remaining = Math.max(0, Math.ceil((totalMs - elapsed) / 1000)); |
| 120 | + timeLeftDisplay.textContent = remaining; |
| 121 | + }, 200); |
| 122 | + |
| 123 | + endTimer = setTimeout(() => { |
| 124 | + gameActive = false; |
| 125 | + clearTimeout(moleTimer); |
| 126 | + clearInterval(tickTimer); |
| 127 | + timeLeftDisplay.textContent = 0; |
| 128 | + if (currentMole && currentMole.isConnected) currentMole.remove(); |
| 129 | + startBtn.textContent = "Start Game"; |
| 130 | + alert(`⏱️ Time's up! Your score: ${score}`); |
| 131 | + }, totalMs); |
| 132 | +}); |
0 commit comments