- JavaScript 60%
- CSS 22%
- HTML 18%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
- MAX_ROOMS, MAX_PLAYERS_PER_ROOM, RATE_CREATE und RATE_JOIN in die Tabelle aufgenommen, dazu kurz erklärt, wogegen sie schützen. - Die Reconnect-Beschreibung war nach der Token-Einführung nicht mehr korrekt: Raumcode + playerId allein genügen nicht mehr. Neuer Punkt zu Sitz-IDs. - Architektur-Block nennt jetzt public/vendor (inkl. lokaler Schrift). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
| public | ||
| .gitignore | ||
| build-cards.js | ||
| cards.json | ||
| original-karten.json | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| server.js | ||
| skala-karten.md | ||
🎲 Top 10
Ein kooperatives Party-Spiel im „Top Ten“-Stil als mobil-first Echtzeit-Webapp. Jede:r spielt am eigenen Handy, alle sehen synchron dieselbe Frage. Jede:r bekommt eine geheime Zahl von 1–10, die nur durch eine Wischgeste sichtbar wird.
Spielprinzip
- Alle ziehen verdeckt eine eindeutige Zahl aus 1–10 (so viele verschiedene Zahlen wie Spieler).
- Pro Runde gibt es eine Karte mit Thema und Skala „von … bis …“ (1 = links, 10 = rechts). Karten mit 🎭 werden vorgespielt statt gesagt.
- Jede:r gibt im echten Leben passend zur eigenen Zahl eine Antwort.
- Ein:e Käpten (rotiert reihum) ordnet die Antworten von 1 nach 10.
- Der Host (= wer den Raum erstellt) steuert die App (Start / Nächste Runde), spielt aber ganz normal mit. Host und Käpten sind getrennte Rollen.
Kartensätze
In der Lobby wählt der Host den Kartensatz:
- ⭐ Original Top Ten – 426 Themen der originalen Top-Ten-Karten
(abfotografiert und transkribiert,
original-karten.json) - 🃏 Eigene Karten – 325 selbst erstellte Themen (
skala-karten.md) - 🔀 Beide gemischt (Standard)
📱 Ein-Handy-Modus (Pass & Play)
Für Runden, in denen nur ein Gerät zur Hand ist: Auf dem Startscreen „Ein-Handy-Modus (Pass & Play)“ wählen, lokale Spielernamen anlegen (bleiben im localStorage gespeichert) und den Kartensatz wählen.
- Zu Rundenbeginn wird das Handy reihum weitergereicht: Jede:r tippt „Ich bin’s“, sieht per Wischgeste heimlich die eigene Zahl und gibt weiter.
- Danach geht das Handy zurück zum Käpten, der Thema, Plättchen und Rundenwechsel auf demselben Gerät steuert.
- „🔢 Zahl nochmal ansehen“ zeigt jede Zahl bei Bedarf erneut (Namensauswahl).
- Das Spiel läuft komplett im Client (Karten via
GET /api/cards); der Spielstand übersteht einen Reload (localStorage).
Team-Wertung (wie im Original)
- Zu Spielbeginn liegen so viele Einhorn-Plättchen 🦄 bereit, wie Spieler mitspielen (max. 8). Eine Partie geht über 5 Runden.
- Immer wenn der Käpten beim Sortieren einen Fehler macht (eine aufgedeckte Zahl ist kleiner als die vorherige), drückt er den 💩-Button – ein Plättchen wandert von der Einhorn- in die Häufchenzone (↩️ nimmt es zurück).
- Sind alle Plättchen Häufchen → sofort verloren. Nach der 5. Runde gibt es die Team-Wertung: 💩 nur Häufchen · 🐿️ mehr Häufchen als Einhörner · 🦄 mind. gleich viele Einhörner · 🌈 nur Einhörner (perfekt).
Setup & Start
Voraussetzung: Node.js ≥ 18.
npm install
node server.js
Danach läuft der Server auf http://localhost:3000. Beim Start gibt die Konsole zusätzlich die LAN-Adresse(n) aus, z. B.:
🎲 Top 10 läuft auf http://localhost:3000
Im selben WLAN erreichbar unter:
👉 http://192.168.x.y:3000
LAN-IP selbst finden
- macOS / Linux:
ipconfig getifaddr en0bzw.hostname -I - Windows:
ipconfig→ „IPv4-Adresse“
Alle Mitspieler müssen im selben WLAN sein und im Browser
http://<LAN-IP>:3000 öffnen — oder einfach den QR-Code in der Lobby scannen
(er kodiert genau diese Beitritts-URL inkl. Raumcode).
Läuft auf Port 3000 schon etwas anderes, kann ein anderer Port gesetzt werden:
PORT=3005 node server.js
Ablauf
- Startscreen: Name eingeben → „Raum erstellen“ (wird Host) oder Code eingeben + „Beitreten“.
- Lobby: Großer Raumcode + QR-Code zum Beitreten, Live-Spielerliste. Ab 3 Spielern kann nur der Host „Spiel starten“ drücken.
- Spiel: Oben Rundennummer + Käpten (👑), in der Mitte das Thema mit Skala-Balken, unten die verdeckte Geheimzahl-Karte: halten & nach oben schieben, um die eigene Zahl zu sehen — beim Loslassen verschwindet sie wieder. Der Host schaltet mit „Nächste Runde“ weiter (neue Karte, neue Zahlen, nächster Käpten — synchron bei allen).
Architektur
server.js Express + Socket.IO, Spielzustand im RAM (Rooms), Host-Logik
build-cards.js Parser: skala-karten.md -> cards.json (Validierung: 325 Karten)
cards.json generierte Kartendaten (wird beim Serverstart neu geparst)
skala-karten.md Quelle der 325 Karten
public/
index.html Screens (Start / Lobby / Spiel / Pass & Play)
style.css Glassmorphism, animierte Gradients, 60fps-Effekte
app.js Socket-Sync, Reconnect, QR, Konfetti, Wischgeste,
Ein-Handy-Modus (lokale Spiellogik + localStorage)
vendor/ lokal ausgelieferte Abhängigkeiten – kein CDN, kein Build:
qrcode.js, confetti.browser.min.js, outfit.css + fonts/
Wichtige Eigenschaften
- Geheimhaltung: Jede Geheimzahl wird per personalisiertem
state-Event nur an genau diesen Spieler gesendet — niemals alle Zahlen an alle. - Host serverseitig erzwungen: Start & Rundenwechsel prüfen serverseitig die
playerIdgegenhostId. Manipulierte Clients können nichts auslösen. - Reconnect: Jeder Spieler hat eine persistente
playerId(localStorage) und bekommt beim Beitritt zusätzlich ein serverseitig erzeugtes Token. Bei Reload/Abbruch tritt er mit Raumcode +playerId+ Token wieder bei und erhält Platz + aktuelle Geheimzahl zurück. Disconnects haben eine Karenzzeit (30 s). - Identitäten: Die
playerIdverlässt den Server nie — andere Spieler sehen imstatenur raumlokale Sitz-IDs (s1,s2, …). Zusammen mit dem Token verhindert das, dass jemand mit einer fremden ID deren Platz, Hostrechte oder Geheimzahl übernimmt. - Host-Migration: Ist der Host länger weg (> 60 s) oder verlässt er den Raum, wird der am längsten verbundene Spieler neuer Host (alle werden informiert).
- Aufräumen: Räume ohne aktive Spieler werden nach ~3 h Inaktivität entfernt.
Konfigurierbare Env-Variablen
| Variable | Default | Bedeutung |
|---|---|---|
PORT |
3000 |
HTTP-Port |
DISCONNECT_GRACE_MS |
30000 |
Karenzzeit, bis ein Spieler entfernt wird |
HOST_GRACE_MS |
60000 |
Karenzzeit, bis der Host migriert wird |
ROOM_IDLE_MS |
10800000 |
Inaktivität, bis ein Raum gelöscht wird |
SWEEP_INTERVAL_MS |
10000 |
Takt des Aufräum-Durchlaufs |
MAX_ROOMS |
500 |
gleichzeitig offene Räume insgesamt |
MAX_PLAYERS_PER_ROOM |
20 |
Spieler je Raum (Reconnects ausgenommen) |
RATE_CREATE |
5 |
createRoom-Versuche je IP und Minute |
RATE_JOIN |
20 |
joinRoom-Versuche je IP und 10 Sekunden |
Die unteren vier Werte begrenzen Missbrauch und sind für normales Spielen großzügig bemessen – im WLAN hat ohnehin jedes Gerät eine eigene IP.
MAX_ROOMSdeckelt den Speicherverbrauch. Es gibt nur 9000 mögliche Raumcodes (1000–9999); ist wirklich keiner mehr frei, lehnt der Server das Erstellen sauber ab, statt beim Suchen hängenzubleiben.RATE_JOINbremst das Durchprobieren der Codes: ungebremst wäre der komplette Coderaum in rund einer Sekunde durch, mit dem Default dauert es gut eine Stunde. Wer schon einen Platz im Raum hat, kommt davon unabhängig per Reconnect zurück.
Zusätzlich nimmt der Server nur Socket.IO-Verbindungen an, deren Origin zum
ausliefernden Host passt – eine fremde Seite im selben WLAN kann sich also
nicht im Hintergrund verbinden.
Betrieb auf diesem Server (Produktion)
Auf diesem Server läuft Top 10 als systemd-Dienst skala.service:
| Eigenschaft | Wert |
|---|---|
| Service | skala.service (/etc/systemd/system/skala.service) |
| Verzeichnis | /var/www/top10 |
| Port | 3005 (PORT=3005, gesetzt in der Unit) |
| Start | node server.js, Restart=on-failure, enabled (Autostart nach Reboot) |
| Logs | journalctl -u skala.service -f |
⚠️ Port 3000 ist NICHT frei – dort läuft eine andere App (
teach_serv). Top 10 hört deshalb auf 3005.
Änderungen ausrollen (Deploy)
Code- oder Karten-Änderungen werden erst nach einem Neustart des Dienstes aktiv. Der einfachste Weg ist der gebündelte Befehl:
npm run deploy # = build-cards + systemctl restart skala.service
Oder manuell:
npm run build-cards # cards.json aus skala-karten.md neu erzeugen (optional)
systemctl restart skala.service
journalctl -u skala.service -n 10 --no-pager # prüfen: "✅ N Karten korrekt geladen"
server.js parst skala-karten.md ohnehin bei jedem Start neu – build-cards
ist also nur nötig, wenn man cards.json für den Commit aktualisieren will.
Deploy-Hinweise (andere Plattformen, optional)
Der Server hält Zustand im Arbeitsspeicher und braucht persistente WebSocket-Verbindungen — geeignet sind Plattformen mit langlebigen Prozessen:
- Render / Railway / Fly.io: Node-Service, Startbefehl
node server.js, Port ausprocess.env.PORT(wird automatisch genutzt). - Sticky Sessions bzw. eine einzelne Instanz verwenden (kein Multi-Instance ohne geteilten Socket.IO-Adapter, da der State lokal im Prozess liegt).
Karten anpassen
skala-karten.md im Format N. <Thema> *Von <low> bis <high>.* bearbeiten
(🎭 = Performance-Karte). Beim nächsten Serverstart wird automatisch neu geparst;
manuell geht es mit npm run build-cards (schreibt cards.json).