PR pixi-reels
All recipes

How to slam-stop

Let the player tap the spin button again to land the reels immediately. `requestSkip()` queues until the result is in, no race-window to manage.

Loading recipe…

The minimum code

spinButton.addEventListener('click', async () => {
  if (reelSet.isSpinning) {
    // requestSkip is the safe variant: if the result hasn't arrived yet,
    // it queues the slam-stop and fires it as soon as setResult() is in.
    // Avoids the "user tapped before /api/spin returned" race.
    reelSet.requestSkip();
    return;
  }
  const promise = reelSet.spin();
  const response = await fetch('/api/spin').then((r) => r.json());
  reelSet.setResult(response.symbols);
  const result = await promise;
  if (result.wasSkipped) {
    // skip paths can use shorter/no win animations
  }
});

requestSkip() vs skip()

skip()requestSkip()
Result already setslams now (and applies round-aware side effects. speed boost in standard mode, cascade auto-slam in tumble mode)slams now
Result not set yetTHROWS. caller must catchqueues until result arrives, then slams
Caller responsibilitycatch the throw and either route to requestSkip() or wait for setResult()none

For player-facing slam-stop UX, prefer requestSkip(). it removes the timing trap.

If you want the round-aware boost behaviour of skip() but also need to be tap-safe pre-setResult, use both:

spinButton.addEventListener('click', () => {
  if (!reelSet.isSpinning) { /* start a new spin */ return; }
  // skip() THROWS before setResult arrives. Route to requestSkip() in
  // the catch so a tap during the server-wait window still queues the
  // slam. and still picks up the boost / cascade auto-slam side effects
  // when the result lands.
  try { reelSet.skipSpin(); }
  catch { reelSet.requestSkip(); }
});

What slam actually does

  1. Emits skip:requested.
  2. Force-completes every active spin phase (including GSAP tweens).
  3. Calls reel.placeSymbols(targetRow) on every reel with the result from setResult().
  4. Snaps the reel container y back to 0.
  5. Fires spin:reelLanded per reel + spin:allLanded + spin:complete with wasSkipped: true. same lifecycle hooks as a normal landing.

Test it

import { createTestReelSet, captureEvents } from 'pixi-reels/testing';

const { reelSet, spinAndLand } = createTestReelSet({ reels: 5, visibleRows: 3, symbolIds: ['a','b','c'] });
const log = captureEvents(reelSet, ['skip:requested', 'skip:completed', 'spin:complete']);

const result = await spinAndLand([['a','a','a'],['b','b','b'],['c','c','c'],['a','b','c'],['c','b','a']]);
expect(result.wasSkipped).toBe(true);
expect(log.map((e) => e.event)).toEqual(['skip:requested','spin:complete','skip:completed']);