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:
segnapunti:
image: santantonio.sytes.net/attilio/segnapunti:1.0.0
image: santantonio.sytes.net/attilio/segnapunti:2.0.0
container_name: segnapunti
ports:
- "3000:3000"
volumes:
- segnapunti-state:/app/.segnapunti
- ./.segnapunti:/app/.segnapunti
restart: unless-stopped
volumes:
segnapunti-state:
+11 -5
View File
@@ -78,15 +78,16 @@
</div>
</div>
<!-- Finestra set vinto -->
<!-- Finestra set vinto / partita finita -->
<div class="overlay" v-if="showSetVinto">
<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-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">
<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>
@@ -224,7 +225,7 @@ export default {
visuStriscia: true,
modalitaPartita: "3/5",
sp: {
striscia: [{ serv: 'home', r: [] }],
striscia: [{ serv: 'h', ris: '' }],
servHome: true,
punt: { home: 0, guest: 0 },
set: { home: 0, guest: 0 },
@@ -250,6 +251,11 @@ export default {
if (guest >= soglia && guest - home >= 2) return 'guest'
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() {
let hasComplete = false
let allValid = true
+3 -3
View File
@@ -132,7 +132,7 @@ export default {
visuStriscia: true,
modalitaPartita: "3/5",
sp: {
striscia: [{ serv: 'home', r: [] }],
striscia: [{ serv: 'h', ris: '' }],
servHome: true,
punt: { home: 0, guest: 0 },
set: { home: 0, guest: 0 },
@@ -197,8 +197,8 @@ export default {
if (!currentSet) return { home: [], guest: [] }
let h = 0, g = 0
const home = [], guest = []
for (const scorer of currentSet.r) {
if (scorer === 'home') { h++; home.push(h); guest.push(' ') }
for (const scorer of currentSet.ris) {
if (scorer === 'h') { h++; home.push(h); guest.push(' ') }
else { g++; guest.push(g); home.push(' ') }
}
return { home, guest }
+24 -12
View File
@@ -5,7 +5,7 @@ export function createInitialState() {
visuStriscia: true,
modalitaPartita: "3/5",
sp: {
striscia: [{ serv: 'home', r: [] }],
striscia: [{ serv: 'h', ris: '' }],
servHome: true,
punt: { home: 0, guest: 0 },
set: { home: 0, guest: 0 },
@@ -33,6 +33,11 @@ export function checkVittoria(state) {
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) {
const s = structuredClone(state)
@@ -43,7 +48,7 @@ export function applyAction(state, action) {
const cambioPalla = (team === "home") !== s.sp.servHome
s.sp.punt[team]++
s.sp.striscia.at(-1).r.push(team)
s.sp.striscia.at(-1).ris += team === 'home' ? 'h' : 'g'
if (cambioPalla) {
s.sp.form[team].push(s.sp.form[team].shift())
@@ -55,18 +60,22 @@ export function applyAction(state, action) {
case "decPunt": {
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 prevServer = currentSet.r.length >= 2
? currentSet.r[currentSet.r.length - 2]
const lastScorerShort = currentSet.ris.at(-1)
const prevServerShort = currentSet.ris.length >= 2
? currentSet.ris.at(-2)
: 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.servHome = prevServer === 'home'
s.sp.servHome = prevServerShort === 'h'
if (wasCambioPalla) {
s.sp.form[lastScorer].unshift(s.sp.form[lastScorer].pop())
@@ -87,11 +96,14 @@ export function applyAction(state, action) {
case "nuovoSet": {
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, r: [] })
s.sp.striscia.push({ serv: team === 'home' ? 'h' : 'g', ris: '' })
s.sp.form = {
home: ["1", "2", "3", "4", "5", "6"],
guest: ["1", "2", "3", "4", "5", "6"],
@@ -102,7 +114,7 @@ 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 ? 'home' : 'guest'
s.sp.striscia.at(-1).serv = s.sp.servHome ? 'h' : 'g'
}
break
}
@@ -117,7 +129,7 @@ export function applyAction(state, action) {
home: ["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
}
+73 -13
View File
@@ -1,5 +1,5 @@
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)', () => {
let state
@@ -33,8 +33,8 @@ describe('Game Logic (gameState.js)', () => {
it('dovrebbe avere la striscia iniziale con un set vuoto', () => {
expect(state.sp.striscia).toHaveLength(1)
expect(state.sp.striscia[0].serv).toBe('home')
expect(state.sp.striscia[0].r).toEqual([])
expect(state.sp.striscia[0].serv).toBe('h')
expect(state.sp.striscia[0].ris).toBe('')
})
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', () => {
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', () => {
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', () => {
let s = applyAction(state, { type: 'incPunt', team: 'home' })
s = applyAction(s, { type: 'incPunt', team: 'guest' })
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', () => {
@@ -180,7 +180,7 @@ describe('Game Logic (gameState.js)', () => {
it('dovrebbe ripristinare la striscia', () => {
const s1 = applyAction(state, { type: 'incPunt', team: 'home' })
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', () => {
@@ -248,14 +248,14 @@ describe('Game Logic (gameState.js)', () => {
it('dovrebbe aggiungere un nuovo set vuoto alla striscia', () => {
const s = applyAction(state, { type: 'nuovoSet', team: 'home' })
expect(s.sp.striscia).toHaveLength(2)
expect(s.sp.striscia.at(-1).r).toEqual([])
expect(s.sp.striscia.at(-1).serv).toBe('home')
expect(s.sp.striscia.at(-1).ris).toBe('')
expect(s.sp.striscia.at(-1).serv).toBe('h')
})
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' })
expect(s.sp.striscia[0].r).toEqual(['home', 'guest', 'home'])
expect(s.sp.striscia[0].ris).toBe('hgh')
})
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.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', () => {
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' })
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', () => {