Files
segnapunti/README.md
T
davide 00a8300a75 chore(release): bump versione a 2.1.0
Aggiorna package.json/package-lock.json, tag immagine in docker-compose.yml
e riferimenti nel README; changelog 2.1.0 con time out/spot video, modalità
amichevole, referto, layout esteso controller e flusso dev containerizzato.
2026-07-08 10:28:18 +02:00

295 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Segnapunti
![Version](https://img.shields.io/badge/versione-2.1.0-blue)
![Node](https://img.shields.io/badge/node-%3E%3D18-green)
![Docker](https://img.shields.io/badge/docker-ready-2496ED?logo=docker&logoColor=white)
![License](https://img.shields.io/badge/licenza-privata-lightgrey)
Segnapunti digitale in tempo reale per partite di pallavolo. Un server centrale gestisce lo stato della partita; un **display** mostra il tabellone pubblico e un **controller** (smartphone o tablet) permette all'operatore di gestire punti, formazioni e cambi.
---
## Indice
- [Architettura](#architettura)
- [Guida utente](#guida-utente)
- [Funzionalità](#funzionalità)
- [Deploy con Docker](#deploy-con-docker)
- [Sviluppo](#sviluppo)
- [Sviluppo con Docker](#sviluppo-con-docker)
- [Test](#test)
---
## Architettura
```
Controller (smartphone) ──WebSocket──┐
├── Server Node.js ── gameState.js
Display (schermo) ──WebSocket──┘ │
└── .segnapunti/state.json
```
Il server è l'unica fonte di verità. Ogni azione del controller viene elaborata e trasmessa in broadcast a tutti i client connessi. Lo stato viene salvato su disco ad ogni azione e ricaricato all'avvio, sopravvivendo ai riavvii del server.
| Percorso | Ruolo |
|---|---|
| `http://<host>:3000/display` | Tabellone pubblico — sola lettura |
| `http://<host>:3000/controller` | Pannello operatore — gestione partita |
| `ws://<host>:3000/ws` | WebSocket endpoint |
---
## Guida utente
### Scenario tipico: schermo fisso + smartphone operatore
#### 1. Avvia il server
```bash
docker compose up -d
```
All'avvio il terminale mostra gli URL locali e di rete.
#### 2. Apri il display
Collega il PC/server allo schermo via HDMI e apri il browser a schermo intero:
```
http://localhost:3000/display
```
Se il display è su un dispositivo separato nella stessa rete:
```
http://<IP-del-server>:3000/display
```
> **Trovare l'IP:** il server lo stampa all'avvio. In alternativa usa `ip a` su Linux.
#### 3. Apri il controller sullo smartphone
Connetti il telefono alla stessa rete Wi-Fi e apri:
```
http://<IP-del-server>:3000/controller
```
> **Installazione come app:** nel browser tocca *"Aggiungi a schermata Home"* per avere il controller come icona dedicata.
---
## Funzionalità
Il sistema è composto da due interfacce complementari: il **display** è in sola lettura e mostra al pubblico lo stato della partita; il **controller** è il pannello operatore da cui parte ogni azione. Tutto ciò che l'operatore tocca sul controller si riflette istantaneamente sul display via WebSocket.
### Display (tabellone pubblico)
Pagina di sola visualizzazione, pensata per uno schermo grande o un proiettore. Mostra:
- **Nomi squadre** con **indicatore di servizio** (l'icona della palla compare accanto alla squadra che sta servendo).
- **Punteggio del set corrente** in caratteri molto grandi, leggibili da lontano.
- **Contatore dei set vinti** per ciascuna squadra.
- **Striscia storica dei punti** del set in corso: una sequenza scorrevole che ricostruisce l'andamento punto su punto.
- **Modalità formazioni**: in alternativa al punteggio, mostra le posizioni dei 6 giocatori in campo (numeri di maglia disposti come a referto).
- **Ordine di visualizzazione invertibile**: le due squadre possono essere scambiate di lato per rispecchiare l'orientamento reale del campo.
- **Overlay time out / spot**: durante un time out copre il tabellone con un countdown e il video scelto dall'operatore, o con la scritta *TIME OUT* (vedi sotto).
- **Annuncio vocale del punteggio** (sintesi vocale del browser), comandato dal controller.
- **Indicatore di connessione**: scompare quando tutto è connesso, diventa un punto rosso lampeggiante con scritta *Disconnesso* in caso di problemi.
- **Fullscreen automatico** all'apertura sui dispositivi mobili.
### Controller (pannello operatore)
Il controller adatta automaticamente il proprio layout all'orientamento del dispositivo:
- **Verticale (smartphone)** — layout compatto: anteprima punteggio toccabile in alto e griglia di pulsanti sotto.
- **Orizzontale (tablet)** — layout esteso "da regia": due grandi pannelli squadra affiancati con punteggio, campo e tasto set, più una barra di azioni in basso.
#### Gestione del punteggio
- **Punto** — tocca il punteggio/la card di una squadra per assegnarle `+1`. Il sistema gestisce automaticamente il **cambio palla** e la **rotazione** della squadra che riconquista il servizio.
- **Annulla punto** — rimuove l'ultimo punto assegnato, ripristinando anche eventuale rotazione e servizio.
- **Tasti SET** — incrementano manualmente il contatore dei set vinti di una squadra (utile per correzioni); il contatore cicla da 0.
- **Dialog automatico SET VINTO / PARTITA FINITA** — compare da solo al raggiungimento delle condizioni di vittoria (25 punti, o 15 nel set decisivo, con 2 di scarto). Da qui puoi:
- **VAI AL SET SUCCESSIVO** — chiude il set, lo registra come vinto e prepara il set successivo aprendo la configurazione delle formazioni;
- **INDIETRO** — annulla l'ultimo punto se il dialog è comparso per errore;
- a partita conclusa, **REFERTO** per generare il referto e **CHIUDI** per tornare al tabellone.
#### Configurazione partita
Dal pulsante **Config** si apre una finestra che permette di impostare:
- **Nomi delle squadre** (Home e Guest);
- **Modalità partita**: `2/3` (al meglio dei 3 set), `3/5` (al meglio dei 5 set) o `Amichevole` (senza vittoria automatica della partita);
- **Formazioni iniziali**: i numeri di maglia dei 6 giocatori in campo per ciascuna squadra, disposti per zona.
#### Cambi giocatore
Il pulsante **Cambi** apre, dopo aver scelto la squadra, un dialog con righe `IN → OUT`. Le sostituzioni vengono **validate** prima di essere applicate: i numeri devono essere cifre, il giocatore entrante non può essere già in campo, quello uscente deve essere effettivamente in formazione, e nessuno può sostituire sé stesso. Eventuali errori sono segnalati in chiaro.
#### Servizio e visualizzazione
- **Cambio Palla** — assegna manualmente il servizio; disponibile **solo a 0-0** (a inizio set).
- **Inverti** — scambia il lato delle due squadre sul display.
- **Formazioni / Punteggio** — alterna sul display la vista formazioni in campo e la vista punteggio.
- **Striscia ON/OFF** — mostra o nasconde lo storico punti del set sul display.
- **Voce** — fa annunciare al display il punteggio corrente con la sintesi vocale (es. *"15 a 12"*, *"15 pari"*, *"zero a zero"*).
#### Time Out e spot pubblicitari
Il pulsante **Time Out** apre una finestra in cui l'operatore sceglie **quale squadra** ha chiamato il time out (obbligatorio) e **quale video** proiettare sul display, scegliendo tra i file della cartella `spot/` oppure *nessun video* (overlay con la scritta *TIME OUT*). Alla conferma:
- il **display** copre il tabellone con un overlay a tutto schermo: countdown in sovraimpressione e video scelto; se non c'è video, o se il video finisce prima del tempo, compare la scritta *TIME OUT* con il nome della squadra. Nell'ultimo secondo il video sfuma in dissolvenza.
- il **controller** resta pienamente operativo: compare un banner *TIME OUT <squadra>* con il conto alla rovescia e il pulsante Time Out mostra i secondi rimanenti. Ripremendolo si chiude il time out in anticipo.
- il **server** chiude automaticamente il time out dopo **30 secondi**, anche se il controller si disconnette o va in standby.
I video da proiettare vanno messi in una cartella **`spot/`** nella root del progetto (non versionata): vengono elencati nella finestra di scelta in ordine alfabetico (solo file `.mp4`). Se un display si riconnette a metà time out, il video riparte dal punto corretto.
> **Audio:** i video partono con audio, ma il display è in genere uno schermo senza tastiera/mouse e i browser bloccano l'autoplay con suono senza un'interazione dell'utente. In assenza di configurazione il video parte automaticamente **muto** (il video si vede comunque). Per avere l'audio, avvia il browser del display in modalità kiosk consentendo l'autoplay con suono (es. Chrome con `--autoplay-policy=no-user-gesture-required`, oppure impostando un'eccezione del sito per audio/video).
#### Reset e referto
- **Reset** — azzera la partita (punteggi, set e formazioni) previa conferma, e riapre subito la configurazione per impostare la partita successiva.
- **Referto** — a fine partita, dal dialog *PARTITA FINITA*, genera un referto di gara stampabile/PDF con i punteggi per set, le formazioni di partenza e l'andamento punto-punto. *Funzione prototipale.*
### Regole pallavolo integrate
Le condizioni di vittoria del set sono applicate automaticamente dal server (è quello che fa comparire il dialog *SET VINTO*):
| Set | Condizione di vittoria |
|---|---|
| Set 14 (modalità 3/5) o 12 (modalità 2/3) | Primo a **25** con almeno 2 punti di scarto |
| Set decisivo (tie-break) | Primo a **15** con almeno 2 punti di scarto |
La **partita** è vinta da chi raggiunge 3 set su 5 (modalità `3/5`) o 2 set su 3 (modalità `2/3`). In modalità **Amichevole** non c'è vittoria automatica della partita: si continua a giocare set senza che compaia *PARTITA FINITA*.
---
## Deploy con Docker
### Prima installazione
```bash
docker compose up -d
```
Lo stato viene salvato nella cartella `./.segnapunti` montata come volume nel container e sopravvive ai riavvii.
> **Video spot in Docker:** la cartella `spot/` non è inclusa nell'immagine. Per usare i video del time out aggiungi un volume in `docker-compose.yml`: `./spot:/app/spot`.
### Aggiornamento a nuova versione
```bash
docker compose pull && docker compose up -d
```
### Build e pubblicazione immagine
Il `Dockerfile` è multi-stage (`deps``dev` / `builder``runtime`); senza specificare `--target`, il build produce l'ultimo stage (`runtime`), l'immagine di produzione:
```bash
docker build \
-t santantonio.sytes.net/attilio/segnapunti:2.1.0 \
-t santantonio.sytes.net/attilio/segnapunti:latest .
docker push santantonio.sytes.net/attilio/segnapunti:2.1.0
docker push santantonio.sytes.net/attilio/segnapunti:latest
```
Per testare in locale l'immagine di produzione senza pubblicarla:
```bash
docker build -t segnapunti:local .
docker run --rm -p 3000:3000 -v ./.segnapunti:/app/.segnapunti segnapunti:local
```
---
## Sviluppo
### Requisiti
| Strumento | Versione minima |
|---|---|
| Node.js | >= 18 |
| npm | >= 9 |
### Avvio
```bash
npm install
npm run dev
```
| URL | Interfaccia |
|---|---|
| `http://localhost:5173/display` | Display |
| `http://localhost:5173/controller` | Controller |
Lo stato viene salvato in `.segnapunti/state.json` anche in modalità dev.
### Comandi disponibili
| Comando | Descrizione |
|---|---|
| `npm run dev` | Dev server con hot reload |
| `npm run build` | Build di produzione in `dist/` |
| `npm run serve` | Build + avvio server produzione |
---
## Sviluppo con Docker
In alternativa a Node.js installato in locale, `docker-compose.dev.yml` builda il target `dev` del `Dockerfile` e monta il codice sorgente nel container, con hot reload identico a `npm run dev`:
```bash
docker compose -f docker-compose.dev.yml up --build
```
| URL | Interfaccia |
|---|---|
| `http://localhost:5173/display` | Display |
| `http://localhost:5173/controller` | Controller |
Le modifiche ai file sorgente sull'host si riflettono subito nel container (bind-mount); `node_modules` resta invece un volume anonimo del container, per non farsi sovrascrivere dal bind-mount e per evitare incompatibilità tra i binari nativi installati sull'host e quelli richiesti dal container (es. host ARM / container `node:20-alpine` su x86).
Per fermare e rimuovere il container:
```bash
docker compose -f docker-compose.dev.yml down
```
---
## Test
| Comando | Descrizione |
|---|---|
| `npm run test:unit` | Unit + integration (Vitest) |
| `npm run test:component` | Componenti Vue (Happy-DOM) |
| `npm run test:stress` | Load test WebSocket (50+ client) |
| `npm run test:all` | Tutti i test tranne E2E |
| `npm run test:ui` | Vitest con interfaccia grafica |
| `npm run test:e2e` | Playwright — Chromium, Firefox, Mobile Chrome |
Le suite Vitest coprono la logica di gioco (`gameState`, inclusa `formInizio`),
il protocollo e il routing del server (incluso l'avvio effettivo di
`startServer`), la persistenza, il rilevamento di rete/piattaforma
(`server-utils`, inclusi i rami WSL/PowerShell), il client WebSocket
(`wsMixin`, connessione/riconnessione/cleanup), i componenti Vue e la
generazione del referto (inclusi i suoi side-effect di stampa). La copertura
statement complessiva è oltre il 99%.
> I test E2E richiedono il server in esecuzione (`npm run serve`) e i browser Playwright installati:
> ```bash
> npx playwright install chromium firefox
> ```
---
## Changelog
Vedere [CHANGELOG.md](CHANGELOG.md) per la storia delle versioni.