pixi-reels 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # pixi-reels
2
2
 
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#158](https://github.com/schmooky/pixi-reels/pull/158) [`22f2b33`](https://github.com/schmooky/pixi-reels/commit/22f2b339a8b2f285a08678c080aaa854e988fde0) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: `BoardGrid` — the generic "board of reels" primitive is now a public export. A grid of cells that each spin independently (`cells`, `spinCells`, `symbolAt`/`reelAt`, `cellBounds`/`cellCenter`, `setProfile`, `place`), with no game rules of its own. `HoldAndWinBoard` is one opinionated board built on it; build your own the same way. `spinCells`' per-cell `onLanded` callback may be async — return a promise and `spinCells` resolves only once every cell has landed and its after-land work has finished.
8
+
9
+ - [#158](https://github.com/schmooky/pixi-reels/pull/158) [`22f2b33`](https://github.com/schmooky/pixi-reels/commit/22f2b339a8b2f285a08678c080aaa854e988fde0) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: Hold & Win board. `HoldAndWinBuilder` builds a `HoldAndWinBoard` — a grid of independently spinning 1×1 cells with the full respin / lock / collect lifecycle (`enter`, `respin`, `release`, `setSymbolAt`, `skip`, `reset`), typed events (`coin:locked`, `board:full`, `feature:end`, …), per-cell geometry (`cellBounds`/`cellCenter`) and live symbol access (`symbolAt`/`reelAt`). Coins are opaque `{ cell, id, data }`, so value, multipliers, collectors and flights stay game-layer. Also exports `EmptySymbol` (a render-nothing symbol), plus `cellKey` and the `HwEffect` type so you can fork `HoldAndWinBoard` + `HoldAndWinState` and keep every import on public API.
10
+
11
+ ### Patch Changes
12
+
13
+ - [#158](https://github.com/schmooky/pixi-reels/pull/158) [`22f2b33`](https://github.com/schmooky/pixi-reels/commit/22f2b339a8b2f285a08678c080aaa854e988fde0) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Fix: harden and complete the Hold & Win board public surface. `HoldAndWinState` (the pure reducer) is now exported from the barrel, so the documented "fork `HoldAndWinBoard` + `HoldAndWinState` and keep every import on public API" path actually resolves. `beginWave`/`respin` now throws on a duplicate hit targeting the same cell in one wave instead of silently dropping the first coin (a malformed result fails loud, matching `enter`'s duplicate-seed guard). A failed `playWin()` reaction to `coin:locked` is now logged via `console.warn` instead of being swallowed silently, and `setSymbolAt`'s JSDoc documents that it must not be called mid-wave.
14
+
15
+ - [#158](https://github.com/schmooky/pixi-reels/pull/158) [`22f2b33`](https://github.com/schmooky/pixi-reels/commit/22f2b339a8b2f285a08678c080aaa854e988fde0) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Fix: harden `HoldAndWinBoard` recovery and mid-wave misuse. If `respin()` throws between starting and closing a wave — most plausibly a game-layer `respin:start` / `cell:landed` / `coin:locked` listener throwing — it now restores the reducer's phase and slams any still-spinning cells before rethrowing, so a failed wave no longer strands the board in `spinning` (where every later `respin()` threw "wave in flight") or leaves an orphaned reel (where the next `respin()` threw "already spinning"). The error still propagates to the caller. The reducer also ignores stray landings outside a wave, so a cell settling after a `reset()` or a recovered error can no longer re-lock a coin into a cleared ledger or flip a finished feature back to active. `release()` and `setSymbolAt()` still throw if called while a wave is in flight. `respin()` now returns a caller-owned `hits` array (a copy of the wave's landings) rather than a live reference into reducer state, so mutating the result can't reach back into the board.
16
+
17
+ ## 1.0.1
18
+
19
+ ### Patch Changes
20
+
21
+ - [#150](https://github.com/schmooky/pixi-reels/pull/150) [`6a96d60`](https://github.com/schmooky/pixi-reels/commit/6a96d603cbc8b9f1b80176268850ad9157177c26) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Fix: buffer-anchored big symbols no longer render empty, and big-symbol blocks no longer jitter, when falling through a tumble cascade. `CascadePlacePhase` now preserves `bufferAbove` target cells, so a "tail-visible" block (anchor above the viewport) keeps its anchor through the animated place path instead of being overwritten with a random symbol and leaving its visible cell empty. The place and drop-in phases now animate each block anchor exactly once instead of once per occupied visible row — previously the duplicate drop tweens fought over the anchor's position (the jitter) and could land it a row off target.
22
+
3
23
  ## 1.0.0
4
24
 
5
25
  ### Major Changes
@@ -0,0 +1,129 @@
1
+ import { Container, Graphics, Ticker } from 'pixi.js';
2
+ import { ReelSet } from '../core/ReelSet.js';
3
+ import { ReelSymbol } from '../symbols/ReelSymbol.js';
4
+ import { SymbolRegistry } from '../symbols/SymbolRegistry.js';
5
+ import { SpeedProfile, SymbolData } from '../config/types.js';
6
+ import { Disposable } from '../utils/Disposable.js';
7
+ /** A cell coordinate in the grid. */
8
+ export interface BoardCell {
9
+ col: number;
10
+ row: number;
11
+ }
12
+ /** A landing target: spin `cell` and stop it showing `id`. */
13
+ export interface BoardSpinTarget {
14
+ cell: BoardCell;
15
+ id: string;
16
+ }
17
+ /** A speed profile, or a per-cell function of one (e.g. a stagger wave). */
18
+ export type BoardProfile = SpeedProfile | ((cell: BoardCell) => SpeedProfile);
19
+ export interface BoardGridOptions {
20
+ /** Grid dimensions. */
21
+ cols: number;
22
+ rows: number;
23
+ /** Cell edge length in pixels. */
24
+ cellSize: number;
25
+ /** Gap between cells. Default 4. */
26
+ gap?: number;
27
+ /** Id a cell shows when blank — also placed in the off-window buffers. Default `'empty'`. */
28
+ emptyId?: string;
29
+ /** Register symbol classes, exactly like `ReelSetBuilder.symbols`. Applied to every cell. */
30
+ symbols: (registry: SymbolRegistry) => void;
31
+ /** Strip weights during the spin. */
32
+ weights?: Record<string, number>;
33
+ /** Per-symbol engine overrides, exactly like `ReelSetBuilder.symbolData`. */
34
+ symbolData?: Record<string, Partial<SymbolData>>;
35
+ /** Injected RNG for the spin strips (deterministic demos / tests). */
36
+ rng?: () => number;
37
+ /** Drives every cell's reel — required. */
38
+ ticker: Ticker;
39
+ /** Per-cell background, drawn behind each reel. */
40
+ chrome?: (g: Graphics, size: number) => void;
41
+ /**
42
+ * Named speed profiles, each registered on every cell and selected by name
43
+ * via {@link BoardGrid.setProfile}. A value may be a flat profile or a
44
+ * per-cell function (for stagger waves). Defaults to a single `'default'`
45
+ * profile, which is the active one until you `setProfile` otherwise.
46
+ */
47
+ profiles?: Record<string, BoardProfile>;
48
+ }
49
+ /**
50
+ * A grid of cells that each spin **independently** — the generic "board of
51
+ * reels" primitive. Every cell is its own 1×1 {@link ReelSet}, so it inherits
52
+ * the engine's phases, speed modes and pooling rather than a parallel lighter
53
+ * reel.
54
+ *
55
+ * Deliberately mechanism-only: it knows nothing about coins, locks, respins,
56
+ * value or any game rule. It lays the grid out, hands back per-cell geometry
57
+ * and live symbol instances, places symbols instantly, and spins a
58
+ * **caller-chosen** set of cells to caller-chosen results. Build your own
59
+ * feature on top by owning the rules in your own code; {@link HoldAndWinBoard}
60
+ * is one such opinionated layer, built entirely on this public surface.
61
+ *
62
+ * ```ts
63
+ * const grid = new BoardGrid({
64
+ * cols: 3, rows: 3, cellSize: 80,
65
+ * symbols: (r) => r.register('prize', PrizeSymbol, {}),
66
+ * weights: { prize: 1, empty: 4 },
67
+ * ticker: app.ticker,
68
+ * });
69
+ * app.stage.addChild(grid.container);
70
+ *
71
+ * await grid.spinCells(
72
+ * grid.cells().map((cell) => ({ cell, id: pick() })), // you decide each result
73
+ * (cell, id) => console.log('landed', cell, id), // react as each settles
74
+ * );
75
+ * ```
76
+ */
77
+ export declare class BoardGrid implements Disposable {
78
+ readonly container: Container;
79
+ readonly cols: number;
80
+ readonly rows: number;
81
+ readonly cellSize: number;
82
+ readonly gap: number;
83
+ readonly emptyId: string;
84
+ private readonly _reels;
85
+ private readonly _cells;
86
+ private _destroyed;
87
+ constructor(opts: BoardGridOptions);
88
+ /** Every cell coordinate, row-major. */
89
+ cells(): BoardCell[];
90
+ /** Board-local bounds of a cell. `container.toGlobal` for stage space. */
91
+ cellBounds(cell: BoardCell): {
92
+ x: number;
93
+ y: number;
94
+ width: number;
95
+ height: number;
96
+ };
97
+ /** Board-local center of a cell — flight / trail start and end points. */
98
+ cellCenter(cell: BoardCell): {
99
+ x: number;
100
+ y: number;
101
+ };
102
+ /** Live symbol instance currently shown in a cell. */
103
+ symbolAt(cell: BoardCell): ReelSymbol;
104
+ /** The cell's underlying 1×1 ReelSet, for driving one cell directly. */
105
+ reelAt(cell: BoardCell): ReelSet;
106
+ /** Select a registered speed profile by name for one cell. */
107
+ setProfile(cell: BoardCell, name: string): void;
108
+ /** Place a symbol instantly (no spin), with blank off-window buffers. */
109
+ place(cell: BoardCell, id: string): void;
110
+ /**
111
+ * Spin each target cell and stop it showing its `id`; `onLanded` fires per
112
+ * cell as it settles, in stagger order. The caller selects which cells spin
113
+ * and to what — this layer applies no lock/free policy of its own. Set
114
+ * profiles via {@link setProfile} first.
115
+ *
116
+ * `onLanded` may be **async**: if it returns a promise, that cell's task
117
+ * awaits it, so the returned promise resolves only once every cell has landed
118
+ * *and* its after-land work has finished. Cells still run concurrently, so an
119
+ * early cell's reveal overlaps with later cells still spinning.
120
+ */
121
+ spinCells(targets: BoardSpinTarget[], onLanded?: (cell: BoardCell, id: string) => void | Promise<void>): Promise<void>;
122
+ /** Slam every in-flight cell to its landed position. Returns the count. */
123
+ skipSpinning(): number;
124
+ get isDestroyed(): boolean;
125
+ destroy(): void;
126
+ private _origin;
127
+ private _reel;
128
+ }
129
+ //# sourceMappingURL=BoardGrid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BoardGrid.d.ts","sourceRoot":"","sources":["../../src/board/BoardGrid.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,138 @@
1
+ import { Container, Graphics, Ticker } from 'pixi.js';
2
+ import { EventEmitter } from '../events/EventEmitter.js';
3
+ import { ReelSet } from '../core/ReelSet.js';
4
+ import { ReelSymbol } from '../symbols/ReelSymbol.js';
5
+ import { SymbolRegistry } from '../symbols/SymbolRegistry.js';
6
+ import { SpeedProfile, SymbolData } from '../config/types.js';
7
+ import { Disposable } from '../utils/Disposable.js';
8
+ import { HwPhase } from './HoldAndWinState.js';
9
+ import { HoldAndWinBoardEvents, HwCell, HwCoin, HwRespinResult } from './HwTypes.js';
10
+ /** Internal config produced by {@link HoldAndWinBuilder.build}. */
11
+ export interface HoldAndWinBoardConfig<TData> {
12
+ cols: number;
13
+ rows: number;
14
+ cell: number;
15
+ gap: number;
16
+ emptyId: string;
17
+ respins: number;
18
+ configurator: (registry: SymbolRegistry) => void;
19
+ weights: Record<string, number> | null;
20
+ symbolData: Record<string, Partial<SymbolData>> | null;
21
+ baseProfile: SpeedProfile;
22
+ stagger: (col: number, row: number) => number;
23
+ anticipateWhen: ((state: {
24
+ locked: number;
25
+ capacity: number;
26
+ respinsLeft: number;
27
+ }) => boolean) | null;
28
+ chrome: ((g: Graphics, size: number) => void) | null;
29
+ ticker: Ticker;
30
+ rng: (() => number) | null;
31
+ }
32
+ /**
33
+ * A Hold & Win board: a grid of independently spinning cells plus the round
34
+ * choreography every H&W game repeats — spin the free cells, lock the hits,
35
+ * reset-or-decrement the respin counter, detect the full board.
36
+ *
37
+ * It composes two collaborators: a `BoardGrid` (the generic "board of reels"
38
+ * mechanism — geometry, instances, spinning) and a `HoldAndWinState` (the pure
39
+ * single-source reducer — ledger, counter, phase). The board is the
40
+ * mediator: it drives the reels, reports each landing to the reducer, and
41
+ * replays the reducer's decided effects onto {@link events}.
42
+ *
43
+ * It deliberately owns nothing about *value*. Coins are opaque `{ cell, id, data }`
44
+ * — `id` picks the registered art, `data` is the game layer's to read and mutate.
45
+ * Adders, doublers, collectors and flights are game design, expressed through
46
+ * three openings rather than board features: {@link events}, {@link symbolAt}
47
+ * (the live `ReelSymbol` instance) and {@link cellBounds}/{@link cellCenter}
48
+ * (pixel geometry for flights).
49
+ *
50
+ * ```ts
51
+ * const board = new HoldAndWinBuilder<{ value: number }>()
52
+ * .grid(5, 3).cellSize(72, { gap: 4 })
53
+ * .symbols((r) => r.register('coin', CoinSymbol, COIN_TRIGGER))
54
+ * .weights({ coin: 1, empty: 3 }).respins(3).ticker(app.ticker)
55
+ * .build();
56
+ *
57
+ * board.events.on('coin:locked', ({ coin }) => hud.add(coin.data.value));
58
+ * board.enter(triggerCoins);
59
+ * while (true) {
60
+ * const round = await server.respin(board.lockedCoins);
61
+ * const result = await board.respin(round.hits);
62
+ * if (result.done) break; // game animates between rounds
63
+ * }
64
+ * ```
65
+ */
66
+ export declare class HoldAndWinBoard<TData = unknown> implements Disposable {
67
+ readonly events: EventEmitter<HoldAndWinBoardEvents<TData>>;
68
+ readonly cols: number;
69
+ readonly rows: number;
70
+ private readonly _grid;
71
+ private readonly _state;
72
+ private readonly _emptyId;
73
+ private readonly _anticipateWhen;
74
+ constructor(cfg: HoldAndWinBoardConfig<TData>);
75
+ get container(): Container;
76
+ get capacity(): number;
77
+ get respinsLeft(): number;
78
+ get lockedCoins(): HwCoin<TData>[];
79
+ get isFull(): boolean;
80
+ get freeCells(): HwCell[];
81
+ /** Where the feature is right now: idle (no feature), active, or spinning. */
82
+ get phase(): HwPhase;
83
+ cellBounds(cell: HwCell): {
84
+ x: number;
85
+ y: number;
86
+ width: number;
87
+ height: number;
88
+ };
89
+ cellCenter(cell: HwCell): {
90
+ x: number;
91
+ y: number;
92
+ };
93
+ /** Live symbol instance currently shown in a cell. */
94
+ symbolAt(cell: HwCell): ReelSymbol;
95
+ /** The cell's underlying 1×1 ReelSet, for driving one cell directly. */
96
+ reelAt(cell: HwCell): ReelSet;
97
+ /**
98
+ * Rewrite a **locked** cell's coin in place — coin → jackpot, mini → major,
99
+ * raise a tier — without disturbing any other cell. The ledger entry is
100
+ * rewritten so `lockedCoins` and totals stay correct. Throws on a free cell.
101
+ * Returns the new live symbol instance.
102
+ *
103
+ * Throws if called while a wave is in flight — `await respin()` first. To
104
+ * upgrade a coin in reaction to its own `coin:locked`, defer the swap until
105
+ * the awaited `respin()` resolves rather than swapping inside the listener.
106
+ */
107
+ setSymbolAt(cell: HwCell, id: string, data?: TData): ReelSymbol;
108
+ /** Activate the feature with the trigger coins. Seeds land locked, instantly. */
109
+ enter(seed: HwCoin<TData>[]): void;
110
+ /**
111
+ * Spin every free cell; `hits` land (and lock) their coins, all other spinning
112
+ * cells land empty. Resolves once the wave has landed and the counter is
113
+ * resolved. The game layer drives pacing between rounds.
114
+ */
115
+ respin(hits: HwCoin<TData>[]): Promise<HwRespinResult<TData>>;
116
+ /**
117
+ * Remove locked coins — the collect moment. Clears the cells (they become
118
+ * free again) and returns the released coins; the flight itself is game-layer
119
+ * animation, started from `cellCenter()` or the `coin:released` event.
120
+ */
121
+ release(cells: HwCell[]): HwCoin<TData>[];
122
+ /**
123
+ * Fast-forward whatever is spinning: every in-flight cell is slammed to its
124
+ * landed position, then `feature:skip` fires so the game layer can cut its own
125
+ * flights short. The normal landing → `coin:locked` → `feature:end` flow still
126
+ * resolves; this only removes the waiting. Returns the number of cells that
127
+ * were in flight.
128
+ */
129
+ skip(): number;
130
+ /** Clear the board back to idle. Fires `feature:reset` (not `coin:released`). */
131
+ reset(): void;
132
+ get isDestroyed(): boolean;
133
+ destroy(): void;
134
+ /** Emit each reducer-decided effect and fire the visual side effects. */
135
+ private _apply;
136
+ private _anticipating;
137
+ }
138
+ //# sourceMappingURL=HoldAndWinBoard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HoldAndWinBoard.d.ts","sourceRoot":"","sources":["../../src/board/HoldAndWinBoard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import { Graphics, Ticker } from 'pixi.js';
2
+ import { SpeedProfile, SymbolData } from '../config/types.js';
3
+ import { SymbolRegistry } from '../symbols/SymbolRegistry.js';
4
+ import { HoldAndWinBoard } from './HoldAndWinBoard.js';
5
+ import { HwCellSizeOptions } from './HwTypes.js';
6
+ /**
7
+ * Fluent builder for {@link HoldAndWinBoard}.
8
+ *
9
+ * A Hold & Win board is a W×H grid of cells that spin **independently** — the
10
+ * mechanic's atomic unit is the cell, the engine's is the column, so each cell
11
+ * is its own 1×1 ReelSet. This builder wires that grid plus the round
12
+ * choreography; everything value-shaped stays in the game layer (see
13
+ * {@link HoldAndWinBoard}).
14
+ *
15
+ * `TData` types the opaque payload carried on each coin's `data`.
16
+ */
17
+ export declare class HoldAndWinBuilder<TData = unknown> {
18
+ private _cols;
19
+ private _rows;
20
+ private _cell;
21
+ private _gap;
22
+ private _emptyId;
23
+ private _respins;
24
+ private _configurator;
25
+ private _weights;
26
+ private _symbolData;
27
+ private _baseProfile;
28
+ private _stagger;
29
+ private _anticipateWhen;
30
+ private _chrome;
31
+ private _ticker;
32
+ private _rng;
33
+ grid(cols: number, rows: number): this;
34
+ cellSize(size: number, opts?: HwCellSizeOptions): this;
35
+ /**
36
+ * Register coin symbol classes, exactly like `ReelSetBuilder.symbols`. Applied
37
+ * to every cell. An {@link EmptySymbol} is auto-registered under {@link emptyId}
38
+ * unless the configurator registers one itself.
39
+ */
40
+ symbols(configurator: (registry: SymbolRegistry) => void): this;
41
+ /** Strip weights during the spin (how often coins flash past empties). */
42
+ weights(weights: Record<string, number>): this;
43
+ /** Symbol id a cell shows when it holds no coin. Default `'empty'`. */
44
+ emptyId(id: string): this;
45
+ /**
46
+ * Per-symbol engine overrides, exactly like `ReelSetBuilder.symbolData`. The
47
+ * headline use is `{ unmask: true }` for coins whose lock/reveal animations
48
+ * expand past the cell. Safe only for server-placed ids (weight 0): unmasked
49
+ * strip symbols mis-track vertically while the reel spins.
50
+ */
51
+ symbolData(overrides: Record<string, Partial<SymbolData>>): this;
52
+ /** Respins granted on enter and restored on every hit. Default 3. */
53
+ respins(count: number): this;
54
+ /** Base spin feel for every cell. Default: NORMAL with a 320ms floor. */
55
+ speedProfile(profile: SpeedProfile): this;
56
+ /**
57
+ * Extra milliseconds of spin per cell on top of the base minimum spin time.
58
+ * Default `(col + row) * 70` — the diagonal landing wave. Return 0 for
59
+ * simultaneous landings.
60
+ */
61
+ stagger(fn: (col: number, row: number) => number): this;
62
+ /**
63
+ * When the predicate returns true for a wave, **every** spinning cell uses a
64
+ * drawn-out tension profile — the "one cell left for Grand" moment. Evaluated
65
+ * once per wave for the whole board (not per cell), against the pre-wave state.
66
+ */
67
+ anticipateWhen(fn: (state: {
68
+ locked: number;
69
+ capacity: number;
70
+ respinsLeft: number;
71
+ }) => boolean): this;
72
+ /** Per-cell background, drawn behind each mini reel. */
73
+ cellChrome(draw: (g: Graphics, size: number) => void): this;
74
+ ticker(ticker: Ticker): this;
75
+ /** Injected RNG for the spin strips (deterministic demos / tests). */
76
+ rng(fn: () => number): this;
77
+ build(): HoldAndWinBoard<TData>;
78
+ }
79
+ //# sourceMappingURL=HoldAndWinBuilder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HoldAndWinBuilder.d.ts","sourceRoot":"","sources":["../../src/board/HoldAndWinBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,qBAAa,iBAAiB,CAAC,KAAK,GAAG,OAAO;IAC5C,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,aAAa,CAAqD;IAC1E,OAAO,CAAC,QAAQ,CAAuC;IACvD,OAAO,CAAC,WAAW,CAAoD;IACvE,OAAO,CAAC,YAAY,CAAkE;IACtF,OAAO,CAAC,QAAQ,CAAwE;IACxF,OAAO,CAAC,eAAe,CAEP;IAChB,OAAO,CAAC,OAAO,CAAsD;IACrE,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,IAAI,CAA+B;IAE3C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAMtC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,iBAAsB,GAAG,IAAI;IAM1D;;;;OAIG;IACH,OAAO,CAAC,YAAY,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI;IAK/D,0EAA0E;IAC1E,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAK9C,uEAAuE;IACvE,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAKzB;;;;;OAKG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,IAAI;IAKhE,qEAAqE;IACrE,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK5B,yEAAyE;IACzE,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAKzC;;;;OAIG;IACH,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI;IAKvD;;;;OAIG;IACH,cAAc,CACZ,EAAE,EAAE,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,GAChF,IAAI;IAKP,wDAAwD;IACxD,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAK3D,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK5B,sEAAsE;IACtE,GAAG,CAAC,EAAE,EAAE,MAAM,MAAM,GAAG,IAAI;IAK3B,KAAK,IAAI,eAAe,CAAC,KAAK,CAAC;CAyBhC"}
@@ -0,0 +1,89 @@
1
+ import { HwCell, HwCoin, HwEffect } from './HwTypes.js';
2
+ export type HwPhase = 'idle' | 'active' | 'spinning';
3
+ /**
4
+ * The pure Hold & Win state machine — the single source of truth for board
5
+ * state, with **zero** PixiJS. It owns the locked-coin ledger, the respin
6
+ * counter, the round number and the feature {@link HwPhase}; every derived
7
+ * value (`freeCells`, `isFull`) is computed from the one ledger, never stored
8
+ * in parallel, so nothing can drift out of sync.
9
+ *
10
+ * It is a *reducer, not a cache*: {@link HoldAndWinBoard} drives the reels and
11
+ * reports each landing here; the methods mutate the ledger and **return the
12
+ * ordered list of {@link HwEffect}s to emit**, which the driver replays onto its
13
+ * event emitter (and uses to fire visual side effects like `playWin()`). Locks
14
+ * happen progressively as cells land in stagger order, so this is an
15
+ * *incremental* reducer ({@link beginWave} → N×{@link land} → {@link endWave}),
16
+ * not a one-shot `reduce(state, hits)`.
17
+ *
18
+ * Being PixiJS-free, it is fully unit-testable: assert the returned effect
19
+ * sequence for hit / miss / full / release / reset / error paths.
20
+ */
21
+ export declare class HoldAndWinState<TData = unknown> {
22
+ private readonly _locked;
23
+ private readonly _cellSet;
24
+ private readonly _allCells;
25
+ private readonly _defaultRespins;
26
+ private _respinsLeft;
27
+ private _round;
28
+ private _phase;
29
+ private _waveLanded;
30
+ constructor(allCells: HwCell[], defaultRespins: number);
31
+ get phase(): HwPhase;
32
+ get respinsLeft(): number;
33
+ get round(): number;
34
+ get capacity(): number;
35
+ get isFull(): boolean;
36
+ lockedCoins(): HwCoin<TData>[];
37
+ freeCells(): HwCell[];
38
+ isLocked(cell: HwCell): boolean;
39
+ coinAt(cell: HwCell): HwCoin<TData> | undefined;
40
+ /** Seed the trigger coins and arm the counter. Idle → active. */
41
+ enter(seed: HwCoin<TData>[]): HwEffect<TData>[];
42
+ /**
43
+ * Open a wave: validate the hits, mark every free cell as spinning, bump the
44
+ * round. Returns the data the driver needs to emit `respin:start` and to land
45
+ * each cell. Active → spinning.
46
+ */
47
+ beginWave(hits: HwCoin<TData>[]): {
48
+ round: number;
49
+ spinning: HwCell[];
50
+ hitByKey: Map<string, HwCoin<TData>>;
51
+ };
52
+ /** Record one cell's landing. `coin` is null on a miss. */
53
+ land(cell: HwCell, coin: HwCoin<TData> | null): HwEffect<TData>[];
54
+ /** Close the wave: resolve the counter, detect full / feature end. */
55
+ endWave(): {
56
+ effects: HwEffect<TData>[];
57
+ landed: HwCoin<TData>[];
58
+ };
59
+ /**
60
+ * Abandon an in-flight wave after a driver error: restore the phase from
61
+ * `spinning` back to `active` so a thrown spin doesn't strand the board (every
62
+ * later `beginWave` would otherwise throw "wave in flight"). Cells that already
63
+ * landed stay locked; the caller decides whether to retry or `reset`.
64
+ */
65
+ abortWave(): void;
66
+ /** Remove locked coins — the collect moment. */
67
+ release(cells: HwCell[]): {
68
+ effects: HwEffect<TData>[];
69
+ released: HwCoin<TData>[];
70
+ };
71
+ /**
72
+ * Rewrite a **locked** cell's coin identity in place (coin → jackpot, mini →
73
+ * major). Throws on a free cell — placing a brand-new tracked coin out of a
74
+ * spin is `enter`/`respin`'s job; for purely decorative art on a free cell use
75
+ * the cell's reel directly.
76
+ */
77
+ swap(cell: HwCell, id: string, data: TData | undefined): void;
78
+ /** Hard clear back to idle. Fires `feature:reset`, never `coin:released`. */
79
+ reset(): HwEffect<TData>[];
80
+ private _setRespins;
81
+ /**
82
+ * Store a coin with a board-owned, frozen `cell` so the ledger key can never
83
+ * be corrupted by a game mutating the coin it was handed. `data` is left
84
+ * mutable by reference — that is the supported way to carry live value.
85
+ */
86
+ private _freeze;
87
+ private _assertInGrid;
88
+ }
89
+ //# sourceMappingURL=HoldAndWinState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HoldAndWinState.d.ts","sourceRoot":"","sources":["../../src/board/HoldAndWinState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAkB,MAAM,cAAc,CAAC;AAG7E,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;AAErD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,eAAe,CAAC,KAAK,GAAG,OAAO;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,WAAW,CAAuB;gBAE9B,QAAQ,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,MAAM;IAQtD,IAAI,KAAK,IAAI,OAAO,CAEnB;IACD,IAAI,WAAW,IAAI,MAAM,CAExB;IACD,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,QAAQ,IAAI,MAAM,CAErB;IACD,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,WAAW,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE;IAI9B,SAAS,IAAI,MAAM,EAAE;IAIrB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI/B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,SAAS;IAM/C,iEAAiE;IACjE,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,EAAE;IAuB/C;;;;OAIG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG;QAChC,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;KACtC;IAuBD,2DAA2D;IAC3D,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,EAAE;IAoBjE,sEAAsE;IACtE,OAAO,IAAI;QAAE,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAA;KAAE;IAiClE;;;;;OAKG;IACH,SAAS,IAAI,IAAI;IAMjB,gDAAgD;IAChD,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG;QAAE,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAA;KAAE;IAiBnF;;;;;OAKG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,GAAG,SAAS,GAAG,IAAI;IAc7D,6EAA6E;IAC7E,KAAK,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE;IAY1B,OAAO,CAAC,WAAW;IAKnB;;;;OAIG;IACH,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,aAAa;CAKtB"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Public types for the Hold & Win board. Kept in one leaf module so the
3
+ * reducer ({@link HoldAndWinState}), the driver ({@link HoldAndWinBoard}) and
4
+ * the builder can share them without import cycles.
5
+ */
6
+ /** Grid coordinate of a board cell. */
7
+ export interface HwCell {
8
+ col: number;
9
+ row: number;
10
+ }
11
+ /**
12
+ * A coin somewhere on the board. `id` selects the registered symbol art;
13
+ * `data` is an opaque game-layer payload the board never interprets.
14
+ *
15
+ * **Ownership contract:** `cell` and `id` belong to the board — treat them as
16
+ * read-only; the board keys its ledger by cell and mutating them is undefined.
17
+ * `data` is yours: mutate its contents freely (a doubler does `coin.data.value
18
+ * *= 2`), that is the intended way to carry live game state on a locked coin.
19
+ */
20
+ export interface HwCoin<TData = unknown> {
21
+ readonly cell: HwCell;
22
+ readonly id: string;
23
+ data?: TData;
24
+ }
25
+ /** Why the respin counter changed — disambiguates a `respins:changed` event. */
26
+ export type HwRespinReason = 'seed' | 'hit-reset' | 'miss';
27
+ /** Resolution of one {@link HoldAndWinBoard.respin} wave. */
28
+ export interface HwRespinResult<TData = unknown> {
29
+ round: number;
30
+ hits: HwCoin<TData>[];
31
+ respinsLeft: number;
32
+ full: boolean;
33
+ /** True when the feature is over (counter exhausted or board full). */
34
+ done: boolean;
35
+ }
36
+ export type HoldAndWinBoardEvents<TData = unknown> = {
37
+ 'feature:enter': [{
38
+ seed: HwCoin<TData>[];
39
+ respins: number;
40
+ }];
41
+ 'respin:start': [{
42
+ round: number;
43
+ respinsLeft: number;
44
+ spinning: HwCell[];
45
+ }];
46
+ /** Fires per cell, in landing (stagger) order. `coin` is null on a miss. */
47
+ 'cell:landed': [{
48
+ cell: HwCell;
49
+ coin: HwCoin<TData> | null;
50
+ }];
51
+ 'coin:locked': [{
52
+ coin: HwCoin<TData>;
53
+ locked: number;
54
+ capacity: number;
55
+ }];
56
+ /** Fired by `release()` — the collect / fly-away moment. */
57
+ 'coin:released': [{
58
+ coin: HwCoin<TData>;
59
+ remaining: number;
60
+ }];
61
+ 'respins:changed': [{
62
+ value: number;
63
+ reason: HwRespinReason;
64
+ }];
65
+ 'respin:end': [{
66
+ round: number;
67
+ hits: HwCoin<TData>[];
68
+ respinsLeft: number;
69
+ }];
70
+ 'board:full': [{
71
+ coins: HwCoin<TData>[];
72
+ }];
73
+ /** Fired by `skip()` so the game layer can cut its own flights / collect short. */
74
+ 'feature:skip': [{
75
+ inFlight: number;
76
+ }];
77
+ /**
78
+ * Fired by `reset()` — a hard clear back to idle. Distinct from `coin:released`
79
+ * (which means "collect this coin"); listeners that maintain derived state
80
+ * from events (HUD totals, meters) clear it here without triggering collect.
81
+ */
82
+ 'feature:reset': [{
83
+ clearedCoins: number;
84
+ }];
85
+ 'feature:end': [{
86
+ coins: HwCoin<TData>[];
87
+ rounds: number;
88
+ full: boolean;
89
+ }];
90
+ };
91
+ /**
92
+ * One state-change the reducer ({@link HoldAndWinState}) decided, ready for the
93
+ * driver to emit. A tagged pair of `{ type, payload }` for every board event the
94
+ * reducer owns — the driver replays them onto its emitter and keys visual side
95
+ * effects (e.g. `playWin()` on `coin:locked`) off the type. `respin:start` and
96
+ * `feature:skip` are driver-owned (they describe in-flight reels, not ledger
97
+ * state) and are not produced here.
98
+ */
99
+ export type HwEffect<TData = unknown> = {
100
+ [K in keyof HoldAndWinBoardEvents<TData>]: {
101
+ type: K;
102
+ payload: HoldAndWinBoardEvents<TData>[K][0];
103
+ };
104
+ }[keyof HoldAndWinBoardEvents<TData>];
105
+ export interface HwCellSizeOptions {
106
+ gap?: number;
107
+ }
108
+ /** `col,row` string key for the board's cell-indexed maps. */
109
+ export declare const cellKey: (c: HwCell) => string;
110
+ //# sourceMappingURL=HwTypes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HwTypes.d.ts","sourceRoot":"","sources":["../../src/board/HwTypes.ts"],"names":[],"mappings":"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"}