Skip to content
Yasir Uslu
All work

Real-time multiplayer game

Okey 101 — Online Table

A 2–4 player, full-rules Turkish Okey 101 that runs in the browser. Invited players share a real-time table; solo players face bots. It works in portrait and landscape on phones and full screen on desktop.

Status
Live
Tools
Next.js 16 · React 19 · TypeScript · Supabase Realtime · Web Crypto · WebAudio · Playwright
Okey 101 — Online Table screenshot

The problem

Browser multiplayer tile games have two hard parts: cheating (peeking at other hands or the deck) and phones dropping the connection. The goal was to solve both without running a dedicated game server.

My role

Sole developer: rules engine, bot, multiplayer layer, encryption, UI, audio and tests.

Approach

  1. 01

    Host-authoritative design

    The rules are a pure reducer. The host’s browser runs the game state and Supabase Realtime only relays messages. There is no game server, and the engine is testable in isolation.

  2. 02

    End-to-end encrypted hands

    Each player generates an ECDH P-256 key pair; the host seals every hand separately with AES-GCM. The public broadcast never contains the deck, other hands or buried discard tiles.

  3. 03

    Open to everyone, still fair

    No sign-in is needed; anyone with the room code can join. Although the channel is public, hands are encrypted, so nobody can see other players’ tiles or the deck, and rules and turns are enforced only in the host’s browser.

  4. 04

    Resilient to disconnects

    Sessions persist on the device and reconnect with backoff. Guest moves carry an id and are retried until the host acknowledges them; a bot covers for a dropped player after 25 seconds.

  5. 05

    Bots, hints and feel

    A memoized search for the best runs and sets drives both the bots and the hint button. Tile sounds are physically synthesized with WebAudio, tiles fly in arcs and iPhones get haptics.

The hard part

An empty rack after refresh

Guests sometimes came back to an empty rack after a refresh. Tracing showed two causes: the old connection’s presence entry lingered, so the host kept sealing with the stale key, and two copies of the same state (old and new key) raced each other. Picking the newest presence and processing states in order fixed it for good.

Quality

Over 360 unit tests (Vitest) and a two-browser end-to-end test against real Supabase (Playwright) covering the access gate, RLS, sealed hands and reconnects. The end-to-end suite runs nightly on GitHub Actions.