# 🏎️ Turbo Race Multiplayer browser-racegame voor **tot 20 spelers**, speelbaar op de telefoon, met een neon/synthwave-look. Server-authoritatief (Node.js + WebSocket), deploybaar op je eigen Kubernetes-node. ## Features - Verticale scrollende weg, portretmodus, **eenhandig besturen** (zet je vinger op de baan; de auto rijdt direct naar die plek en blijft staan). - Keyboard-up / pijltjes op desktop. - **Host** opent de kamer met `?host=` en start heimelijk met de `S`-toets (30-seconden countdown, nieuwe aanmelders blijven welkom). De host **rijdt niet mee**, maar volgt als toeschouwer de **koploper (#1)** en ziet live de top 5 in het koploperspaneel. - Obstakels: **pylonen** (vertragen) en **olievlekken** (wegslijden). - Power-ups: **⚡ boost** en **🛡 schild** (respawnen na 4,5s). - Live positie, snelheid, afstand, leaderboard en minimap. - **1:30 minuten** race over een baan van 23.000 m; wie over de finish is wint, anders wint bij aflopen van de tijd wie het verst is. Daarna **uitslag + podium** en direct een nieuwe ronde. ## Snel starten (lokaal) ```bash npm install npm start # → http://localhost:8080 ``` Open `http://localhost:8080` op je laptop én op je telefoon (zelfde wifi). Vul een naam in, klik aanmelden, en stuur met je vingers. > 🎮 **De host is toeschouwer en rijdt niet zelf mee.** Open als host: > `http://localhost:8080/?host=dev-host` > en druk op de **S**-toets om de start-telling in gang te zetten > (pas als de kamer vol is of iedereen klaar is 😉). Tijdens de race volgt de > **camera de nummer 1** (ook bij wisselende koppositie) en zie je live de > **top 5 koplopers**; na afloop de volledige uitslag. ### Besturing | Apparaat | Actie | | --- | --- | | Telefoon | Zet je vinger op de baan; de auto rijdt direct naar die plek en blijft staan tot je beweegt | | Toetsenbord | `←` / `A` en `→` / `D` | | Host | `S` in lobby of uitslag = race (laten) starten · camera volgt de koploper (#1), rijdt niet mee | ## Configuratie (omgevingsvariabelen) | Variabele | Standaard | Betekenis | | --- | --- | --- | | `PORT` | `8080` | HTTP-poort (ook WebSocket op `/ws`) | | `MAX_PLAYERS` | `20` | Maximaal aantal coureurs per kamer | | `ADMIN_TOKEN` | `dev-host` | Geheim token waarmee je host wordt | | `COUNTDOWN_S` | `30` | Aftellen nadat de host op `S` drukt | | `RACE_DURATION_S` | `90` | Maximale racetijd in seconden | | `TRACK_LENGTH` | `23000` | Baanglengte in meter (finish = einde van de baan) | > ⚠️ Gebruik in productie een **sterk `ADMIN_TOKEN`** (random string), anders kan > iedereen met de URL host worden. ## Tests Headless simulatie: engine-unit-test **plus** live integratietest met een echte server, 20 websocket-clients en een host die de race start en de uitslag leest. ```bash npm test ``` ## Docker ```bash docker build -t turbo-race:latest . docker run --rm -p 8080:8080 \ -e ADMIN_TOKEN='moeilijk-te-raden-token' \ turbo-race:latest ``` ## Deployen op Kubernetes Manifests staan in `k8s/` (Kustomize). Bouw de image op je node (of push naar een registry) en pas de image-tag in `k8s/base/deployment.yaml` aan. ```bash # 1) image bouwen (op de node zelf) docker build -t turbo-race:latest . # 2) token instellen (kustomization generatiert een Secret) # Bewerk de literal 'admin-token=...' in k8s/base/kustomization.yaml # 3) deployen kubectl apply -k k8s/base kubectl rollout status deploy/turbo-race kubectl get svc turbo-race # -> NodePort 30090 ``` Speel nu via: `http://:30090` - Healthcheck: `curl http://:30090/healthz` → `{"ok":true,...}` - Met een domein + ingress-controller voeg je `k8s/ingress.yaml` toe (pas de `host` aan, WebSocket-tunnels worden lang opengehouden). ### Lokaal testen met Kind (macOS/Linux) Op een **echte node** is NodePort 30090 direct bereikbaar op het node-IP. Met **Kind** moet de poort wél expliciet naar de host gemapt worden: dienst `k8s/kind-config.yaml` mapt `30090 → localhost`. ```bash kind create cluster --config k8s/kind-config.yaml # alleen bij (her)aanmaken kind load docker-image turbo-race:latest kubectl apply -k k8s/base # speel op: http://localhost:30090 ``` > ℹ️ Kind hergebruikt een bestaand cluster; als NodePort niet naar `localhost` > werkt, verwijder het cluster eerst (`kind delete cluster`) en maak het opnieuw > aan met bovenstaande `--config` (port-mappings staan vast bij creatie). > ⚠️ **Belangrijk:** de game staat **in-memory** (1 kamer). Houd > `replicas: 1` — meerdere replica's zouden spelers over losse kamers verspreiden. ## Architectuur ``` public/ frontend (geen bundler): index.html, style.css, client.js, render.js server/engine.js server-side spelengine: fysica, obstakels, power-ups, fases, ranking server/index.js HTTP/static + WebSocket /ws + events + broadcasts test/simulate.js headless test (fluweel: 20 clients ipv 1) ``` - Engine loop 40 Hz; **snapshots op ~16 Hz**; clients **interpoleren** de rendering. - Baan is **deterministisch** opgebouwd uit één random seed → eerlijk & identiek voor elke speler. - De host is een **toeschouwer** (neemt geen spelerslot in); de `host_start`-melding wordt alleen geaccepteerd van sockets die met het juiste token zijn aangemeld. ## Flow & regels 1. **Lobby** — namen aanmelden tot max 20 (fases live zichtbaar). 2. Host drukt `S` → **30s countdown** (heen-en-weer banners, nieuwe spelers welkom). 3. **Race** — rechte weg met obstakels en power-ups; finish is het einde van de baan. Wie als eerste over de line is, wint; anders wint bij het aflopen van de tijd wie het verst is. 4. **Uitslag** — podium + volledige lijst; de host start opnieuw met `S`.