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 |
-
update
|
|
|
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);
|