No description
Find a file
Julian Appel 92eb59d2ef Teamdatenbank ohne lowdb, Pfad unabhaengig vom Startverzeichnis
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>
2026-08-19 20:39:12 +02:00
scoreboard Teamdatenbank ohne lowdb, Pfad unabhaengig vom Startverzeichnis 2026-08-19 20:39:12 +02:00
.gitignore Added socketio, and moment 2024-04-21 12:48:59 +02:00
README.md Added comments and readme 2026-04-10 22:16:51 +02:00

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 timerEnded an 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