<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pokémon Catcher Deluxe+</title>
<link rel="stylesheet" href="style.css">
<link rel="icon" href="https://img.icons8.com/color/48/000000/pokeball-2.png" type="image/png">
</head>
<body>
<h1>Pokémon Catcher Deluxe+</h1>
<div class="controls-container">
<div class="difficulty-select">
<label>Difficulty:</label>
<input type="radio" id="diff-easy" name="difficulty" value="easy" checked>
<label for="diff-easy">Easy</label>
<input type="radio" id="diff-medium" name="difficulty" value="medium">
<label for="diff-medium">Medium</label>
<input type="radio" id="diff-hard" name="difficulty" value="hard">
<label for="diff-hard">Hard</label>
</div>
<!-- In-Game Buttons (initially hidden) -->
<div id="ingame-controls" class="hidden">
<button id="ingame-restart-button" class="control-button">Restart</button>
<button id="ingame-end-button" class="control-button">End Game</button>
</div>
<button id="mute-button" title="Toggle Sound">🎵</button>
</div>
<div class="game-info-container">
<div class="game-info">
<p>Score: <span id="score">0</span></p>
<p>Time Left: <span id="time-left">30</span>s</p>
</div>
<div class="high-score-info">
<p>High Score: <span id="high-score">0</span></p>
</div>
</div>
<div id="game-area">
<!-- Pokémon will appear here -->
<div id="start-message">
<p>Select difficulty and click Start!</p>
<button id="start-button">Start Game</button>
</div>
<div id="game-over-message" class="hidden">
<h2>Game Over!</h2>
<p>Your final score: <span id="final-score">0</span></p>
<p id="new-high-score-msg" class="hidden">🏆 New High Score! 🏆</p>
<button id="restart-button">Play Again?</button>
</div>
</div>
<p class="instructions">Click Pokémon before they flee! Shinies are worth more!</p>
<!-- Audio Files -->
<audio id="catch-sound" src="https://www.myinstants.com/media/sounds/pokemon-catch.mp3" preload="auto"></audio>
<audio id="flee-sound" src="https://www.myinstants.com/media/sounds/pokemon-flee.mp3" preload="auto"></audio>
<audio id="shiny-catch-sound" src="https://www.myinstants.com/media/sounds/itemfinder-sound-effect.mp3" preload="auto"></audio>
<audio id="bg-music" src="https://vgmsite.com/soundtracks/pokemon-gameboy-sound-collection/vvhl DISC 1 TRACK 1/101-opening.mp3" loop preload="auto"></audio>
<audio id="game-over-sound" src="https://www.myinstants.com/media/sounds/pokemon-center-recovery-sound-effect-hd.mp3" preload="auto"></audio>
<!-- NEW AUDIO -->
<audio id="win-sound" src="https://www.myinstants.com/media/sounds/pokemon_victory_sound_effect.mp3" preload="auto"></audio> <!-- For New High Score -->
<audio id="restart-sound" src="https://www.myinstants.com/media/sounds/sound-971_hifi.mp3" preload="auto"></audio> <!-- Simple UI sound -->
<audio id="button-click-sound" src="https://www.myinstants.com/media/sounds/sound-971_hifi.mp3" preload="auto"></audio> <!-- Same as restart for general clicks -->
<script src="script.js"></script>
</body>
</html>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&display=swap');
:root {
--font-pixel: 'VT323', monospace;
--font-title: 'Press Start 2P', cursive;
--color-bg: #e0f2f7; /* Lighter blue sky */
--color-grass: #a5d6a7;
--color-grass-dark: #81c784;
--color-text: #004d40; /* Dark Teal */
--color-red: #e53935;
--color-blue: #1e88e5;
--color-yellow: #fdd835;
--color-brown: #795548;
--color-brown-light: #a1887f;
--color-frame: #6d4c41; /* Darker brown for frame */
--color-shiny: gold;
}
body {
font-family: var(--font-pixel);
display: flex;
flex-direction: column;
align-items: center;
background: linear-gradient(to bottom, var(--color-bg) 70%, var(--color-grass) 95%);
min-height: 100vh;
color: var(--color-text);
margin: 0;
padding: 20px 15px;
user-select: none;
}
h1 {
font-family: var(--font-title);
color: var(--color-red);
text-shadow: 2px 2px var(--color-yellow);
margin-bottom: 15px;
font-size: clamp(1.5em, 4vw, 2.2em); /* Responsive font size */
text-align: center;
}
.controls-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 90%;
max-width: 600px;
margin-bottom: 15px;
background-color: rgba(255, 255, 255, 0.6);
padding: 8px 15px;
border-radius: 8px;
border: 2px solid var(--color-brown-light);
font-size: 1.3em;
}
.difficulty-select label {
margin: 0 5px 0 10px;
}
.difficulty-select input[type="radio"] {
margin-right: 3px;
transform: scale(1.2); /* Make radio buttons slightly bigger */
cursor: pointer;
}
#mute-button {
font-size: 1.5em;
background: none;
border: none;
cursor: pointer;
padding: 0 5px;
transition: transform 0.2s ease;
}
#mute-button:hover {
transform: scale(1.2);
}
#mute-button.muted {
filter: grayscale(100%);
opacity: 0.7;
}
.game-info-container {
display: flex;
justify-content: space-between;
width: 90%;
max-width: 600px;
margin-bottom: 15px;
font-size: 1.6em; /* Slightly smaller */
gap: 15px;
}
.game-info, .high-score-info {
background-color: rgba(255, 255, 255, 0.8);
padding: 10px 15px;
border-radius: 8px;
border: 3px solid var(--color-brown-light);
text-align: center;
flex-grow: 1; /* Make them share space */
}
.game-info span, .high-score-info span {
font-weight: bold;
color: var(--color-blue);
display: inline-block; /* Prevent wrapping */
min-width: 30px; /* Ensure space */
text-align: right;
}
.high-score-info span {
color: var(--color-red);
}
#game-area {
width: 90%;
max-width: 600px;
height: 450px; /* Fixed height is often better for games */
border: 10px ridge var(--color-frame); /* More distinct frame */
background-color: var(--color-grass); /* Base color */
/* Subtle grass texture */
background-image:
linear-gradient(90deg, rgba(0,0,0,0.03) 50%, transparent 50%),
linear-gradient(rgba(0,0,0,0.03) 50%, transparent 50%);
background-size: 15px 15px;
position: relative;
overflow: hidden;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25);
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
#start-message, #game-over-message {
background-color: rgba(255, 250, 230, 0.95); /* Creamier */
padding: clamp(20px, 5vw, 40px); /* Responsive padding */
border-radius: 15px;
border: 5px double var(--color-brown); /* Double border */
font-size: clamp(1.2em, 3vw, 1.6em);
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
z-index: 10; /* Ensure message is above potential Pokémon */
}
#game-over-message h2 {
color: var(--color-red);
margin-top: 0;
font-family: var(--font-title);
font-size: 1.2em;
}
#new-high-score-msg {
color: var(--color-yellow);
text-shadow: 1px 1px var(--color-red);
font-weight: bold;
margin-top: 10px;
}
#game-area button {
font-family: var(--font-title);
padding: 12px 25px;
font-size: clamp(0.8em, 2.5vw, 1em);
cursor: pointer;
border-width: 4px;
border-style: outset;
border-color: #ccc #666 #666 #ccc; /* 3D button look */
background-color: var(--color-red);
color: white;
text-shadow: 1px 1px #a00;
border-radius: 5px;
transition: all 0.1s ease;
margin-top: 15px;
}
#game-area button:hover {
filter: brightness(1.1);
}
#game-area button:active {
border-style: inset;
transform: translate(1px, 1px);
filter: brightness(0.9);
}
.pokemon-sprite {
--pokemon-size: clamp(45px, 8vw, 65px); /* Responsive size */
width: var(--pokemon-size);
height: var(--pokemon-size);
position: absolute;
cursor: url('https://img.icons8.com/color/32/000000/pokeball-2.png'), pointer;
transition: transform 0.2s ease-out, opacity 0.3s ease-in-out;
animation: hop 0.7s infinite ease-in-out alternate;
image-rendering: pixelated;
z-index: 5; /* Above background, below messages */
will-change: transform, opacity; /* Performance hint */
}
@keyframes hop {
from { transform: translateY(0); }
to { transform: translateY(-6px); }
}
.pokemon-sprite:hover {
transform: scale(1.15) translateY(-3px); /* Adjust hop */
filter: brightness(1.1);
}
/* --- NEW ANIMATIONS --- */
.pokemon-caught {
animation: catchAnim 0.4s ease-out forwards;
pointer-events: none; /* Prevent further clicks */
}
@keyframes catchAnim {
0% { transform: scale(1.1); opacity: 1; }
50% { transform: scale(1.3) rotate(20deg); opacity: 0.8; }
100% { transform: scale(0.1) rotate(-30deg); opacity: 0; }
}
.pokemon-flee {
animation: fleeAnim 0.5s ease-in forwards;
pointer-events: none;
}
@keyframes fleeAnim {
0% { transform: translateY(0); opacity: 1; }
100% { transform: translateY(40px) scale(0.8); opacity: 0; }
}
/* --- SHINY STYLING --- */
.pokemon-sprite.shiny {
/* Simple filter effect for shiny - might not look perfect for all sprites */
filter: drop-shadow(0 0 5px var(--color-shiny)) brightness(1.2) saturate(1.5);
/* Alternative: Use specific shiny sprites if you have URLs */
/* background-image: url('shiny_sprite_url'); */
animation: hop 0.7s infinite ease-in-out alternate, shinySparkle 1.5s infinite linear;
}
@keyframes shinySparkle {
0%, 100% { filter: drop-shadow(0 0 5px var(--color-shiny)) brightness(1.2) saturate(1.5); }
50% { filter: drop-shadow(0 0 10px var(--color-shiny)) brightness(1.4) saturate(1.8); }
}
.hidden {
display: none !important;
}
.instructions {
margin-top: 15px;
font-size: clamp(1em, 2.5vw, 1.3em);
font-style: italic;
text-align: center;
max-width: 600px;
}
/* ... (Keep all previous CSS from the "Deluxe" version) ... */
/* --- Add styles for new In-Game Controls --- */
.controls-container {
/* Adjust layout slightly if needed */
flex-wrap: wrap; /* Allow wrapping if space is tight */
justify-content: space-around;
gap: 10px;
}
#ingame-controls {
display: flex; /* Use flex for the inner buttons */
gap: 10px; /* Space between restart and end buttons */
}
.control-button {
padding: 6px 12px;
font-size: 0.9em; /* Slightly smaller */
font-family: var(--font-pixel); /* Use pixel font */
cursor: pointer;
border-width: 3px;
border-style: outset;
border-color: #ccc #666 #666 #ccc;
color: white;
border-radius: 4px;
transition: all 0.1s ease;
}
#ingame-restart-button {
background-color: var(--color-blue); /* Blue for restart */
text-shadow: 1px 1px #004080;
}
#ingame-restart-button:hover { filter: brightness(1.1); }
#ingame-restart-button:active { border-style: inset; transform: translate(1px, 1px); filter: brightness(0.9); }
#ingame-end-button {
background-color: var(--color-red); /* Red for end */
text-shadow: 1px 1px #a00;
}
#ingame-end-button:hover { filter: brightness(1.1); }
#ingame-end-button:active { border-style: inset; transform: translate(1px, 1px); filter: brightness(0.9); }
#new-high-score-msg {
color: var(--color-yellow);
text-shadow: 1px 1px var(--color-red);
font-weight: bold;
margin-top: 10px;
font-size: 1.2em; /* Make it bigger */
animation: pulseHighScore 1s infinite ease-in-out;
}
@keyframes pulseHighScore {
0%, 100% { transform: scale(1); opacity: 1;}
50% { transform: scale(1.1); opacity: 0.8; }
}
/* Ensure messages still have high z-index */
#start-message, #game-over-message {
z-index: 10;
}
.pokemon-sprite {
z-index: 5;
}
document.addEventListener('DOMContentLoaded', () => {
// DOM Elements
const scoreElement = document.getElementById('score');
const timeLeftElement = document.getElementById('time-left');
const gameArea = document.getElementById('game-area');
const startButton = document.getElementById('start-button');
const restartButton = document.getElementById('restart-button'); // Play Again button
const startMessage = document.getElementById('start-message');
const gameOverMessage = document.getElementById('game-over-message');
const finalScoreElement = document.getElementById('final-score');
const highScoreElement = document.getElementById('high-score');
const newHighScoreMsg = document.getElementById('new-high-score-msg');
const difficultyRadios = document.querySelectorAll('input[name="difficulty"]');
const muteButton = document.getElementById('mute-button');
// NEW In-Game Controls
const ingameControls = document.getElementById('ingame-controls');
const ingameRestartButton = document.getElementById('ingame-restart-button');
const ingameEndButton = document.getElementById('ingame-end-button');
// Audio Elements
const catchSound = document.getElementById('catch-sound');
const fleeSound = document.getElementById('flee-sound');
const shinyCatchSound = document.getElementById('shiny-catch-sound');
const bgMusic = document.getElementById('bg-music');
const gameOverSound = document.getElementById('game-over-sound');
// NEW AUDIO
const winSound = document.getElementById('win-sound');
const restartSound = document.getElementById('restart-sound');
const buttonClickSound = document.getElementById('button-click-sound');
// Game Settings (base values, adjusted by difficulty)
const BASE_GAME_TIME = 30;
const BASE_MIN_SPAWN = 800;
const BASE_MAX_SPAWN = 2000;
const BASE_POKEMON_LIFESPAN = 2500;
const SHINY_CHANCE = 0.10;
const SHINY_MULTIPLIER = 5;
// Game State Variables
let score = 0;
let timeLeft = BASE_GAME_TIME;
let currentHighScore = 0;
let gameInterval = null;
let activePokemon = {};
let isGameRunning = false;
let isMuted = false;
let difficulty = 'easy';
const difficulties = {
easy: { spawnFactor: 1.2, lifeFactor: 1.3, time: BASE_GAME_TIME },
medium: { spawnFactor: 1.0, lifeFactor: 1.0, time: BASE_GAME_TIME },
hard: { spawnFactor: 0.7, lifeFactor: 0.7, time: BASE_GAME_TIME }
};
// --- Pokémon Data --- EXPANDED LIST
const pokemonIds = [
1, 4, 7, 10, 13, 16, 19, 21, 23, 25, 27, 29, 32, 35, 37, 39, 41, 43, 46, 48, 50,
52, 54, 56, 58, 60, 63, 66, 69, 72, 74, 77, 79, 81, 83, 84, 86, 88, 90, 92, 95, 96, 98,
100, 102, 104, 108, 109, 111, 113, 114, 115, 116, 118, 120, 122, 123, 124, 125, 126, 127, 128, 129,
131, 132, 133, 137, 138, 140, 142, 143, 147, 152, 155, 158, 161, 163, 165, 167, 170, 172, 173, 174,
175, 177, 179, 183, 185, 187, 190, 191, 193, 194, 198, 200, 201, 202, 203, 204, 206, 207, 209, 211,
213, 214, 215, 216, 218, 220, 222, 223, 225, 226, 227, 228, 231, 234, 235, 236, 238, 239, 240, 241,
246 // Up to Larvitar
];
const getPokemonSpriteUrl = (id) => `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${id}.png`;
// --- Initialization ---
function init() {
loadHighScore();
updateHighScoreDisplay();
// Main Buttons
startButton.addEventListener('click', handleStartClick);
restartButton.addEventListener('click', handleStartClick); // Play Again uses same logic
muteButton.addEventListener('click', handleMuteToggle);
// In-Game Buttons
ingameRestartButton.addEventListener('click', handleIngameRestart);
ingameEndButton.addEventListener('click', handleIngameEnd);
// Difficulty Radios
difficultyRadios.forEach(radio => {
radio.addEventListener('change', handleDifficultyChange);
});
difficulty = document.querySelector('input[name="difficulty"]:checked').value;
}
// --- Button Handlers (with sound) ---
function handleStartClick() {
playSound(buttonClickSound);
startGame();
}
function handleMuteToggle() {
// Don't play click sound for mute
toggleMute();
}
function handleDifficultyChange(e) {
playSound(buttonClickSound);
if (!isGameRunning) {
difficulty = e.target.value;
console.log("Difficulty set to:", difficulty);
} else {
e.target.checked = (e.target.value === difficulty); // Prevent change mid-game
}
}
function handleIngameRestart() {
playSound(restartSound);
console.log("Restarting game...");
// Stop current game cleanly *without* showing Game Over screen yet
isGameRunning = false; // Set flag first
clearInterval(gameInterval);
clearTimeout(activePokemon.timeoutId);
removeAllPokemonElements();
stopBgMusic(); // Stop music before restarting
activePokemon = {};
// Immediately start new game
startGame();
}
function handleIngameEnd() {
playSound(gameOverSound); // Play end sound immediately
console.log("Ending game via button...");
endGame(); // Call the normal end game function
}
// --- Game Flow ---
function startGame() {
console.log(`Starting game on ${difficulty}...`);
score = 0;
timeLeft = difficulties[difficulty].time;
isGameRunning = true;
activePokemon = {};
// Update UI
scoreElement.textContent = score;
timeLeftElement.textContent = timeLeft;
startMessage.classList.add('hidden');
gameOverMessage.classList.add('hidden');
newHighScoreMsg.classList.add('hidden');
ingameControls.classList.remove('hidden'); // SHOW in-game buttons
removeAllPokemonElements();
enableDifficultySelection(false);
// Start Timers
clearInterval(gameInterval); // Ensure no duplicates
gameInterval = setInterval(updateTimer, 1000);
scheduleSpawn();
// Music
playBgMusic();
}
function updateTimer() {
timeLeft--;
timeLeftElement.textContent = timeLeft;
if (timeLeft <= 0) {
endGame();
}
}
function endGame() {
if (!isGameRunning && !gameOverMessage.classList.contains('hidden')) return; // Prevent double endGame calls
console.log("Ending game...");
isGameRunning = false;
clearInterval(gameInterval);
clearTimeout(activePokemon.timeoutId);
removeAllPokemonElements();
activePokemon = {};
// UI Updates
finalScoreElement.textContent = score;
const isNewHighScore = checkHighScore(); // Check and update high score
gameOverMessage.classList.remove('hidden');
ingameControls.classList.add('hidden'); // HIDE in-game buttons
enableDifficultySelection(true);
// Sound
stopBgMusic();
playSound(isNewHighScore ? winSound : gameOverSound); // Play win or game over sound
}
// --- Spawning Logic ---
function scheduleSpawn() {
if (!isGameRunning) return;
clearTimeout(activePokemon.timeoutId); // Clear any pending spawn/flee
const { spawnFactor } = difficulties[difficulty];
const spawnDelay = Math.random() * (BASE_MAX_SPAWN - BASE_MIN_SPAWN) * spawnFactor + (BASE_MIN_SPAWN * spawnFactor);
// Set timeout specifically for the *next* spawn action
activePokemon.timeoutId = setTimeout(spawnPokemon, spawnDelay);
}
function spawnPokemon() {
if (!isGameRunning) return;
// Clear previous visual element if it somehow lingered (safety net)
// removeAllPokemonElements(); // Reconsidered: Might cause flicker. State management should be primary.
const pokemonId = pokemonIds[Math.floor(Math.random() * pokemonIds.length)];
const isShiny = Math.random() < SHINY_CHANCE;
const { lifeFactor } = difficulties[difficulty];
const lifespan = BASE_POKEMON_LIFESPAN * lifeFactor;
const pokemonElement = document.createElement('img');
pokemonElement.src = getPokemonSpriteUrl(pokemonId);
pokemonElement.classList.add('pokemon-sprite');
if (isShiny) {
pokemonElement.classList.add('shiny');
}
pokemonElement.alt = `Pokemon ${pokemonId}${isShiny ? ' (Shiny)' : ''}`;
pokemonElement.draggable = false;
pokemonElement.dataset.pokemonId = pokemonId;
pokemonElement.dataset.isShiny = isShiny;
// Position calculation (same as before)
const gameAreaRect = gameArea.getBoundingClientRect();
const pokemonSize = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--pokemon-size') || '60'); // Get from CSS var
const maxLeft = gameAreaRect.width - pokemonSize - 10;
const maxTop = gameAreaRect.height - pokemonSize - 10;
pokemonElement.style.left = `${Math.max(5, Math.random() * maxLeft)}px`;
pokemonElement.style.top = `${Math.max(5, Math.random() * maxTop)}px`;
pokemonElement.addEventListener('mousedown', handleCatchAttempt);
gameArea.appendChild(pokemonElement);
// Set flee timeout for this specific Pokemon
const fleeTimeoutId = setTimeout(() => fleePokemon(pokemonElement), lifespan);
// Update active Pokemon state - IMPORTANT: timeoutId now refers to FLEE
activePokemon = {
id: pokemonId,
element: pokemonElement,
timeoutId: fleeTimeoutId, // This timeout is for FLEEING
isShiny: isShiny
};
// NOTE: The next spawn is scheduled *independently* via scheduleSpawn calls.
// We do NOT schedule the next spawn from within spawnPokemon itself anymore.
}
// --- Catching & Fleeing ---
function handleCatchAttempt(event) {
// Check if the clicked element is the currently active one
if (!isGameRunning || !activePokemon.element || event.target !== activePokemon.element) {
return;
}
const caughtPokemonElement = activePokemon.element;
const wasShiny = activePokemon.isShiny;
// Prevent flee timer for this Pokémon
clearTimeout(activePokemon.timeoutId);
// --- BUG FIX ---
// Immediately clear state and schedule the NEXT spawn
const currentPokemonId = activePokemon.id; // Store before clearing
activePokemon = {}; // Clear active state
scheduleSpawn(); // <--- ADDED THIS LINE TO FIX THE BUG
// ---------------
// Score update
const points = wasShiny ? SHINY_MULTIPLIER : 1;
score += points;
scoreElement.textContent = score;
// Sound
playSound(wasShiny ? shinyCatchSound : catchSound);
// Animation & Removal
caughtPokemonElement.removeEventListener('mousedown', handleCatchAttempt);
caughtPokemonElement.classList.add('pokemon-caught');
setTimeout(() => {
if (caughtPokemonElement.parentNode === gameArea) {
gameArea.removeChild(caughtPokemonElement);
}
}, 400); // Match catchAnim duration
console.log(`Caught Pokemon ${currentPokemonId}! Score: ${score}`);
}
function fleePokemon(pokemonElement) {
// Check if the fleeing element is the one we are tracking
if (!isGameRunning || !activePokemon.element || pokemonElement !== activePokemon.element) {
if (pokemonElement && pokemonElement.parentNode === gameArea) {
gameArea.removeChild(pokemonElement); // Cleanup stray element
}
return;
}
console.log(`Pokemon ${activePokemon.id} fled!`);
const fleeingElement = activePokemon.element; // Keep reference for animation
// --- BUG FIX related ---
// Clear state and schedule the next spawn because this one is gone
activePokemon = {}; // Clear active state
scheduleSpawn(); // <--- ENSURE NEXT SPAWN IS SCHEDULED AFTER A FLEE TOO
// ---------------------
// Play sound
playSound(fleeSound);
// Animation & Removal
fleeingElement.removeEventListener('mousedown', handleCatchAttempt);
fleeingElement.classList.add('pokemon-flee');
setTimeout(() => {
if (fleeingElement.parentNode === gameArea) {
gameArea.removeChild(fleeingElement);
}
}, 500); // Match fleeAnim duration
}
// --- Helper Functions ---
function removeAllPokemonElements() {
const existingPokemon = gameArea.querySelectorAll('.pokemon-sprite');
existingPokemon.forEach(p => {
if(p.parentNode === gameArea) {
gameArea.removeChild(p);
}
});
}
function enableDifficultySelection(enable) {
difficultyRadios.forEach(radio => {
radio.disabled = !enable;
});
}
// --- High Score Logic ---
function loadHighScore() {
const savedScore = localStorage.getItem('pokemonCatcherHighScore');
currentHighScore = savedScore ? parseInt(savedScore, 10) : 0;
console.log("Loaded high score:", currentHighScore);
}
function saveHighScore() {
localStorage.setItem('pokemonCatcherHighScore', currentHighScore.toString());
console.log("Saved high score:", currentHighScore);
}
function updateHighScoreDisplay() {
highScoreElement.textContent = currentHighScore;
}
function checkHighScore() {
let isNew = false; // Track if it's a new high score
if (score > currentHighScore) {
console.log("New high score achieved!");
currentHighScore = score;
saveHighScore();
updateHighScoreDisplay();
newHighScoreMsg.classList.remove('hidden');
isNew = true;
} else {
newHighScoreMsg.classList.add('hidden');
}
return isNew; // Return status for sound selection
}
// --- Music & Sound Logic ---
function playSound(audioElement) {
if (!isMuted && audioElement) {
audioElement.currentTime = 0;
// Lower volume slightly for UI sounds if desired
if (audioElement === buttonClickSound || audioElement === restartSound) {
audioElement.volume = 0.6;
} else {
audioElement.volume = 1.0; // Reset volume for others
}
audioElement.play().catch(e => console.error("Audio play failed:", e));
}
}
function playBgMusic() {
if (!isMuted && bgMusic) {
bgMusic.volume = 0.3; // Background music should be quieter
bgMusic.currentTime = 0;
bgMusic.play().catch(e => console.error("Background music failed:", e));
}
}
function stopBgMusic() {
if (bgMusic) {
bgMusic.pause();
bgMusic.currentTime = 0;
}
}
function toggleMute() {
isMuted = !isMuted;
muteButton.textContent = isMuted ? '🔇' : '🎵';
muteButton.classList.toggle('muted', isMuted);
console.log("Muted:", isMuted);
if (isMuted) {
stopBgMusic();
// Stop other sounds maybe? Usually just affects music.
} else {
if (isGameRunning) {
playBgMusic();
}
}
}
// --- Initialization ---
init();
}); // End DOMContentLoaded
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.