import { Ticker } from 'pixi.js'; import { gsap } from 'gsap'; import { SpeedProfile, SymbolData, OffsetConfig, MultiWaysConfig, ReelAnchor } from '../config/types.js'; import { MaskStrategy } from './ReelViewport.js'; import { ReelSet } from './ReelSet.js'; import { SymbolRegistry } from '../symbols/SymbolRegistry.js'; import { PhaseFactory } from '../spin/phases/PhaseFactory.js'; import { SpinningMode } from '../spin/modes/SpinningMode.js'; import { FrameMiddleware } from '../frame/FrameBuilder.js'; import { ColumnTarget } from '../frame/ColumnTarget.js'; import { TumbleConfig } from '../cascade/TumbleConfig.js'; /** * The configurator you call before every reel set. * * `ReelSetBuilder` is a fluent, chainable builder: every call returns the * builder so you can string setup onto one expression. It hides the * twenty-odd subsystems you would otherwise have to wire by hand, and its * `.build()` step validates that every required piece is present (throws * at construction, not at first spin). * * Required calls (in any order): `.reels(n)`, `.visibleRows(n)`, * `.symbolSize(w, h)`, `.symbols((registry) => ...)`, `.ticker(app.ticker)`. * Optional: `.symbolGap()`, `.weights()`, `.symbolData()`, `.speed()`, * `.bufferSymbols()`, `.offset()`, `.frameMiddleware()`, `.phases()`, * `.spinningMode()`. * * ```ts * const reelSet = new ReelSetBuilder() * .reels(5) * .visibleRows(3) * .symbolSize(200, 200) * .symbols((r) => { * r.register('cherry', SpriteSymbol, { textures: { cherry: tex } }); * }) * .weights({ cherry: 20 }) * .ticker(app.ticker) * .build(); * ``` */ export declare class ReelSetBuilder { private _reelCount?; private _visibleRows?; private _symbolWidth?; private _symbolHeight?; private _symbolGap; private _bufferAbove; private _bufferBelow; private _symbolRegistry; private _weights; private _speeds; private _initialSpeed; private _offset; private _ticker?; private _spinningMode; private _phaseFactory; private _middlewares; private _initialFrame?; private _symbolDataOverrides; private _tumbleConfig?; private _defaultSpinMode; /** Per-reel static row counts (jagged shapes like 3-5-5-5-3). */ private _visibleRowsPerReel?; /** Per-reel pixel-box heights. used for both pyramids and MultiWays. */ private _reelPixelHeights?; /** Vertical alignment of short reels inside the tallest reel's box. */ private _reelAnchor; /** MultiWays configuration. Set by `.multiways(...)`. */ private _multiways?; /** Per-reel AdjustPhase tween duration in ms (MultiWays only). */ private _pinMigrationDuration; /** GSAP easing string used by AdjustPhase. Default: 'power2.out'. */ private _pinMigrationEase; /** Mask strategy. Default: per-reel `RectMaskStrategy`. */ private _maskStrategy; /** True if the user explicitly set a mask strategy (no auto-pick override). */ private _maskStrategyExplicit; private _rng; private _poolCapacity?; /** Set number of reel columns. */ reels(count: number): this; /** * Number of visible rows per reel (uniform across all reels). * Mutually exclusive with `visibleRowsPerReel()`. calling both throws * at `build()`. * * @example * builder.reels(5).visibleRows(3) // classic 5x3 */ visibleRows(count: number): this; /** * Per-reel static row counts. Length MUST equal `reels()`. Mutually * exclusive with `visibleRows()`; calling both throws at `build()`. * * @example * builder.reels(5).visibleRowsPerReel([3, 5, 5, 5, 3]) // pyramid */ visibleRowsPerReel(rows: number[]): this; /** * Per-reel pixel-box heights. Length MUST equal `reels()`. * * - Pyramid: defaults to `visibleRowsPerReel[i] * symbolHeight`. Override * to make all reels the same height with different cell heights per * reel. * - MultiWays: every entry equals the same fixed reel height. Cell * height per reel is derived as `reelPixelHeight / visibleRows[i]`. * * Precedence: when both `reelPixelHeights` and `reelAnchor` are set, * `reelPixelHeights` wins. anchor is derived from the explicit boxes. */ reelPixelHeights(heights: number[]): this; /** Vertical alignment of short reels inside the tallest reel's box. Default 'center'. */ reelAnchor(anchor: ReelAnchor): this; /** * Custom mask strategy for the viewport. Defaults to {@link RectMaskStrategy} * (one clip rect per reel. clean for pyramid + uniform layouts). * * Use {@link SharedRectMaskStrategy} when reels have horizontal gaps * AND symbols (typically big symbols) need to overlap across reel * boundaries. the per-reel default would clip them at the gaps. * * Or pass any custom `MaskStrategy` for non-rectangular masks (rounded * frames, hexagonal grids, etc.). * * @example * import { SharedRectMaskStrategy } from 'pixi-reels'; * builder.maskStrategy(new SharedRectMaskStrategy()) */ maskStrategy(strategy: MaskStrategy): this; /** * Configure this slot as MultiWays: per-spin row variation. Pass minRows, * maxRows, and the fixed reel pixel height. After build, call * `reelSet.setShape(rowsPerReel)` mid-spin to set the next stop's shape. * * Mutually exclusive with big-symbol registration (`SymbolData.size`). * Mutually exclusive with cascade mode in v1. */ multiways(config: MultiWaysConfig): this; /** * AdjustPhase tween duration in ms (MultiWays only). Pass a number for a * uniform duration across reels, or a function `(reelIndex) => number` * for per-reel control. Default: 200. Pass `0` for an instant snap (no * tween). * * AdjustPhase plays on top of whatever stop staggering you've configured *. its duration is independent of `stopDelay`. */ pinMigrationDuration(value: number | ((reelIndex: number) => number)): this; /** * GSAP easing string used by AdjustPhase tweens (MultiWays only). * Applied to both the cell-resize tween and any pin-overlay migration * tween. Defaults to `'power2.out'`. See gsap.com/docs/v3/Eases for * the full vocabulary. * * @example * builder.pinMigrationEase('back.out(1.4)') // pop-in feel * builder.pinMigrationEase('expo.inOut') // slow start + slow end */ pinMigrationEase(ease: string): this; /** Set symbol dimensions in pixels. */ symbolSize(width: number, height: number): this; /** Set gap between symbols. Default: { x: 0, y: 0 }. */ symbolGap(x: number, y: number): this; /** * Set number of buffer symbols above/below the visible area. Default: 1. * * Buffer rows are off-screen cells the reel keeps around the visible * window so symbols can fade/slide in cleanly. The motion layer's wrap * detection assumes at least one buffer row above and one below. the * minimum supported count is **1**. Passing `0` (or a negative number) * is clamped to `1` and a single console warning is printed; the * builder does not throw, so existing user code keeps running. * * **Tumble-only reel sets** may drop the below-window buffer entirely * with the object form: `bufferSymbols({ above: 1, below: 0 })`. A pure * tumble never scrolls the strip, so nothing ever wraps through the * below-window cells. they exist only to be hidden by the mask. This * requires `.tumble(...)` on the builder (validated at `build()`), and * strip spins (`spin({ mode: 'standard' })`) and `nudge()` throw on * such a set. `above` keeps the minimum of 1 (drop-in movers are * pre-positioned above the window). */ bufferSymbols(count: number | { above: number; below: number; }): this; private _clampBufferMin1; /** One-shot guard so we don't spam consoles when builders are constructed in a loop. */ private static _bufferWarnedThisProcess; /** Configure symbols via a registry callback. */ symbols(configurator: (registry: SymbolRegistry) => void): this; /** Set weights for random symbol generation. */ weights(weights: Record): this; /** * Per-symbol metadata overrides (zIndex, unmask, or a custom weight that * replaces the one from `weights()`). Merged into the final symbolsData map *. any field you don't specify falls back to the default. * * `zIndex` sorts within ONE reel's container only. it can never lift a * symbol above the reel to its right (reels are separate containers). * Cross-reel and out-of-mask layering needs `unmask: true`, which is an * **at-rest** presentation: while the reel spins the symbol stays masked * like everything else; on land, visible-row instances are lifted into * the viewport-wide `unmaskedContainer` (above every reel and the mask) * and pulled back down when the next spin starts. * * @example * .symbolData({ * wild: { zIndex: 5 }, // above reel-mates (same reel only) * bonus: { zIndex: 10, unmask: true }, // landed: above all reels + mask * }) */ symbolData(overrides: Record>): this; /** Add a named speed profile. */ speed(name: string, profile: SpeedProfile): this; /** Set which speed profile to use initially. Default: 'normal'. */ initialSpeed(name: string): this; /** Set X-axis offset config (e.g., trapezoid perspective). Default: 'none'. */ offsetConfig(config: OffsetConfig): this; /** Set the PixiJS ticker for frame updates. */ ticker(ticker: Ticker): this; /** * Inject the source of randomness used to fill the scrolling strip (buffer * fill, the symbols shown during SPIN before `setResult` lands, nudge * padding). Must return a value in [0, 1). Default: `Math.random`. * * **Why you'd set this:** server-authoritative *outcomes* do not make the * on-screen strip reproducible — the symbols a player sees scrolling are * drawn from this RNG. Injecting a seeded, audited PRNG lets you replay the * exact visual sequence from a seed, which provably-fair and regulated * real-money deployments are eventually required to produce. * * @example * import { ReelSetBuilder } from 'pixi-reels'; * const seeded = mulberry32(serverSeed); // your audited PRNG * const reelSet = new ReelSetBuilder().reels(5).visibleRows(3) * .symbols(...).ticker(app.ticker).rng(seeded).build(); */ rng(fn: () => number): this; /** * Override the per-symbol-id recycle-pool capacity. By default the engine * sizes the pool to the whole strip (every visible + buffer cell), so even a * grid that is briefly all one symbol recycles instead of churning through * `destroy()` + recreate. Set this only to cap memory on very large grids, or * to raise headroom for unusually heavy simultaneous symbol swaps. */ poolCapacity(maxPerSymbol: number): this; /** * Inject the GSAP instance the engine should use for tweens. * * **When you need this:** if your app already imports `gsap` and your * bundler resolves `gsap` to a different module instance than the one * `pixi-reels` resolved (common with symlinked workspaces, npm-link, or * misconfigured `dedupe`), every tween you start on a target the engine * also tweens will fight a separate timeline. Symptoms: spotlights that * render but never finish, animations that double-fire, tweens that * silently drop on hidden tabs in only one of the two instances. * * Calling `.gsap(myGsap)` rebinds every internal phase, motion tween, * pin-flight tween, and SpriteSymbol win pulse to the GSAP you pass. * guaranteed to be the same instance that drives your own animations. * * Default: the `gsap` import resolved at the engine's own * `node_modules/gsap` path. If your app and the engine resolve to the * same instance (the common case in production bundles with proper * `dedupe`), you do NOT need to call this. * * Idempotent. calling again with the same instance is a no-op. Calling * with a different instance after `.build()` only affects tweens * started after the swap. * * @example * import { gsap } from 'gsap'; * const reelSet = new ReelSetBuilder() * .reels(5).visibleRows(3).symbolSize(200, 200) * .symbols(...) * .ticker(app.ticker) * .gsap(gsap) // ensure engine and app share one instance * .build(); */ gsap(instance: typeof gsap): this; /** Set the spinning mode. Default: StandardMode. */ spinningMode(mode: SpinningMode): this; /** Add custom frame middleware. */ frameMiddleware(middleware: FrameMiddleware): this; /** Override default phases. */ phases(configurator: (factory: PhaseFactory) => void): this; /** * Enable tumble cascade mechanics. Replaces strip-spin + bounce-stop with * a three-phase pipeline: * * 1. **`cascade:fall`**. on `spin()`, existing visible symbols fall * off the bottom of the viewport. * 2. **`cascade:place`**. when `setResult()` arrives, new symbol * identities swap into the buffer at their final grid positions. * 3. **`cascade:dropIn`**. new symbols animate from above (and * survivors slide down to fill holes) into the grid. * * For a Moment B refill after wins are cleared, call * `reelSet.refill({ winners, grid })`. that skips fall + wait and runs * `place` + `dropIn` only, with gravity-correct geometry driven by the * `winners` list (untouched symbols don't animate; survivors slide; * new symbols come from above). * * Every phase boundary fires a `cascade:*` event on * `reelSet.events`. per-symbol events (`cascade:fall:symbol` / * `cascade:dropIn:symbol`) carry the symbol, view, and the timing the * library is about to apply, so listeners can run parallel tweens on * any other property in sync with the library's `view.y` motion. * * Override any individual phase via `.phases(f => f.register('cascade:fall', MyPhase))`. * * @example * builder.tumble({ * fall: { duration: 300, ease: 'sine.in', rowStagger: 60 }, * dropIn: { duration: 600, ease: 'power2.out', rowStagger: 60, distance: 'perHole' }, * }); */ tumble(config?: TumbleConfig): this; /** * Set the initial symbol grid the reels show before the first spin. * * One `ColumnTarget` per reel. `visible` lists the symbols in the visible * window; optional `bufferAbove` / `bufferBelow` prefill cells outside it * (`[0]` is the slot closest to the visible window, later indices go * further out). * * @example * builder.initialFrame([ * { visible: ['A','B','C'] }, * { visible: ['A','B','C'], bufferAbove: ['COIN'] }, * { visible: ['A','B','C'], bufferBelow: ['SCATTER'] }, * ]); */ initialFrame(frame: ColumnTarget[]): this; /** Build the ReelSet. Validates configuration and assembles all internal objects. */ build(): ReelSet; private _validate; } //# sourceMappingURL=ReelSetBuilder.d.ts.map