<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HTML Ping Pong</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="score">
        <div id="player-score">0</div>
        <div id="ai-score">0</div>
    </div>
    <div id="game-board">
        <div class="net"></div>
        <div id="ball"></div>
        <div id="paddle-left" class="paddle"></div>
        <div id="paddle-right" class="paddle"></div>
        <div id="start-message" class="message">Click to Start</div>
        <div id="win-message" class="message" style="display: none;"></div>
    </div>

    <script src="script.js"></script>
</body>
</html>
body {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    background-color: #2c3e50; /* Dark background */
    margin: 0;
    font-family: 'Arial', sans-serif;
    flex-direction: column; /* Stack score above board */
    overflow: hidden; /* Prevent scrollbars if board is slightly too big */
}

.score {
    display: flex;
    justify-content: space-around;
    width: 600px; /* Match game board width */
    font-size: 2.5em;
    color: #ecf0f1; /* Light text color */
    margin-bottom: 20px;
    font-weight: bold;
    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}

#game-board {
    width: 600px;
    height: 400px;
    background: linear-gradient(to bottom, #16a085, #1abc9c); /* Green gradient for table */
    border: 5px solid #ecf0f1; /* White border */
    position: relative; /* Crucial for positioning elements inside */
    cursor: none; /* Hide cursor over game board */
    box-shadow: 0 10px 20px rgba(0, 0, 0, 0.4);
    overflow: hidden; /* Keep ball and paddles inside visually */
}

.net {
    position: absolute;
    left: 50%;
    top: 0;
    width: 4px; /* Net thickness */
    height: 100%;
    border-left: 4px dashed rgba(255, 255, 255, 0.5); /* Dashed white line */
    transform: translateX(-50%); /* Center the net */
}

#ball {
    width: 20px;
    height: 20px;
    background-color: #f1c40f; /* Yellow ball */
    border-radius: 50%; /* Make it round */
    position: absolute;
    /* Start position set by JS */
    box-shadow: 0 0 8px rgba(0, 0, 0, 0.5);
}

.paddle {
    width: 15px;
    height: 100px;
    background-color: #e74c3c; /* Red paddle color */
    position: absolute;
    border-radius: 5px;
    box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.4);
}

#paddle-left {
    left: 10px;
    /* top position set by JS */
}

#paddle-right {
    right: 10px;
    background-color: #3498db; /* Blue AI paddle */
    /* top position set by JS */
}

