- Elixir 59.9%
- Svelte 25.1%
- TypeScript 10.8%
- Shell 2.2%
- Dockerfile 1%
- Other 1%
Players.sanitize_display_name/1 als zentrale Funktion: - Steuerzeichen (x00–x1f, x7f) entfernen - Whitespace trimmen - Auf 50 Zeichen begrenzen (String.length, nicht byte_size) - Fallback auf "Spieler" Angewandt an allen Eingabe-Stellen: - GameController: do_create, do_create_vs_bot, join - GameChannel: join (display_name → GameServer) - DashboardChannel: find_match (validate_display_name) Private Kopie in GameController entfernt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|---|---|---|
| client | ||
| protocol | ||
| server | ||
| .gitignore | ||
| CLAUDE.md | ||
| container_start.sh | ||
| dev.sh | ||
| Dockerfile | ||
| README.md | ||
Schiffe versenken
Multiplayer-Browserspiel auf Basis von Elixir/Phoenix und Svelte 5.
Architektur
battleship/
├── server/ # Elixir/Phoenix – Spiellogik, WebSocket-API
├── client/ # Svelte 5 SPA – Browser-Client
├── protocol/ # Kanonische API-Typen (TypeScript) + OpenAPI-Spec
├── Dockerfile # Multi-stage Build (node → elixir → alpine)
└── container_start.sh # Build + Start mit automatischer Secret-Verwaltung
Der Server ist die authoritative Wahrheit für jeden Spielzustand – Schummeln ist nicht möglich. Kommunikation läuft ausschließlich über Phoenix Channels (WebSockets). SSL wird extern terminiert.
Spielfluss
Gegen einen anderen Spieler:
- Spieler A erstellt ein Spiel → erhält einen Join-Code (z. B.
A3F1-9C2E) - Spieler A teilt den Code oder die URL mit Spieler B
- Beide platzieren ihre Schiffe gleichzeitig
- Rundenbasiertes Schießen – bei Treffer darf nochmal geschossen werden
- Wer zuerst alle gegnerischen Schiffe versenkt, gewinnt
Gegen den Computer: Wie oben, aber Spieler B ist Admiral Silizium – ein serverseitiger Bot mit Hunt/Target-Strategie: zufälliges Schießen, nach Treffer gezielte Nachbarfelder bis zum Versenken. Der Admiral tritt als eigener Spieler in der Rangliste an.
Voraussetzungen
| Tool | Version |
|---|---|
| Elixir | ≥ 1.17 |
| Erlang/OTP | ≥ 27 |
| Node.js | ≥ 22 |
Docker (Linux) oder Apple container (macOS) |
beliebig |
Lokale Entwicklung
# Beides zusammen starten (empfohlen)
./dev.sh
Oder getrennt:
# Server einrichten und starten (Port 4000)
cd server
mix setup # deps.get + Datenbank anlegen + Migrationen (einmalig)
mix phx.server
# Client starten (Port 5173) – in einem zweiten Terminal
cd client
npm install
npm run dev
Der Client ist dann unter http://localhost:5173 erreichbar. Der Vite-Dev-Server leitet /api, /socket und /dashboard_socket automatisch an Phoenix weiter.
Tests & Qualität
cd server
mix test # alle Tests
mix precommit # compile --warnings-as-errors + format + test (vor jedem Commit)
Admin-Oberfläche
Erreichbar unter /admin mit dem ADMIN_TOKEN als Passwort:
- Wartungsmodus ein-/ausschalten (sperrt neue Spiele)
- Spielerliste mit Statistiken – einzelne Spieler löschbar
- Statistiken zurücksetzen (Spieler-Accounts bleiben erhalten)
- Aktive Gefechte und Gefechtshistorie einsehen
API-Dokumentation
Alle Datenstrukturen zwischen Server und Clients sind in protocol/ definiert:
| Datei | Inhalt |
|---|---|
protocol/events.ts |
TypeScript-Typen für HTTP und WebSocket (Single Source of Truth) |
protocol/openapi.yaml |
REST-API-Spec (POST /api/games, POST /api/games/:code/join) |
Deployment (Container)
Schnellstart
./container_start.sh
Das Script erledigt alles automatisch:
- Erkennt selbstständig, ob
docker(Linux) odercontainer(macOS) verfügbar ist - Beim ersten Aufruf: fragt nach Hostname und Port, generiert
SECRET_KEY_BASE,PLAYER_TOKEN_SECRETundADMIN_TOKENund speichert sie in.battleship-env - Bei jedem weiteren Aufruf: lädt die gespeicherten Werte – Secrets bleiben unverändert (wichtig:
PLAYER_TOKEN_SECRETmuss stabil bleiben, sonst werden alle laufenden Spieler-Tokens ungültig) - Baut das Image neu
- Stoppt einen ggf. laufenden Container und startet einen neuen im Hintergrund
Die Datei .battleship-env liegt im Projektverzeichnis, ist nur für den Besitzer lesbar und wird nicht eingecheckt.
Manuell (fortgeschritten)
Wer Build und Start selbst steuern möchte:
macOS:
container build -t battleship .
container run -d -p 4000:4000 \
-v battleship-data:/data \
-e SECRET_KEY_BASE=<64-byte-hex> \
-e PLAYER_TOKEN_SECRET=<64-byte-hex> \
-e PHX_HOST=battleship.example.com \
-e ADMIN_TOKEN=geheimes-passwort \
--restart unless-stopped \
battleship
Linux (Docker):
docker build -t battleship .
docker run -d -p 4000:4000 \
-v battleship-data:/data \
-e SECRET_KEY_BASE=<64-byte-hex> \
-e PLAYER_TOKEN_SECRET=<64-byte-hex> \
-e PHX_HOST=battleship.example.com \
-e ADMIN_TOKEN=geheimes-passwort \
--restart unless-stopped \
battleship
Secrets einmalig generieren: openssl rand -hex 64
Das Image enthält Client und Server in einem einzigen Container. Beim Start werden Datenbank-Migrationen automatisch ausgeführt.
Container verwalten
# Status prüfen
docker ps
# Logs verfolgen
docker logs -f battleship
# Stoppen / Neustarten
docker stop battleship
docker restart battleship
Konfiguration (Umgebungsvariablen)
| Variable | Pflicht | Beschreibung |
|---|---|---|
SECRET_KEY_BASE |
✅ | Phoenix-Session-Signing (64 Byte hex) |
PLAYER_TOKEN_SECRET |
✅ | Signing-Secret für Spieler-Tokens (64 Byte hex, muss über Neustarts stabil bleiben) |
ADMIN_TOKEN |
✅ | Bearer-Token für /api/admin-Endpoints |
PHX_HOST |
✅ | Öffentlicher Hostname (z. B. battleship.example.com) |
PORT |
– | HTTP-Port (Standard: 4000) |
DATABASE_PATH |
– | Pfad zur SQLite-Datei (Standard: /data/battleship.db) |
Reverse Proxy
Der Container exponiert nur Port 4000. Darüber laufen HTTP, die REST-API und WebSockets gemeinsam. Der Proxy muss lediglich WebSocket-Upgrades durchleiten.
nginx
server {
listen 443 ssl;
server_name battleship.example.com;
ssl_certificate /etc/ssl/certs/battleship.crt;
ssl_certificate_key /etc/ssl/private/battleship.key;
location / {
proxy_pass http://127.0.0.1:4000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Apache (mod_proxy)
Benötigte Module: proxy, proxy_http, proxy_wstunnel, ssl, rewrite.
<VirtualHost *:443>
ServerName battleship.example.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/battleship.crt
SSLCertificateKeyFile /etc/ssl/private/battleship.key
RewriteEngine on
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteRule ^/(.*)$ ws://127.0.0.1:4000/$1 [P,L]
ProxyPass / http://127.0.0.1:4000/
ProxyPassReverse / http://127.0.0.1:4000/
RequestHeader set X-Forwarded-Proto "https"
</VirtualHost>