No description
  • JavaScript 60%
  • CSS 22%
  • HTML 18%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jonas Krug 45b0185974 Doku: Env-Variablen der Härtung und korrigierte Reconnect-Beschreibung
- 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>
2026-07-29 18:25:32 +00:00
public Ein-Handy-Modus, Sicherheits-Härtung und Responsive-Fixes 2026-07-29 18:21:09 +00:00
.gitignore Top 10: Bugfixes für Zahlen, Käpten-Skip und überarbeitete Kartensätze 2026-06-27 23:00:46 +00:00
build-cards.js Originale Top-Ten-Karten, Kartensatz-Auswahl, offizielle Regeln & Team-Wertung 2026-07-05 15:38:20 +00:00
cards.json Originale Top-Ten-Karten, Kartensatz-Auswahl, offizielle Regeln & Team-Wertung 2026-07-05 15:38:20 +00:00
original-karten.json Originale Top-Ten-Karten, Kartensatz-Auswahl, offizielle Regeln & Team-Wertung 2026-07-05 15:38:20 +00:00
package-lock.json Top 10: Bugfixes für Zahlen, Käpten-Skip und überarbeitete Kartensätze 2026-06-27 23:00:46 +00:00
package.json Doku: Produktions-Deploy via skala.service (Port 3005) im README + npm run deploy 2026-06-27 23:18:46 +00:00
README.md Doku: Env-Variablen der Härtung und korrigierte Reconnect-Beschreibung 2026-07-29 18:25:32 +00:00
server.js Ein-Handy-Modus, Sicherheits-Härtung und Responsive-Fixes 2026-07-29 18:21:09 +00:00
skala-karten.md Top 10: Bugfixes für Zahlen, Käpten-Skip und überarbeitete Kartensätze 2026-06-27 23:00:46 +00:00

🎲 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 en0 bzw. 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

  1. Startscreen: Name eingeben → „Raum erstellen“ (wird Host) oder Code eingeben + „Beitreten“.
  2. Lobby: Großer Raumcode + QR-Code zum Beitreten, Live-Spielerliste. Ab 3 Spielern kann nur der Host „Spiel starten“ drücken.
  3. 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 playerId gegen hostId. 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 playerId verlässt den Server nie — andere Spieler sehen im state nur 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_ROOMS deckelt 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_JOIN bremst 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 aus process.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).