From cad393c71a902505fa571789bafeae4a1fc36063 Mon Sep 17 00:00:00 2001 From: Alberto Moretti Date: Thu, 2 Jul 2026 14:14:31 +0200 Subject: [PATCH] missing match crazy shit --- README.md | 4 +++ app.js | 102 +++++++++++++++++++++++++++++++++++++++++++++++++---- styles.css | 62 ++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2b39b82..34b8ece 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/app.js b/app.js index 5690c99..50809bd 100644 --- a/app.js +++ b/app.js @@ -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 = "❌ NO MATCH ❌"; + 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 = " "; // 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 ? "🛁 " : ""}${targetRoom.nome}${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 = `NESSUN MATCH`; + 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 ? "🛁 " : ""}${room.nome}${room.jacuzzi ? " 🛁" : ""}`; // atterraggio occupants[roomId].push(person); diff --git a/styles.css b/styles.css index 018ef3b..5b9c551 100644 --- a/styles.css +++ b/styles.css @@ -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;