I Took Pong Wars and made it Playable
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.
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?
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.