import { createServer } from 'http' import express from 'express' import { WebSocketServer } from 'ws' import { fileURLToPath } from 'url' import { dirname, join } from 'path' // Import shared game logic // We need to read it as a dynamic import since it uses ES modules const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) // Inline the game state logic for the server (avoid complex ESM import from src/) function createInitialState() { return { order: true, visuForm: false, visuStriscia: true, modalitaPartita: "3/5", sp: { striscia: { home: [0], guest: [0] }, servHome: true, punt: { home: 0, guest: 0 }, set: { home: 0, guest: 0 }, nomi: { home: "Antoniana", guest: "Guest" }, form: { home: ["1", "2", "3", "4", "5", "6"], guest: ["1", "2", "3", "4", "5", "6"], }, storicoServizio: [], }, } } 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 let isSetDecisivo = false if (state.modalitaPartita === "2/3") { isSetDecisivo = totSet >= 2 } else { isSetDecisivo = 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 } function applyAction(state, action) { const s = JSON.parse(JSON.stringify(state)) switch (action.type) { case "incPunt": { const team = action.team if (checkVittoria(s)) break s.sp.storicoServizio.push({ servHome: s.sp.servHome, cambioPalla: (team === "home" && !s.sp.servHome) || (team === "guest" && s.sp.servHome), }) s.sp.punt[team]++ if (team === "home") { s.sp.striscia.home.push(s.sp.punt.home) s.sp.striscia.guest.push(" ") } else { s.sp.striscia.guest.push(s.sp.punt.guest) s.sp.striscia.home.push(" ") } const cambioPalla = (team === "home" && !s.sp.servHome) || (team === "guest" && s.sp.servHome) if (cambioPalla) { s.sp.form[team].push(s.sp.form[team].shift()) } s.sp.servHome = team === "home" break } case "decPunt": { if (s.sp.striscia.home.length > 1 && s.sp.storicoServizio.length > 0) { const tmpHome = s.sp.striscia.home.pop() s.sp.striscia.guest.pop() const statoServizio = s.sp.storicoServizio.pop() if (tmpHome === " ") { s.sp.punt.guest-- if (statoServizio.cambioPalla) { s.sp.form.guest.unshift(s.sp.form.guest.pop()) } } else { s.sp.punt.home-- if (statoServizio.cambioPalla) { s.sp.form.home.unshift(s.sp.form.home.pop()) } } s.sp.servHome = statoServizio.servHome } break } case "incSet": { const team = action.team if (s.sp.set[team] === 2) { s.sp.set[team] = 0 } else { s.sp.set[team]++ } break } case "cambiaPalla": { if (s.sp.punt.home === 0 && s.sp.punt.guest === 0) { s.sp.servHome = !s.sp.servHome } break } case "resetta": { s.visuForm = false s.sp.punt.home = 0 s.sp.punt.guest = 0 s.sp.form = { home: ["1", "2", "3", "4", "5", "6"], guest: ["1", "2", "3", "4", "5", "6"], } s.sp.striscia = { home: [0], guest: [0] } s.sp.storicoServizio = [] break } case "toggleFormazione": { s.visuForm = !s.visuForm; break } case "toggleStriscia": { s.visuStriscia = !s.visuStriscia; break } case "toggleOrder": { s.order = !s.order; 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] 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) } } if (valid) { s.sp.form[team] = formAggiornata } break } default: break } return s } // ——— Server Setup ——— const app = express() const PORT = process.env.PORT || 3000 // Serve the Vite build output app.use(express.static(join(__dirname, 'dist'))) // SPA fallback: serve index.html for all non-file routes app.get('/{*splat}', (req, res) => { res.sendFile(join(__dirname, 'dist', 'index.html')) }) const server = createServer(app) // WebSocket server const wss = new WebSocketServer({ server }) // Global game state let gameState = createInitialState() // Track client roles const clients = new Map() // ws -> { role: 'display' | 'controller' } wss.on('connection', (ws) => { console.log('New WebSocket connection') ws.on('message', (data) => { try { const msg = JSON.parse(data.toString()) if (msg.type === 'register') { clients.set(ws, { role: msg.role || 'display' }) console.log(`Client registered as: ${msg.role || 'display'}`) // Send current state immediately ws.send(JSON.stringify({ type: 'state', state: gameState })) return } if (msg.type === 'action') { // Only controllers can send actions const client = clients.get(ws) if (!client || client.role !== 'controller') { console.log('Action rejected: not a controller') return } // Apply the action to game state gameState = applyAction(gameState, msg.action) // Broadcast new state to ALL connected clients const stateMsg = JSON.stringify({ type: 'state', state: gameState }) wss.clients.forEach((c) => { if (c.readyState === 1) { // WebSocket.OPEN c.send(stateMsg) } }) } } catch (err) { console.error('Error processing message:', err) } }) ws.on('close', () => { clients.delete(ws) console.log('Client disconnected') }) }) server.listen(PORT, '0.0.0.0', () => { console.log(`\n🏐 Segnapunti Server running on:`) console.log(` Display: http://localhost:${PORT}/`) console.log(` Controller: http://localhost:${PORT}/controller`) console.log(`\n Per accedere da altri dispositivi sulla rete locale,`) console.log(` usa l'IP di questo computer, es: http://192.168.1.x:${PORT}/controller\n`) })