lowdb v7 ist ESM-only, das Projekt ist CommonJS. Der bisherige Umweg ueber eine async IIFE mit await import() hatte zur Folge, dass db bei einem sehr fruehen Request noch undefined sein konnte. Fuer eine Liste von Teamnamen genuegt fs: read/write lesen und schreiben db.json direkt und synchron, die Race-Condition entfaellt und lowdb kann als Abhaengigkeit raus. Die exportierte API bleibt unveraendert, score.js und routes/admin.js brauchen keine Anpassung. Der Pfad zu db.json wird jetzt ueber __dirname aufgeloest statt relativ zum Arbeitsverzeichnis, damit die Datei unabhaengig vom Startverzeichnis immer an derselben Stelle liegt. Fehlt oder bricht die Datei, wird auf eine leere Teamliste zurueckgefallen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| scoreboard | ||
| .gitignore | ||
| README.md | ||
Scoreboard-JS
Echtzeit-Anzeigetafel für Sportveranstaltungen — zeigt Timer und Spielstand auf einem separaten Monitor an und wird über ein Admin-Panel gesteuert.
Übersicht
Das System besteht aus zwei Teilen:
| Teil | URL | Beschreibung |
|---|---|---|
| Monitor | http://<host>:3000/ |
Vollbildanzeige für den Beamer/Monitor (Timer + Score) |
| Admin-Panel | http://<host>:3000/admin |
Steuerung von Timer und Scoreboard |
Alle Änderungen vom Admin-Panel werden über WebSockets (Socket.IO) in Echtzeit auf dem Monitor angezeigt — ohne Seiten-Reload.
Voraussetzungen
- Node.js (v18 oder neuer empfohlen)
- npm
Installation & Start
cd scoreboard
npm install
npm start
Der Server startet mit nodemon und ist anschließend unter Port 3000 erreichbar.
Der Socket.IO-Server läuft separat auf Port 3001.
Projektstruktur
scoreboard/
├── app.js # Express-App, Middleware, Routen
├── bin/www # HTTP-Server, startet die App
├── controllers/
│ ├── socketio.js # Socket.IO-Server (Port 3001)
│ ├── timer.js # Timer-Logik (Start, Pause, Reset, IncDec)
│ ├── score.js # Score-Logik (Punkte, Teams, Seitenwechsel)
│ └── cli.js # CLI-Controller (aktuell Testcode)
├── routes/
│ ├── admin.js # REST-Endpunkte für das Admin-Panel
│ └── index.js # Monitor-Ansicht + WebSocket-Verbindungshandler
├── views/
│ ├── admin.hbs # Admin-Panel (Handlebars-Template)
│ ├── index.hbs # Monitor ohne Scoreboard (nur Timer)
│ └── indexScore.hbs # Monitor mit Scoreboard (Timer + Score + Teamnamen)
└── public/
├── javascripts/
│ ├── admin.js # Frontend-Logik Admin-Panel
│ ├── index.js # Frontend-Logik Monitor (ohne Score)
│ └── indexScore.js # Frontend-Logik Monitor (mit Score)
└── stylesheets/
├── admin.css
├── index.css
├── indexScore.css
└── seven-segment.css # Seven-Segment-Display-Schrift für den Timer
Funktionen
Timer
- Countdown-Timer mit konfigurierbarer Startzeit (Standard: 7:00 Minuten)
- Start / Pause
- Zurücksetzen auf eine neue Zeit (0:00 bis 10:00, in 15-Sekunden-Schritten)
- Feineinstellung im laufenden Betrieb: ±1s, ±5s, ±10s
- Nach Ablauf wird
timerEndedan alle Clients gesendet und "ENDE" auf dem Monitor angezeigt
Scoreboard
- Punktestand für Team A und Team B
- Punkte einzeln erhöhen (+1) oder verringern (-1)
- Score direkt auf einen bestimmten Wert setzen
- Score zurücksetzen (beide Teams auf 0)
- Scoreboard ein-/ausblenden: Der Monitor wechselt zwischen Timer-only-Ansicht (
index.hbs) und Timer+Score-Ansicht (indexScore.hbs)
Teams konfigurieren
Jedes Team hat folgende Felder:
| Feld | Beschreibung |
|---|---|
name |
Hauptname des Teams |
name2 |
Zweiter Name (nur bei Spielgemeinschaft) |
isSpielgemeinschaft |
Wenn aktiv, werden beide Namen untereinander angezeigt |
Seitenwechsel (Halbzeit)
Über "Seitenwechsel" werden Team A und Team B auf dem Monitor die Seiten tauschen — der Spielstand bleibt unverändert, nur die Anzeigepositionen (links/rechts) wechseln.
Monitor neu laden
Der Button "Monitor neu laden" im Admin-Panel sendet ein refresh-Event an alle verbundenen Clients und löst einen Seiten-Reload des Monitors aus. Nützlich z.B. nach dem Ein-/Ausblenden des Scoreboards.
Architektur / Datenfluss
Admin-Panel (Browser)
│
│ HTTP REST (fetch)
▼
Express-Server :3000
routes/admin.js
│
├── controllers/timer.js ──┐
└── controllers/score.js ──┤
│ io.sockets.emit(event, data)
▼
Socket.IO-Server :3001
│
WebSocket-Verbindung
│
┌───────────────┴───────────────┐
▼ ▼
Monitor (index.hbs) Monitor (indexScore.hbs)
index.js indexScore.js
WebSocket-Events
| Event | Richtung | Beschreibung |
|---|---|---|
timerDurationLeft |
Server → Client | Aktuelle Restzeit als String MM:SS |
timerEnded |
Server → Client | Timer abgelaufen |
score |
Server → Client | Aktueller Spielstand als String X:Y |
scoreSideswitch |
Server → Client | Seitenwechsel ausgelöst — Frontend neu laden |
refresh |
Server → Client | Kompletten Seiten-Reload auslösen |
REST-API (Admin-Endpunkte)
Alle Endpunkte sind unter /admin erreichbar.
Allgemein
| Methode | Endpunkt | Beschreibung |
|---|---|---|
| GET | /admin/refreshMonitor |
Sendet refresh an alle Monitors |
Timer
| Methode | Endpunkt | Body | Beschreibung |
|---|---|---|---|
| GET | /admin/timerStart |
— | Timer starten |
| GET | /admin/timerPause |
— | Timer pausieren |
| POST | /admin/timerReset |
{ duration: <Sekunden> } |
Timer zurücksetzen |
| POST | /admin/timerIncDec |
{ value: <Sekunden> } |
Timer erhöhen (positiv) / verringern (negativ) |
| GET | /admin/timerGetValues |
— | Aktuelle Timer-Werte abrufen |
Antwortformat timerGetValues:
{
"isPaused": true,
"duration": "<ISO 8601>",
"durationLeft": "<ISO 8601>",
"print": "07:00"
}
Scoreboard
| Methode | Endpunkt | Body | Beschreibung |
|---|---|---|---|
| GET | /admin/scoreToggle |
— | Scoreboard ein-/ausblenden |
| GET | /admin/scoreGetValues |
— | Aktuelle Score-Werte abrufen |
| GET | /admin/scoreClearScore |
— | Spielstand beider Teams auf 0 |
| GET | /admin/scoreToggleSideswitch |
— | Seiten der Teams tauschen |
| POST | /admin/scoreSetScore |
{ team, score } |
Score direkt setzen |
| POST | /admin/scoreAlterScore |
{ team, dir } |
Score um 1 ändern (dir: "inc" / "dec") |
| POST | /admin/scoreConfigTeams |
{ teamA: { name, name2, isSpielgemeinschaft }, teamB: { ... } } |
Teams konfigurieren |
team ist jeweils "teamA" oder "teamB".
Antwortformat scoreGetValues:
{
"enabled": false,
"teamA": { "name": "Team A", "name2": "", "score": 0, "isSpielgemeinschaft": 0 },
"teamB": { "name": "Team B", "name2": "", "score": 0, "isSpielgemeinschaft": 0 },
"sideswitch": false,
"print": "0:0"
}
Abhängigkeiten
| Paket | Verwendung |
|---|---|
express |
HTTP-Server und Routing |
socket.io |
WebSocket-Kommunikation |
moment |
Timer-Berechnung und Zeitformatierung |
hbs |
Handlebars als View-Engine |
morgan |
HTTP-Request-Logging |
nodemon |
Automatischer Server-Neustart bei Dateiänderungen |