6 Commits

Author SHA1 Message Date
davide 6aeeb47f16 chore(docker): usa immagine registry 2.0.0 invece di build locale 2026-05-13 10:21:49 +02:00
davide a094110be3 feat: blocca partita a fine match e mostra dialog dedicato
Aggiunge checkVittoriaPartita per rilevare la vittoria della partita
(2 set in 2/3, 3 set in 3/5). nuovoSet ora registra il set vincente
senza resettare il punteggio quando la partita è finita. Il controller
mostra "PARTITA FINITA" al posto di "SET VINTO" con solo il tasto CHIUDI.
2026-05-13 10:06:43 +02:00
davide 756f78358c Merge branch 'issue#15' 2026-05-13 09:55:16 +02:00
davide fb4177056f chore(docker): usa build locale invece di immagine remota per dev 2026-05-13 09:45:16 +02:00
davide 303c548ab8 refactor(striscia): compatta struttura dati da array a stringa
Sostituisce r: ['home','guest',...] con ris: 'hg...' e i valori
serv 'home'/'guest' con 'h'/'g'. Nessun cambiamento funzionale.
2026-05-13 09:35:24 +02:00
davide 43e49c4c66 chore(docker): sostituisce named volume con bind mount nella root del repository 2026-05-12 18:55:55 +02:00
5 changed files with 113 additions and 38 deletions
+2 -5
View File
@@ -1,12 +1,9 @@
services: services:
segnapunti: segnapunti:
image: santantonio.sytes.net/attilio/segnapunti:1.0.0 image: santantonio.sytes.net/attilio/segnapunti:2.0.0
container_name: segnapunti container_name: segnapunti
ports: ports:
- "3000:3000" - "3000:3000"
volumes: volumes:
- segnapunti-state:/app/.segnapunti - ./.segnapunti:/app/.segnapunti
restart: unless-stopped restart: unless-stopped
volumes:
segnapunti-state:
+11 -5
View File
@@ -78,15 +78,16 @@
</div> </div>
</div> </div>
<!-- Finestra set vinto --> <!-- Finestra set vinto / partita finita -->
<div class="overlay" v-if="showSetVinto"> <div class="overlay" v-if="showSetVinto">
<div class="dialog"> <div class="dialog">
<div class="dialog-title">SET VINTO</div> <div class="dialog-title">{{ isPartitaFinita ? 'PARTITA FINITA' : 'SET VINTO' }}</div>
<div class="dialog-winner">{{ state.sp.nomi[setVintoTeam] }}</div> <div class="dialog-winner">{{ state.sp.nomi[setVintoTeam] }}</div>
<div class="dialog-subtitle">Configura le formazioni per il prossimo set</div> <div class="dialog-subtitle" v-if="!isPartitaFinita">Configura le formazioni per il prossimo set</div>
<div class="dialog-buttons"> <div class="dialog-buttons">
<button class="btn btn-cancel" @click="undoUltimoPoint()">INDIETRO</button> <button class="btn btn-cancel" @click="undoUltimoPoint()">INDIETRO</button>
<button class="btn btn-confirm" @click="doNuovoSet()">VAI AL SET SUCCESSIVO</button> <button v-if="isPartitaFinita" class="btn btn-confirm" @click="showSetVinto = false">CHIUDI</button>
<button v-else class="btn btn-confirm" @click="doNuovoSet()">VAI AL SET SUCCESSIVO</button>
</div> </div>
</div> </div>
</div> </div>
@@ -224,7 +225,7 @@ export default {
visuStriscia: true, visuStriscia: true,
modalitaPartita: "3/5", modalitaPartita: "3/5",
sp: { sp: {
striscia: [{ serv: 'home', r: [] }], striscia: [{ serv: 'h', ris: '' }],
servHome: true, servHome: true,
punt: { home: 0, guest: 0 }, punt: { home: 0, guest: 0 },
set: { home: 0, guest: 0 }, set: { home: 0, guest: 0 },
@@ -250,6 +251,11 @@ export default {
if (guest >= soglia && guest - home >= 2) return 'guest' if (guest >= soglia && guest - home >= 2) return 'guest'
return null return null
}, },
isPartitaFinita() {
if (!this.setVintoTeam) return false
const setsToWin = this.state.modalitaPartita === '2/3' ? 2 : 3
return this.state.sp.set[this.setVintoTeam] + 1 >= setsToWin
},
cambiValid() { cambiValid() {
let hasComplete = false let hasComplete = false
let allValid = true let allValid = true
+3 -3
View File
@@ -132,7 +132,7 @@ export default {
visuStriscia: true, visuStriscia: true,
modalitaPartita: "3/5", modalitaPartita: "3/5",
sp: { sp: {
striscia: [{ serv: 'home', r: [] }], striscia: [{ serv: 'h', ris: '' }],
servHome: true, servHome: true,
punt: { home: 0, guest: 0 }, punt: { home: 0, guest: 0 },
set: { home: 0, guest: 0 }, set: { home: 0, guest: 0 },
@@ -197,8 +197,8 @@ export default {
if (!currentSet) return { home: [], guest: [] } if (!currentSet) return { home: [], guest: [] }
let h = 0, g = 0 let h = 0, g = 0
const home = [], guest = [] const home = [], guest = []
for (const scorer of currentSet.r) { for (const scorer of currentSet.ris) {
if (scorer === 'home') { h++; home.push(h); guest.push(' ') } if (scorer === 'h') { h++; home.push(h); guest.push(' ') }
else { g++; guest.push(g); home.push(' ') } else { g++; guest.push(g); home.push(' ') }
} }
return { home, guest } return { home, guest }
+24 -12
View File
@@ -5,7 +5,7 @@ export function createInitialState() {
visuStriscia: true, visuStriscia: true,
modalitaPartita: "3/5", modalitaPartita: "3/5",
sp: { sp: {
striscia: [{ serv: 'home', r: [] }], striscia: [{ serv: 'h', ris: '' }],
servHome: true, servHome: true,
punt: { home: 0, guest: 0 }, punt: { home: 0, guest: 0 },
set: { home: 0, guest: 0 }, set: { home: 0, guest: 0 },
@@ -33,6 +33,11 @@ export function checkVittoria(state) {
return false return false
} }
export function checkVittoriaPartita(state) {
const setsToWin = state.modalitaPartita === "2/3" ? 2 : 3
return state.sp.set.home >= setsToWin || state.sp.set.guest >= setsToWin
}
export function applyAction(state, action) { export function applyAction(state, action) {
const s = structuredClone(state) const s = structuredClone(state)
@@ -43,7 +48,7 @@ export function applyAction(state, action) {
const cambioPalla = (team === "home") !== s.sp.servHome const cambioPalla = (team === "home") !== s.sp.servHome
s.sp.punt[team]++ s.sp.punt[team]++
s.sp.striscia.at(-1).r.push(team) s.sp.striscia.at(-1).ris += team === 'home' ? 'h' : 'g'
if (cambioPalla) { if (cambioPalla) {
s.sp.form[team].push(s.sp.form[team].shift()) s.sp.form[team].push(s.sp.form[team].shift())
@@ -55,18 +60,22 @@ export function applyAction(state, action) {
case "decPunt": { case "decPunt": {
const currentSet = s.sp.striscia.at(-1) const currentSet = s.sp.striscia.at(-1)
if (currentSet.r.length === 0) break if (currentSet.ris.length === 0) break
const lastScorer = currentSet.r[currentSet.r.length - 1] const lastScorerShort = currentSet.ris.at(-1)
const prevServer = currentSet.r.length >= 2 const prevServerShort = currentSet.ris.length >= 2
? currentSet.r[currentSet.r.length - 2] ? currentSet.ris.at(-2)
: currentSet.serv : currentSet.serv
const wasCambioPalla = lastScorer !== prevServer const wasCambioPalla = lastScorerShort !== prevServerShort
currentSet.ris = currentSet.ris.slice(0, -1)
const lastScorer = lastScorerShort === 'h' ? 'home' : 'guest'
const prevServer = prevServerShort === 'h' ? 'home' : 'guest'
currentSet.r.pop()
s.sp.punt[lastScorer]-- s.sp.punt[lastScorer]--
s.sp.servHome = prevServer === 'home' s.sp.servHome = prevServerShort === 'h'
if (wasCambioPalla) { if (wasCambioPalla) {
s.sp.form[lastScorer].unshift(s.sp.form[lastScorer].pop()) s.sp.form[lastScorer].unshift(s.sp.form[lastScorer].pop())
@@ -87,11 +96,14 @@ export function applyAction(state, action) {
case "nuovoSet": { case "nuovoSet": {
const team = action.team const team = action.team
if (team !== 'home' && team !== 'guest') break if (team !== 'home' && team !== 'guest') break
if (checkVittoriaPartita(s)) break
const setsToWin = s.modalitaPartita === "2/3" ? 2 : 3
s.sp.set[team]++ s.sp.set[team]++
if (s.sp.set[team] >= setsToWin) break
s.sp.punt.home = 0 s.sp.punt.home = 0
s.sp.punt.guest = 0 s.sp.punt.guest = 0
s.sp.servHome = team === 'home' s.sp.servHome = team === 'home'
s.sp.striscia.push({ serv: team, r: [] }) s.sp.striscia.push({ serv: team === 'home' ? 'h' : 'g', ris: '' })
s.sp.form = { s.sp.form = {
home: ["1", "2", "3", "4", "5", "6"], home: ["1", "2", "3", "4", "5", "6"],
guest: ["1", "2", "3", "4", "5", "6"], guest: ["1", "2", "3", "4", "5", "6"],
@@ -102,7 +114,7 @@ export function applyAction(state, action) {
case "cambiaPalla": { case "cambiaPalla": {
if (s.sp.punt.home === 0 && s.sp.punt.guest === 0) { if (s.sp.punt.home === 0 && s.sp.punt.guest === 0) {
s.sp.servHome = !s.sp.servHome s.sp.servHome = !s.sp.servHome
s.sp.striscia.at(-1).serv = s.sp.servHome ? 'home' : 'guest' s.sp.striscia.at(-1).serv = s.sp.servHome ? 'h' : 'g'
} }
break break
} }
@@ -117,7 +129,7 @@ export function applyAction(state, action) {
home: ["1", "2", "3", "4", "5", "6"], home: ["1", "2", "3", "4", "5", "6"],
guest: ["1", "2", "3", "4", "5", "6"], guest: ["1", "2", "3", "4", "5", "6"],
} }
s.sp.striscia = [{ serv: s.sp.servHome ? 'home' : 'guest', r: [] }] s.sp.striscia = [{ serv: s.sp.servHome ? 'h' : 'g', ris: '' }]
break break
} }
+73 -13
View File
@@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach } from 'vitest' import { describe, it, expect, beforeEach } from 'vitest'
import { createInitialState, applyAction, checkVittoria } from '../../src/gameState.js' import { createInitialState, applyAction, checkVittoria, checkVittoriaPartita } from '../../src/gameState.js'
describe('Game Logic (gameState.js)', () => { describe('Game Logic (gameState.js)', () => {
let state let state
@@ -33,8 +33,8 @@ describe('Game Logic (gameState.js)', () => {
it('dovrebbe avere la striscia iniziale con un set vuoto', () => { it('dovrebbe avere la striscia iniziale con un set vuoto', () => {
expect(state.sp.striscia).toHaveLength(1) expect(state.sp.striscia).toHaveLength(1)
expect(state.sp.striscia[0].serv).toBe('home') expect(state.sp.striscia[0].serv).toBe('h')
expect(state.sp.striscia[0].r).toEqual([]) expect(state.sp.striscia[0].ris).toBe('')
}) })
it('dovrebbe avere modalità 3/5 di default', () => { it('dovrebbe avere modalità 3/5 di default', () => {
@@ -113,19 +113,19 @@ describe('Game Logic (gameState.js)', () => {
it('dovrebbe aggiornare la striscia per punto Home', () => { it('dovrebbe aggiornare la striscia per punto Home', () => {
const s = applyAction(state, { type: 'incPunt', team: 'home' }) const s = applyAction(state, { type: 'incPunt', team: 'home' })
expect(s.sp.striscia.at(-1).r).toEqual(['home']) expect(s.sp.striscia.at(-1).ris).toBe('h')
}) })
it('dovrebbe aggiornare la striscia per punto Guest', () => { it('dovrebbe aggiornare la striscia per punto Guest', () => {
const s = applyAction(state, { type: 'incPunt', team: 'guest' }) const s = applyAction(state, { type: 'incPunt', team: 'guest' })
expect(s.sp.striscia.at(-1).r).toEqual(['guest']) expect(s.sp.striscia.at(-1).ris).toBe('g')
}) })
it('dovrebbe registrare scorer nella striscia', () => { it('dovrebbe registrare scorer nella striscia', () => {
let s = applyAction(state, { type: 'incPunt', team: 'home' }) let s = applyAction(state, { type: 'incPunt', team: 'home' })
s = applyAction(s, { type: 'incPunt', team: 'guest' }) s = applyAction(s, { type: 'incPunt', team: 'guest' })
s = applyAction(s, { type: 'incPunt', team: 'home' }) s = applyAction(s, { type: 'incPunt', team: 'home' })
expect(s.sp.striscia.at(-1).r).toEqual(['home', 'guest', 'home']) expect(s.sp.striscia.at(-1).ris).toBe('hgh')
}) })
it('non dovrebbe incrementare i punti dopo vittoria', () => { it('non dovrebbe incrementare i punti dopo vittoria', () => {
@@ -180,7 +180,7 @@ describe('Game Logic (gameState.js)', () => {
it('dovrebbe ripristinare la striscia', () => { it('dovrebbe ripristinare la striscia', () => {
const s1 = applyAction(state, { type: 'incPunt', team: 'home' }) const s1 = applyAction(state, { type: 'incPunt', team: 'home' })
const s2 = applyAction(s1, { type: 'decPunt' }) const s2 = applyAction(s1, { type: 'decPunt' })
expect(s2.sp.striscia.at(-1).r).toEqual([]) expect(s2.sp.striscia.at(-1).ris).toBe('')
}) })
it('dovrebbe gestire undo multipli in sequenza', () => { it('dovrebbe gestire undo multipli in sequenza', () => {
@@ -248,14 +248,14 @@ describe('Game Logic (gameState.js)', () => {
it('dovrebbe aggiungere un nuovo set vuoto alla striscia', () => { it('dovrebbe aggiungere un nuovo set vuoto alla striscia', () => {
const s = applyAction(state, { type: 'nuovoSet', team: 'home' }) const s = applyAction(state, { type: 'nuovoSet', team: 'home' })
expect(s.sp.striscia).toHaveLength(2) expect(s.sp.striscia).toHaveLength(2)
expect(s.sp.striscia.at(-1).r).toEqual([]) expect(s.sp.striscia.at(-1).ris).toBe('')
expect(s.sp.striscia.at(-1).serv).toBe('home') expect(s.sp.striscia.at(-1).serv).toBe('h')
}) })
it('dovrebbe conservare il set precedente nella striscia', () => { it('dovrebbe conservare il set precedente nella striscia', () => {
state.sp.striscia[0].r = ['home', 'guest', 'home'] state.sp.striscia[0].ris = 'hgh'
const s = applyAction(state, { type: 'nuovoSet', team: 'home' }) const s = applyAction(state, { type: 'nuovoSet', team: 'home' })
expect(s.sp.striscia[0].r).toEqual(['home', 'guest', 'home']) expect(s.sp.striscia[0].ris).toBe('hgh')
}) })
it('dovrebbe resettare le formazioni', () => { it('dovrebbe resettare le formazioni', () => {
@@ -271,6 +271,66 @@ describe('Game Logic (gameState.js)', () => {
expect(s.sp.set.home).toBe(0) expect(s.sp.set.home).toBe(0)
expect(s.sp.set.guest).toBe(0) expect(s.sp.set.guest).toBe(0)
}) })
it('in 2/3 dovrebbe registrare il set vincente senza resettare il punteggio', () => {
state.modalitaPartita = '2/3'
state.sp.set.home = 1
state.sp.punt.home = 25
state.sp.punt.guest = 18
const s = applyAction(state, { type: 'nuovoSet', team: 'home' })
expect(s.sp.set.home).toBe(2)
expect(s.sp.punt.home).toBe(25)
expect(s.sp.punt.guest).toBe(18)
})
it('in 3/5 dovrebbe registrare il set vincente senza resettare il punteggio', () => {
state.modalitaPartita = '3/5'
state.sp.set.home = 2
state.sp.punt.home = 25
state.sp.punt.guest = 20
const s = applyAction(state, { type: 'nuovoSet', team: 'home' })
expect(s.sp.set.home).toBe(3)
expect(s.sp.punt.home).toBe(25)
expect(s.sp.punt.guest).toBe(20)
})
it('dovrebbe ignorare nuovoSet se la partita è già finita', () => {
state.modalitaPartita = '2/3'
state.sp.set.home = 2
const s = applyAction(state, { type: 'nuovoSet', team: 'home' })
expect(s.sp.set.home).toBe(2)
})
})
// =============================================
// VITTORIA PARTITA (checkVittoriaPartita)
// =============================================
describe('checkVittoriaPartita', () => {
it('in 2/3 restituisce false se nessuno ha 2 set', () => {
state.modalitaPartita = '2/3'
state.sp.set.home = 1
state.sp.set.guest = 0
expect(checkVittoriaPartita(state)).toBe(false)
})
it('in 2/3 restituisce true se home ha 2 set', () => {
state.modalitaPartita = '2/3'
state.sp.set.home = 2
expect(checkVittoriaPartita(state)).toBe(true)
})
it('in 3/5 restituisce false se nessuno ha 3 set', () => {
state.modalitaPartita = '3/5'
state.sp.set.home = 2
state.sp.set.guest = 2
expect(checkVittoriaPartita(state)).toBe(false)
})
it('in 3/5 restituisce true se guest ha 3 set', () => {
state.modalitaPartita = '3/5'
state.sp.set.guest = 3
expect(checkVittoriaPartita(state)).toBe(true)
})
}) })
// ============================================= // =============================================
@@ -661,10 +721,10 @@ describe('Game Logic (gameState.js)', () => {
}) })
it('dovrebbe resettare la striscia a un set vuoto', () => { it('dovrebbe resettare la striscia a un set vuoto', () => {
state.sp.striscia = [{ serv: 'home', r: ['home', 'guest', 'home'] }, { serv: 'home', r: ['guest'] }] state.sp.striscia = [{ serv: 'h', ris: 'hgh' }, { serv: 'h', ris: 'g' }]
const s = applyAction(state, { type: 'resetta' }) const s = applyAction(state, { type: 'resetta' })
expect(s.sp.striscia).toHaveLength(1) expect(s.sp.striscia).toHaveLength(1)
expect(s.sp.striscia[0].r).toEqual([]) expect(s.sp.striscia[0].ris).toBe('')
}) })
it('dovrebbe impostare visuForm a false', () => { it('dovrebbe impostare visuForm a false', () => {