fitst version

This commit is contained in:
martijn.diersmann@pm.me
2026-09-26 22:21:07 +02:00
commit 32c7696bda
18 changed files with 3151 additions and 0 deletions
+143
View File
@@ -0,0 +1,143 @@
# 🏎️ 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**
(linkerhelft / rechterhelft van het scherm vasthouden).
- Keyboard-up / pijltjes op desktop.
- **Host** opent de kamer met `?host=<token>` en start heimelijk met de `S`-toets
(30-seconden countdown, nieuwe aanmelders blijven welkom).
- 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 ook gewoon een speler.** 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 😉).
### Besturing
| Apparaat | Actie |
| --- | --- |
| Telefoon | Linkerhelft vasthouden = links, rechterhelft = rechts |
| Toetsenbord | `←` / `A` en `→` / `D` |
| Host | `S` in lobby of uitslag = race (laten) starten |
## 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://<IP-van-je-node>:30090`
- Healthcheck: `curl http://<node-ip>: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 normale speler; 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`.