Skip to content
Carlos Morais.
Work

Alpha EdTech · Project lead

RaceGame: real-time multiplayer racing

A top-down race for up to 10 players on the same track, where the server decides where every car is and the browser draws the race without waiting for it.

Arrow keys or WASD to drive, space for nitro, Enter to start, R to restart and Esc to pause. Blue badges with a bolt are nitro; barrels, logs and cones slow you down. The dashed blue line marks the next checkpoint.

Car colour
Difficulty
Car colour

On a phone, use the buttons below the track.

Arrow keys or WASD to drive, space for nitro, Enter to start, R to restart and Esc to pause. Blue badges with a bolt are nitro; barrels, logs and cones slow you down. The dashed blue line marks the next checkpoint.

In 30 seconds

  • Lead of a team of 4 on an Alpha EdTech project, built in 3 weeks.
  • I wrote the game engine on both sides: an authoritative server at 30 ticks/s, client-side prediction and interpolation, over WebSocket.
  • Tested with a full room of 10 players. In 2026 I rebuilt it with Claude Code to run on this page.
When
Nov 2024 · 3 weeks
Role
Project lead · game engine
Team
4 people
Stack
TypeScript, React, Canvas, Node.js, Express, ws, PostgreSQL, Redis, Docker

Context

A cycle 2 project at Alpha EdTech, a non-profit code academy that trains developers full-time on real projects. The team had three weeks to ship a browser multiplayer game, with sign-in, rooms and a leaderboard.

We picked a top-down race: a track with walls, checkpoints, laps, nitro pickups and up to 10 cars racing at once.

The problem

In a real-time race everyone has to see the same race. If each browser computes its own position, two players disagree on who is ahead, and anyone can cheat by editing the JavaScript.

If instead the browser only draws what the server sends, your own car answers the arrow keys with network lag, and the other cars jump between one update and the next.

My part

As lead, I coordinated the team and drove the technical decisions and the frontend/backend integration. In the code, I owned the game engine on both sides. The rest of the team handled sign-in, rooms and the lobby, and the screens.

  • Server game loop: speed and steering physics, wall collision, checkpoints, laps, pickups and the end of the race.
  • Client game loop on Canvas, with prediction for your own car and interpolation for opponents.
  • A large share of the WebSocket server and the client/server message contract.
  • Production Docker Compose (frontend, backend, PostgreSQL and Redis).

Technical decisions

The server is the source of truth

The browser never sends a position, only which keys are pressed. The server runs the loop at 30 ticks per second, applies physics and collisions and decides where every car is. There is no way to “teleport” a car from the console.

Client-side prediction, with reconciliation

So your own car doesn't wait on the network, the client runs the same physics locally and keeps every move numbered. When the server state arrives, it compares by move number and only corrects the position if the two versions disagree.

Interpolation for the other cars

Opponents are not predicted: on every frame, each car covers half the distance to its last received position. That removes the jitter between updates without inventing movement.

Only send what changed

Before broadcasting, the server compares position, speed and rotation with the previous tick. A car that isn't moving produces no message, which cuts traffic with 10 players in a room.

WebSocket without Socket.IO

On the server, the ws library; in the browser, the native WebSocket API. Messages follow a typed TypeScript contract on both sides. Fewer layers between the loop and the wire, and every message is ours.

Architecture

In the 2024 version, the browser sends keys and draws; the Express server authenticates over the REST API, keeps users and cars in PostgreSQL and live rooms in Redis, and runs the game loop that broadcasts state over WebSocket.

Outcome

The game shipped and was tested with a full room, 10 players in the same race, running on an Alpha EdTech server. The video in the next section is a clip of that match.

The 2026 rewrite

The playable version at the top of this page is a 2026 reimplementation, built from the 2024 engine. The original depended on sign-in, the database and Alpha EdTech's server, and its art isn't licensed for republishing; to make it playable here, the game was rebuilt with the same handling and the same track.

The rewrite was done with Claude Code, Anthropic's AI coding tool: I decided the design, the physics and what went into each round, played and reviewed every one, and the code was written in pairing with the AI.

  • The engine became a dependency-free TypeScript package, with every tick deterministic and tested, including a property test that guarantees a car never ends a move inside a wall.
  • Collision was redone: the car moves one axis at a time and bounces off walls instead of sticking to them, which ended cars getting stuck in a corner of the track.
  • Three modes: practice, against bots, and time trial, against the ghost of your best run, run entirely in the browser; online runs on the server (NestJS and ws, 30 ticks per second), and the browser uses the same engine to predict your own car.
  • A compact message format (about 11 KB/s per player in a room of 10) and abuse limits protect the free-tier server.
  • The track is drawn in code from the engine's collision boxes.

Original version, November 2024: a test match with a full room, 10 players.