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 = ` ${index + 1} ${score.player_name} ${score.score} `; leaderboardBody.innerHTML += row; }); } catch (error) { console.error('Error updating leaderboard:', error); } } // Keyboard controls function move(e) { if (e.key === 'ArrowRight' || e.key === 'd') { player.dx = 1; } else if (e.key === 'ArrowLeft' || e.key === 'a') { player.dx = -1; } } function stopMove(e) { if (['ArrowRight', 'd', 'ArrowLeft', 'a'].includes(e.key)) { player.dx = 0; } } function restartGame() { score = 0; lives = 3; oranges = []; gameOver = false; gameOverSoundPlayed = false; player.x = canvas.width / 2 - player.width / 2; const nameModal = bootstrap.Modal.getInstance(document.getElementById('nameModal')); if (nameModal) { nameModal.hide(); } update(0); } function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } document.addEventListener('keydown', move); document.addEventListener('keyup', stopMove); const nameForm = document.getElementById('nameForm'); nameForm.addEventListener('submit', async (e) => { e.preventDefault(); const playerName = document.getElementById('playerName').value; const csrftoken = getCookie('csrftoken'); try { await fetch('/api/scores/create/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrftoken, }, body: JSON.stringify({ player_name: playerName, score: score }), }); await updateLeaderboard(); } catch (error) { console.error('Error saving score:', error); } restartGame(); }); // Start creating oranges periodically setInterval(createOrange, 1200); // Initial call to start the game loop and leaderboard update(0); updateLeaderboard(); });