Bouncing Balls – Score System
by PixelRider35424 lines15.9 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bouncing Balls – Score System</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
canvas {
display: block;
background: #000;
width: 100vw;
height: 100vh;
}
#score-display {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
color: #fff;
font: 28px/1.4 'Courier New', monospace;
background: rgba(0, 0, 0, 0.75);
padding: 10px 30px;
border-radius: 30px;
border: 2px solid #4fc3f7;
box-shadow: 0 0 30px rgba(79, 195, 247, 0.15);
pointer-events: none;
user-select: none;
z-index: 10;
text-align: center;
letter-spacing: 1px;
}
#score-display .score-value {
color: #4fc3f7;
font-weight: bold;
font-size: 32px;
}
#score-display .score-label {
color: #888;
font-size: 16px;
margin-right: 8px;
}
#controls-hint {
position: fixed;
bottom: 20px;
right: 20px;
color: #555;
font: 14px/1.4 monospace;
background: rgba(0, 0, 0, 0.6);
padding: 6px 12px;
border-radius: 16px;
border: 1px solid #333;
pointer-events: none;
user-select: none;
z-index: 10;
}
.score-popup {
position: fixed;
font: bold 32px/1 'Courier New', monospace;
pointer-events: none;
z-index: 20;
animation: floatUp 1s ease-out forwards;
text-shadow: 0 0 20px rgba(255,255,255,0.3);
}
@keyframes floatUp {
0% {
opacity: 1;
transform: translateY(0) scale(1);
}
100% {
opacity: 0;
transform: translateY(-80px) scale(1.2);
}
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="score-display">
<span class="score-label"> SCORE</span>
<span class="score-value" id="scoreValue">0</span>
</div>
<script>
(function() {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const scoreSpan = document.getElementById('scoreValue');
// ----- SCORE -----
let score = 0;
function updateScoreDisplay() {
scoreSpan.textContent = score;
}
function showScorePopup(x, y, text, color = '#4fc3f7') {
const popup = document.createElement('div');
popup.className = 'score-popup';
popup.textContent = text;
popup.style.left = x + 'px';
popup.style.top = y + 'px';
popup.style.color = color;
document.body.appendChild(popup);
setTimeout(() => popup.remove(), 1000);
}
// ----- resize canvas -----
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// ----- PADDLE -----
const paddle = {
width: 120,
height: 16,
x: 0,
y: 0,
speed: 8,
color: '#4fc3f7'
};
function resetPaddle() {
paddle.y = canvas.height - 40;
paddle.x = (canvas.width - paddle.width) / 2;
}
resetPaddle();
// ----- keyboard state -----
const keys = { left: false, right: false };
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') { keys.left = true;
e.preventDefault(); }
else if (e.key === 'ArrowRight') { keys.right = true;
e.preventDefault(); }
});
document.addEventListener('keyup', (e) => {
if (e.key === 'ArrowLeft') { keys.left = false;
e.preventDefault(); }
else if (e.key === 'ArrowRight') { keys.right = false;
e.preventDefault(); }
});
// ----- ball array -----
let balls = [];
// ----- helpers -----
const rand = (min, max) => Math.random() * (max - min) + min;
const randInt = (min, max) => Math.floor(rand(min, max + 1));
// ----- create a ball -----
function createBall() {
const radius = rand(8, 35);
const hue = randInt(0, 360);
const sat = randInt(70, 100);
const light = randInt(50, 80);
const color = `hsl(${hue}, ${sat}%, ${light}%)`;
const margin = radius + 2;
const x = rand(margin, canvas.width - margin);
const y = rand(margin, canvas.height - margin);
const angle = rand(0, Math.PI * 2);
const speed = rand(4.5, 8.0);
const vx = Math.cos(angle) * speed;
const vy = Math.sin(angle) * speed;
return { x, y, vx, vy, radius, color, scored: false };
}
// ----- add a new ball every 3 seconds -----
function addBall() {
if (balls.length < 500) {
const newBall = createBall();
balls.push(newBall);
}
}
setInterval(addBall, 3000);
// ----- seed with 3 balls -----
for (let i = 0; i < 3; i++) {
balls.push(createBall());
}
updateScoreDisplay();
// ----- collision: elastic bounce between balls -----
function resolveBallCollisions() {
const count = balls.length;
for (let i = 0; i < count; i++) {
for (let j = i + 1; j < count; j++) {
const a = balls[i];
const b = balls[j];
const dx = b.x - a.x;
const dy = b.y - a.y;
const dist = Math.hypot(dx, dy);
const minDist = a.radius + b.radius;
if (dist < minDist && dist > 0.001) {
const nx = dx / dist;
const ny = dy / dist;
const overlap = (minDist - dist) * 0.5;
a.x -= nx * overlap;
a.y -= ny * overlap;
b.x += nx * overlap;
b.y += ny * overlap;
const dvx = a.vx - b.vx;
const dvy = a.vy - b.vy;
const dvn = dvx * nx + dvy * ny;
if (dvn > 0) {
const massA = a.radius * a.radius;
const massB = b.radius * b.radius;
const totalMass = massA + massB;
const impulse = (2 * dvn) / totalMass;
a.vx -= impulse * massB * nx;
a.vy -= impulse * massB * ny;
b.vx += impulse * massA * nx;
b.vy += impulse * massA * ny;
}
}
}
}
}
// ----- update: bounce on top, left, right – wrap bottom, paddle collision -----
function updateBalls() {
const w = canvas.width;
const h = canvas.height;
// move paddle
if (keys.left) paddle.x -= paddle.speed;
if (keys.right) paddle.x += paddle.speed;
if (paddle.x < 0) paddle.x = 0;
if (paddle.x + paddle.width > w) paddle.x = w - paddle.width;
for (const ball of balls) {
ball.x += ball.vx;
ball.y += ball.vy;
const r = ball.radius;
// LEFT wall bounce
if (ball.x - r < 0) {
ball.x = r;
ball.vx = -ball.vx;
}
// RIGHT wall bounce
else if (ball.x + r > w) {
ball.x = w - r;
ball.vx = -ball.vx;
}
// TOP wall bounce
if (ball.y - r < 0) {
ball.y = r;
ball.vy = -ball.vy;
}
// PADDLE collision (only if ball is moving downward)
if (ball.vy > 0) {
const ballRight = ball.x + r;
const ballLeft = ball.x - r;
const paddleRight = paddle.x + paddle.width;
const paddleLeft = paddle.x;
if (ballRight > paddleLeft && ballLeft < paddleRight) {
const ballBottom = ball.y + r;
const paddleTop = paddle.y;
const paddleBottom = paddle.y + paddle.height;
if (ballBottom >= paddleTop && ball.y - r <= paddleBottom) {
// Ball hit paddle - SCORE +10
if (!ball.scored) {
ball.scored = true;
score += 10;
updateScoreDisplay();
// Show popup
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const popupX = (ball.x / scaleX) + rect.left - 20;
const popupY = (ball.y / scaleY) + rect.top - 40;
showScorePopup(popupX, popupY, '+10', '#4fc3f7');
}
ball.y = paddleTop - r;
ball.vy = -Math.abs(ball.vy);
const hitPos = (ball.x - paddle.x) / paddle.width;
const angle = (hitPos - 0.5) * 1.2;
const speed = Math.hypot(ball.vx, ball.vy);
ball.vx = Math.sin(angle) * speed;
ball.vy = -Math.cos(angle) * speed;
}
}
}
// BOTTOM: ball escapes - LOSE 1 POINT
if (ball.y - r > h) {
if (!ball.scored) {
// Ball escaped without hitting paddle
score -= 1;
updateScoreDisplay();
// Show popup
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const popupX = (ball.x / scaleX) + rect.left - 20;
const popupY = (ball.y / scaleY) + rect.top - 20;
showScorePopup(popupX, popupY, '-1', '#ff6b6b');
}
// Reset ball to top
ball.y = -r;
ball.x = rand(r, w - r);
ball.scored = false; // Reset scored flag for next round
}
}
resolveBallCollisions();
}
// ----- draw everything -----
function drawBalls() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw paddle
ctx.shadowColor = 'rgba(79, 195, 247, 0.3)';
ctx.shadowBlur = 20;
ctx.fillStyle = paddle.color;
ctx.beginPath();
ctx.roundRect(paddle.x, paddle.y, paddle.width, paddle.height, 8);
ctx.fill();
ctx.shadowBlur = 0;
// draw balls
for (const ball of balls) {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = ball.color;
ctx.fill();
ctx.shadowColor = 'rgba(255,255,255,0.12)';
ctx.shadowBlur = 8;
ctx.fill();
ctx.shadowBlur = 0;
}
}
// ----- roundRect polyfill -----
if (!CanvasRenderingContext2D.prototype.roundRect) {
CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, radii) {
const r = typeof radii === 'number' ? radii : (radii || 0);
this.moveTo(x + r, y);
this.lineTo(x + w - r, y);
this.quadraticCurveTo(x + w, y, x + w, y + r);
this.lineTo(x + w, y + h - r);
this.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
this.lineTo(x + r, y + h);
this.quadraticCurveTo(x, y + h, x, y + h - r);
this.lineTo(x, y + r);
this.quadraticCurveTo(x, y, x + r, y);
return this;
};
}
// ----- animation loop -----
function animate() {
updateBalls();
drawBalls();
requestAnimationFrame(animate);
}
animate();
// ----- handle resize -----
window.addEventListener('resize', () => {
resizeCanvas();
resetPaddle();
});
// ----- click to add a ball -----
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const x = (e.clientX - rect.left) * scaleX;
const y = (e.clientY - rect.top) * scaleY;
const newBall = createBall();
newBall.x = Math.min(Math.max(x, newBall.radius + 2), canvas.width - newBall.radius - 2);
newBall.y = Math.min(Math.max(y, newBall.radius + 2), canvas.height - newBall.radius - 2);
balls.push(newBall);
});
console.log('?? Score system active! +10 for paddle hits, -1 for escapes.');
})();
</script>
</body>
</html>Game Source: Bouncing Balls – Score System
Creator: PixelRider35
Libraries: none
Complexity: complex (424 lines, 15.9 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: bouncing-balls-score-system-pixelrider35" to link back to the original. Then publish at arcadelab.ai/publish.