VOOZH about

URL: https://dev.to/dev48v/i-built-pong-in-60-lines-of-javascript-five-state-variables-and-a-setinterval-2f9o

โ‡ฑ I Built Pong in 60 Lines of JavaScript โ€” Five State Variables and a setInterval - DEV Community


๐ŸŒ Live demo (LOOK ยท UNDERSTAND ยท BUILD): https://dev48v.infy.uk/game/day2-pong.html

Day 2 of my GameFromZero series โ€” 50 playable mini-games in 50 days, all in the browser, all one HTML file.

Today: Pong. The simplest arcade game ever made and the one that teaches the game loop pattern that powers every game after it.


What you're going to learn

The game-loop pattern. Every action game in history is the same shape:

every frame (16ms โ‰ˆ 60 FPS):
 step() โ€” update state (positions, velocities, collisions, scores)
 draw() โ€” repaint the canvas from current state

That's it. Pong is the cleanest possible illustration of this loop.


The state โ€” five numbers

let ly, ry; // left paddle Y, right paddle Y
let bx, by; // ball X, ball Y
let vx, vy; // ball velocity X, ball velocity Y
let scoreL, scoreR;

That is the entire universe. No classes, no objects, no engine. Five primitive numbers describe the whole game.


The physics โ€” 8 lines

// 1. Move ball
bx += vx;
by += vy;

// 2. Bounce off top/bottom walls
if (by < BR || by > H - BR) vy = -vy;

// 3. Bounce off left paddle
if (bx < PW + BR && by > ly && by < ly + PH) {
 vx = Math.abs(vx) * 1.05; // reverse + speed up 5%
 vy += ((by - (ly + PH/2)) / PH) * 3; // angle from hit position
}

// 4. Score when ball passes a goal
if (bx < 0) { scoreR++; reset(); }
if (bx > W) { scoreL++; reset(); }

Each line is a single rule of the universe. The speed-up on every paddle hit (* 1.05) is what makes Pong intense โ€” long rallies get faster until somebody misses.

The deflection trick vy += ((by - paddleMid) / PH) * 3 is the entire secret behind paddle games: hit position changes the angle. Hit the top of the paddle, ball goes up. Hit the bottom, ball goes down. Lets a skilled player steer.


Input โ€” flag the keys

const keys = {};
addEventListener('keydown', e => { keys[e.key.toLowerCase()] = true; });
addEventListener('keyup', e => { keys[e.key.toLowerCase()] = false; });

// Every tick:
if (keys.w) ly -= 6;
if (keys.s) ly += 6;

DON'T move the paddle inside the keydown handler โ€” that gives you choppy keyboard-repeat-rate movement. Always poll the keys map in your step() so movement is locked to the frame rate.


CPU opponent in 4 lines

const target = by - PH / 2; // where the paddle CENTER should be
if (ry + 5 < target) ry += 4; // below target โ†’ move down
else if (ry - 5 > target) ry -= 4; // above โ†’ move up
ry = clamp(ry, 0, H - PH);

4 is the difficulty knob. Lower = easier CPU. The +5 / -5 deadband prevents jittery paddle motion when the ball passes through the center.


The whole file (~60 lines)

<!DOCTYPE html>
<html><body style="background:#0f172a;color:#e2e8f0;text-align:center;font-family:system-ui;">
 <h1>๐Ÿ“ Pong</h1>
 <div><span id="scoreL">0</span> ยท <span id="scoreR">0</span></div>
 <canvas id="c" width="600" height="400" style="background:#020617;border:2px solid #334155;"></canvas>
 <p>W / S to move ยท Space to restart</p>
<script>
const c=document.getElementById('c'), ctx=c.getContext('2d');
const SL=document.getElementById('scoreL'), SR=document.getElementById('scoreR');
const PH=80, PW=10, BR=8;
let ly,ry,bx,by,vx,vy,scoreL,scoreR;
const keys={};
function reset(){ ly=ry=c.height/2-PH/2; bx=c.width/2; by=c.height/2;
 vx=(Math.random()>.5?1:-1)*4; vy=(Math.random()*4)-2; }
function newGame(){ scoreL=scoreR=0; SL.textContent=0; SR.textContent=0; reset(); }
function step(){
 if(keys.w) ly-=6; if(keys.s) ly+=6;
 ly=Math.max(0,Math.min(c.height-PH,ly));
 const t=by-PH/2;
 if(ry+5<t) ry+=4; else if(ry-5>t) ry-=4;
 ry=Math.max(0,Math.min(c.height-PH,ry));
 bx+=vx; by+=vy;
 if(by<BR||by>c.height-BR) vy=-vy;
 if(bx<PW+BR && by>ly && by<ly+PH){ vx=Math.abs(vx)*1.05; vy+=((by-(ly+PH/2))/PH)*3; }
 if(bx>c.width-PW-BR && by>ry && by<ry+PH){ vx=-Math.abs(vx)*1.05; vy+=((by-(ry+PH/2))/PH)*3; }
 if(bx<0){ scoreR++; SR.textContent=scoreR; reset(); }
 if(bx>c.width){ scoreL++; SL.textContent=scoreL; reset(); }
}
function draw(){
 ctx.fillStyle='#020617'; ctx.fillRect(0,0,c.width,c.height);
 ctx.fillStyle='#fff';
 for(let i=0;i<c.height;i+=20) ctx.fillRect(c.width/2-1,i,2,10);
 ctx.fillStyle='#22c55e'; ctx.fillRect(0,ly,PW,PH);
 ctx.fillStyle='#f43f5e'; ctx.fillRect(c.width-PW,ry,PW,PH);
 ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(bx,by,BR,0,Math.PI*2); ctx.fill();
}
addEventListener('keydown',e=>{ keys[e.key.toLowerCase()]=true; if(e.key==='')newGame(); });
addEventListener('keyup', e=>{ keys[e.key.toLowerCase()]=false; });
newGame();
setInterval(()=>{ step(); draw(); }, 16);
</script>
</body></html>

Save as index.html, double-click. Done.


What this unlocks

Same skeleton โ†’ many games:

Game What changes
Pong what you just built
Breakout one paddle, replace right side with a brick wall
Air Hockey two paddles, add side walls, no scoring zones at the goals
Brick Out same as Breakout but bricks in a grid
Snake swap continuous velocity for discrete grid steps (Day 1)

State + step + draw + setInterval is the only game-engine skeleton you need for arcade games. Engines like Phaser / Pixi add features, but the core loop is identical.


Try it now

Three tabs in one page:
https://dev48v.infy.uk/game/day2-pong.html

  • LOOK โ€” play it
  • UNDERSTAND โ€” 9 click-through steps with diagrams + WHY for each physics rule
  • BUILD โ€” copy the HTML, save, open

What's next in GameFromZero

Day 3: Tetris. Block rotation, gravity, line clearing. Same loop, more state.

Series: 50 playable browser games ยท zero install.

๐ŸŒ All games: https://dev48v.infy.uk/gamefromzero.php

Some comments may only be visible to logged-in visitors. Sign in to view all comments.