suono gambling
This commit is contained in:
@@ -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
74
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);
|
||||
|
||||
BIN
sounds/coins.mp3
Normal file
BIN
sounds/coins.mp3
Normal file
Binary file not shown.
BIN
sounds/jackpot.mp3
Normal file
BIN
sounds/jackpot.mp3
Normal file
Binary file not shown.
BIN
sounds/reel-spin.mp3
Normal file
BIN
sounds/reel-spin.mp3
Normal file
Binary file not shown.
BIN
sounds/win.mp3
Normal file
BIN
sounds/win.mp3
Normal file
Binary file not shown.
Reference in New Issue
Block a user