document.addEventListener('DOMContentLoaded', () => { const canvas = document.getElementById('gameCanvas'); if (!canvas) { console.error('Canvas element not found!'); return; } const ctx = canvas.getContext('2d'); // Use fixed canvas dimensions from HTML canvas.width = 800; canvas.height = 600; const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); let score = 0; let lives = 3; let gameOver = false; let gameOverSoundPlayed = false; // Player (catcher) const player = { width: 100, height: 50, x: canvas.width / 2 - 50, y: canvas.height - 60, speed: 500, dx: 0, // A simple representation of a character (e.g., a basket) draw() { // Basket body ctx.fillStyle = '#8B4513'; // SaddleBrown ctx.beginPath(); ctx.moveTo(this.x, this.y); ctx.lineTo(this.x + this.width, this.y); ctx.lineTo(this.x + this.width - 15, this.y + this.height); ctx.lineTo(this.x + 15, this.y + this.height); ctx.closePath(); ctx.fill(); // Basket rim ctx.strokeStyle = '#5C2F0E'; // Darker brown ctx.lineWidth = 8; ctx.beginPath(); ctx.moveTo(this.x, this.y); ctx.lineTo(this.x + this.width, this.y); ctx.stroke(); } }; // Orange const orangeProps = { size: 20, speed: 150, }; let oranges = []; function createOrange() { oranges.push({ x: Math.random() * (canvas.width - orangeProps.size), y: -orangeProps.size, // Start above the canvas size: orangeProps.size, speed: orangeProps.speed + Math.random() * 100, draw() { // Orange body ctx.beginPath(); ctx.arc(this.x + this.size / 2, this.y + this.size / 2, this.size, 0, Math.PI * 2); ctx.fillStyle = '#FFA500'; // Orange color ctx.fill(); // Leaf ctx.beginPath(); ctx.ellipse(this.x + this.size / 2 + 5, this.y, 8, 3, Math.PI / 4, 0, Math.PI * 2); ctx.fillStyle = '#228B22'; // ForestGreen ctx.fill(); } }); } let lastTime = 0; // Game loop function update(timestamp) { if (gameOver) { drawGameOver(); return; } const deltaTime = (timestamp - lastTime) / 1000; // Delta time in seconds lastTime = timestamp; // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw background elements (like the tree trunk/branches if desired) drawScene(); // Draw and move player player.draw(); player.x += player.dx * player.speed * deltaTime; // Wall detection for player if (player.x < 0) player.x = 0; if (player.x + player.width > canvas.width) { player.x = canvas.width - player.width; } // Handle oranges oranges.forEach((orange, index) => { orange.y += orange.speed * deltaTime; orange.draw(); // Collision with player if ( orange.y + orange.size > player.y && orange.x < player.x + player.width && orange.x + orange.size > player.x ) { score++; playCatchSound(); oranges.splice(index, 1); // Remove caught orange } // Orange missed (hit the ground) if (orange.y > canvas.height) { lives--; oranges.splice(index, 1); if (lives <= 0) { gameOver = true; } } }); // Draw score and lives drawUI(); requestAnimationFrame(update); } function drawScene() { // Simple ground ctx.fillStyle = '#228B22'; // ForestGreen ctx.fillRect(0, canvas.height - 20, canvas.width, 20); } function drawUI() { ctx.fillStyle = '#E6F1FF'; ctx.font = '24px Poppins, sans-serif'; ctx.fillText(`Score: ${score}`, 20, 40); ctx.fillText(`Lives: ${lives}`, canvas.width - 120, 40); } function drawGameOver() { if (!gameOverSoundPlayed) { playGameOverSound(); gameOverSoundPlayed = true; document.getElementById('finalScore').textContent = score; const nameModal = new bootstrap.Modal(document.getElementById('nameModal')); nameModal.show(); } ctx.fillStyle = 'rgba(10, 25, 47, 0.8)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#E6F1FF'; ctx.font = '50px Poppins, sans-serif'; ctx.textAlign = 'center'; ctx.fillText('GAME OVER', canvas.width / 2, canvas.height / 2 - 40); ctx.font = '30px Poppins, sans-serif'; ctx.fillText(`Final Score: ${score}`, canvas.width / 2, canvas.height / 2 + 20); } function playCatchSound() { if (!audioCtx) return; const oscillator = audioCtx.createOscillator(); const gainNode = audioCtx.createGain(); oscillator.connect(gainNode); gainNode.connect(audioCtx.destination); oscillator.type = 'sine'; oscillator.frequency.setValueAtTime(880, audioCtx.currentTime); // A5 note gainNode.gain.setValueAtTime(0.1, audioCtx.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.00001, audioCtx.currentTime + 0.5); oscillator.start(audioCtx.currentTime); oscillator.stop(audioCtx.currentTime + 0.5); } function playGameOverSound() { if (!audioCtx) return; const oscillator = audioCtx.createOscillator(); const gainNode = audioCtx.createGain(); oscillator.connect(gainNode); gainNode.connect(audioCtx.destination); oscillator.type = 'triangle'; gainNode.gain.setValueAtTime(0.1, audioCtx.currentTime); const frequencies = [440, 220, 110]; // A4, A3, A2 frequencies.forEach((freq, i) => { oscillator.frequency.setValueAtTime(freq, audioCtx.currentTime + i * 0.3); }); gainNode.gain.exponentialRampToValueAtTime(0.00001, audioCtx.currentTime + 1); oscillator.start(audioCtx.currentTime); oscillator.stop(audioCtx.currentTime + 1); } async function updateLeaderboard() { try { const response = await fetch('/api/scores/'); const scores = await response.json(); const leaderboardBody = document.getElementById('leaderboard-body'); leaderboardBody.innerHTML = ''; scores.forEach((score, index) => { const row = `