feat(timeout): sostituisci toggleTimeout con startTimeout/stopTimeout e auto-chiusura a 30s

Serve una squadra richiedente e un video selezionabile, non più un semplice
flag. Il timer dei 30s è armato lato server (non nel controller) così
sopravvive a refresh/disconnessione/standby del tablet che pilota il gioco.
This commit is contained in:
2026-07-03 10:13:51 +02:00
parent 0441b0c7f5
commit d11b47c809
2 changed files with 52 additions and 3 deletions
+18 -2
View File
@@ -22,6 +22,9 @@ export function createInitialState() {
visuForm: false,
visuStriscia: true,
timeout: false,
timeoutTeam: null,
timeoutVideo: null,
timeoutStartedAt: null,
modalitaPartita: "3/5",
sp: {
striscia: [{ serv: 'h', ris: '', vinc: null }],
@@ -165,8 +168,21 @@ export function applyAction(state, action) {
break
}
case "toggleTimeout": {
s.timeout = !s.timeout
case "startTimeout": {
const team = action.team
if (team !== 'home' && team !== 'guest') break
s.timeout = true
s.timeoutTeam = team
s.timeoutVideo = action.video ?? null
s.timeoutStartedAt = action.startedAt ?? null
break
}
case "stopTimeout": {
s.timeout = false
s.timeoutTeam = null
s.timeoutVideo = null
s.timeoutStartedAt = null
break
}
+34 -1
View File
@@ -5,6 +5,8 @@ import { createInitialState, applyAction } from './gameState.js'
* @param {WebSocketServer} wss - Istanza del server WebSocket.
* @returns {Object} Oggetto con metodi di gestione dello stato.
*/
const DURATA_TIMEOUT_MS = 30000
export function setupWebSocketHandler(wss, options = {}) {
// Stato globale della partita.
let gameState = options.initialState ?? createInitialState()
@@ -12,6 +14,17 @@ export function setupWebSocketHandler(wss, options = {}) {
// Mappa dei ruoli associati ai client connessi.
const clients = new Map() // ws -> { role: 'display' | 'controller' }
// Timer che chiude automaticamente il time out dopo 30s, indipendentemente
// dal controller (sopravvive a refresh/disconnessione/standby del tablet).
let timeoutTimer = null
function annullaTimerTimeout() {
if (timeoutTimer) {
clearTimeout(timeoutTimer)
timeoutTimer = null
}
}
/**
* Gestisce i messaggi in arrivo dal client
*/
@@ -87,10 +100,16 @@ export function setupWebSocketHandler(wss, options = {}) {
return
}
// Il momento di inizio del time out lo decide il server (non il client),
// cosi' gameState.js resta puro/deterministico e testabile.
const action = msg.action.type === 'startTimeout'
? { ...msg.action, startedAt: Date.now() }
: msg.action
// Applica l'azione allo stato della partita.
const previousState = gameState
try {
gameState = applyAction(gameState, msg.action)
gameState = applyAction(gameState, action)
} catch (err) {
console.error('Error applying action:', err)
sendError(ws, 'Failed to apply action')
@@ -98,6 +117,20 @@ export function setupWebSocketHandler(wss, options = {}) {
return
}
// Qualsiasi azione che tocca il time out invalida il timer di auto-chiusura
// pendente; se il time out e' (ri)partito, ne arma uno nuovo.
if (action.type === 'startTimeout' || action.type === 'stopTimeout') {
annullaTimerTimeout()
}
if (action.type === 'startTimeout' && gameState.timeout) {
timeoutTimer = setTimeout(() => {
timeoutTimer = null
gameState = applyAction(gameState, { type: 'stopTimeout' })
broadcastState()
options.onStateChange?.(gameState)
}, DURATA_TIMEOUT_MS)
}
// Propaga il nuovo stato a tutti i client connessi.
broadcastState()
options.onStateChange?.(gameState)