Files
martijn.diersmann@pm.me 4bdac1e6e8 laatste versie
2026-09-26 23:25:04 +02:00

148 lines
5.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🏎️ 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=<token>` 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://<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 **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`.