diff --git a/src/components/ControllerPage.vue b/src/components/ControllerPage.vue index c2cfee3..f0ee409 100644 --- a/src/components/ControllerPage.vue +++ b/src/components/ControllerPage.vue @@ -10,15 +10,15 @@
{{ state.sp.nomi.home }}
-
{{ state.sp.punt.home }}
-
SET {{ state.sp.set.home }}
- Servizio +
{{ punt.home }}
+
SET {{ set.home }}
+ Servizio
{{ state.sp.nomi.guest }}
-
{{ state.sp.punt.guest }}
-
SET {{ state.sp.set.guest }}
- Servizio +
{{ punt.guest }}
+
SET {{ set.guest }}
+ Servizio
@@ -190,16 +190,13 @@ diff --git a/src/components/DisplayPage.vue b/src/components/DisplayPage.vue index 7068329..ce48988 100644 --- a/src/components/DisplayPage.vue +++ b/src/components/DisplayPage.vue @@ -7,22 +7,22 @@ {{ state.sp.nomi.home }} - Servizio + Servizio - {{ state.sp.punt.home }} + {{ punt.home }} - set {{ state.sp.set.home }} + set {{ set.home }}
- {{ state.sp.punt.guest }} + {{ punt.guest }} - Servizio + Servizio {{ state.sp.nomi.guest }} - set {{ state.sp.set.guest }} + set {{ set.guest }}
@@ -39,8 +39,8 @@
-
{{ state.sp.punt.home }}
-
{{ state.sp.punt.guest }}
+
{{ punt.home }}
+
{{ punt.guest }}
@@ -51,22 +51,22 @@ {{ state.sp.nomi.guest }} - Servizio + Servizio - {{ state.sp.punt.guest }} + {{ punt.guest }} - set {{ state.sp.set.guest }} + set {{ set.guest }}
- {{ state.sp.punt.home }} + {{ punt.home }} - Servizio + Servizio {{ state.sp.nomi.home }} - set {{ state.sp.set.home }} + set {{ set.home }}
@@ -83,8 +83,8 @@
-
{{ state.sp.punt.guest }}
-
{{ state.sp.punt.home }}
+
{{ punt.guest }}
+
{{ punt.home }}
@@ -116,80 +116,15 @@ diff --git a/src/gameState.js b/src/gameState.js index cfe56cd..6c0f5a8 100644 --- a/src/gameState.js +++ b/src/gameState.js @@ -1,3 +1,21 @@ +export function punteggio(striscia) { + let home = 0, guest = 0 + for (const c of striscia.at(-1).ris) c === 'h' ? home++ : guest++ + return { home, guest } +} + +export function servizio(striscia) { + const set = striscia.at(-1) + return set.ris.length === 0 ? set.serv === 'h' : set.ris.at(-1) === 'h' +} + +export function setVinti(striscia) { + return { + home: striscia.filter(s => s.vinc === 'h').length, + guest: striscia.filter(s => s.vinc === 'g').length, + } +} + export function createInitialState() { return { order: true, @@ -5,10 +23,7 @@ export function createInitialState() { visuStriscia: true, modalitaPartita: "3/5", sp: { - striscia: [{ serv: 'h', ris: '' }], - servHome: true, - punt: { home: 0, guest: 0 }, - set: { home: 0, guest: 0 }, + striscia: [{ serv: 'h', ris: '', vinc: null }], nomi: { home: "Antoniana", guest: "Guest" }, form: { home: ["1", "2", "3", "4", "5", "6"], @@ -19,12 +34,9 @@ export function createInitialState() { } export function checkVittoria(state) { - const puntHome = state.sp.punt.home - const puntGuest = state.sp.punt.guest - const setHome = state.sp.set.home - const setGuest = state.sp.set.guest - const totSet = setHome + setGuest - + const { home: puntHome, guest: puntGuest } = punteggio(state.sp.striscia) + const sv = setVinti(state.sp.striscia) + const totSet = sv.home + sv.guest const isSetDecisivo = state.modalitaPartita === "2/3" ? totSet >= 2 : totSet >= 4 const punteggioVittoria = isSetDecisivo ? 15 : 25 @@ -35,7 +47,8 @@ export function checkVittoria(state) { export function checkVittoriaPartita(state) { const setsToWin = state.modalitaPartita === "2/3" ? 2 : 3 - return state.sp.set.home >= setsToWin || state.sp.set.guest >= setsToWin + const sv = setVinti(state.sp.striscia) + return sv.home >= setsToWin || sv.guest >= setsToWin } export function applyAction(state, action) { @@ -46,15 +59,13 @@ export function applyAction(state, action) { const team = action.team if (checkVittoria(s)) break - const cambioPalla = (team === "home") !== s.sp.servHome - s.sp.punt[team]++ + const servHome = servizio(s.sp.striscia) + const cambioPalla = (team === "home") !== servHome s.sp.striscia.at(-1).ris += team === 'home' ? 'h' : 'g' if (cambioPalla) { s.sp.form[team].push(s.sp.form[team].shift()) } - - s.sp.servHome = team === "home" break } @@ -72,10 +83,6 @@ export function applyAction(state, action) { currentSet.ris = currentSet.ris.slice(0, -1) const lastScorer = lastScorerShort === 'h' ? 'home' : 'guest' - const prevServer = prevServerShort === 'h' ? 'home' : 'guest' - - s.sp.punt[lastScorer]-- - s.sp.servHome = prevServerShort === 'h' if (wasCambioPalla) { s.sp.form[lastScorer].unshift(s.sp.form[lastScorer].pop()) @@ -85,10 +92,17 @@ export function applyAction(state, action) { case "incSet": { const team = action.team - if (s.sp.set[team] === 2) { - s.sp.set[team] = 0 + const sv = setVinti(s.sp.striscia) + const count = sv[team] + const teamShort = team === 'home' ? 'h' : 'g' + if (count >= 2) { + // cicla a 0: rimuove le voci fantasma per questo team + s.sp.striscia = s.sp.striscia.filter( + entry => !(entry._phantom && entry.vinc === teamShort) + ) } else { - s.sp.set[team]++ + // inserisce una voce fantasma prima dell'ultimo set (quello in corso) + s.sp.striscia.splice(-1, 0, { serv: teamShort, ris: '', vinc: teamShort, _phantom: true }) } break } @@ -97,13 +111,9 @@ export function applyAction(state, action) { const team = action.team if (team !== 'home' && team !== 'guest') break if (checkVittoriaPartita(s)) break - const setsToWin = s.modalitaPartita === "2/3" ? 2 : 3 - s.sp.set[team]++ - if (s.sp.set[team] >= setsToWin) break - s.sp.punt.home = 0 - s.sp.punt.guest = 0 - s.sp.servHome = team === 'home' - s.sp.striscia.push({ serv: team === 'home' ? 'h' : 'g', ris: '' }) + s.sp.striscia.at(-1).vinc = team === 'home' ? 'h' : 'g' + if (checkVittoriaPartita(s)) break + s.sp.striscia.push({ serv: team === 'home' ? 'h' : 'g', ris: '', vinc: null }) s.sp.form = { home: ["1", "2", "3", "4", "5", "6"], guest: ["1", "2", "3", "4", "5", "6"], @@ -112,24 +122,21 @@ export function applyAction(state, action) { } case "cambiaPalla": { - if (s.sp.punt.home === 0 && s.sp.punt.guest === 0) { - s.sp.servHome = !s.sp.servHome - s.sp.striscia.at(-1).serv = s.sp.servHome ? 'h' : 'g' + const currentSet = s.sp.striscia.at(-1) + if (currentSet.ris.length === 0) { + currentSet.serv = currentSet.serv === 'h' ? 'g' : 'h' } break } case "resetta": { s.visuForm = false - s.sp.punt.home = 0 - s.sp.punt.guest = 0 - s.sp.set.home = 0 - s.sp.set.guest = 0 + const servIniziale = s.sp.striscia[0]?.serv ?? 'h' + s.sp.striscia = [{ serv: servIniziale, ris: '', vinc: null }] s.sp.form = { home: ["1", "2", "3", "4", "5", "6"], guest: ["1", "2", "3", "4", "5", "6"], } - s.sp.striscia = [{ serv: s.sp.servHome ? 'h' : 'g', ris: '' }] break } diff --git a/src/wsMixin.js b/src/wsMixin.js new file mode 100644 index 0000000..eb6ab01 --- /dev/null +++ b/src/wsMixin.js @@ -0,0 +1,146 @@ +import { createInitialState, punteggio, servizio, setVinti } from './gameState.js' + +export function createWsMixin(role) { + return { + data() { + return { + ws: null, + wsConnected: false, + isConnecting: false, + reconnectTimeout: null, + reconnectAttempts: 0, + maxReconnectDelay: 30000, + state: createInitialState(), + } + }, + computed: { + punt() { return punteggio(this.state.sp.striscia) }, + servHome() { return servizio(this.state.sp.striscia) }, + set() { return setVinti(this.state.sp.striscia) }, + }, + mounted() { + this.connectWebSocket() + if (import.meta.hot) { + import.meta.hot.on('vite:beforeUpdate', () => { + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout) + this.reconnectTimeout = null + } + }) + } + }, + beforeUnmount() { + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout) + this.reconnectTimeout = null + } + if (this.ws) { + this.ws.onclose = null + this.ws.onerror = null + this.ws.onmessage = null + this.ws.onopen = null + try { + if (this.ws.readyState === WebSocket.OPEN) { + this.ws.close(1000, 'Component unmounting') + } else if (this.ws.readyState === WebSocket.CONNECTING) { + this.ws.close() + } + } catch (err) { + console.error(`[${role}] Error closing WebSocket:`, err) + } + this.ws = null + } + }, + methods: { + connectWebSocket() { + if (this.isConnecting) return + if (this.ws) { + this.ws.onclose = null + this.ws.onerror = null + this.ws.onmessage = null + this.ws.onopen = null + try { + if (this.ws.readyState === WebSocket.OPEN) { + this.ws.close(1000, 'Reconnecting') + } else if (this.ws.readyState === WebSocket.CONNECTING) { + this.ws.close() + } + } catch (err) { + console.error(`[${role}] Error closing previous WebSocket:`, err) + } + this.ws = null + } + + this.isConnecting = true + const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:' + const params = new URLSearchParams(location.search) + const defaultHost = (location.hostname === 'localhost' || location.hostname === '::1') + ? `127.0.0.1${location.port ? `:${location.port}` : ''}` + : location.host + const wsUrl = `${protocol}//${params.get('wsHost') || defaultHost}/ws` + + try { + this.ws = new WebSocket(wsUrl) + } catch (err) { + console.error(`[${role}] Failed to create WebSocket:`, err) + this.isConnecting = false + this.scheduleReconnect() + return + } + + this.ws.onopen = () => { + this.isConnecting = false + this.wsConnected = true + this.reconnectAttempts = 0 + try { + this.ws?.send(JSON.stringify({ type: 'register', role })) + } catch (err) { + console.error(`[${role}] Failed to register:`, err) + } + } + + this.ws.onmessage = (event) => { + try { + const msg = JSON.parse(event.data) + if (msg.type === 'state') this.state = msg.state + else this.onWsMessage?.(msg) + } catch (e) { + console.error(`[${role}] Error parsing message:`, e) + } + } + + this.ws.onclose = (event) => { + this.isConnecting = false + this.wsConnected = false + if (event.code !== 1000 && event.code !== 1001) this.scheduleReconnect() + } + + this.ws.onerror = () => { + this.isConnecting = false + this.wsConnected = false + } + }, + + scheduleReconnect() { + if (this.reconnectTimeout) return + const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), this.maxReconnectDelay) + this.reconnectAttempts++ + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null + this.connectWebSocket() + }, delay) + }, + + sendWs(msg) { + if (!this.wsConnected || this.ws?.readyState !== WebSocket.OPEN) return false + try { + this.ws.send(JSON.stringify(msg)) + return true + } catch (err) { + console.error(`[${role}] Failed to send:`, err) + return false + } + }, + }, + } +}