suono gambling

This commit is contained in:
2026-07-02 14:22:02 +02:00
parent cad393c71a
commit d31be8c683
6 changed files with 75 additions and 6 deletions

View File

@@ -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).

74
app.js
View File

@@ -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);

BIN
sounds/coins.mp3 Normal file

Binary file not shown.

BIN
sounds/jackpot.mp3 Normal file

Binary file not shown.

BIN
sounds/reel-spin.mp3 Normal file

Binary file not shown.

BIN
sounds/win.mp3 Normal file

Binary file not shown.