Loading recipe…
The minimum code
const presenter = new WinPresenter(reelSet, {
symbolAnim: async (symbol) => /* your pop animation */,
});
await reelSet.runCascade({
detectWinners: (g) => yourDetector(g),
nextGrid: (prev, winners) => yourNextGrid(prev, winners),
onCascade: async ({ chain, winners }) => {
if (winners.length === 0) return;
await presenter.show([{
id: chain,
cells: winners.map(w => ({ reelIndex: w.reel, rowIndex: w.row })),
value: winners.length * 10,
}]);
},
// `destroySymbols` runs AFTER `onCascade` resolves. The presenter already
// drove the visual feedback, so we suppress the implode's zIndex bump
// (the destroy still snaps the cells to alpha 0, but invisibly).
destroyOptions: { zIndex: null },
});
One shape, two use cases
A “win” is just cells. Whether those cells came from a payline, a cluster, or a cascade pop, the presenter treats them the same. No isPayline / isCluster narrowing. just win.cells.
For mixed rounds (a scatter cluster + a payline in the same spin), pass them together:
await presenter.show([
{ id: 0, cells: paylineCells, value: 50 },
{ id: 1, cells: scatterCells, value: 500 }, // sorts first (default value desc)
]);
UX guardrails
destroyOptions: { zIndex: null }keeps the library’s implode invisible. the presenter already played the visual.presenter.abort()onspin:start. slam-spin mid-cascade cancels cleanly.- If you’d rather KEEP the library’s implode AS WELL as the presenter pop, drop
destroyOptionsentirely. The presenter resolves beforedestroySymbolsruns, so they read as one continuous pop.