Files
segnapunti/server.js
Davide Grilli a40fad7194 Separa app in client-server con WebSocket
- Aggiunto server Express + WebSocket (server.js)
- Creata pagina Display (solo visualizzazione punteggio)
- Creata pagina Controller (pannello comandi da mobile)
- Aggiunto Vue Router con rotte / e /controller
- Estratta logica di gioco condivisa in gameState.js
2026-02-10 00:42:48 +01:00

236 lines
8.3 KiB
JavaScript

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`)
})