diff --git a/assets/pasted-20251121-165555-7485ce01.png b/assets/pasted-20251121-165555-7485ce01.png new file mode 100644 index 0000000..f1992d1 Binary files /dev/null and b/assets/pasted-20251121-165555-7485ce01.png differ diff --git a/assets/pasted-20251121-170044-1e327a41.png b/assets/pasted-20251121-170044-1e327a41.png new file mode 100644 index 0000000..d72376a Binary files /dev/null and b/assets/pasted-20251121-170044-1e327a41.png differ diff --git a/assets/pasted-20251121-170343-31e8648f.png b/assets/pasted-20251121-170343-31e8648f.png new file mode 100644 index 0000000..e0738b7 Binary files /dev/null and b/assets/pasted-20251121-170343-31e8648f.png differ diff --git a/assets/pasted-20251121-170801-c7b6de55.png b/assets/pasted-20251121-170801-c7b6de55.png new file mode 100644 index 0000000..8894dd0 Binary files /dev/null and b/assets/pasted-20251121-170801-c7b6de55.png differ diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index a3bee3f..52289eb 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/settings.py b/config/settings.py index 7253f29..29e50d0 100644 --- a/config/settings.py +++ b/config/settings.py @@ -27,14 +27,8 @@ ALLOWED_HOSTS = [ ] CSRF_TRUSTED_ORIGINS = [ - origin for origin in [ - os.getenv("HOST_FQDN", ""), - os.getenv("CSRF_TRUSTED_ORIGIN", "") - ] if origin -] -CSRF_TRUSTED_ORIGINS = [ - f"https://{host}" if not host.startswith(("http://", "https://")) else host - for host in CSRF_TRUSTED_ORIGINS + f"https://{os.getenv('HOST_FQDN', '')}", + "https://*.appwizzy.dev", ] # Cookies must always be HTTPS-only; SameSite=Lax keeps CSRF working behind the proxy. @@ -144,7 +138,8 @@ USE_TZ = True # https://docs.djangoproject.com/en/5.2/howto/static-files/ STATIC_URL = 'static/' -STATIC_ROOT = BASE_DIR / 'static' +STATICFILES_DIRS = [BASE_DIR / 'static'] +STATIC_ROOT = BASE_DIR / 'staticfiles' # Email EMAIL_BACKEND = os.getenv( diff --git a/core/templates/core/index.html b/core/templates/core/index.html index f3d112c..cea3883 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -14,23 +14,17 @@
-
-
-
- -
+
+
+

How to Play

+

Use the left and right arrow keys to move your character.

+

Catch as many oranges as you can before you run out of lives!

+
+
+
-
-
-
-

How to Play

-

Use the left and right arrow keys to move your character.

-

Catch as many oranges as you can before you run out of lives!

