├── README.md ├── index.html ├── style.css └── script.js /README.md: -------------------------------------------------------------------------------- 1 | # Whack-a-Mole 2 | 3 | A state-of-the-art Whack-a-Mole game where moles pop up from random holes and the player must click them before they disappear. The game features dynamic difficulty adjustments, sound effects, and visual particle effects when moles are hit. 4 | 5 | ## How It Works 6 | 7 | - **Game Board:** A 3x3 grid represents the mole holes. 8 | - **Gameplay:** Moles pop up at random intervals with their appearance duration shortening as the game progresses, increasing the challenge. 9 | - **Scoring & Timer:** Clicking on a mole increases your score. The game lasts 30 seconds. 10 | - **Effects:** A pop sound plays when a mole appears and a hit sound along with particle effects are triggered when you click a mole. 11 | 12 | ## Files 13 | 14 | - `index.html` - Contains the HTML structure of the game. 15 | - `style.css` - Provides modern styling and smooth animations. 16 | - `script.js` - Implements the game logic including mole spawning, score tracking, dynamic difficulty, and visual/audio feedback. 17 | 18 | ## Setup 19 | 20 | 1. Clone or download the repository. 21 | 2. Place a mole image in the appropriate location or adjust the CSS (`mole.png`) as needed. 22 | 3. Ensure the sound files (`pop.mp3` and `hit.mp3`) are located in a `sounds/` folder (or update the paths in `index.html`). 23 | 4. Open `index.html` in your browser to play the game. 24 | 25 | ## Enjoy! 26 | 27 | Click the **Start Game** button to begin. Try to hit as many moles as possible before time runs out! 28 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Whack-a-Mole 7 | 8 | 9 | 10 |
11 |
12 |

Whack-a-Mole

13 |
14 | Score: 0 15 | Time: 30s 16 |
17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | body { 8 | background: linear-gradient(135deg, #1d2b64, #f8cdda); 9 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | min-height: 100vh; 14 | color: #fff; 15 | } 16 | 17 | .game-container { 18 | text-align: center; 19 | padding: 20px; 20 | } 21 | 22 | header h1 { 23 | font-size: 3rem; 24 | margin-bottom: 10px; 25 | } 26 | 27 | .score-timer { 28 | display: flex; 29 | justify-content: center; 30 | gap: 20px; 31 | margin-bottom: 20px; 32 | font-size: 1.2rem; 33 | } 34 | 35 | #start-btn { 36 | padding: 10px 20px; 37 | font-size: 1rem; 38 | background: #ff5722; 39 | border: none; 40 | border-radius: 5px; 41 | cursor: pointer; 42 | transition: background 0.3s; 43 | } 44 | 45 | #start-btn:hover { 46 | background: #e64a19; 47 | } 48 | 49 | .board { 50 | display: grid; 51 | grid-template-columns: repeat(3, 120px); 52 | grid-gap: 20px; 53 | justify-content: center; 54 | } 55 | 56 | .hole { 57 | width: 120px; 58 | height: 120px; 59 | background: #3e2723; 60 | border-radius: 50%; 61 | position: relative; 62 | overflow: hidden; 63 | box-shadow: inset 0 0 10px #000; 64 | } 65 | 66 | .mole { 67 | width: 80px; 68 | height: 80px; 69 | background: #8b4513 url('mole.png') no-repeat center; 70 | background-size: contain; 71 | position: absolute; 72 | bottom: -100%; 73 | left: 50%; 74 | transform: translateX(-50%); 75 | transition: bottom 0.3s ease-out; 76 | cursor: pointer; 77 | } 78 | 79 | .hole.up .mole { 80 | bottom: 10px; 81 | } 82 | 83 | .particle { 84 | position: absolute; 85 | width: 5px; 86 | height: 5px; 87 | background: yellow; 88 | border-radius: 50%; 89 | pointer-events: none; 90 | animation: particle-move 0.5s ease-out forwards; 91 | } 92 | 93 | @keyframes particle-move { 94 | to { 95 | opacity: 0; 96 | transform: translateY(-30px) scale(0.5); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', () => { 2 | const holes = document.querySelectorAll('.hole'); 3 | const scoreDisplay = document.getElementById('score'); 4 | const timeDisplay = document.getElementById('time'); 5 | const startBtn = document.getElementById('start-btn'); 6 | const popSound = document.getElementById('pop-sound'); 7 | const hitSound = document.getElementById('hit-sound'); 8 | let lastHole; 9 | let timeUp = false; 10 | let score = 0; 11 | const gameTime = 30; 12 | let startTime; 13 | 14 | function randomTime(min, max) { 15 | return Math.round(Math.random() * (max - min) + min); 16 | } 17 | 18 | function randomHole(holes) { 19 | const idx = Math.floor(Math.random() * holes.length); 20 | const hole = holes[idx]; 21 | if (hole === lastHole) { 22 | return randomHole(holes); 23 | } 24 | lastHole = hole; 25 | return hole; 26 | } 27 | 28 | function peep() { 29 | if (timeUp) return; 30 | const elapsed = Date.now() - startTime; 31 | const timeLeftMs = gameTime * 1000 - elapsed; 32 | const difficultyFactor = 0.5 + (timeLeftMs / (gameTime * 1000)) * 0.5; 33 | const minTime = 600 * difficultyFactor; 34 | const maxTime = 1000 * difficultyFactor; 35 | const time = randomTime(minTime, maxTime); 36 | const hole = randomHole(holes); 37 | hole.classList.add('up'); 38 | popSound.currentTime = 0; 39 | popSound.play(); 40 | setTimeout(() => { 41 | hole.classList.remove('up'); 42 | if (!timeUp) peep(); 43 | }, time); 44 | } 45 | 46 | function startGame() { 47 | score = 0; 48 | scoreDisplay.textContent = score; 49 | timeUp = false; 50 | startBtn.disabled = true; 51 | startTime = Date.now(); 52 | timeDisplay.textContent = gameTime; 53 | peep(); 54 | const timer = setInterval(() => { 55 | const timeLeft = gameTime - Math.floor((Date.now() - startTime) / 1000); 56 | timeDisplay.textContent = timeLeft; 57 | if (timeLeft <= 0) { 58 | clearInterval(timer); 59 | timeUp = true; 60 | startBtn.disabled = false; 61 | } 62 | }, 1000); 63 | } 64 | 65 | function hitMole(e) { 66 | score++; 67 | scoreDisplay.textContent = score; 68 | this.parentElement.classList.remove('up'); 69 | hitSound.currentTime = 0; 70 | hitSound.play(); 71 | createParticles(e.clientX, e.clientY); 72 | } 73 | 74 | function createParticles(x, y) { 75 | for (let i = 0; i < 10; i++) { 76 | const particle = document.createElement('div'); 77 | particle.classList.add('particle'); 78 | particle.style.left = x + 'px'; 79 | particle.style.top = y + 'px'; 80 | document.body.appendChild(particle); 81 | particle.addEventListener('animationend', () => { 82 | particle.remove(); 83 | }); 84 | } 85 | } 86 | 87 | const moles = document.querySelectorAll('.mole'); 88 | moles.forEach(mole => mole.addEventListener('click', hitMole)); 89 | startBtn.addEventListener('click', startGame); 90 | }); 91 | --------------------------------------------------------------------------------