.message {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 2em;
    color: rgba(255, 255, 255, 0.8);
    background-color: rgba(0, 0, 0, 0.6);
    padding: 15px 30px;
    border-radius: 10px;
    text-align: center;
    z-index: 10; /* Ensure message is on top */
}
document.addEventListener('DOMContentLoaded', () => {
    const gameBoard = document.getElementById('game-board');
    const ball = document.getElementById('ball');
    const paddleLeft = document.getElementById('paddle-left');
    const paddleRight = document.getElementById('paddle-right');
    const playerScoreElem = document.getElementById('player-score');
    const aiScoreElem = document.getElementById('ai-score');
    const startMessage = document.getElementById('start-message');
    const winMessage = document.getElementById('win-message');

    let boardRect = gameBoard.getBoundingClientRect();
    let ballX, ballY, ballSpeedX, ballSpeedY;
    let paddleLeftY = gameBoard.clientHeight / 2 - paddleLeft.offsetHeight / 2;
    let paddleRightY = gameBoard.clientHeight / 2 - paddleRight.offsetHeight / 2;
    let playerScore = 0;
    let aiScore = 0;

    const PADDLE_HEIGHT = paddleLeft.offsetHeight;
    const BALL_SIZE = ball.offsetWidth;
    const PADDLE_SPEED = 6; // AI paddle speed adjustment
    const WINNING_SCORE = 5;

    let gameRunning = false;
    let animationFrameId;

    // --- Initialization ---

    function resetBall(serveDirection) {
        ballX = gameBoard.clientWidth / 2 - BALL_SIZE / 2;
        ballY = gameBoard.clientHeight / 2 - BALL_SIZE / 2;

        // Randomize initial vertical angle slightly
        let angle = (Math.random() * Math.PI / 4) - (Math.PI / 8); // -22.5 to +22.5 degrees

        ballSpeedY = 4 * Math.sin(angle); // Slower initial speed
        ballSpeedX = 4 * serveDirection * Math.cos(angle); // Serve left or right
    }

    function updateScore() {
        playerScoreElem.textContent = playerScore;
        aiScoreElem.textContent = aiScore;
    }

    function resetGame() {
        playerScore = 0;
        aiScore = 0;
        updateScore();
        paddleLeftY = gameBoard.clientHeight / 2 - PADDLE_HEIGHT / 2;
        paddleRightY = gameBoard.clientHeight / 2 - PADDLE_HEIGHT / 2;
        paddleLeft.style.top = `${paddleLeftY}px`;
        paddleRight.style.top = `${paddleRightY}px`;
        winMessage.style.display = 'none';
        startMessage.style.display = 'block';
        cancelAnimationFrame(animationFrameId); // Stop previous loop if any
        gameRunning = false;
    }

    // --- Game Loop ---

    function gameLoop() {
        if (!gameRunning) return;

        // 1. Move Ball
        ballX += ballSpeedX;
        ballY += ballSpeedY;

        // 2. Ball Collision Detection (Walls)
        // Top/Bottom Walls
        if (ballY <= 0 || ballY >= gameBoard.clientHeight - BALL_SIZE) {
            ballSpeedY *= -1;
            ballY = Math.max(0, Math.min(ballY, gameBoard.clientHeight - BALL_SIZE)); // Prevent sticking
        }

        // Score Walls
        if (ballX <= 0) {
            aiScore++;
            updateScore();
            checkForWin();
             if (!gameRunning) return; // Stop if game ended
            resetBall(1); // Serve to AI
        } else if (ballX >= gameBoard.clientWidth - BALL_SIZE) {
            playerScore++;
            updateScore();
            checkForWin();
             if (!gameRunning) return; // Stop if game ended
            resetBall(-1); // Serve to Player
        }

        // 3. Ball Collision Detection (Paddles)
        let paddle = (ballSpeedX < 0) ? paddleLeft : paddleRight;
        let paddleY = (ballSpeedX < 0) ? paddleLeftY : paddleRightY;
        let paddleX = (ballSpeedX < 0) ? paddle.offsetLeft + paddle.offsetWidth : paddle.offsetLeft;

        if (
            // Check if ball is aligned horizontally with the correct paddle
            (ballSpeedX < 0 && ballX <= paddleX && ballX > paddle.offsetLeft) ||
            (ballSpeedX > 0 && ballX + BALL_SIZE >= paddleX && ballX < paddle.offsetLeft + paddle.offsetWidth)
        ) {
            // Check if ball is aligned vertically with the paddle
            if (ballY + BALL_SIZE > paddleY && ballY < paddleY + PADDLE_HEIGHT) {
                // Collision!
                ballSpeedX *= -1;

                // Make ball bounce angle depend on where it hits the paddle
                let hitPos = (ballY + BALL_SIZE / 2) - (paddleY + PADDLE_HEIGHT / 2);
                let normalizedHitPos = hitPos / (PADDLE_HEIGHT / 2); // Range -1 to 1
                let bounceAngle = normalizedHitPos * (Math.PI / 3); // Max bounce angle (e.g., 60 degrees)

                let currentSpeed = Math.sqrt(ballSpeedX * ballSpeedX + ballSpeedY * ballSpeedY);
                currentSpeed *= 1.05; // Increase speed slightly on hit
                currentSpeed = Math.min(currentSpeed, 12); // Max speed limit

                ballSpeedY = currentSpeed * Math.sin(bounceAngle);
                // Keep the horizontal direction, adjust magnitude based on angle
                ballSpeedX = currentSpeed * Math.cos(bounceAngle) * Math.sign(ballSpeedX);

                 // Prevent ball getting stuck inside paddle slightly
                 if (ballSpeedX > 0) {
                    ballX = paddleLeft.offsetLeft + paddleLeft.offsetWidth;
                 } else {
                    ballX = paddleRight.offsetLeft - BALL_SIZE;
                 }
            }
        }


        // 4. Move AI Paddle (Simple follow)
        let targetY = ballY - PADDLE_HEIGHT / 2;
        let dy = targetY - paddleRightY;

        // Introduce slight delay/inertia
        if (Math.abs(dy) > PADDLE_SPEED / 2) {
             paddleRightY += Math.sign(dy) * PADDLE_SPEED * 0.6; // AI slightly slower
        }

        // Clamp AI paddle position
        paddleRightY = Math.max(0, Math.min(paddleRightY, gameBoard.clientHeight - PADDLE_HEIGHT));


        // 5. Render updates
        ball.style.left = `${ballX}px`;
        ball.style.top = `${ballY}px`;
        paddleRight.style.top = `${paddleRightY}px`;
        // Player paddle position updated by mousemove event

        // 6. Request next frame
        animationFrameId = requestAnimationFrame(gameLoop);
    }

    // --- Input Handling ---

    gameBoard.addEventListener('mousemove', (e) => {
        // Calculate mouse position relative to the game board
        boardRect = gameBoard.getBoundingClientRect(); // Update in case of scroll/resize
        let mouseY = e.clientY - boardRect.top;

        // Move player paddle (paddleLeft)
        paddleLeftY = mouseY - PADDLE_HEIGHT / 2;

        // Clamp paddle position to board boundaries
        paddleLeftY = Math.max(0, Math.min(paddleLeftY, gameBoard.clientHeight - PADDLE_HEIGHT));

        paddleLeft.style.top = `${paddleLeftY}px`;
    });

    // --- Game State Control ---

     function checkForWin() {
        let winner = null;
        if (playerScore >= WINNING_SCORE) {
            winner = "Player";
        } else if (aiScore >= WINNING_SCORE) {
            winner = "AI";
        }

        if (winner) {
            gameRunning = false;
            winMessage.textContent = `${winner} Wins! Click to Play Again`;
            winMessage.style.display = 'block';
            startMessage.style.display = 'none';
             cancelAnimationFrame(animationFrameId);
        }
    }

    function startGame() {
        if (!gameRunning) {
            // Check if game just ended
            if (playerScore >= WINNING_SCORE || aiScore >= WINNING_SCORE) {
                resetGame(); // Reset scores and positions if restarting after win
            }
            startMessage.style.display = 'none';
            winMessage.style.display = 'none';
            resetBall(Math.random() > 0.5 ? 1 : -1); // Random serve direction
            gameRunning = true;
            animationFrameId = requestAnimationFrame(gameLoop);
        }
    }

    // Click to start/restart
    gameBoard.addEventListener('click', startGame);


    // --- Initial Setup ---
     resetGame(); // Set initial positions and score display

     // Adjust boardRect on resize
     window.addEventListener('resize', () => {
         boardRect = gameBoard.getBoundingClientRect();
     });
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.