| Server IP : 202.61.199.114 / Your IP : 216.73.217.139 Web Server : nginx/1.22.1 System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64 User : web20 ( 1018) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /proc/3951573/root/tmp/rrpkg/ |
Upload File : |
/* Reel Royale – ReelEngine
Echte Walzen mit Strip-Scrolling, Motion-Blur, gestaffeltem Stopp & Bounce. */
import { symbolMarkup, wait } from '../util.js';
export class ReelEngine {
constructor(root) {
this.root = root; // #reels
this.game = null;
this.reels = []; // { el, strip }
this.codes = [];
this.spinning = false;
this.forceStop = false;
this.onReelStop = null; // callback(index)
this.onAnticipate = null; // callback(index) – Walze im Spannungs-Lauf
}
_isScatter(code) {
return (this.game.symbols[code] && this.game.symbols[code].type) === 'scatter';
}
build(game) {
this.game = game;
this.codes = Object.keys(game.symbols);
this.root.innerHTML = '';
this.reels = [];
for (let c = 0; c < game.reels; c++) {
const reel = document.createElement('div');
reel.className = 'rr-reel';
const strip = document.createElement('div');
strip.className = 'rr-reel-strip';
reel.appendChild(strip);
this.root.appendChild(reel);
this.reels.push({ el: reel, strip });
// Klick auf Walze stoppt den Spin
reel.addEventListener('click', () => { if (this.spinning) this.forceStop = true; });
}
this.setGrid(this._randomGrid());
}
_randomGrid() {
const g = this.game;
const grid = [];
for (let r = 0; r < g.rows; r++) {
grid[r] = [];
for (let c = 0; c < g.reels; c++) grid[r][c] = this.codes[(Math.random() * this.codes.length) | 0];
}
return grid;
}
_cellHTML(code, extra = '') {
const sym = this.game.symbols[code];
return `<div class="rr-cell ${extra}" data-code="${code}">${symbolMarkup(sym)}</div>`;
}
/** Setzt die sichtbaren 3 Symbole je Walze ohne Animation. */
setGrid(grid) {
this.reels.forEach((reel, c) => {
let html = '';
for (let r = 0; r < this.game.rows; r++) html += this._cellHTML(grid[r][c]);
reel.strip.style.transition = 'none';
reel.strip.style.transform = 'translateY(0)';
reel.strip.innerHTML = html;
});
}
cellAt(col, row) {
const reel = this.reels[col];
if (!reel) return null;
return reel.strip.children[row] || null;
}
/** Dreht alle Walzen und landet auf finalGrid. Promise löst nach letztem Stopp. */
async spin(finalGrid, { fast = false } = {}) {
this.spinning = true;
this.forceStop = false;
const g = this.game;
const N = fast ? 12 : 16; // durchlaufende Zufallssymbole (kürzeres Band = flüssiger)
const baseDur = fast ? 560 : 880; // langsamer & weicher als zuvor
const stepDelay = fast ? 120 : 210; // ruhigeres, gestaffeltes Stoppen
// Strips mit langem Zufallsband + finalen 3 Symbolen aufbauen
this.reels.forEach((reel, c) => {
let html = '';
for (let i = 0; i < N; i++) html += this._cellHTML(this.codes[(Math.random() * this.codes.length) | 0]);
for (let r = 0; r < g.rows; r++) html += this._cellHTML(finalGrid[r][c]);
reel.strip.style.transition = 'none';
reel.strip.style.transform = 'translateY(0)';
reel.strip.innerHTML = html;
reel.el.classList.add('rr-blur');
reel._stopped = false;
});
// Reflow erzwingen
void this.root.offsetHeight;
// Anticipation bestimmen: Walze c bekommt Spannungs-Lauf, wenn auf den
// Walzen davor bereits >= 2 Scatter im Endergebnis liegen.
const anticipate = [];
let scattersBefore = 0;
for (let c = 0; c < this.reels.length; c++) {
anticipate[c] = scattersBefore >= 2;
for (let r = 0; r < g.rows; r++) if (this._isScatter(finalGrid[r][c])) scattersBefore++;
}
let firstAnt = this.reels.length;
for (let c = 0; c < this.reels.length; c++) if (anticipate[c]) { firstAnt = c; break; }
const stopReel = (c, ant) => new Promise((resolve) => {
const reel = this.reels[c];
if (reel._stopped) return resolve();
reel._stopped = true;
const cellH = reel.el.clientWidth; // quadratische Zellen
let dur = baseDur;
let travel = N;
if (ant) {
// Spannungs-Lauf: doppelt so lang & doppelt so schnell → längeres Band
travel = N * 4;
let html = '';
for (let i = 0; i < travel; i++) html += this._cellHTML(this.codes[(Math.random() * this.codes.length) | 0]);
for (let r = 0; r < g.rows; r++) html += this._cellHTML(finalGrid[r][c]);
reel.strip.style.transition = 'none';
reel.strip.style.transform = 'translateY(0)';
reel.strip.innerHTML = html;
reel.el.classList.add('rr-reel-anticipate');
void reel.el.offsetHeight;
dur = baseDur * 2;
reel.strip.style.transition = `transform ${dur}ms cubic-bezier(.2,.62,.22,1)`;
if (this.onAnticipate) this.onAnticipate(c);
} else {
reel.strip.style.transition = `transform ${dur}ms cubic-bezier(.22,.78,.26,1)`;
}
reel.strip.style.transform = `translate3d(0,${-travel * cellH}px,0)`;
setTimeout(() => {
// Auf finale 3 reduzieren + Bounce
reel.el.classList.remove('rr-blur', 'rr-reel-anticipate');
let html = '';
for (let r = 0; r < g.rows; r++) html += this._cellHTML(finalGrid[r][c], 'is-land');
reel.strip.style.transition = 'none';
reel.strip.style.transform = 'translateY(0)';
reel.strip.innerHTML = html;
reel.el.classList.add('rr-reel-stop');
setTimeout(() => reel.el.classList.remove('rr-reel-stop'), 440);
if (this.onReelStop) this.onReelStop(c);
resolve();
}, dur);
});
// Phase 1: normale Walzen gestaffelt (überlappend) stoppen.
const normalPromises = [];
for (let c = 0; c < firstAnt; c++) {
normalPromises.push((async () => {
let waited = 0;
const target = c * stepDelay;
while (waited < target && !this.forceStop) { await wait(20); waited += 20; }
return stopReel(c, false);
})());
}
await Promise.all(normalPromises);
// Phase 2: Spannungs-Walzen nacheinander (dramatisch), bei Klick sofort normal.
for (let c = firstAnt; c < this.reels.length; c++) {
await stopReel(c, !this.forceStop);
}
this.spinning = false;
}
}