-
-
-
-
+
@@ -51,27 +45,4 @@
- - - {% endblock %} \ No newline at end of file diff --git a/static/css/custom.css b/static/css/custom.css index 26dbe21..c6779e3 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -109,12 +109,14 @@ body { border-radius: 10px; box-shadow: 0 0 30px rgba(255, 165, 0, 0.3); overflow: hidden; + /* The canvas itself is 800x600 */ + width: 800px; + height: 600px; + margin: 0 auto; /* Center the canvas */ } #gameCanvas { display: block; - width: 100%; - height: auto; } .instructions { @@ -122,6 +124,8 @@ body { padding: 2rem; border-radius: 10px; color: var(--text-light); + max-width: 800px; /* Match canvas width */ + margin: 2rem auto; /* Center and add space */ } .instructions-title { @@ -138,4 +142,14 @@ body { .game-subtitle { font-size: 1.2rem; } -} + + .canvas-wrapper { + width: 100%; + height: auto; + } + + #gameCanvas { + width: 100%; + height: auto; + } +} \ No newline at end of file diff --git a/static/js/game.js b/static/js/game.js index 8c71bd1..9fd816e 100644 --- a/static/js/game.js +++ b/static/js/game.js @@ -6,7 +6,7 @@ document.addEventListener('DOMContentLoaded', () => { } const ctx = canvas.getContext('2d'); - // Use fixed canvas dimensions from HTML + // Set fixed canvas size canvas.width = 800; canvas.height = 600; @@ -14,9 +14,9 @@ document.addEventListener('DOMContentLoaded', () => { let score = 0; let lives = 3; let gameOver = false; - let gameOverSoundPlayed = false; + let gameStarted = false; + let orangeInterval; - // Player (catcher) const player = { width: 100, height: 50, @@ -24,10 +24,8 @@ document.addEventListener('DOMContentLoaded', () => { 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.fillStyle = '#8B4513'; ctx.beginPath(); ctx.moveTo(this.x, this.y); ctx.lineTo(this.x + this.width, this.y); @@ -35,9 +33,7 @@ document.addEventListener('DOMContentLoaded', () => { ctx.lineTo(this.x + 15, this.y + this.height); ctx.closePath(); ctx.fill(); - - // Basket rim - ctx.strokeStyle = '#5C2F0E'; // Darker brown + ctx.strokeStyle = '#5C2F0E'; ctx.lineWidth = 8; ctx.beginPath(); ctx.moveTo(this.x, this.y); @@ -46,7 +42,6 @@ document.addEventListener('DOMContentLoaded', () => { } }; - // Orange const orangeProps = { size: 20, speed: 150, @@ -56,58 +51,49 @@ document.addEventListener('DOMContentLoaded', () => { function createOrange() { oranges.push({ x: Math.random() * (canvas.width - orangeProps.size), - y: -orangeProps.size, // Start above the canvas + y: -orangeProps.size, 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.fillStyle = '#FFA500'; 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.fillStyle = '#228B22'; ctx.fill(); } }); } let lastTime = 0; - // Game loop + function update(timestamp) { + if (!gameStarted) return; + if (gameOver) { drawGameOver(); return; } - const deltaTime = (timestamp - lastTime) / 1000; // Delta time in seconds + const deltaTime = (timestamp - lastTime) / 1000; 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 && @@ -115,10 +101,9 @@ document.addEventListener('DOMContentLoaded', () => { ) { score++; playCatchSound(); - oranges.splice(index, 1); // Remove caught orange + oranges.splice(index, 1); } - // Orange missed (hit the ground) if (orange.y > canvas.height) { lives--; oranges.splice(index, 1); @@ -127,84 +112,63 @@ document.addEventListener('DOMContentLoaded', () => { } } }); - - // Draw score and lives - drawUI(); + drawUI(); requestAnimationFrame(update); } - + function drawScene() { - // Simple ground - ctx.fillStyle = '#228B22'; // ForestGreen + ctx.fillStyle = '#0A192F'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = '#228B22'; ctx.fillRect(0, canvas.height - 20, canvas.width, 20); } function drawUI() { ctx.fillStyle = '#E6F1FF'; ctx.font = '24px Poppins, sans-serif'; + ctx.textAlign = 'left'; ctx.fillText(`Score: ${score}`, 20, 40); - ctx.fillText(`Lives: ${lives}`, canvas.width - 120, 40); + ctx.textAlign = 'right'; + ctx.fillText(`Lives: ${lives}`, canvas.width - 20, 40); + ctx.textAlign = 'center'; } - + 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)'; + clearInterval(orangeInterval); + ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; 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); + ctx.font = '20px Poppins, sans-serif'; + ctx.fillText('Click to Restart', canvas.width / 2, canvas.height / 2 + 70); + } + + function drawInitialMessage() { + ctx.fillStyle = '#E6F1FF'; + ctx.font = '30px Poppins, sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText('Click "Play Now" to Start!', canvas.width / 2, canvas.height / 2); } 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 + oscillator.frequency.setValueAtTime(880, audioCtx.currentTime); 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/'); @@ -224,8 +188,8 @@ document.addEventListener('DOMContentLoaded', () => { } } - // Keyboard controls function move(e) { + if (!gameStarted || gameOver) return; if (e.key === 'ArrowRight' || e.key === 'd') { player.dx = 1; } else if (e.key === 'ArrowLeft' || e.key === 'a') { @@ -238,66 +202,78 @@ document.addEventListener('DOMContentLoaded', () => { player.dx = 0; } } - + + function initGame() { + score = 0; + lives = 3; + oranges = []; + gameOver = false; + gameStarted = false; + player.x = canvas.width / 2 - player.width / 2; + drawScene(); + drawInitialMessage(); + } + 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); + startCountdown(); } - 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; - } + function startCountdown() { + let count = 3; + const countdown = setInterval(() => { + ctx.clearRect(0, 0, canvas.width, canvas.height); + drawScene(); + ctx.fillStyle = '#E6F1FF'; + ctx.font = '60px Poppins, sans-serif'; + ctx.textAlign = 'center'; + if (count > 0) { + ctx.fillText(count, canvas.width / 2, canvas.height / 2); + count--; + } else { + clearInterval(countdown); + ctx.fillText('Go!', canvas.width / 2, canvas.height / 2); + setTimeout(startGame, 500); } - } - return cookieValue; + }, 1000); + } + + function startGame() { + gameStarted = true; + lastTime = performance.now(); + if (orangeInterval) clearInterval(orangeInterval); + orangeInterval = setInterval(createOrange, 1200); + requestAnimationFrame(update); } + // Event Listeners document.addEventListener('keydown', move); document.addEventListener('keyup', stopMove); + + const playButton = document.querySelector('.btn-play'); + const gameArea = document.getElementById('game-area'); + + if (playButton && gameArea) { + playButton.addEventListener('click', (e) => { + e.preventDefault(); + gameArea.scrollIntoView({ behavior: 'smooth' }); + if (!gameStarted && !gameOver) { + startCountdown(); + } + }); + } - 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); + canvas.addEventListener('click', () => { + if (gameOver) { + restartGame(); } - - restartGame(); }); - // Start creating oranges periodically - setInterval(createOrange, 1200); - - // Initial call to start the game loop and leaderboard - update(0); + // Initial Setup + initGame(); updateLeaderboard(); }); \ No newline at end of file diff --git a/staticfiles/css/custom.css b/staticfiles/css/custom.css index 108056f..c6779e3 100644 --- a/staticfiles/css/custom.css +++ b/staticfiles/css/custom.css @@ -1,21 +1,155 @@ +/* +Orange Catcher - Custom Stylesheet +*/ :root { - --bg-color-start: #6a11cb; - --bg-color-end: #2575fc; - --text-color: #ffffff; - --card-bg-color: rgba(255, 255, 255, 0.01); - --card-border-color: rgba(255, 255, 255, 0.1); + --primary-orange: #FFA500; + --secondary-deep-blue: #0A192F; + --accent-yellow: #FFD700; + --text-light: #E6F1FF; + --background-dark-start: #0A192F; + --background-dark-end: #1C2D4A; + --font-headings: 'Poppins', sans-serif; + --font-body: 'Lato', sans-serif; } + +html { + scroll-behavior: smooth; +} + body { - margin: 0; - font-family: 'Inter', sans-serif; - background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end)); - color: var(--text-color); - display: flex; - justify-content: center; - align-items: center; - min-height: 100vh; - text-align: center; - overflow: hidden; - position: relative; + background-color: var(--background-dark-start); + color: var(--text-light); + font-family: var(--font-body); } + +/* Hero Section */ +.hero-section { + position: relative; + height: 80vh; + min-height: 500px; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + background: radial-gradient(circle, var(--background-dark-end), var(--background-dark-start)); + overflow: hidden; + padding: 0 15px; +} + +.hero-section .content-wrapper { + z-index: 2; +} + +.game-title { + font-family: var(--font-headings); + font-size: 4.5rem; + font-weight: 700; + color: white; + text-shadow: 0 4px 15px rgba(0,0,0,0.2); +} + +.game-subtitle { + font-size: 1.5rem; + color: var(--text-light); + opacity: 0.9; +} + +.btn-play { + background-color: var(--primary-orange); + color: var(--secondary-deep-blue); + font-family: var(--font-headings); + font-weight: 700; + font-size: 1.2rem; + padding: 12px 40px; + border-radius: 50px; + border: none; + margin-top: 20px; + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.btn-play:hover { + transform: translateY(-3px); + box-shadow: 0 10px 20px rgba(255, 165, 0, 0.2); + color: var(--secondary-deep-blue); +} + +/* Decorative Shapes */ +.hero-section .shape-1, .hero-section .shape-2 { + position: absolute; + border-radius: 50%; + background: var(--primary-orange); + opacity: 0.1; + z-index: 1; +} + +.hero-section .shape-1 { + width: 200px; + height: 200px; + top: 10%; + left: 15%; +} + +.hero-section .shape-2 { + width: 100px; + height: 100px; + bottom: 15%; + right: 20%; +} + +/* Game Container */ +.game-container { + padding-top: 4rem; + padding-bottom: 4rem; +} + +.canvas-wrapper { + background-color: #000; + border: 4px solid var(--primary-orange); + border-radius: 10px; + box-shadow: 0 0 30px rgba(255, 165, 0, 0.3); + overflow: hidden; + /* The canvas itself is 800x600 */ + width: 800px; + height: 600px; + margin: 0 auto; /* Center the canvas */ +} + +#gameCanvas { + display: block; +} + +.instructions { + background-color: var(--background-dark-end); + padding: 2rem; + border-radius: 10px; + color: var(--text-light); + max-width: 800px; /* Match canvas width */ + margin: 2rem auto; /* Center and add space */ +} + +.instructions-title { + font-family: var(--font-headings); + color: var(--primary-orange); + margin-bottom: 1rem; +} + +/* Responsive */ +@media (max-width: 768px) { + .game-title { + font-size: 3rem; + } + .game-subtitle { + font-size: 1.2rem; + } + + .canvas-wrapper { + width: 100%; + height: auto; + } + + #gameCanvas { + width: 100%; + height: auto; + } +} \ No newline at end of file diff --git a/staticfiles/js/game.js b/staticfiles/js/game.js new file mode 100644 index 0000000..9fd816e --- /dev/null +++ b/staticfiles/js/game.js @@ -0,0 +1,279 @@ +document.addEventListener('DOMContentLoaded', () => { + const canvas = document.getElementById('gameCanvas'); + if (!canvas) { + console.error('Canvas element not found!'); + return; + } + const ctx = canvas.getContext('2d'); + + // Set fixed canvas size + canvas.width = 800; + canvas.height = 600; + + const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + let score = 0; + let lives = 3; + let gameOver = false; + let gameStarted = false; + let orangeInterval; + + const player = { + width: 100, + height: 50, + x: canvas.width / 2 - 50, + y: canvas.height - 60, + speed: 500, + dx: 0, + draw() { + ctx.fillStyle = '#8B4513'; + 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(); + ctx.strokeStyle = '#5C2F0E'; + ctx.lineWidth = 8; + ctx.beginPath(); + ctx.moveTo(this.x, this.y); + ctx.lineTo(this.x + this.width, this.y); + ctx.stroke(); + } + }; + + const orangeProps = { + size: 20, + speed: 150, + }; + let oranges = []; + + function createOrange() { + oranges.push({ + x: Math.random() * (canvas.width - orangeProps.size), + y: -orangeProps.size, + size: orangeProps.size, + speed: orangeProps.speed + Math.random() * 100, + draw() { + ctx.beginPath(); + ctx.arc(this.x + this.size / 2, this.y + this.size / 2, this.size, 0, Math.PI * 2); + ctx.fillStyle = '#FFA500'; + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(this.x + this.size / 2 + 5, this.y, 8, 3, Math.PI / 4, 0, Math.PI * 2); + ctx.fillStyle = '#228B22'; + ctx.fill(); + } + }); + } + + let lastTime = 0; + + function update(timestamp) { + if (!gameStarted) return; + + if (gameOver) { + drawGameOver(); + return; + } + + const deltaTime = (timestamp - lastTime) / 1000; + lastTime = timestamp; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + drawScene(); + player.draw(); + player.x += player.dx * player.speed * deltaTime; + + if (player.x < 0) player.x = 0; + if (player.x + player.width > canvas.width) { + player.x = canvas.width - player.width; + } + + oranges.forEach((orange, index) => { + orange.y += orange.speed * deltaTime; + orange.draw(); + + 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); + } + + if (orange.y > canvas.height) { + lives--; + oranges.splice(index, 1); + if (lives <= 0) { + gameOver = true; + } + } + }); + + drawUI(); + requestAnimationFrame(update); + } + + function drawScene() { + ctx.fillStyle = '#0A192F'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = '#228B22'; + ctx.fillRect(0, canvas.height - 20, canvas.width, 20); + } + + function drawUI() { + ctx.fillStyle = '#E6F1FF'; + ctx.font = '24px Poppins, sans-serif'; + ctx.textAlign = 'left'; + ctx.fillText(`Score: ${score}`, 20, 40); + ctx.textAlign = 'right'; + ctx.fillText(`Lives: ${lives}`, canvas.width - 20, 40); + ctx.textAlign = 'center'; + } + + function drawGameOver() { + clearInterval(orangeInterval); + ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; + 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); + ctx.font = '20px Poppins, sans-serif'; + ctx.fillText('Click to Restart', canvas.width / 2, canvas.height / 2 + 70); + } + + function drawInitialMessage() { + ctx.fillStyle = '#E6F1FF'; + ctx.font = '30px Poppins, sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText('Click "Play Now" to Start!', canvas.width / 2, canvas.height / 2); + } + + 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); + 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); + } + + 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); + } + } + + function move(e) { + if (!gameStarted || gameOver) return; + 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 initGame() { + score = 0; + lives = 3; + oranges = []; + gameOver = false; + gameStarted = false; + player.x = canvas.width / 2 - player.width / 2; + drawScene(); + drawInitialMessage(); + } + + function restartGame() { + score = 0; + lives = 3; + oranges = []; + gameOver = false; + player.x = canvas.width / 2 - player.width / 2; + startCountdown(); + } + + function startCountdown() { + let count = 3; + const countdown = setInterval(() => { + ctx.clearRect(0, 0, canvas.width, canvas.height); + drawScene(); + ctx.fillStyle = '#E6F1FF'; + ctx.font = '60px Poppins, sans-serif'; + ctx.textAlign = 'center'; + if (count > 0) { + ctx.fillText(count, canvas.width / 2, canvas.height / 2); + count--; + } else { + clearInterval(countdown); + ctx.fillText('Go!', canvas.width / 2, canvas.height / 2); + setTimeout(startGame, 500); + } + }, 1000); + } + + function startGame() { + gameStarted = true; + lastTime = performance.now(); + if (orangeInterval) clearInterval(orangeInterval); + orangeInterval = setInterval(createOrange, 1200); + requestAnimationFrame(update); + } + + // Event Listeners + document.addEventListener('keydown', move); + document.addEventListener('keyup', stopMove); + + const playButton = document.querySelector('.btn-play'); + const gameArea = document.getElementById('game-area'); + + if (playButton && gameArea) { + playButton.addEventListener('click', (e) => { + e.preventDefault(); + gameArea.scrollIntoView({ behavior: 'smooth' }); + if (!gameStarted && !gameOver) { + startCountdown(); + } + }); + } + + canvas.addEventListener('click', () => { + if (gameOver) { + restartGame(); + } + }); + + // Initial Setup + initGame(); + updateLeaderboard(); +}); \ No newline at end of file