PR pixi-reels
All recipes

Hold & Win respin

Coins lock on land; every new coin resets the respin counter; fill the grid for the grand jackpot. The "coin" formula every studio is shipping in 2024.

Loading recipe…

Press Run a few times. every coin that lands stays put on the next respin.

The full Hold & Win loop. Combines setResult() with a game-side held map to keep coins sticky across respins. The library doesn’t own the mechanic. it gives you deterministic landings and event hooks; the respin accounting is yours.

import { ReelSetBuilder, SpriteSymbol } from 'pixi-reels';

const held = new Map<string, string>();   // "r,row" -> symbolId

async function holdAndWinRound() {
  const maxRespins = 3;
  let respinsLeft = maxRespins;

  while (respinsLeft > 0) {
    const promise = reelSet.spin();

    // Produce a grid that keeps held coins in place, lands 0..N new coins
    const grid = buildGridKeepingHeld(held);
    reelSet.setResult(grid);
    const result = await promise;

    const newCoins = findNewCoins(result.symbols, held);
    if (newCoins.length > 0) {
      respinsLeft = maxRespins;     // reset on every new coin
      for (const c of newCoins) held.set(`${c.r},${c.row}`, c.symbolId);
    } else {
      respinsLeft--;
    }

    const totalCells = reelSet.reels.length * reelSet.reels[0].getVisibleSymbols().length;
    if (held.size === totalCells) {
      console.log('GRAND JACKPOT');
      break;
    }
  }
}

The three moving parts

  1. buildGridKeepingHeld(held). every held position MUST appear with its locked symbolId; the rest are random / from your weighted generator.
  2. findNewCoins(grid, held). any coin on the grid that isn’t already in held is a new land.
  3. Respin counter. resets to maxRespins on every new land, decrements on every dry spin. When it hits zero, the round ends.

Full playable demo

For the playable version with a cheat panel (force landing, guaranteed jackpot, reset), see the hold-and-win-respin demo.

Simpler variant: spin({ holdReels }) on a single ReelSet

The single-coin / column-respin patterns (“hold reels 0 and 4, reroll the middle three”) used to require N independent ReelSet instances bundled into a row. With the new holdReels option that’s one spin() call:

// Hold the outer columns; only middle three reroll.
const spin = reelSet.spin({ holdReels: [0, 4] });
reelSet.setResult(serverGrid); // entries at indices 0 and 4 are ignored
await spin;

Behaviour:

  • Held reels skip START / SPIN / STOP entirely. they don’t move at all.
  • They count as already-landed for spin:allLanded, so the resolver fires once the non-held reels land.
  • setAnticipation([...]) silently filters held indices; setStopDelays([...]) ignores entries at held indices.
  • SpinResult.symbols reports the full visible grid (held rows unchanged, non-held rows landed).

When to pick which:

  • Per-cell hold-and-win (this recipe’s main pattern, with coins locked one cell at a time): the per-column ReelSet array is still the cleanest model. Each cell is its own ReelSet with visibleRows(1).
  • Single-reel respin / partial reroll (whole columns held, others spin): use spin({ holdReels }) on a single ReelSet. Spotlight, anticipation, win presenters keep working since everything stays inside one engine.