Pong vs Computer — Free HTML CSS JS Snippet

Pong vs Computer · Animations · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Rate-limited AI paddle: proportional cpuStep = diff * 0.09 clamped to CPU_MAX_SPEED, deliberately beatable
Angle-reflection bounce physics: bounce angle derived from exact paddle contact position, up to ±60 degrees
Progressive rally speed: ball speed multiplies by 1.06 on every paddle hit, capped at a maximum of 9
Dual input scheme: mouse movement and Arrow Up/Down keys both control the player paddle simultaneously
Canvas coordinate scale correction: mouse Y position scaled by H / rect.height for accurate tracking at any render size
requestAnimationFrame game loop: smooth per-frame position updates for the ball and both paddles
AABB paddle collision: axis-aligned range check on ball x-position and y-overlap before applying a bounce
First-to-7 match state: WIN_SCORE constant ends the round and shows a win/loss overlay with final score

About this UI Snippet

Pong vs Computer — Angle-Reflection Ball Physics & Rate-Limited AI Paddle Tracking

Screenshot of the Pong vs Computer snippet rendered live

Pong is the original video game archetype, and its two mechanics — realistic ball bounce physics and a computer opponent that feels fair rather than either trivial or unbeatable — are exactly what separate a convincing Pong clone from a flat, boring one. This snippet implements both properly: the ball's bounce angle genuinely depends on where it strikes each paddle, and the computer paddle tracks the ball with a deliberately imperfect, rate-limited speed so a human player can actually win.

Why a perfectly-tracking AI paddle is not fun

A naive Pong AI simply sets cpuY = ball.y every frame, producing a paddle that never misses — mathematically unbeatable and immediately obvious as artificial. This snippet's update() function instead computes diff = ball.y - cpuCenter (how far the CPU paddle's centre is from the ball) and applies only a fraction of that distance each frame: cpuStep = clamp(diff * 0.09, -CPU_MAX_SPEED, CPU_MAX_SPEED). The 0.09 proportional factor means the paddle always chases the ball but never snaps to it instantly, and CPU_MAX_SPEED = 4.2 caps how many pixels it can move in a single frame even when the ball is far away. The result is an opponent that plays a genuinely strong game on straightforward shots but can be beaten with sharp angle changes and fast cross-court hits that outrun its top speed — exactly the behaviour of a satisfying, beatable AI.

Angle-reflection bounce physics, not a flat mirror bounce

Real Pong's signature feel comes from the paddle acting like a curved surface rather than a flat wall: hitting the ball near the paddle's edge sends it off at a steep angle, while hitting it dead centre sends it nearly straight back. bounceOffPaddle(paddleY) computes relativeHit = (ball.y - paddleY) / PADDLE_H, a value from 0 (top of paddle) to 1 (bottom of paddle), then maps it to an angle: angle = (clampedRel - 0.5) * (Math.PI / 3), giving a range of plus or minus 60 degrees from horizontal. The ball's new velocity is then recomputed from that angle and a speed that increases slightly on every hit — speed = Math.min(9, Math.hypot(ball.vx, ball.vy) * 1.06) — capped at 9 so rallies gradually intensify without becoming physically uncontrollable. This is real angle-reflection physics driven by contact position, not a simple vx *= -1 mirror bounce.

Dual control scheme and canvas coordinate mapping

The player's paddle responds to both mouse movement and Arrow Up/Down keys. Mouse control reads e.clientY, subtracts the canvas's bounding-rect offset, and multiplies by H / rect.height to correctly map the mouse's screen-pixel position to the canvas's internal coordinate space — this scale correction matters because the canvas element is styled at width: 100% in CSS while its internal drawing buffer stays fixed at 480x320, so without the scale factor the paddle would track incorrectly on any screen where the canvas is rendered larger or smaller than its native resolution. Arrow key control simply nudges playerY by a fixed 6px per frame while the key is held, tracked through a keys object updated on keydown/keyup.

