Update script.js
Browse files
script.js
CHANGED
|
@@ -5,155 +5,248 @@ const ball = document.getElementById('ball');
|
|
| 5 |
const playerScoreDisplay = document.getElementById('playerScore');
|
| 6 |
const botScoreDisplay = document.getElementById('botScore');
|
| 7 |
|
| 8 |
-
|
| 9 |
-
let paddleRightY = window.innerHeight / 2 - 40;
|
| 10 |
-
let ballX = window.innerWidth / 2;
|
| 11 |
-
let ballY = window.innerHeight / 2;
|
| 12 |
-
let ballSpeedX = 3;
|
| 13 |
-
let ballSpeedY = 0;
|
| 14 |
-
let playerScore = 0;
|
| 15 |
-
let botScore = 0;
|
| 16 |
-
let isGamePaused = true;
|
| 17 |
-
|
| 18 |
const paddleHeight = 80;
|
| 19 |
const paddleWidth = 10;
|
| 20 |
const ballSize = 15;
|
| 21 |
-
const maxAngle = 45;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
|
|
|
| 23 |
gameArea.addEventListener('touchmove', (e) => {
|
| 24 |
e.preventDefault();
|
|
|
|
|
|
|
| 25 |
let touchY = e.touches[0].clientY;
|
|
|
|
| 26 |
|
| 27 |
-
if
|
|
|
|
| 28 |
paddleLeftY = touchY - paddleHeight / 2;
|
|
|
|
| 29 |
paddleLeftY = Math.max(0, Math.min(paddleLeftY, window.innerHeight - paddleHeight));
|
|
|
|
| 30 |
|
|
|
|
| 31 |
if (isGamePaused) {
|
|
|
|
|
|
|
| 32 |
isGamePaused = false;
|
| 33 |
}
|
| 34 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
});
|
| 36 |
|
|
|
|
|
|
|
| 37 |
function handlePaddleCollision(paddleY, paddleX, isLeftPaddle) {
|
| 38 |
const ballRadius = ballSize / 2;
|
| 39 |
-
const
|
|
|
|
|
|
|
| 40 |
const paddleTop = paddleY;
|
| 41 |
const paddleBottom = paddleY + paddleHeight;
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
let closestY = ballY;
|
| 52 |
-
if (ballY < paddleTop) {
|
| 53 |
-
closestY = paddleTop;
|
| 54 |
-
} else if (ballY > paddleBottom) {
|
| 55 |
-
closestY = paddleBottom;
|
| 56 |
}
|
| 57 |
|
| 58 |
-
//
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
//
|
| 73 |
-
|
| 74 |
-
const
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
//
|
| 80 |
-
const
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
}
|
| 92 |
}
|
| 93 |
|
|
|
|
|
|
|
| 94 |
function update() {
|
| 95 |
if (!isGamePaused) {
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
ballX += ballSpeedX;
|
| 98 |
ballY += ballSpeedY;
|
| 99 |
|
| 100 |
// Ball collision with top and bottom walls
|
| 101 |
-
if (ballY <= 0
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
}
|
| 104 |
|
| 105 |
-
//
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
-
// Check collision with right paddle using swept collision detection
|
| 109 |
-
handlePaddleCollision(paddleRightY, window.innerWidth - paddleWidth - 25, false);
|
| 110 |
|
| 111 |
-
// Ball out of bounds
|
| 112 |
-
if (ballX <= 0) {
|
| 113 |
botScore++;
|
| 114 |
botScoreDisplay.textContent = botScore;
|
| 115 |
-
resetBall('right');
|
| 116 |
-
} else if (ballX >= window.innerWidth) {
|
| 117 |
playerScore++;
|
| 118 |
playerScoreDisplay.textContent = playerScore;
|
| 119 |
-
resetBall('left');
|
| 120 |
}
|
| 121 |
-
}
|
| 122 |
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
if (
|
| 126 |
-
paddleRightY +
|
| 127 |
-
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
}
|
| 130 |
}
|
| 131 |
|
| 132 |
-
|
| 133 |
-
|
| 134 |
ball.style.left = ballX + 'px';
|
| 135 |
ball.style.top = ballY + 'px';
|
| 136 |
-
|
| 137 |
-
|
|
|
|
| 138 |
|
|
|
|
| 139 |
requestAnimationFrame(update);
|
| 140 |
}
|
| 141 |
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
ballSpeedX =
|
|
|
|
|
|
|
|
|
|
| 153 |
}
|
| 154 |
|
|
|
|
| 155 |
ballSpeedY = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
}
|
| 157 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
// Start the game loop
|
| 159 |
-
|
|
|
|
| 5 |
const playerScoreDisplay = document.getElementById('playerScore');
|
| 6 |
const botScoreDisplay = document.getElementById('botScore');
|
| 7 |
|
| 8 |
+
// --- Constants ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
const paddleHeight = 80;
|
| 10 |
const paddleWidth = 10;
|
| 11 |
const ballSize = 15;
|
| 12 |
+
const maxAngle = 45; // Max bounce angle in degrees
|
| 13 |
+
const initialBallSpeedX = 4; // Starting horizontal speed
|
| 14 |
+
const speedIncreaseFactor = 1.1; // Speed multiplier on hit
|
| 15 |
+
const maxBallSpeed = 15; // Maximum overall ball speed
|
| 16 |
+
const botPaddleSpeed = 4; // Speed of the bot paddle
|
| 17 |
+
|
| 18 |
+
// --- Game State Variables ---
|
| 19 |
+
let paddleLeftY = window.innerHeight / 2 - paddleHeight / 2;
|
| 20 |
+
let paddleRightY = window.innerHeight / 2 - paddleHeight / 2;
|
| 21 |
+
let ballX = window.innerWidth / 2 - ballSize / 2;
|
| 22 |
+
let ballY = window.innerHeight / 2 - ballSize / 2;
|
| 23 |
+
let ballSpeedX = initialBallSpeedX; // Start with initial speed
|
| 24 |
+
let ballSpeedY = 0;
|
| 25 |
+
let playerScore = 0;
|
| 26 |
+
let botScore = 0;
|
| 27 |
+
let isGamePaused = true; // Start paused
|
| 28 |
|
| 29 |
+
// --- Event Listeners ---
|
| 30 |
gameArea.addEventListener('touchmove', (e) => {
|
| 31 |
e.preventDefault();
|
| 32 |
+
if (!e.touches || e.touches.length === 0) return; // Safety check
|
| 33 |
+
|
| 34 |
let touchY = e.touches[0].clientY;
|
| 35 |
+
let touchX = e.touches[0].clientX;
|
| 36 |
|
| 37 |
+
// Only control left paddle if touch is on the left half
|
| 38 |
+
if (touchX < window.innerWidth / 2) {
|
| 39 |
paddleLeftY = touchY - paddleHeight / 2;
|
| 40 |
+
// Clamp paddle position within screen bounds
|
| 41 |
paddleLeftY = Math.max(0, Math.min(paddleLeftY, window.innerHeight - paddleHeight));
|
| 42 |
+
paddleLeft.style.top = paddleLeftY + 'px';
|
| 43 |
|
| 44 |
+
// Start the game on first touch if paused
|
| 45 |
if (isGamePaused) {
|
| 46 |
+
// Determine initial direction based on which side was touched (less relevant here, but good practice)
|
| 47 |
+
// ballSpeedX = initialBallSpeedX; // Or keep the direction from resetBall
|
| 48 |
isGamePaused = false;
|
| 49 |
}
|
| 50 |
}
|
| 51 |
+
}, { passive: false }); // Use passive: false if preventDefault is needed
|
| 52 |
+
|
| 53 |
+
// Add a click/tap listener to start the game as well
|
| 54 |
+
gameArea.addEventListener('click', () => {
|
| 55 |
+
if (isGamePaused) {
|
| 56 |
+
isGamePaused = false;
|
| 57 |
+
// Optional: decide initial direction if needed
|
| 58 |
+
// if (ballX < window.innerWidth / 2) ballSpeedX = initialBallSpeedX;
|
| 59 |
+
// else ballSpeedX = -initialBallSpeedX;
|
| 60 |
+
}
|
| 61 |
});
|
| 62 |
|
| 63 |
+
|
| 64 |
+
// --- Collision Handling ---
|
| 65 |
function handlePaddleCollision(paddleY, paddleX, isLeftPaddle) {
|
| 66 |
const ballRadius = ballSize / 2;
|
| 67 |
+
const ballCenterX = ballX + ballRadius;
|
| 68 |
+
const ballCenterY = ballY + ballRadius;
|
| 69 |
+
|
| 70 |
const paddleTop = paddleY;
|
| 71 |
const paddleBottom = paddleY + paddleHeight;
|
| 72 |
+
const paddleLeftEdge = paddleX;
|
| 73 |
+
const paddleRightEdge = paddleX + paddleWidth;
|
| 74 |
+
|
| 75 |
+
// Simple AABB collision check first (optimization)
|
| 76 |
+
if (ballCenterX + ballRadius < paddleLeftEdge ||
|
| 77 |
+
ballCenterX - ballRadius > paddleRightEdge ||
|
| 78 |
+
ballCenterY + ballRadius < paddleTop ||
|
| 79 |
+
ballCenterY - ballRadius > paddleBottom) {
|
| 80 |
+
return; // No collision based on bounding boxes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
}
|
| 82 |
|
| 83 |
+
// More precise check: Find closest point on paddle to ball center
|
| 84 |
+
let closestX = Math.max(paddleLeftEdge, Math.min(ballCenterX, paddleRightEdge));
|
| 85 |
+
let closestY = Math.max(paddleTop, Math.min(ballCenterY, paddleBottom));
|
| 86 |
+
|
| 87 |
+
// Calculate distance between ball center and closest point
|
| 88 |
+
const dx = ballCenterX - closestX;
|
| 89 |
+
const dy = ballCenterY - closestY;
|
| 90 |
+
const distanceSquared = (dx * dx) + (dy * dy);
|
| 91 |
+
|
| 92 |
+
// Check if collision occurred (distance < radius)
|
| 93 |
+
if (distanceSquared < (ballRadius * ballRadius)) {
|
| 94 |
+
|
| 95 |
+
// --- Collision Response ---
|
| 96 |
+
|
| 97 |
+
// 1. Calculate where the ball hit the paddle vertically (normalized)
|
| 98 |
+
// hitPosition: -1 (top edge) to +1 (bottom edge)
|
| 99 |
+
const paddleCenterY = paddleTop + paddleHeight / 2;
|
| 100 |
+
let hitPosition = (ballCenterY - paddleCenterY) / (paddleHeight / 2);
|
| 101 |
+
// Clamp hitPosition to avoid extreme angles if hit exactly on corner
|
| 102 |
+
hitPosition = Math.max(-1, Math.min(1, hitPosition));
|
| 103 |
+
|
| 104 |
+
// 2. Calculate the bounce angle in radians
|
| 105 |
+
const bounceAngle = hitPosition * maxAngle * (Math.PI / 180);
|
| 106 |
+
|
| 107 |
+
// 3. Calculate the current speed
|
| 108 |
+
const currentSpeed = Math.sqrt(ballSpeedX * ballSpeedX + ballSpeedY * ballSpeedY);
|
| 109 |
+
|
| 110 |
+
// 4. Calculate the new speed (increase and cap)
|
| 111 |
+
let newSpeed = currentSpeed * speedIncreaseFactor;
|
| 112 |
+
newSpeed = Math.min(newSpeed, maxBallSpeed); // Apply max speed limit
|
| 113 |
+
|
| 114 |
+
// 5. Calculate new X and Y speeds based on angle and new total speed
|
| 115 |
+
// Determine the outward direction for X based on which paddle was hit
|
| 116 |
+
const directionX = isLeftPaddle ? 1 : -1;
|
| 117 |
+
ballSpeedX = directionX * newSpeed * Math.cos(bounceAngle);
|
| 118 |
+
ballSpeedY = newSpeed * Math.sin(bounceAngle);
|
| 119 |
+
|
| 120 |
+
// 6. Reposition ball slightly outside the paddle to prevent sticking (optional but recommended)
|
| 121 |
+
// Move ball along the collision normal (simplified as horizontal push)
|
| 122 |
+
const overlap = ballRadius - Math.sqrt(distanceSquared);
|
| 123 |
+
if (isLeftPaddle && dx < 0) { // Ball was moving right, hit left paddle
|
| 124 |
+
ballX += overlap; // Push right
|
| 125 |
+
} else if (!isLeftPaddle && dx > 0) { // Ball was moving left, hit right paddle
|
| 126 |
+
ballX -= overlap; // Push left
|
| 127 |
+
}
|
| 128 |
+
// A more accurate push would be along the dx, dy vector, but horizontal is often sufficient
|
| 129 |
+
|
| 130 |
+
// Ensure ball is definitely outside after adjustment
|
| 131 |
+
if (isLeftPaddle && ballX + ballSize < paddleRightEdge) {
|
| 132 |
+
ballX = paddleRightEdge - ballSize / 2; // Adjust precisely if needed
|
| 133 |
+
} else if (!isLeftPaddle && ballX > paddleLeftEdge) {
|
| 134 |
+
ballX = paddleLeftEdge - ballSize / 2; // Adjust precisely if needed
|
| 135 |
+
}
|
| 136 |
}
|
| 137 |
}
|
| 138 |
|
| 139 |
+
|
| 140 |
+
// --- Game Update Loop ---
|
| 141 |
function update() {
|
| 142 |
if (!isGamePaused) {
|
| 143 |
+
// Store previous position for collision checks if needed (not strictly needed with current collision)
|
| 144 |
+
// let previousBallX = ballX;
|
| 145 |
+
// let previousBallY = ballY;
|
| 146 |
+
|
| 147 |
+
// Move Ball
|
| 148 |
ballX += ballSpeedX;
|
| 149 |
ballY += ballSpeedY;
|
| 150 |
|
| 151 |
// Ball collision with top and bottom walls
|
| 152 |
+
if (ballY <= 0) {
|
| 153 |
+
ballY = 0; // Prevent sticking
|
| 154 |
+
ballSpeedY = Math.abs(ballSpeedY); // Ensure it bounces down
|
| 155 |
+
} else if (ballY >= window.innerHeight - ballSize) {
|
| 156 |
+
ballY = window.innerHeight - ballSize; // Prevent sticking
|
| 157 |
+
ballSpeedY = -Math.abs(ballSpeedY); // Ensure it bounces up
|
| 158 |
}
|
| 159 |
|
| 160 |
+
// Paddle Collisions
|
| 161 |
+
// Check collision with left paddle
|
| 162 |
+
if (ballSpeedX < 0) { // Only check left paddle if ball is moving left
|
| 163 |
+
handlePaddleCollision(paddleLeftY, 0, true); // Left paddle is at x=0
|
| 164 |
+
}
|
| 165 |
+
// Check collision with right paddle
|
| 166 |
+
else if (ballSpeedX > 0) { // Only check right paddle if ball is moving right
|
| 167 |
+
handlePaddleCollision(paddleRightY, window.innerWidth - paddleWidth, false); // Right paddle position
|
| 168 |
+
}
|
| 169 |
|
|
|
|
|
|
|
| 170 |
|
| 171 |
+
// Ball out of bounds (Score)
|
| 172 |
+
if (ballX + ballSize <= 0) { // Ball went past left edge
|
| 173 |
botScore++;
|
| 174 |
botScoreDisplay.textContent = botScore;
|
| 175 |
+
resetBall('right'); // Bot serves next
|
| 176 |
+
} else if (ballX >= window.innerWidth) { // Ball went past right edge
|
| 177 |
playerScore++;
|
| 178 |
playerScoreDisplay.textContent = playerScore;
|
| 179 |
+
resetBall('left'); // Player serves next
|
| 180 |
}
|
|
|
|
| 181 |
|
| 182 |
+
// --- Bot AI Movement ---
|
| 183 |
+
// Move bot paddle only if the ball is moving towards it
|
| 184 |
+
if (ballSpeedX > 0) {
|
| 185 |
+
const botPaddleCenter = paddleRightY + paddleHeight / 2;
|
| 186 |
+
const ballCenter = ballY + ballSize / 2;
|
| 187 |
+
const targetY = ballCenter - paddleHeight / 2; // Target top position for bot paddle
|
| 188 |
+
|
| 189 |
+
// Move towards the ball's Y position, but not faster than botPaddleSpeed
|
| 190 |
+
if (paddleRightY < targetY) {
|
| 191 |
+
paddleRightY += Math.min(botPaddleSpeed, targetY - paddleRightY);
|
| 192 |
+
} else if (paddleRightY > targetY) {
|
| 193 |
+
paddleRightY -= Math.min(botPaddleSpeed, paddleRightY - targetY);
|
| 194 |
+
}
|
| 195 |
+
// Clamp bot paddle position
|
| 196 |
+
paddleRightY = Math.max(0, Math.min(paddleRightY, window.innerHeight - paddleHeight));
|
| 197 |
+
paddleRight.style.top = paddleRightY + 'px';
|
| 198 |
}
|
| 199 |
}
|
| 200 |
|
| 201 |
+
// Update visual positions regardless of pause state (allows resetBall positioning)
|
|
|
|
| 202 |
ball.style.left = ballX + 'px';
|
| 203 |
ball.style.top = ballY + 'px';
|
| 204 |
+
// Player paddle updated in event listener
|
| 205 |
+
// Bot paddle updated in AI logic or here if needed outside AI block
|
| 206 |
+
|
| 207 |
|
| 208 |
+
// Request next frame
|
| 209 |
requestAnimationFrame(update);
|
| 210 |
}
|
| 211 |
|
| 212 |
+
// --- Reset Ball Function ---
|
| 213 |
+
function resetBall(scoringSide) {
|
| 214 |
+
isGamePaused = true; // Pause until next touch/click
|
| 215 |
+
|
| 216 |
+
// Center the ball vertically
|
| 217 |
+
ballY = window.innerHeight / 2 - ballSize / 2;
|
| 218 |
+
|
| 219 |
+
// Position ball near the paddle that will serve
|
| 220 |
+
if (scoringSide === 'left') { // Player scored, player serves (ball starts near left paddle)
|
| 221 |
+
ballX = paddleWidth + 20; // Start just right of left paddle
|
| 222 |
+
ballSpeedX = initialBallSpeedX; // Move right
|
| 223 |
+
} else { // Bot scored, bot serves (ball starts near right paddle)
|
| 224 |
+
ballX = window.innerWidth - paddleWidth - ballSize - 20; // Start just left of right paddle
|
| 225 |
+
ballSpeedX = -initialBallSpeedX; // Move left
|
| 226 |
}
|
| 227 |
|
| 228 |
+
// Reset vertical speed
|
| 229 |
ballSpeedY = 0;
|
| 230 |
+
|
| 231 |
+
// Optional: Briefly show ball position before game restarts on touch/click
|
| 232 |
+
ball.style.left = ballX + 'px';
|
| 233 |
+
ball.style.top = ballY + 'px';
|
| 234 |
+
|
| 235 |
+
// Display message (optional)
|
| 236 |
+
// gameMessage.textContent = "Tap to Serve";
|
| 237 |
+
// gameMessage.style.display = 'block';
|
| 238 |
}
|
| 239 |
|
| 240 |
+
// --- Initial Setup ---
|
| 241 |
+
// Set initial paddle positions visually
|
| 242 |
+
paddleLeft.style.top = paddleLeftY + 'px';
|
| 243 |
+
paddleRight.style.top = paddleRightY + 'px';
|
| 244 |
+
// Set initial ball position visually
|
| 245 |
+
ball.style.left = ballX + 'px';
|
| 246 |
+
ball.style.top = ballY + 'px';
|
| 247 |
+
// Set initial scores visually
|
| 248 |
+
playerScoreDisplay.textContent = playerScore;
|
| 249 |
+
botScoreDisplay.textContent = botScore;
|
| 250 |
+
|
| 251 |
// Start the game loop
|
| 252 |
+
requestAnimationFrame(update);
|