Multiplayer Coin Race — Real‑Time Web Game

A very simple but technically rich multiplayer browser game: players race to collect coins on a 2D board, with live leaderboards updating in real time.

2025-02 websocketsrealtimegamebackenddeployment
Multiplayer Coin Race — Real‑Time Web Game

Overview

I built a small, production‑ready multiplayer web game where players collect coins on a 2D board. The hard part isn’t graphics—it’s synchronizing state across clients in real time while keeping the server authoritative. I used Socket.IO for low‑latency messaging, implemented time‑based movement so speed is consistent across frame rates, and built a live leaderboard that updates for everyone instantly.

What it demonstrates

  • Networked game loops: update → broadcast → reconcile on the client.
  • State synchronization: server as the source of truth; clients render snapshots + minor local interpolation.
  • Concurrency & events in Node.js: handling many players without cross‑talk.
  • Secure web ops: Helmet (CSP, noSniff, hidePoweredBy), CORS, reverse proxying, SSL.
  • Real deployment: Node service (systemd) behind Nginx with an HTTPS subdomain.

Architecture (high level)

  • Frontend (Vanilla JS + Canvas):
    • Renders players/coins, sends input (intent) at a steady cadence.
    • Uses deltaTime to keep motion consistent.
    • Receives authoritative state packets and reconciles prediction.
  • Backend (Node + Express + Socket.IO):
    • Tracks players, coin positions, collisions, and scoring.
    • Validates moves, resolves coin pickups, respawns coins.
    • Broadcasts world snapshots and leaderboard updates.
  • Security & Deployment:
    • Helmet for CSP / header hardening; CORS restricted to the game origin.
    • Nginx reverse proxy → Node service managed via systemd (auto‑restart on crash).
    • DNS + SSL for a clean https://… subdomain.

Key details

  • Authoritative server: prevents client‑side cheating (e.g., fake hits/scoring).
  • Frame‑rate independent movement: positions update based on elapsed time, not frames.
  • Dynamic leaderboard: sorted by score and broadcast on each coin collection.
  • Resilience: systemd restarts on failure; Nginx health checks and access logs for debugging.

Ops notes

  • Nginx handles TLS and forwards ws/wss upgrades to the Node app.
  • systemd keeps the service alive (Restart=always) and logs to journalctl.
  • CSP blocks inline scripts and restricts origins to the game’s assets and socket endpoint.

Lessons learned

  • Keep the server authoritative even for tiny games; it massively simplifies trust and fairness.
  • Delta-time movement avoids “fast PCs move faster” bugs.
  • Socket message shape stability matters—small, consistent payloads beat ad‑hoc fields.

Stack

Node.jsExpressSocket.IOVanilla JSCanvasHelmetCORSNginxUbuntusystemd