Wall bounces, scoring, and match state

The ball reflects off the top and bottom walls with a simple vy *= -1, since walls (unlike paddles) are flat and don't need angle variation. Paddle collision is detected with an axis-aligned range check on both x position and y overlap with the paddle's height before applying the angle-reflection bounce. When the ball passes fully off either the left or right edge, the corresponding score increments, resetPositions() re-centres both paddles and calls serveBall() to launch a new ball toward a random side, and the whole match resolves once either score reaches WIN_SCORE = 7, at which point endMatch() stops the requestAnimationFrame loop and shows a win/loss overlay with a "Play Again" button.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how the proportional cpuStep calculation and CPU_MAX_SPEED cap combine to produce an AI that is competitive but beatable — understanding that tuning is the key to extending the difficulty in either direction. It's also a strong candidate for AI-assisted additions: ask the assistant to add a difficulty selector that adjusts CPU_MAX_SPEED and the tracking factor together, to add a local two-player mode by replacing the CPU logic with a second keyboard control scheme, or to add a subtle particle or screen-shake effect on paddle hits for extra game feel. You could also ask it to review the AABB paddle collision ranges to confirm the ball can never tunnel through a paddle at high speed (a classic bug in naive collision code), and to suggest a fix such as continuous collision detection if it finds a gap. Treat the code as a physics sandbox to question and improve, not a finished black box.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a Pong game against a computer opponent using the HTML5 Canvas API in plain HTML, CSS, and JavaScript — no frameworks, no build tooling.

Requirements:
- Two paddles on a canvas: the human player's paddle controlled by mouse movement over the canvas (correctly scaled from screen pixels to canvas coordinates) and by Arrow Up/Down keys, restricted to vertical movement only and clamped within the canvas bounds; the computer's paddle on the opposite side.
- A computer-controlled paddle that tracks the ball's vertical position using a deliberately imperfect, rate-limited movement speed (for example a proportional step toward the ball's position capped at a maximum pixels-per-frame value) rather than snapping instantly to the ball — a perfectly tracking paddle must be avoidable since it would be unbeatable and not fun.
- A ball that moves continuously via a requestAnimationFrame loop, bounces off the top and bottom walls with simple vertical reflection, and bounces off either paddle with real angle-reflection physics where the rebound angle depends on exactly where along the paddle's height the ball made contact (centre hits return nearly straight, edge hits return at a steep angle).
- Ball speed that increases slightly with each paddle hit (capped at a reasonable maximum) so rallies build tension over time rather than staying at a flat constant speed.
- A visible score for both the player and the computer that increments when the ball fully passes the opposing side, immediately followed by re-centring both paddles and serving a new ball toward a random side with a slight randomized angle.
- A "first to 7 points wins" round-end state that stops the game loop, clearly displays who won and the final score, and offers a restart control that resets both scores and paddle positions and starts a fresh match.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Start the matchClick "Start Game" to hide the overlay and begin the requestAnimationFrame loop. The ball serves from centre toward a random side with a slight random vertical angle so every rally starts differently.
  2. 2
    Control your paddleMove your mouse over the canvas to directly position your paddle at the corresponding height, or hold Arrow Up/Down to nudge it 6px per frame — both input methods work simultaneously and are clamped to stay within the canvas bounds.
  3. 3
    Watch the CPU track imperfectlyThe computer paddle chases the ball's Y position using a proportional step capped at CPU_MAX_SPEED (4.2px/frame), so fast or sharply-angled shots can outrun its tracking speed — this is intentional and is what makes the AI beatable rather than a perfect wall.
  4. 4
    Aim your returns using paddle positionbounceOffPaddle() computes the ball's new angle from exactly where it struck your paddle: hitting near the top or bottom edge sends the ball off at a steep angle (up to 60 degrees), while a centre hit returns it nearly straight across.
  5. 5
    Play to 7 pointsEach time the ball passes fully off either side, the scoring player's counter increments and the ball re-serves from centre. The first side to reach WIN_SCORE (7) ends the match with a win/loss overlay showing the final score.
  6. 6
    ReplayClick "Play Again" after a match ends to call resetMatch(), which zeroes both scores, re-centres both paddles, serves a fresh ball, and restarts the game loop from a clean state.

