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, visuForm: false, visuStriscia: true, timeout: false, timeoutTeam: null, timeoutVideo: null, timeoutStartedAt: null, modalitaPartita: "3/5", sp: { striscia: [{ serv: 'h', ris: '', vinc: null }], nomi: { home: "Antoniana", guest: "Guest" }, form: { home: ["1", "2", "3", "4", "5", "6"], guest: ["1", "2", "3", "4", "5", "6"], }, }, } } export function checkVittoria(state) { 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 if (puntHome >= punteggioVittoria && puntHome - puntGuest >= 2) return true if (puntGuest >= punteggioVittoria && puntGuest - puntHome >= 2) return true return false } export function checkVittoriaPartita(state) { if (state.modalitaPartita === 'amichevole') return false const setsToWin = state.modalitaPartita === "2/3" ? 2 : 3 const sv = setVinti(state.sp.striscia) return sv.home >= setsToWin || sv.guest >= setsToWin } export function applyAction(state, action) { const s = structuredClone(state) switch (action.type) { case "incPunt": { const team = action.team if (checkVittoria(s)) break const servHome = servizio(s.sp.striscia) const cambioPalla = (team === "home") !== servHome const setCorrente = s.sp.striscia.at(-1) if (setCorrente.ris === '') { setCorrente.formInizio = { home: [...s.sp.form.home], guest: [...s.sp.form.guest], } } s.sp.striscia.at(-1).ris += team === 'home' ? 'h' : 'g' if (cambioPalla) { s.sp.form[team].push(s.sp.form[team].shift()) } break } case "decPunt": { const currentSet = s.sp.striscia.at(-1) if (currentSet.ris.length === 0) break const lastScorerShort = currentSet.ris.at(-1) const prevServerShort = currentSet.ris.length >= 2 ? currentSet.ris.at(-2) : currentSet.serv const wasCambioPalla = lastScorerShort !== prevServerShort currentSet.ris = currentSet.ris.slice(0, -1) if (currentSet.ris === '') delete currentSet.formInizio const lastScorer = lastScorerShort === 'h' ? 'home' : 'guest' if (wasCambioPalla) { s.sp.form[lastScorer].unshift(s.sp.form[lastScorer].pop()) } break } case "incSet": { const team = action.team 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 { // 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 } case "nuovoSet": { const team = action.team if (team !== 'home' && team !== 'guest') break if (checkVittoriaPartita(s)) break 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"], } break } case "cambiaPalla": { 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 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"], } break } case "toggleFormazione": { s.visuForm = !s.visuForm break } case "toggleStriscia": { s.visuStriscia = !s.visuStriscia break } case "toggleOrder": { s.order = !s.order break } case "startTimeout": { const team = action.team if (team !== 'home' && team !== 'guest') break if (!s.timeout) { const setCorrente = s.sp.striscia.at(-1) ;(setCorrente.timeouts ??= []).push({ team, punteggio: punteggio(s.sp.striscia), }) } 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 } case "setNomi": { if (action.home !== undefined) s.sp.nomi.home = action.home if (action.guest !== undefined) s.sp.nomi.guest = action.guest break } case "setModalita": { s.modalitaPartita = action.modalita break } case "setFormazione": { if (action.team && action.form) { s.sp.form[action.team] = [...action.form] } break } case "confermaCambi": { const team = action.team const cambi = action.cambi || [] const form = s.sp.form[team].map((val) => String(val).trim()) const formAggiornata = [...form] const cambiApplicati = [] let valid = true for (const cambio of cambi) { const cin = (cambio.in || "").trim() const cout = (cambio.out || "").trim() if (!cin || !cout) continue if (!/^\d+$/.test(cin) || !/^\d+$/.test(cout)) { valid = false; break } if (cin === cout) { valid = false; break } if (formAggiornata.includes(cin)) { valid = false; break } if (!formAggiornata.includes(cout)) { valid = false; break } const idx = formAggiornata.findIndex((val) => String(val).trim() === cout) if (idx !== -1) { formAggiornata.splice(idx, 1, cin) cambiApplicati.push({ in: cin, out: cout }) } } if (valid) { s.sp.form[team] = formAggiornata if (cambiApplicati.length > 0) { const setCorrente = s.sp.striscia.at(-1) const puntAttuale = punteggio(s.sp.striscia) ;(setCorrente.cambi ??= []).push( ...cambiApplicati.map(c => ({ team, ...c, punteggio: { ...puntAttuale } })) ) } } break } default: break } return s }