diff --git a/README.md b/README.md index 34b8ece..62ce581 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,10 @@ python3 -m http.server 8080 - **START / SPINNA!** — rivela la persona successiva con un giro di slot. - **AUTO ▶** — rivela tutti in sequenza da solo (modalità proiettore). - **RE-ROLL 🎲** — rifà il sorteggio (nuova assegnazione). -- **🔊** — on/off dei suoni (sintetizzati, nessun file audio). +- **🔊** — on/off dei suoni: **campioni reali stile slot** (whirr dei rulli nel + roll, jingle di vincita, sirena di jackpot, monete — in `sounds/`, royalty-free + [Mixkit](https://mixkit.co/free-sound-effects/slot-machine/)) con **fallback + sintetizzato** se i file non caricano (es. offline totale). - **Scommesse** — prima di ogni giro clicca una stanza nel pannello a destra per puntare 10 gettoni; se indovini vinci in base alla quota. Contro di te giocano 3 scommettitori simulati (leaderboard live). @@ -51,6 +54,7 @@ vengono annullate e la persona **ri-tira** (senza avanzare). Max 2 no-match di f | `data.js` | Stanze e partecipanti (sorgente dati) | | `matching.js` | Algoritmo di assegnazione (testabile in Node) | | `app.js` | Motore reveal: rulli, audio, three.js, coriandoli, scommesse | +| `sounds/` | SFX reali stile slot (Mixkit, royalty-free): reel-spin, win, jackpot, coins | | `vendor/` | Librerie e font in locale (three.js, canvas-confetti, woff2) — app offline | | `docs/` | Ricerca su stile gambling, suspense, interattività, implementazione | @@ -63,5 +67,4 @@ nomi, generi o stanze — l'app si adatta da sola. - **Scommesse multi-persona reali** dai telefoni (ora: singolo operatore + bot simulati). Servirebbe `BroadcastChannel` o un piccolo backend. -- **SFX reali** al posto dei suoni sintetizzati. - **Gran finale** dedicato all'ultimo jackpot (ora tutti i jackpot sono uguali). diff --git a/app.js b/app.js index 50809bd..752ee86 100644 --- a/app.js +++ b/app.js @@ -142,6 +142,64 @@ }, }; + /* ---------- Campioni reali stile slot (Mixkit, royalty-free) ---------- + File in /sounds. Usiamo HTMLAudioElement (non fetch+decode) così + funziona anche aprendo l'app via file:// dove Chrome blocca fetch dei + file locali. Rispetta il mute globale (Audio.on) e fa fallback ai suoni + sintetizzati se un file non carica. */ + const Samples = { + files: { + reelSpin: "sounds/reel-spin.mp3", // whirr dei rulli durante il roll + win: "sounds/win.mp3", // jingle di vincita (match normale) + jackpot: "sounds/jackpot.mp3", // sirena di jackpot (idromassaggio) + coins: "sounds/coins.mp3", // pioggia di monete + }, + el: {}, + ready: {}, + preload() { + Object.entries(this.files).forEach(([k, url]) => { + const a = new window.Audio(url); // window.Audio: qui "Audio" è l'oggetto synth + a.preload = "auto"; + a.addEventListener("canplaythrough", () => (this.ready[k] = true), { once: true }); + a.addEventListener("error", () => (this.ready[k] = false)); + this.el[k] = a; + }); + }, + // ritorna una funzione stop(fadeSec) per interromperlo (utile per il loop) + play(key, opts = {}) { + const { vol = 0.8, loop = false, fallback } = opts; + if (!Audio.on) return () => {}; + const base = this.el[key]; + if (!base || this.ready[key] === false) { + if (fallback) fallback(); + return () => {}; + } + const a = loop ? base : base.cloneNode(); // clone -> più play sovrapposti + a.loop = loop; + a.volume = vol; + try { + a.currentTime = 0; + } catch (e) {} + const p = a.play(); + if (p && p.catch) p.catch(() => fallback && fallback()); + return (fade = 0.2) => { + const steps = 8; + let i = 0; + const iv = setInterval(() => { + i++; + a.volume = Math.max(0, vol * (1 - i / steps)); + if (i >= steps) { + clearInterval(iv); + try { + a.pause(); + } catch (e) {} + } + }, (fade * 1000) / steps); + }; + }, + }; + Samples.preload(); + /* ---------- Coriandoli (canvas a schermo intero, dimensionato a mano) ---------- */ // IMPORTANTE: niente useWorker + niente resize automatico. Dimensiono io il // canvas alla viewport, altrimenti il buffer resta 300x150 e i coriandoli @@ -216,7 +274,10 @@ // EFFETTO PAZZO quando i rulli non combaciano: sirena + strobo + glitch + timbro function crazyNoMatch() { - Audio.siren(); + // buzz di errore "sbagliato!" (doppio, stile game-show) prima del caos + Audio.buzz(); + setTimeout(() => Audio.buzz(), 180); + setTimeout(() => Audio.siren(), 320); // la sirena entra dopo il buzz screenShake(); // strobo a schermo intero @@ -466,6 +527,8 @@ setTimeout(zoomMachineSteady, mainDur / 2); const stopRiser = Audio.riser(5.6); + // whirr reale dei rulli sotto ai tick/riser sintetizzati + const stopWheel = Samples.play("reelSpin", { vol: 0.65 }); // tick sincronizzati sul 3° rullo (gira più a lungo) let ticking = true; @@ -491,6 +554,7 @@ ticking = false; stopRiser(); + stopWheel(0.25); // taglia il whirr quando l'ultimo rullo atterra zoomMachineSteady(); } @@ -787,20 +851,22 @@ const isJackpot = room.jacuzzi; Audio.ding(); // "click" di aggancio della stanza - Audio.fanfare(); // 🎺 trombe alla selezione if (isJackpot) { - Audio.jackpot(); + // sirena reale di jackpot; fallback: fanfara + jackpot sintetizzati + Samples.play("jackpot", { vol: 0.9, fallback: () => { Audio.fanfare(); Audio.jackpot(); } }); celebrate("jackpot"); screenShake(); screenFlash(); mc(`🎉 JACKPOT! ${person} conquista ${room.nome} con IDROMASSAGGIO! 🛁`); log(`JACKPOT: ${person} → ${room.nome} 🛁`, "jackpot"); } else { + // jingle reale di vincita; fallback: fanfara sintetizzata + Samples.play("win", { vol: 0.85, fallback: () => Audio.fanfare() }); celebrate("normal"); mc(`${person} va in ${room.nome}!`); log(`${person} → ${room.nome}`); } - Audio.coin(); + Samples.play("coins", { vol: 0.6, fallback: () => Audio.coin() }); // la macchina resta zoomata per godersi la vittoria, poi torna al posto setTimeout(zoomMachineOut, isJackpot ? 1200 : 800); diff --git a/sounds/coins.mp3 b/sounds/coins.mp3 new file mode 100644 index 0000000..2d9abb5 Binary files /dev/null and b/sounds/coins.mp3 differ diff --git a/sounds/jackpot.mp3 b/sounds/jackpot.mp3 new file mode 100644 index 0000000..7cedd19 Binary files /dev/null and b/sounds/jackpot.mp3 differ diff --git a/sounds/reel-spin.mp3 b/sounds/reel-spin.mp3 new file mode 100644 index 0000000..23f2d9d Binary files /dev/null and b/sounds/reel-spin.mp3 differ diff --git a/sounds/win.mp3 b/sounds/win.mp3 new file mode 100644 index 0000000..be581b5 Binary files /dev/null and b/sounds/win.mp3 differ