Real-world uses

Common Use Cases

Retro arcade mini-game for a portfolio or landing page
Pong is the most universally recognised arcade game in existence, making it an effective, low-effort addition to a personal site's games section or an engaging Easter egg on an otherwise static page. The self-contained canvas implementation needs no dependencies and starts instantly.
Teaching realistic 2D bounce physics and beatable AI design
The angle-reflection paddle bounce and the rate-limited CPU tracking are two of the most commonly requested but poorly implemented game-programming concepts. This snippet is a clean, annotated reference for teaching both — particularly the game-design principle that an AI opponent should be deliberately imperfect to remain fun, contrasted against the AI difficulty tuning discussed in the Connect Four vs Computer snippet.
Reference implementation for canvas coordinate scaling
The mouse-to-canvas coordinate mapping in the mousemove handler — correcting for the difference between a canvas's CSS-rendered size and its internal drawing-buffer resolution — is a subtle bug source in many canvas games. This snippet demonstrates the correct fix and is a useful reference for any canvas project using CSS-responsive sizing.
Physics and game-loop coding exercise reference
The combination of requestAnimationFrame timing, velocity-based ball movement, and reflection-angle mathematics makes this a solid worked example for coding exercises or interview prep focused on basic 2D game physics without needing an external physics engine.
Themed reskin for a branded two-player promotional game
Swap the indigo and red paddle colours, the dark arcade background, and the centre-line dash pattern for brand colours to turn this into a promotional activation — for example a "beat our mascot" mini-game linked from a marketing campaign landing page.
Local competitive variant starting point
Because the CPU paddle logic is isolated inside a single block of update(), it is straightforward to replace with a second set of keyboard controls (for example W/S for player two) to convert this into a local two-player competitive Pong match on a shared keyboard.

Got questions?

Frequently Asked Questions

The CPU paddle moves toward the ball's Y position using a proportional step (roughly 9% of the remaining distance per frame) capped at a maximum speed of 4.2 pixels per frame. On a fast or sharply-angled shot, the ball can cross the court faster than the paddle can close that distance, causing a miss. This is intentional — a paddle that snaps instantly to the ball's position every frame would be mathematically unbeatable and not enjoyable to play against.

bounceOffPaddle() calculates relativeHit as the fraction of the paddle's height where contact occurred (0 at the top, 1 at the bottom), then maps that to an angle between -60 and +60 degrees using (relativeHit - 0.5) * (Math.PI / 3). A centre hit produces a near-horizontal return; a hit near either edge sends the ball off at a steep angle, exactly like the physical spin-and-angle behaviour of the original arcade game.

Yes — every time the ball hits either paddle, its speed is multiplied by 1.06 via Math.hypot(ball.vx, ball.vy) * 1.06, capped at a maximum of 9 to keep the game controllable. This means long rallies become progressively faster and more tense, while short rallies stay at a moderate, learnable pace.

The canvas element is styled with width: 100% in CSS so it resizes responsively, but its internal drawing buffer stays fixed at 480x320 pixels. Without correcting for this, a mouse position read directly from e.clientY would be wrong on any screen where the canvas renders at a different size than its native resolution. Multiplying by H / rect.height converts the mouse's on-screen pixel position into the canvas's internal coordinate space accurately at any display size.

Adjust CPU_MAX_SPEED (its top pixel-per-frame movement speed) and the 0.09 proportional tracking factor inside update(). Raising either value makes the CPU track the ball more aggressively and win more often; lowering them makes it slower to react and easier to beat. Setting CPU_MAX_SPEED very high while keeping the tracking factor at 1 effectively recreates the unbeatable "perfect paddle" this snippet deliberately avoids.