303 lines
9.5 KiB
JavaScript
303 lines
9.5 KiB
JavaScript
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 = `<tr>
|
|
<th scope="row">${index + 1}</th>
|
|
<td>${score.player_name}</td>
|
|
<td>${score.score}</td>
|
|
</tr>`;
|
|
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();
|
|
}); |