In a Pong-style 2D game, a ball (a circle of radius BALLRADIUS) can bounce (with perfect elasticity) on the top or bottom of the screen. When it hits the left or right side of the screen, one of the players scores a point. This function calculates the y-coordinate at which a ball initially at coordinates (x, y) and travelling at velocity (vx, vy) will next hit the left or right side of the screen.
const WIDTH = 1000;
const HEIGHT = 1000;
const BALLRADIUS = 10;
function predictBallDestination(x, y, vx, vy) {
/* Predict y coordinate at which a ball at (x, y) and travelling at velocity
(vx, vy) will next hit the left or right side of the screen. The ball can bounce
on the top or bottom of the screen with perfect elasticity. */
if (vy === 0) { // Traveling horizontally
return y;
}
if (vx === 0) { // Traveling vertically
throw new Error("Ball.predict: ball can never reach the side because vx is 0");
}
// Calculate horizontal distance to the side towards which it is traveling
const dx = vx > 0 ? WIDTH - BALLRADIUS - x : x - BALLRADIUS;
// Calculate total vertical distance it will travel
const dy = Math.abs(vy * dx / vx);
// Calculate remaining vertical distance to travel after the first bounce
let remainder;
if (vy > 0) { // Initially going down
if (dy <= HEIGHT - y - BALLRADIUS) {
return y + dy; // no bounce
}
remainder = dy - (HEIGHT - y - BALLRADIUS);
} else { // Initially going up
if (dy <= y - BALLRADIUS) {
return y - dy; // no bounce
}
remainder = dy - (y - BALLRADIUS);
}
// Calculate distance it will still have to travel after the last bounce
const lastPart = remainder % (HEIGHT - BALLRADIUS * 2);
const bounces = 1 + Math.floor(remainder / (HEIGHT - BALLRADIUS * 2));
const evenBounces = bounces % 2 === 0;
console.log("Bounces", bounces);
if ((vy > 0 && evenBounces) || (vy < 0 && !evenBounces)) {
// If the ball is going down after the last bounce
return lastPart + BALLRADIUS;
} else { // going up after the last bounce
return HEIGHT - BALLRADIUS - lastPart;
}
}
console.log(predictBallDestination(500, 500, 1, 0)); // 500 - travels right and hits the right side at (WIDTH - 10, 500)
console.log(predictBallDestination(500, 500, 1, .5)); // 745 - travels down-right without bounce
console.log(predictBallDestination(500, 500, 1, 1)); // 990 - hits at the corner
console.log(predictBallDestination(500, 500, 1, 2)); // 500 - bounces once at the middle bottom
console.log(predictBallDestination(500, 500, 1, 4)); // 500 - bounces twice and still hits in the middle
I would appreciate any suggestions to simplify the logic or otherwise improve this function. Is there some way to take care of all the different cases (going up or down, no bounce, odd number of bounces, even number of bounces) with a single formula?