I Took Pong Wars and made it Playable

I Took Pong Wars and made it Playable
October 31, 2025 | Read time: 5 minutes

Escape the vendor-locked in edge-runtime!

Sevalla is the home to your web projects. Host and manage your applications, databases, and static sites in a single, intuitive platform.

Try Sevalla with $50 credit now

Sponsor this newsletter to reach 9,000+ active developers

Recently, I stumbled across Pong Wars by Koen van Gilst and really liked it.

Two balls. A grid. No controls. Just pure, meditative chaos as Day and Night battle for territory. It is beautiful.

But I didn’t like the colors. So, I recreated it into a fully interactive, color-customizable, pause-and-reset version that still fits in one single HTML.

 

Let’s get started!

Step 1: Start with the core grid and balls

I kept Koen’s grid setup. A 24×24 array of tiles, half day, half night.
But I switched to an ownership array for faster checks: 0 for Day, 1 for Night.

                            
const ownership = new Array(gridWidth * gridHeight).fill(0).map((_, idx) =>
  idx % gridWidth < gridWidth / 2 ? 0 : 1
);                            
                        

Balls stay the same at first: positions, velocities, colors.

                            
const balls = [
  { x: canvas.width / 4, y: canvas.height / 2, vx: 8, vy: -8, get color() { return nightColor; } },
  { x: (canvas.width / 4) * 3, y: canvas.height / 2, vx: -8, vy: 8, get color() { return dayColor; } }
];                            
                        

I used getters for colors, so they update live.

Step 2: Add play/pause for control

The original runs forever. I added a toggle.

                            
let isPlaying = true;

function togglePlayPause() {
  isPlaying = !isPlaying;
  // Update icons
  if (isPlaying) requestAnimationFrame(render);
}                            
                        

Hook it up to a button with SVG icons for play and pause.
Now you can freeze the game anytime.

Step 3: Build a reset function

This lets you restart without reloading.

                            
function resetGame() {
  // Reset ownership
  ownership.forEach((_, idx) => {
    ownership[idx] = idx % gridWidth < gridWidth / 2 ? 0 : 1;
  });
  // Reset balls
  balls[0].x = canvas.width / 4; balls[0].y = canvas.height / 2; balls[0].vx = 8; balls[0].vy = -8;
  balls[1].x = (canvas.width / 4) * 3; balls[1].y = canvas.height / 2; balls[1].vx = -8; balls[1].vy = 8;
  if (!isPlaying) togglePlayPause();
}                            
                        

One click takes you back to the start.

Step 4: Introduce the settings panel for colors

I added a slide-in panel with Day and Night color pickers.

                            
<div id="configPanel">
  <label>Day color: <input type="color" id="dayColorInput" value="#FFFFFF"/></label>
  <label>Night color: <input type="color" id="nightColorInput" value="#000000"/></label>
</div>                            
                        

CSS for slide-in:

                            
#configPanel {
  position: fixed; top: 10px; right: -280px; width: 220px;
  transition: right 0.18s ease-out;
}
#configPanel.visible { right: 10px; }                            
                        

Toggle it with a gear button:

                            
function toggleConfigPanel() {
  configPanel.classList.toggle("visible");
}                            
                        

You can change colors on the fly. Pick a new Day color, and tiles, balls, everything updates instantly.

Step 5: Make colors change on the fly

I used getters everywhere.

                            
get color() { return ownership[idx] === 0 ? dayColor : nightColor; }                            
                        

In render:

                            
grid.forEach(drawTile); // Auto-pulls new colors                            
                        

Balls too:

                            
get color() { return nightColor; } // For Day ball, etc.                            
                        

Event listeners:

                            
dayColorInput.addEventListener("input", e => { dayColor = e.target.value; updateAllColors(); });                            
                        

No refresh is needed.

Step 6: Tie in the background gradient

The page background also changes live.

                            
function updateBackground() {
  document.body.style.background = `linear-gradient(to bottom, ${nightColor} 0%, ${dayColor} 100%)`;
}                            
                        

Call it in color handlers. Pick purple and gold, and the whole page gradients to match.

Step 7: Upgrade the loop to requestAnimationFrame

I ditched setInterval for smoother frames.

                            
function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // Draw grid, count scores, draw/move balls
  if (isPlaying) animationFrameId = requestAnimationFrame(render);
}                            
                        

I added randomness and speed clamps.

                            
ball.vx += (Math.random() - 0.5) * 0.1;
const speed = Math.sqrt(ball.vx ** 2 + ball.vy ** 2);
if (speed > 10) { /* scale */ }                            
                        

Step 8: Polish the UI with floating buttons

Fixed-position circles for reset, play/pause, and config.

                            
#resetBtn { right: 140px; }
#playPauseBtn { right: 80px; }
#configBtn { right: 20px; }                            
                        

The Result?

Dynamic Pong Wars

A living playground you can watch, tweak, pause, and restart. All in one click.

You can play with it here: markodenic.tech/dynamic-pong-wars

Full credit where it is due

Koen van Gilst originally made this “game”.
Go star his repo: github.com/vnglst/pong-wars.

How did you like this tutorial-style write-up? Should I do more of these kinds of tutorials? Let me know.

Get one quick tip every Saturday to learn to code, boost productivity, or monetize your content.