missing match crazy shit

This commit is contained in:
2026-07-02 14:14:31 +02:00
parent 41b73574b1
commit cad393c71a
3 changed files with 161 additions and 7 deletions

View File

@@ -31,6 +31,10 @@ python3 -m http.server 8080
I posti con **jacuzzi** (Argo, Eolo, "Quanto te la rischi") sono i jackpot e
vengono rivelati per ultimi, con celebrazione extra.
**Nessun match (~30%):** ogni tanto i 3 rulli si fermano su stanze diverse →
effetto pazzo (sirena + strobo + glitch + timbro "NO MATCH"), le puntate del giro
vengono annullate e la persona **ri-tira** (senza avanzare). Max 2 no-match di fila.
## Regole del matching
- Ogni stanza è **mono-genere** (M con M, F con F).

102
app.js
View File

@@ -55,6 +55,22 @@
buzz() {
this.blip(140, 0.45, "sawtooth", 0.3, 70);
},
// sirena da allarme per il "nessun match"
siren() {
if (!this.on || !this.ctx) return;
const t = this.ctx.currentTime;
const o = this.ctx.createOscillator();
const g = this.ctx.createGain();
o.type = "sawtooth";
const freqs = [880, 440, 880, 440, 880, 440, 880];
freqs.forEach((f, i) => o.frequency.setValueAtTime(f, t + i * 0.16));
g.gain.setValueAtTime(0.16, t);
g.gain.setValueAtTime(0.16, t + 1.05);
g.gain.exponentialRampToValueAtTime(0.0001, t + 1.2);
o.connect(g).connect(this.master);
o.start(t);
o.stop(t + 1.25);
},
// nota di ottone per la fanfara
brass(freq, when, dur, vol = 0.25) {
if (!this.on || !this.ctx) return;
@@ -198,6 +214,38 @@
f.classList.add("go");
}
// EFFETTO PAZZO quando i rulli non combaciano: sirena + strobo + glitch + timbro
function crazyNoMatch() {
Audio.siren();
screenShake();
// strobo a schermo intero
let strobe = $(".strobe");
if (!strobe) {
strobe = document.createElement("div");
strobe.className = "strobe";
document.body.appendChild(strobe);
}
strobe.classList.remove("go");
void strobe.offsetWidth;
strobe.classList.add("go");
// glitch sui rulli
const win = $(".reel-window");
win.classList.add("glitch");
setTimeout(() => win.classList.remove("glitch"), 900);
// timbro "NESSUN MATCH"
const stamp = document.createElement("div");
stamp.className = "nomatch-stamp";
stamp.innerHTML = "<span>❌ NO MATCH ❌</span>";
document.body.appendChild(stamp);
setTimeout(() => stamp.remove(), 1300);
// coriandoli "sbagliati" (solo rossi) per rincarare la beffa
myConfetti({ particleCount: 120, spread: 180, startVelocity: 40, origin: { y: 0.4 }, colors: ["#ff3b5c", "#8b0022"] });
}
/* ---------- Sfondo three.js: pioggia di monete ---------- */
function initBackground() {
if (typeof THREE === "undefined") return;
@@ -332,8 +380,15 @@
const roomAtIndex = (el, i) =>
el.children[i] ? roomById[el.children[i].dataset.room] : null;
// Probabilità di ragebait (finto stop) sul rullo finale.
// Probabilità di ragebait (finto stop) per colonna.
const TEASE_CHANCE = 0.3;
// Probabilità che il giro NON dia match (3 stanze diverse) -> respin.
const NOMATCH_CHANCE = 0.3;
// 3 stanze DIVERSE per un "nessun match" (mai una linea vincente)
function pickMismatch() {
return Matching.shuffle(ROOMS.map((r) => r.id), Math.random).slice(0, 3);
}
function bounceReel(el, finalY) {
return el.animate(
@@ -398,10 +453,11 @@
// allineandosi sulla stanza assegnata. OGNI colonna può fare il ragebait
// (in modo indipendente); le colonne più a destra tengono la suspense più a
// lungo, così il dramma "cascata" verso l'ultimo rullo.
async function spinReel(targetId) {
const targetIndex = reelEls.map((el) => buildStripIn(el, targetId))[0]; // uguale per tutti
async function spinReel(reelTargets, opts) {
const tease = !opts || opts.tease !== false;
// ogni rullo può atterrare su una stanza diversa (per il "nessun match")
const targetIndex = reelEls.map((el, i) => buildStripIn(el, reelTargets[i]))[0];
const finalY = -(targetIndex - 1) * ITEM_H;
const targetRoom = roomById[targetId];
$("#reel-result").innerHTML = "&nbsp;";
// ZOOM + shake; lo shake si ferma a metà spin (quando inizia a rallentare)
@@ -428,7 +484,7 @@
await Promise.all(
reelEls.map((el, i) => {
// ogni colonna ha una sua chance di bluff; suspense crescente verso destra
const teaseMs = Math.random() < TEASE_CHANCE ? 380 + i * 260 : 0;
const teaseMs = tease && Math.random() < TEASE_CHANCE ? 380 + i * 260 : 0;
return reelLand(el, targetIndex, finalY, durs[i], teaseMs);
})
);
@@ -436,7 +492,6 @@
ticking = false;
stopRiser();
zoomMachineSteady();
$("#reel-result").innerHTML = `${targetRoom.jacuzzi ? "🛁 " : ""}<b>${targetRoom.nome}</b>${targetRoom.jacuzzi ? " 🛁" : ""}`;
}
/* ---------- Zoom macchina ---------- */
@@ -476,6 +531,7 @@
let auto = false;
let busy = false;
let leverArmed = false;
let noMatchStreak = 0; // no-match consecutivi (tetto per non incastrare)
// scommettitori (TU + banco simulato)
let players = [];
@@ -501,6 +557,7 @@
ROOMS.forEach((r) => (occupants[r.id] = []));
cursor = 0;
currentBet = null;
noMatchStreak = 0;
if (!keepChips) {
players = [
{ name: "TU", chips: 100, you: true },
@@ -686,7 +743,38 @@
setTimeout(() => $("#lever").classList.remove("pull"), 320);
if (window.__coinBoost) window.__coinBoost();
await spinReel(roomId);
// 30% dei giri: NESSUN MATCH (3 stanze diverse) -> effetto pazzo + respin.
// Tetto di 2 no-match di fila per non bloccare nessuno.
const noMatch = noMatchStreak < 2 && Math.random() < NOMATCH_CHANCE;
if (noMatch) {
noMatchStreak++;
await spinReel(pickMismatch(), { tease: false });
$("#reel-result").innerHTML = `<b class="rr-nomatch">NESSUN MATCH</b>`;
crazyNoMatch();
currentBet = null; // puntate annullate (push): nessun gettone cambia
log(`${person}: nessun match! Si ri-tira 💥`);
mc("💥 NESSUN MATCH! Ri-tira la leva! ⬇️");
setTimeout(zoomMachineOut, 1000);
busy = false;
// stesso giocatore: NON avanza il cursore
if (auto) {
setTimeout(nextTurn, 1600);
} else {
setTimeout(() => {
if (!busy && cursor < revealQueue.length && !auto) {
armLever(true);
mc("💥 NESSUN MATCH! Ri-tira la leva! ⬇️");
}
}, 1100);
}
return;
}
// --- MATCH: i 3 rulli si allineano sulla stanza assegnata ---
noMatchStreak = 0;
await spinReel([roomId, roomId, roomId], { tease: true });
$("#reel-result").innerHTML = `${room.jacuzzi ? "🛁 " : ""}<b>${room.nome}</b>${room.jacuzzi ? " 🛁" : ""}`;
// atterraggio
occupants[roomId].push(person);

View File

@@ -668,6 +668,68 @@ body {
}
}
/* ===== NESSUN MATCH: effetto pazzo ===== */
.strobe {
position: fixed;
inset: 0;
z-index: 58;
pointer-events: none;
opacity: 0;
}
.strobe.go {
animation: strobe 1s linear;
}
@keyframes strobe {
0%, 100% { opacity: 0; }
8% { opacity: 1; background: rgba(255, 0, 60, 0.45); }
16% { opacity: 1; background: rgba(0, 229, 255, 0.4); }
24% { opacity: 1; background: rgba(255, 210, 63, 0.4); }
40% { opacity: 1; background: rgba(255, 0, 60, 0.4); }
56% { opacity: 1; background: rgba(0, 229, 255, 0.38); }
72% { opacity: 1; background: rgba(255, 46, 136, 0.4); }
88% { opacity: 1; background: rgba(255, 0, 60, 0.35); }
}
.reel-window.glitch {
animation: glitch 0.08s steps(2) infinite;
}
@keyframes glitch {
0% { transform: translate(0, 0) skewX(0); filter: hue-rotate(0deg); }
25% { transform: translate(-7px, 3px) skewX(4deg); filter: hue-rotate(90deg); }
50% { transform: translate(6px, -4px) skewX(-4deg); filter: hue-rotate(200deg); }
75% { transform: translate(-5px, 5px) skewX(3deg); filter: hue-rotate(300deg); }
100% { transform: translate(0, 0) skewX(0); filter: hue-rotate(0deg); }
}
.nomatch-stamp {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 59;
pointer-events: none;
}
.nomatch-stamp span {
font-family: "Anton", sans-serif;
font-size: clamp(44px, 10vw, 140px);
letter-spacing: 3px;
color: var(--red);
text-shadow: 0 0 30px var(--red), 0 0 70px #ff004c;
transform: rotate(-8deg) scale(0.3);
opacity: 0;
animation: stampIn 1.3s ease;
}
@keyframes stampIn {
0% { opacity: 0; transform: rotate(-8deg) scale(0.3); }
15% { opacity: 1; transform: rotate(-8deg) scale(1.15); }
28% { transform: rotate(-8deg) scale(1); }
82% { opacity: 1; transform: rotate(-8deg) scale(1); }
100% { opacity: 0; transform: rotate(-8deg) scale(1.05); }
}
.rr-nomatch {
color: var(--red);
text-shadow: 0 0 16px var(--red);
}
@media (max-width: 980px) {
.stage {
grid-template-columns: 1fr;