123 lines
4.2 KiB
JavaScript
123 lines
4.2 KiB
JavaScript
import { SYMBOLS } from './Symbol.js';
|
|
|
|
const SYMBOL_HEIGHT = 110;
|
|
const SYMBOL_WIDTH = 200;
|
|
const VISIBLE_COUNT = 3;
|
|
const REEL_HEIGHT = SYMBOL_HEIGHT * VISIBLE_COUNT;
|
|
const BUFFER = 2; // extra cells above/below visible area for smooth scroll
|
|
const POOL_SIZE = VISIBLE_COUNT + BUFFER * 2;
|
|
|
|
export class Reel {
|
|
constructor(scene, x, y) {
|
|
this.scene = scene;
|
|
this.x = x;
|
|
this.y = y;
|
|
this.w = SYMBOL_WIDTH;
|
|
this.h = REEL_HEIGHT;
|
|
|
|
// Virtual infinite strip: cycle through SYMBOLS array using modulo
|
|
this.strip = SYMBOLS; // reference, never mutated
|
|
this.scrollY = 0;
|
|
|
|
// Container with mask for clipping
|
|
this.container = scene.add.container(x, y);
|
|
const maskShape = scene.make.graphics({ add: false });
|
|
maskShape.fillRect(x, y, this.w, this.h);
|
|
this.container.setMask(maskShape.createGeometryMask());
|
|
|
|
// Small pool of Graphics + Sprite + Text triples (only as many as visible + buffer)
|
|
this.cells = [];
|
|
for (let i = 0; i < POOL_SIZE; i++) {
|
|
const gfx = scene.add.graphics();
|
|
const spr = scene.add.sprite(0, 0, 'symbols', 0).setOrigin(0.5, 0.5);
|
|
this.container.add(gfx);
|
|
this.container.add(spr);
|
|
this.cells.push({ gfx, spr });
|
|
}
|
|
|
|
this._draw();
|
|
}
|
|
|
|
// Returns the symbol showing in the center (result) slot
|
|
getCenter() {
|
|
const s = -this.scrollY;
|
|
const centerScrollY = s + SYMBOL_HEIGHT;
|
|
const idx = Math.floor(centerScrollY / SYMBOL_HEIGHT);
|
|
return this.strip[((idx % this.strip.length) + this.strip.length) % this.strip.length];
|
|
}
|
|
|
|
_draw() {
|
|
// Negate scrollY so increasing scrollY moves symbols downward (top-to-bottom direction)
|
|
const s = -this.scrollY;
|
|
const topCellIdx = Math.floor(s / SYMBOL_HEIGHT);
|
|
|
|
for (let i = 0; i < POOL_SIZE; i++) {
|
|
const virtualIdx = topCellIdx - BUFFER + i;
|
|
const symbolIdx = ((virtualIdx % this.strip.length) + this.strip.length) % this.strip.length;
|
|
const sym = this.strip[symbolIdx];
|
|
const cellY = virtualIdx * SYMBOL_HEIGHT - s;
|
|
|
|
const { gfx, spr } = this.cells[i];
|
|
gfx.clear();
|
|
gfx.fillStyle(sym.color, 1);
|
|
gfx.fillRoundedRect(2, cellY + 2, this.w - 4, SYMBOL_HEIGHT - 4, 8);
|
|
gfx.lineStyle(2, 0xffffff, 0.35);
|
|
gfx.strokeRoundedRect(2, cellY + 2, this.w - 4, SYMBOL_HEIGHT - 4, 8);
|
|
|
|
spr.setFrame(symbolIdx);
|
|
spr.setPosition(this.w / 2, cellY + SYMBOL_HEIGHT / 2);
|
|
}
|
|
}
|
|
|
|
// Spin to targetSymbol. Duration controls how long the fast phase lasts.
|
|
// onComplete fires when the reel finishes.
|
|
spin(targetSymbol, duration, onComplete) {
|
|
const scene = this.scene;
|
|
|
|
// Pre-calculate a target scrollY that:
|
|
// - lands targetSymbol in the center slot (scrollY = targetIdx * SYMBOL_HEIGHT - SYMBOL_HEIGHT)
|
|
// - is strictly greater than current scrollY by at least MIN_ADVANCE
|
|
const MIN_ADVANCE = SYMBOL_HEIGHT * 10; // enough for a satisfying visible spin
|
|
const stripHeight = this.strip.length * SYMBOL_HEIGHT;
|
|
|
|
const targetSymbolIdx = this.strip.findIndex(s => s.id === targetSymbol.id);
|
|
// With negated draw: center when -scrollY = targetSymbolIdx * SYMBOL_HEIGHT - SYMBOL_HEIGHT
|
|
// => scrollY = SYMBOL_HEIGHT - targetSymbolIdx * SYMBOL_HEIGHT
|
|
let targetScrollY = SYMBOL_HEIGHT - targetSymbolIdx * SYMBOL_HEIGHT;
|
|
|
|
// scrollY must DECREASE by at least MIN_ADVANCE (spin goes in negative direction)
|
|
while (targetScrollY >= this.scrollY - MIN_ADVANCE) {
|
|
targetScrollY -= stripHeight;
|
|
}
|
|
|
|
// Phase 1: fast linear scroll to ~2 symbols before the final landing
|
|
const fastTarget = targetScrollY + SYMBOL_HEIGHT * 2;
|
|
|
|
scene.tweens.add({
|
|
targets: this,
|
|
scrollY: fastTarget,
|
|
duration: duration,
|
|
ease: 'Linear',
|
|
onUpdate: () => this._draw(),
|
|
onComplete: () => {
|
|
// Phase 2: decelerate smoothly into the final position
|
|
scene.tweens.add({
|
|
targets: this,
|
|
scrollY: targetScrollY,
|
|
duration: 650,
|
|
ease: 'Cubic.easeOut',
|
|
onUpdate: () => this._draw(),
|
|
onComplete: () => {
|
|
this.scrollY = targetScrollY;
|
|
this._draw();
|
|
if (onComplete) onComplete();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
getWidth() { return this.w; }
|
|
getHeight() { return this.h; }
|
|
}
|