pixi-reels 1.0.1 → 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 +14 -0
- package/dist/board/BoardGrid.d.ts +129 -0
- package/dist/board/BoardGrid.d.ts.map +1 -0
- package/dist/board/HoldAndWinBoard.d.ts +138 -0
- package/dist/board/HoldAndWinBoard.d.ts.map +1 -0
- package/dist/board/HoldAndWinBuilder.d.ts +79 -0
- package/dist/board/HoldAndWinBuilder.d.ts.map +1 -0
- package/dist/board/HoldAndWinState.d.ts +89 -0
- package/dist/board/HoldAndWinState.d.ts.map +1 -0
- package/dist/board/HwTypes.d.ts +110 -0
- package/dist/board/HwTypes.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +533 -11
- package/dist/index.js.map +1 -1
- package/dist/symbols/EmptySymbol.d.ts +17 -0
- package/dist/symbols/EmptySymbol.d.ts.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
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
|
+
|
|
3
17
|
## 1.0.1
|
|
4
18
|
|
|
5
19
|
### Patch 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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"}
|