pixi-reels 0.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/LICENSE +28 -0
- package/README.md +167 -0
- package/dist/SpineSymbol-B40TevSr.cjs +2 -0
- package/dist/SpineSymbol-B40TevSr.cjs.map +1 -0
- package/dist/SpineSymbol-D2jhCzFW.js +82 -0
- package/dist/SpineSymbol-D2jhCzFW.js.map +1 -0
- package/dist/config/SpeedPresets.d.ts +49 -0
- package/dist/config/SpeedPresets.d.ts.map +1 -0
- package/dist/config/defaults.d.ts +12 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/types.d.ts +93 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/core/Reel.d.ts +106 -0
- package/dist/core/Reel.d.ts.map +1 -0
- package/dist/core/ReelMotion.d.ts +43 -0
- package/dist/core/ReelMotion.d.ts.map +1 -0
- package/dist/core/ReelSet.d.ts +101 -0
- package/dist/core/ReelSet.d.ts.map +1 -0
- package/dist/core/ReelSetBuilder.d.ts +102 -0
- package/dist/core/ReelSetBuilder.d.ts.map +1 -0
- package/dist/core/ReelViewport.d.ts +43 -0
- package/dist/core/ReelViewport.d.ts.map +1 -0
- package/dist/core/StopSequencer.d.ts +26 -0
- package/dist/core/StopSequencer.d.ts.map +1 -0
- package/dist/debug/debug.d.ts +65 -0
- package/dist/debug/debug.d.ts.map +1 -0
- package/dist/events/EventEmitter.d.ts +25 -0
- package/dist/events/EventEmitter.d.ts.map +1 -0
- package/dist/events/ReelEvents.d.ts +40 -0
- package/dist/events/ReelEvents.d.ts.map +1 -0
- package/dist/frame/FrameBuilder.d.ts +50 -0
- package/dist/frame/FrameBuilder.d.ts.map +1 -0
- package/dist/frame/OffsetCalculator.d.ts +19 -0
- package/dist/frame/OffsetCalculator.d.ts.map +1 -0
- package/dist/frame/RandomSymbolProvider.d.ts +27 -0
- package/dist/frame/RandomSymbolProvider.d.ts.map +1 -0
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1454 -0
- package/dist/index.js.map +1 -0
- package/dist/pool/ObjectPool.d.ts +36 -0
- package/dist/pool/ObjectPool.d.ts.map +1 -0
- package/dist/speed/SpeedManager.d.ts +38 -0
- package/dist/speed/SpeedManager.d.ts.map +1 -0
- package/dist/spin/SpinController.d.ts +71 -0
- package/dist/spin/SpinController.d.ts.map +1 -0
- package/dist/spin/modes/CascadeMode.d.ts +16 -0
- package/dist/spin/modes/CascadeMode.d.ts.map +1 -0
- package/dist/spin/modes/ImmediateMode.d.ts +10 -0
- package/dist/spin/modes/ImmediateMode.d.ts.map +1 -0
- package/dist/spin/modes/SpinningMode.d.ts +18 -0
- package/dist/spin/modes/SpinningMode.d.ts.map +1 -0
- package/dist/spin/modes/StandardMode.d.ts +10 -0
- package/dist/spin/modes/StandardMode.d.ts.map +1 -0
- package/dist/spin/phases/AnticipationPhase.d.ts +24 -0
- package/dist/spin/phases/AnticipationPhase.d.ts.map +1 -0
- package/dist/spin/phases/PhaseFactory.d.ts +21 -0
- package/dist/spin/phases/PhaseFactory.d.ts.map +1 -0
- package/dist/spin/phases/ReelPhase.d.ts +39 -0
- package/dist/spin/phases/ReelPhase.d.ts.map +1 -0
- package/dist/spin/phases/SpinPhase.d.ts +25 -0
- package/dist/spin/phases/SpinPhase.d.ts.map +1 -0
- package/dist/spin/phases/StartPhase.d.ts +26 -0
- package/dist/spin/phases/StartPhase.d.ts.map +1 -0
- package/dist/spin/phases/StopPhase.d.ts +37 -0
- package/dist/spin/phases/StopPhase.d.ts.map +1 -0
- package/dist/spine/SpineReelSymbol.d.ts +111 -0
- package/dist/spine/SpineReelSymbol.d.ts.map +1 -0
- package/dist/spine/index.d.ts +5 -0
- package/dist/spine/index.d.ts.map +1 -0
- package/dist/spine.cjs +2 -0
- package/dist/spine.cjs.map +1 -0
- package/dist/spine.js +123 -0
- package/dist/spine.js.map +1 -0
- package/dist/spotlight/SymbolSpotlight.d.ts +70 -0
- package/dist/spotlight/SymbolSpotlight.d.ts.map +1 -0
- package/dist/symbols/AnimatedSpriteSymbol.d.ts +30 -0
- package/dist/symbols/AnimatedSpriteSymbol.d.ts.map +1 -0
- package/dist/symbols/ReelSymbol.d.ts +84 -0
- package/dist/symbols/ReelSymbol.d.ts.map +1 -0
- package/dist/symbols/SpineSymbol.d.ts +34 -0
- package/dist/symbols/SpineSymbol.d.ts.map +1 -0
- package/dist/symbols/SpriteSymbol.d.ts +29 -0
- package/dist/symbols/SpriteSymbol.d.ts.map +1 -0
- package/dist/symbols/SymbolFactory.d.ts +20 -0
- package/dist/symbols/SymbolFactory.d.ts.map +1 -0
- package/dist/symbols/SymbolRegistry.d.ts +25 -0
- package/dist/symbols/SymbolRegistry.d.ts.map +1 -0
- package/dist/testing/FakeTicker.d.ts +45 -0
- package/dist/testing/FakeTicker.d.ts.map +1 -0
- package/dist/testing/HeadlessSymbol.d.ts +28 -0
- package/dist/testing/HeadlessSymbol.d.ts.map +1 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/testHarness.d.ts +70 -0
- package/dist/testing/testHarness.d.ts.map +1 -0
- package/dist/utils/Disposable.d.ts +10 -0
- package/dist/utils/Disposable.d.ts.map +1 -0
- package/dist/utils/TickerRef.d.ts +30 -0
- package/dist/utils/TickerRef.d.ts.map +1 -0
- package/package.json +86 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 schmooky and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
This repository bundles third-party runtimes and libraries (PixiJS, GSAP, the
|
|
24
|
+
Spine Runtime, and others) governed by their own license terms. See each
|
|
25
|
+
package's node_modules or upstream project for those terms. In particular, the
|
|
26
|
+
Spine Runtime (@esotericsoftware/spine-*) is licensed under the Spine Runtimes
|
|
27
|
+
License Agreement and requires users to hold a valid Spine Editor license when
|
|
28
|
+
integrating it into their own products.
|
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# pixi-reels
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/pixi-reels)
|
|
4
|
+
[](https://www.npmjs.com/package/pixi-reels)
|
|
5
|
+
[](https://bundlephobia.com/package/pixi-reels)
|
|
6
|
+
[](https://github.com/schmooky/pixi-reels/actions/workflows/ci.yml)
|
|
7
|
+
[](https://github.com/schmooky/pixi-reels/actions/workflows/release.yml)
|
|
8
|
+
[](https://github.com/schmooky/pixi-reels/actions/workflows/codeql.yml)
|
|
9
|
+
[](./LICENSE)
|
|
10
|
+
[](https://pixijs.com/)
|
|
11
|
+
[](https://www.typescriptlang.org/)
|
|
12
|
+
|
|
13
|
+
A slot machine reel engine for [PixiJS v8](https://pixijs.com/). Fluent builder, typed events, and the weighty spin+stop feel modeled on real-money games — in about 35 kB gzipped.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add pixi-reels pixi.js gsap
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { Application } from 'pixi.js';
|
|
21
|
+
import { ReelSetBuilder, SpriteSymbol, SpeedPresets } from 'pixi-reels';
|
|
22
|
+
|
|
23
|
+
const app = new Application();
|
|
24
|
+
await app.init({ width: 900, height: 540, background: '#0a0d14' });
|
|
25
|
+
document.body.appendChild(app.canvas);
|
|
26
|
+
|
|
27
|
+
const reelSet = new ReelSetBuilder()
|
|
28
|
+
.reels(5).visibleSymbols(3).symbolSize(140, 140)
|
|
29
|
+
.symbols((r) => {
|
|
30
|
+
r.register('cherry', SpriteSymbol, { textures: { cherry: cherryTex } });
|
|
31
|
+
r.register('seven', SpriteSymbol, { textures: { seven: sevenTex } });
|
|
32
|
+
r.register('bar', SpriteSymbol, { textures: { bar: barTex } });
|
|
33
|
+
})
|
|
34
|
+
.weights({ cherry: 40, seven: 10, bar: 20 })
|
|
35
|
+
.speed('normal', SpeedPresets.NORMAL)
|
|
36
|
+
.speed('turbo', SpeedPresets.TURBO)
|
|
37
|
+
.ticker(app.ticker)
|
|
38
|
+
.build();
|
|
39
|
+
|
|
40
|
+
app.stage.addChild(reelSet);
|
|
41
|
+
|
|
42
|
+
// Kick off the spin, tell it where to land, await the bounce.
|
|
43
|
+
const spin = reelSet.spin();
|
|
44
|
+
reelSet.setResult(await fetchSpinFromServer());
|
|
45
|
+
const { symbols } = await spin;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## What it does
|
|
49
|
+
|
|
50
|
+
- **Spin lifecycle** — `START -> SPIN -> ANTICIPATION -> STOP` phases, each pluggable.
|
|
51
|
+
- **Weighty stops** — the reel carries momentum through the target frame, snaps, then bounces. No floaty ease-in deceleration.
|
|
52
|
+
- **Speed modes** — Normal / Turbo / SuperTurbo built in, or register your own profile.
|
|
53
|
+
- **Skip / slam-stop** — second tap of the spin button immediately lands the reels on target.
|
|
54
|
+
- **Win spotlight** — dim non-winners, promote winning symbols above the mask, cycle lines.
|
|
55
|
+
- **Symbol plugins** — SpriteSymbol, AnimatedSpriteSymbol, SpineSymbol, or implement `ReelSymbol`.
|
|
56
|
+
- **Frame middleware** — intercept the symbol generator (e.g. "no triples", multiplier injection).
|
|
57
|
+
- **Object pooling** — zero-allocation spinning via `ObjectPool<T>`.
|
|
58
|
+
- **Typed events** — `spin:start`, `spin:reelLanded`, `speed:changed`, `spotlight:end`, ...
|
|
59
|
+
- **Headless testing** — `createTestReelSet` + `FakeTicker` run the full lifecycle in Node.
|
|
60
|
+
- **Debug mode** — `enableDebug(reelSet)` exposes JSON and ASCII snapshots on `window`.
|
|
61
|
+
|
|
62
|
+
## Docs
|
|
63
|
+
|
|
64
|
+
The docs site lives in [`apps/site`](apps/site/). Run it locally:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pnpm site:dev # http://localhost:4321
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
You'll find:
|
|
71
|
+
|
|
72
|
+
- **Guides** — getting started, spin lifecycle, symbols, speed modes, win animations, debugging.
|
|
73
|
+
- **Recipes** — small how-tos for common mechanics (walking wilds, sticky wilds, cascade, mystery reveal, hold & win, ...). Each ships with a live mini-demo.
|
|
74
|
+
- **Demos** — full mechanic sandboxes with cheat panels. One click forces a scatter, a near-miss, a guaranteed jackpot.
|
|
75
|
+
- **Sandbox** — in-browser TypeScript playground; edit the file, hit Run, reels rebuild.
|
|
76
|
+
- **Wiki** — API reference.
|
|
77
|
+
|
|
78
|
+
## Examples
|
|
79
|
+
|
|
80
|
+
Runnable apps in [`examples/`](examples/):
|
|
81
|
+
|
|
82
|
+
| Example | What it shows | Run |
|
|
83
|
+
|------------------|------------------------------------------------------------|----------------------------------------|
|
|
84
|
+
| `classic-spin` | 5x3 line-pay slot with Spine symbols and speed toggle | `pnpm --filter classic-spin dev` |
|
|
85
|
+
| `cascade-tumble` | 6x5 tumble mechanic with win spotlight between stages | `pnpm --filter cascade-tumble dev` |
|
|
86
|
+
| `hold-and-win` | 5x3 base game + respin bonus with locking coins | `pnpm --filter hold-and-win dev` |
|
|
87
|
+
| `sandbox` | Single editable TS file, HMR rebuild | `pnpm --filter sandbox dev` |
|
|
88
|
+
|
|
89
|
+
## Core API at a glance
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
reelSet.spin(): Promise<SpinResult> // Start spinning
|
|
93
|
+
reelSet.setResult(symbols: string[][]) // Pass the target grid (triggers the stop)
|
|
94
|
+
reelSet.setAnticipation([3, 4]) // Slow reels 3+4 before their landing
|
|
95
|
+
reelSet.setStopDelays([0, 140, 280, 600, 1100]) // Override per-reel stop stagger
|
|
96
|
+
reelSet.skip() // Slam-stop
|
|
97
|
+
reelSet.setSpeed('turbo') // Switch speed profile
|
|
98
|
+
reelSet.spotlight.show(positions, opts) // One-shot win highlight
|
|
99
|
+
reelSet.spotlight.cycle(lines, opts) // Cycle through win lines
|
|
100
|
+
reelSet.events.on('spin:reelLanded', (i, s) => {/* ... */})
|
|
101
|
+
reelSet.destroy() // Full teardown
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Full reference: `/wiki/` on the docs site.
|
|
105
|
+
|
|
106
|
+
## Spine symbols (optional)
|
|
107
|
+
|
|
108
|
+
pixi-reels ships a Spine adapter on a separate subpath so the runtime tree-shakes out when you don't need it:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { SpineReelSymbol } from 'pixi-reels/spine';
|
|
112
|
+
|
|
113
|
+
r.register('wild', SpineReelSymbol, {
|
|
114
|
+
spineMap: { wild: { skeleton: 'wildData', atlas: 'myAtlas' } },
|
|
115
|
+
autoPlayBlur: true, // plays `blur` during spin
|
|
116
|
+
autoPlayLanding: true, // plays `landing` on reel stop
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Install the peer: `pnpm add @esotericsoftware/spine-pixi-v8`.
|
|
121
|
+
|
|
122
|
+
## Debug mode
|
|
123
|
+
|
|
124
|
+
Handy for development and essential when an AI agent needs to inspect reel state without parsing a canvas:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { enableDebug } from 'pixi-reels';
|
|
128
|
+
enableDebug(reelSet);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
In the browser console (or via Playwright / an agent's `eval`):
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
__PIXI_REELS_DEBUG.log() // ASCII grid + state snapshot
|
|
135
|
+
__PIXI_REELS_DEBUG.snapshot() // Full JSON state
|
|
136
|
+
__PIXI_REELS_DEBUG.trace() // Log every domain event as it fires
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Architecture
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
ReelSetBuilder --builds--> ReelSet
|
|
143
|
+
|- SpinController ..... orchestrates the phases per reel
|
|
144
|
+
|- SpeedManager ....... named profiles + live switching
|
|
145
|
+
|- SymbolSpotlight .... win animations
|
|
146
|
+
|- ReelViewport ....... masked + unmasked containers
|
|
147
|
+
'- Reel[] ............. one per column
|
|
148
|
+
|- ReelSymbol[] .. SpriteSymbol / AnimatedSpriteSymbol / SpineSymbol
|
|
149
|
+
|- ReelMotion .... y displacement + wrap
|
|
150
|
+
'- StopSequencer . target-frame consumption
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Single ticker, no circular deps, no default exports, tree-shakes cleanly.
|
|
154
|
+
|
|
155
|
+
## Peer dependencies
|
|
156
|
+
|
|
157
|
+
- `pixi.js` ^8.17.0
|
|
158
|
+
- `gsap` ^3.14.0
|
|
159
|
+
- `@esotericsoftware/spine-pixi-v8` ^4.2.108 _(optional — only if you use `SpineReelSymbol`)_
|
|
160
|
+
|
|
161
|
+
## Contributing
|
|
162
|
+
|
|
163
|
+
PRs welcome. [CONTRIBUTING.md](./CONTRIBUTING.md) covers the workflow, changesets, and the handful of style rules the lint guards enforce.
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
let e=require(`pixi.js`);var t=class{view;_symbolId=``;_isDestroyed=!1;constructor(){this.view=new e.Container}get symbolId(){return this._symbolId}get isDestroyed(){return this._isDestroyed}activate(e){this._symbolId=e,this.view.visible=!0,this.onActivate(e)}deactivate(){this.stopAnimation(),this.onDeactivate(),this._symbolId=``,this.view.visible=!1}reset(){this.deactivate()}destroy(){this._isDestroyed||=(this.deactivate(),this.onDestroy(),this.view.destroy({children:!0}),!0)}onDestroy(){}onReelSpinStart(){}onReelSpinEnd(){}onReelLanded(){}},n=null;async function r(){try{n=(await import(`@esotericsoftware/spine-pixi-v8`)).Spine}catch{}}r();var i=class extends t{_spine=null;_skeletonDataMap;_idleAnimation;_winAnimation;_defaultSkin;_winResolve=null;_currentSkeletonKey=``;constructor(e){if(super(),!n)throw Error(`SpineSymbol requires @esotericsoftware/spine-pixi-v8 to be installed. Install it with: npm install @esotericsoftware/spine-pixi-v8`);this._skeletonDataMap=e.skeletonDataMap,this._idleAnimation=e.idleAnimation??`idle`,this._winAnimation=e.winAnimation??`win`,this._defaultSkin=e.defaultSkin??`default`}onActivate(e){let t=this._skeletonDataMap[e];t&&(this._currentSkeletonKey!==e&&(this._spine&&(this.view.removeChild(this._spine),this._spine.destroy()),this._spine=new n({skeletonData:t}),this.view.addChild(this._spine),this._currentSkeletonKey=e),this._spine.skeleton.data.findSkin(this._defaultSkin)&&(this._spine.skeleton.setSkinByName(this._defaultSkin),this._spine.skeleton.setSlotsToSetupPose()),this._spine.skeleton.data.findAnimation(this._idleAnimation)&&this._spine.state.setAnimation(0,this._idleAnimation,!0))}onDeactivate(){this._spine&&(this._spine.state.clearListeners(),this._spine.state.clearTracks()),this._winResolve=null}async playWin(){if(this._spine&&this._spine.skeleton.data.findAnimation(this._winAnimation))return new Promise(e=>{this._winResolve=e;let t=this._spine.state.setAnimation(0,this._winAnimation,!1);this._spine.state.addListener({complete:n=>{n===t&&(this._spine.state.clearListeners(),this._spine.skeleton.data.findAnimation(this._idleAnimation)&&this._spine.state.setAnimation(0,this._idleAnimation,!0),this._winResolve=null,e())}})})}stopAnimation(){this._spine&&(this._spine.state.clearListeners(),this._spine.skeleton.data.findAnimation(this._idleAnimation)&&this._spine.state.setAnimation(0,this._idleAnimation,!0),this._winResolve&&=(this._winResolve(),null))}resize(e,t){if(!this._spine)return;let n=this._spine.getBounds();n.width>0&&n.height>0&&this._spine.scale.set(e/n.width,t/n.height)}onDestroy(){this._spine&&=(this._spine.state.clearListeners(),this._spine.destroy(),null)}};Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return i}});
|
|
2
|
+
//# sourceMappingURL=SpineSymbol-B40TevSr.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpineSymbol-B40TevSr.cjs","names":[],"sources":["../src/symbols/ReelSymbol.ts","../src/symbols/SpineSymbol.ts"],"sourcesContent":["import { Container } from 'pixi.js';\nimport type { Disposable } from '../utils/Disposable.js';\n\n/**\n * One visible cell on a reel — the thing that actually draws.\n *\n * `ReelSymbol` is the abstract base class. Subclass it to pick a rendering\n * technology (`SpriteSymbol`, `AnimatedSpriteSymbol`, `SpineSymbol`, or a\n * custom class of your own). The reel set pools instances aggressively:\n * one instance is reused many times as it scrolls off one identity and on\n * to another, so implementations must never assume \"I was just created\".\n *\n * Required lifecycle hooks:\n *\n * - `onActivate(symbolId)` — the pool just handed me a new identity. Swap\n * texture, restart animations, bring myself out of any \"ended\" pose.\n * - `onDeactivate()` — I am about to be pooled. Pause animations, clear\n * listeners, leave myself in a clean state for the next activation.\n * - `playWin()` — the spotlight is celebrating me. Return a promise that\n * resolves when the one-shot animation is done.\n * - `stopAnimation()` — spotlight is over, return to idle.\n * - `resize(w, h)` — the reel's cell size changed (on every symbol swap).\n * Store the dimensions and reposition internal children. Forgetting\n * this is the single most common \"why do my symbols scatter\" bug.\n *\n * ```\n * create → activate(symbolId) → [playWin / stopAnimation]\n * → deactivate\n * → activate(newId) → ...\n * ```\n *\n * There's no hidden GC. Hold resources? Override `onDestroy()`.\n */\nexport abstract class ReelSymbol implements Disposable {\n /** The PixiJS container that holds this symbol's visual. */\n public readonly view: Container;\n\n private _symbolId: string = '';\n private _isDestroyed = false;\n\n constructor() {\n this.view = new Container();\n }\n\n get symbolId(): string {\n return this._symbolId;\n }\n\n get isDestroyed(): boolean {\n return this._isDestroyed;\n }\n\n /**\n * Activate the symbol with a new identity.\n * Called when the symbol enters the visible reel or is recycled from the pool.\n */\n activate(symbolId: string): void {\n this._symbolId = symbolId;\n this.view.visible = true;\n this.onActivate(symbolId);\n }\n\n /**\n * Deactivate the symbol before returning it to the pool.\n * Stops any running animations and hides the view.\n */\n deactivate(): void {\n this.stopAnimation();\n this.onDeactivate();\n this._symbolId = '';\n this.view.visible = false;\n }\n\n /** Pool reset — aliases deactivate. */\n reset(): void {\n this.deactivate();\n }\n\n destroy(): void {\n if (this._isDestroyed) return;\n this.deactivate();\n this.onDestroy();\n this.view.destroy({ children: true });\n this._isDestroyed = true;\n }\n\n /** Subclass hook: set up visuals for the given symbolId. */\n protected abstract onActivate(symbolId: string): void;\n\n /** Subclass hook: clean up visuals. */\n protected abstract onDeactivate(): void;\n\n /** Subclass hook: additional cleanup on destroy. */\n protected onDestroy(): void {\n // Override if needed\n }\n\n /** Play the win/highlight animation for this symbol. Resolves when complete. */\n abstract playWin(): Promise<void>;\n\n /** Immediately stop any running animation and return to idle. */\n abstract stopAnimation(): void;\n\n /** Resize the symbol's visual to fit the given dimensions. */\n abstract resize(width: number, height: number): void;\n\n /**\n * Lifecycle hook: the owning reel has started spinning.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayBlur) to swap to\n * a blur animation automatically.\n */\n onReelSpinStart(): void {}\n\n /**\n * Lifecycle hook: the owning reel is about to stop (just before bounce).\n * Default: no-op.\n */\n onReelSpinEnd(): void {}\n\n /**\n * Lifecycle hook: the owning reel has landed on its final symbols.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayLanding) to fire\n * a landing animation concurrently with the bounce.\n */\n onReelLanded(): void {}\n}\n","import { ReelSymbol } from './ReelSymbol.js';\n\n// Spine types imported dynamically — this is an optional peer dependency\nlet SpineClass: any = null;\n\nasync function loadSpine(): Promise<void> {\n try {\n const spineModule = await import('@esotericsoftware/spine-pixi-v8');\n SpineClass = spineModule.Spine;\n } catch {\n // Spine not available — SpineSymbol will throw on construction\n }\n}\n\n// Attempt to load Spine on module init\nloadSpine();\n\nexport interface SpineSymbolOptions {\n /** Map of symbolId → SkeletonData. */\n skeletonDataMap: Record<string, any>;\n /** Default animation name to play in idle. Default: 'idle'. */\n idleAnimation?: string;\n /** Animation name to play on win. Default: 'win'. */\n winAnimation?: string;\n /** Default skin name. Default: 'default'. */\n defaultSkin?: string;\n}\n\n/**\n * Symbol implementation using Spine 2D skeletal animation.\n *\n * Requires `@esotericsoftware/spine-pixi-v8` as an optional peer dependency.\n * If Spine is not installed, constructing a SpineSymbol will throw.\n */\nexport class SpineSymbol extends ReelSymbol {\n private _spine: any = null;\n private _skeletonDataMap: Record<string, any>;\n private _idleAnimation: string;\n private _winAnimation: string;\n private _defaultSkin: string;\n private _winResolve: (() => void) | null = null;\n private _currentSkeletonKey: string = '';\n\n constructor(options: SpineSymbolOptions) {\n super();\n if (!SpineClass) {\n throw new Error(\n 'SpineSymbol requires @esotericsoftware/spine-pixi-v8 to be installed. ' +\n 'Install it with: npm install @esotericsoftware/spine-pixi-v8',\n );\n }\n this._skeletonDataMap = options.skeletonDataMap;\n this._idleAnimation = options.idleAnimation ?? 'idle';\n this._winAnimation = options.winAnimation ?? 'win';\n this._defaultSkin = options.defaultSkin ?? 'default';\n }\n\n protected onActivate(symbolId: string): void {\n const skeletonData = this._skeletonDataMap[symbolId];\n if (!skeletonData) return;\n\n // Reuse existing spine if same skeleton data\n if (this._currentSkeletonKey !== symbolId) {\n if (this._spine) {\n this.view.removeChild(this._spine);\n this._spine.destroy();\n }\n this._spine = new SpineClass({ skeletonData });\n this.view.addChild(this._spine);\n this._currentSkeletonKey = symbolId;\n }\n\n // Set to idle\n if (this._spine.skeleton.data.findSkin(this._defaultSkin)) {\n this._spine.skeleton.setSkinByName(this._defaultSkin);\n this._spine.skeleton.setSlotsToSetupPose();\n }\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n }\n\n protected onDeactivate(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.state.clearTracks();\n }\n this._winResolve = null;\n }\n\n async playWin(): Promise<void> {\n if (!this._spine) return;\n if (!this._spine.skeleton.data.findAnimation(this._winAnimation)) return;\n\n return new Promise<void>((resolve) => {\n this._winResolve = resolve;\n const entry = this._spine.state.setAnimation(0, this._winAnimation, false);\n this._spine.state.addListener({\n complete: (trackEntry: any) => {\n if (trackEntry === entry) {\n this._spine.state.clearListeners();\n // Return to idle\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n this._winResolve = null;\n resolve();\n }\n },\n });\n });\n }\n\n stopAnimation(): void {\n if (!this._spine) return;\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n if (this._winResolve) {\n this._winResolve();\n this._winResolve = null;\n }\n }\n\n resize(width: number, height: number): void {\n if (!this._spine) return;\n const bounds = this._spine.getBounds();\n if (bounds.width > 0 && bounds.height > 0) {\n this._spine.scale.set(\n width / bounds.width,\n height / bounds.height,\n );\n }\n }\n\n protected override onDestroy(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.destroy();\n this._spine = null;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Container as e } from "pixi.js";
|
|
2
|
+
//#region src/symbols/ReelSymbol.ts
|
|
3
|
+
var t = class {
|
|
4
|
+
view;
|
|
5
|
+
_symbolId = "";
|
|
6
|
+
_isDestroyed = !1;
|
|
7
|
+
constructor() {
|
|
8
|
+
this.view = new e();
|
|
9
|
+
}
|
|
10
|
+
get symbolId() {
|
|
11
|
+
return this._symbolId;
|
|
12
|
+
}
|
|
13
|
+
get isDestroyed() {
|
|
14
|
+
return this._isDestroyed;
|
|
15
|
+
}
|
|
16
|
+
activate(e) {
|
|
17
|
+
this._symbolId = e, this.view.visible = !0, this.onActivate(e);
|
|
18
|
+
}
|
|
19
|
+
deactivate() {
|
|
20
|
+
this.stopAnimation(), this.onDeactivate(), this._symbolId = "", this.view.visible = !1;
|
|
21
|
+
}
|
|
22
|
+
reset() {
|
|
23
|
+
this.deactivate();
|
|
24
|
+
}
|
|
25
|
+
destroy() {
|
|
26
|
+
this._isDestroyed ||= (this.deactivate(), this.onDestroy(), this.view.destroy({ children: !0 }), !0);
|
|
27
|
+
}
|
|
28
|
+
onDestroy() {}
|
|
29
|
+
onReelSpinStart() {}
|
|
30
|
+
onReelSpinEnd() {}
|
|
31
|
+
onReelLanded() {}
|
|
32
|
+
}, n = null;
|
|
33
|
+
async function r() {
|
|
34
|
+
try {
|
|
35
|
+
n = (await import("@esotericsoftware/spine-pixi-v8")).Spine;
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
r();
|
|
39
|
+
var i = class extends t {
|
|
40
|
+
_spine = null;
|
|
41
|
+
_skeletonDataMap;
|
|
42
|
+
_idleAnimation;
|
|
43
|
+
_winAnimation;
|
|
44
|
+
_defaultSkin;
|
|
45
|
+
_winResolve = null;
|
|
46
|
+
_currentSkeletonKey = "";
|
|
47
|
+
constructor(e) {
|
|
48
|
+
if (super(), !n) throw Error("SpineSymbol requires @esotericsoftware/spine-pixi-v8 to be installed. Install it with: npm install @esotericsoftware/spine-pixi-v8");
|
|
49
|
+
this._skeletonDataMap = e.skeletonDataMap, this._idleAnimation = e.idleAnimation ?? "idle", this._winAnimation = e.winAnimation ?? "win", this._defaultSkin = e.defaultSkin ?? "default";
|
|
50
|
+
}
|
|
51
|
+
onActivate(e) {
|
|
52
|
+
let t = this._skeletonDataMap[e];
|
|
53
|
+
t && (this._currentSkeletonKey !== e && (this._spine && (this.view.removeChild(this._spine), this._spine.destroy()), this._spine = new n({ skeletonData: t }), this.view.addChild(this._spine), this._currentSkeletonKey = e), this._spine.skeleton.data.findSkin(this._defaultSkin) && (this._spine.skeleton.setSkinByName(this._defaultSkin), this._spine.skeleton.setSlotsToSetupPose()), this._spine.skeleton.data.findAnimation(this._idleAnimation) && this._spine.state.setAnimation(0, this._idleAnimation, !0));
|
|
54
|
+
}
|
|
55
|
+
onDeactivate() {
|
|
56
|
+
this._spine && (this._spine.state.clearListeners(), this._spine.state.clearTracks()), this._winResolve = null;
|
|
57
|
+
}
|
|
58
|
+
async playWin() {
|
|
59
|
+
if (this._spine && this._spine.skeleton.data.findAnimation(this._winAnimation)) return new Promise((e) => {
|
|
60
|
+
this._winResolve = e;
|
|
61
|
+
let t = this._spine.state.setAnimation(0, this._winAnimation, !1);
|
|
62
|
+
this._spine.state.addListener({ complete: (n) => {
|
|
63
|
+
n === t && (this._spine.state.clearListeners(), this._spine.skeleton.data.findAnimation(this._idleAnimation) && this._spine.state.setAnimation(0, this._idleAnimation, !0), this._winResolve = null, e());
|
|
64
|
+
} });
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
stopAnimation() {
|
|
68
|
+
this._spine && (this._spine.state.clearListeners(), this._spine.skeleton.data.findAnimation(this._idleAnimation) && this._spine.state.setAnimation(0, this._idleAnimation, !0), this._winResolve &&= (this._winResolve(), null));
|
|
69
|
+
}
|
|
70
|
+
resize(e, t) {
|
|
71
|
+
if (!this._spine) return;
|
|
72
|
+
let n = this._spine.getBounds();
|
|
73
|
+
n.width > 0 && n.height > 0 && this._spine.scale.set(e / n.width, t / n.height);
|
|
74
|
+
}
|
|
75
|
+
onDestroy() {
|
|
76
|
+
this._spine &&= (this._spine.state.clearListeners(), this._spine.destroy(), null);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
//#endregion
|
|
80
|
+
export { t as n, i as t };
|
|
81
|
+
|
|
82
|
+
//# sourceMappingURL=SpineSymbol-D2jhCzFW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpineSymbol-D2jhCzFW.js","names":[],"sources":["../src/symbols/ReelSymbol.ts","../src/symbols/SpineSymbol.ts"],"sourcesContent":["import { Container } from 'pixi.js';\nimport type { Disposable } from '../utils/Disposable.js';\n\n/**\n * One visible cell on a reel — the thing that actually draws.\n *\n * `ReelSymbol` is the abstract base class. Subclass it to pick a rendering\n * technology (`SpriteSymbol`, `AnimatedSpriteSymbol`, `SpineSymbol`, or a\n * custom class of your own). The reel set pools instances aggressively:\n * one instance is reused many times as it scrolls off one identity and on\n * to another, so implementations must never assume \"I was just created\".\n *\n * Required lifecycle hooks:\n *\n * - `onActivate(symbolId)` — the pool just handed me a new identity. Swap\n * texture, restart animations, bring myself out of any \"ended\" pose.\n * - `onDeactivate()` — I am about to be pooled. Pause animations, clear\n * listeners, leave myself in a clean state for the next activation.\n * - `playWin()` — the spotlight is celebrating me. Return a promise that\n * resolves when the one-shot animation is done.\n * - `stopAnimation()` — spotlight is over, return to idle.\n * - `resize(w, h)` — the reel's cell size changed (on every symbol swap).\n * Store the dimensions and reposition internal children. Forgetting\n * this is the single most common \"why do my symbols scatter\" bug.\n *\n * ```\n * create → activate(symbolId) → [playWin / stopAnimation]\n * → deactivate\n * → activate(newId) → ...\n * ```\n *\n * There's no hidden GC. Hold resources? Override `onDestroy()`.\n */\nexport abstract class ReelSymbol implements Disposable {\n /** The PixiJS container that holds this symbol's visual. */\n public readonly view: Container;\n\n private _symbolId: string = '';\n private _isDestroyed = false;\n\n constructor() {\n this.view = new Container();\n }\n\n get symbolId(): string {\n return this._symbolId;\n }\n\n get isDestroyed(): boolean {\n return this._isDestroyed;\n }\n\n /**\n * Activate the symbol with a new identity.\n * Called when the symbol enters the visible reel or is recycled from the pool.\n */\n activate(symbolId: string): void {\n this._symbolId = symbolId;\n this.view.visible = true;\n this.onActivate(symbolId);\n }\n\n /**\n * Deactivate the symbol before returning it to the pool.\n * Stops any running animations and hides the view.\n */\n deactivate(): void {\n this.stopAnimation();\n this.onDeactivate();\n this._symbolId = '';\n this.view.visible = false;\n }\n\n /** Pool reset — aliases deactivate. */\n reset(): void {\n this.deactivate();\n }\n\n destroy(): void {\n if (this._isDestroyed) return;\n this.deactivate();\n this.onDestroy();\n this.view.destroy({ children: true });\n this._isDestroyed = true;\n }\n\n /** Subclass hook: set up visuals for the given symbolId. */\n protected abstract onActivate(symbolId: string): void;\n\n /** Subclass hook: clean up visuals. */\n protected abstract onDeactivate(): void;\n\n /** Subclass hook: additional cleanup on destroy. */\n protected onDestroy(): void {\n // Override if needed\n }\n\n /** Play the win/highlight animation for this symbol. Resolves when complete. */\n abstract playWin(): Promise<void>;\n\n /** Immediately stop any running animation and return to idle. */\n abstract stopAnimation(): void;\n\n /** Resize the symbol's visual to fit the given dimensions. */\n abstract resize(width: number, height: number): void;\n\n /**\n * Lifecycle hook: the owning reel has started spinning.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayBlur) to swap to\n * a blur animation automatically.\n */\n onReelSpinStart(): void {}\n\n /**\n * Lifecycle hook: the owning reel is about to stop (just before bounce).\n * Default: no-op.\n */\n onReelSpinEnd(): void {}\n\n /**\n * Lifecycle hook: the owning reel has landed on its final symbols.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayLanding) to fire\n * a landing animation concurrently with the bounce.\n */\n onReelLanded(): void {}\n}\n","import { ReelSymbol } from './ReelSymbol.js';\n\n// Spine types imported dynamically — this is an optional peer dependency\nlet SpineClass: any = null;\n\nasync function loadSpine(): Promise<void> {\n try {\n const spineModule = await import('@esotericsoftware/spine-pixi-v8');\n SpineClass = spineModule.Spine;\n } catch {\n // Spine not available — SpineSymbol will throw on construction\n }\n}\n\n// Attempt to load Spine on module init\nloadSpine();\n\nexport interface SpineSymbolOptions {\n /** Map of symbolId → SkeletonData. */\n skeletonDataMap: Record<string, any>;\n /** Default animation name to play in idle. Default: 'idle'. */\n idleAnimation?: string;\n /** Animation name to play on win. Default: 'win'. */\n winAnimation?: string;\n /** Default skin name. Default: 'default'. */\n defaultSkin?: string;\n}\n\n/**\n * Symbol implementation using Spine 2D skeletal animation.\n *\n * Requires `@esotericsoftware/spine-pixi-v8` as an optional peer dependency.\n * If Spine is not installed, constructing a SpineSymbol will throw.\n */\nexport class SpineSymbol extends ReelSymbol {\n private _spine: any = null;\n private _skeletonDataMap: Record<string, any>;\n private _idleAnimation: string;\n private _winAnimation: string;\n private _defaultSkin: string;\n private _winResolve: (() => void) | null = null;\n private _currentSkeletonKey: string = '';\n\n constructor(options: SpineSymbolOptions) {\n super();\n if (!SpineClass) {\n throw new Error(\n 'SpineSymbol requires @esotericsoftware/spine-pixi-v8 to be installed. ' +\n 'Install it with: npm install @esotericsoftware/spine-pixi-v8',\n );\n }\n this._skeletonDataMap = options.skeletonDataMap;\n this._idleAnimation = options.idleAnimation ?? 'idle';\n this._winAnimation = options.winAnimation ?? 'win';\n this._defaultSkin = options.defaultSkin ?? 'default';\n }\n\n protected onActivate(symbolId: string): void {\n const skeletonData = this._skeletonDataMap[symbolId];\n if (!skeletonData) return;\n\n // Reuse existing spine if same skeleton data\n if (this._currentSkeletonKey !== symbolId) {\n if (this._spine) {\n this.view.removeChild(this._spine);\n this._spine.destroy();\n }\n this._spine = new SpineClass({ skeletonData });\n this.view.addChild(this._spine);\n this._currentSkeletonKey = symbolId;\n }\n\n // Set to idle\n if (this._spine.skeleton.data.findSkin(this._defaultSkin)) {\n this._spine.skeleton.setSkinByName(this._defaultSkin);\n this._spine.skeleton.setSlotsToSetupPose();\n }\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n }\n\n protected onDeactivate(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.state.clearTracks();\n }\n this._winResolve = null;\n }\n\n async playWin(): Promise<void> {\n if (!this._spine) return;\n if (!this._spine.skeleton.data.findAnimation(this._winAnimation)) return;\n\n return new Promise<void>((resolve) => {\n this._winResolve = resolve;\n const entry = this._spine.state.setAnimation(0, this._winAnimation, false);\n this._spine.state.addListener({\n complete: (trackEntry: any) => {\n if (trackEntry === entry) {\n this._spine.state.clearListeners();\n // Return to idle\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n this._winResolve = null;\n resolve();\n }\n },\n });\n });\n }\n\n stopAnimation(): void {\n if (!this._spine) return;\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n if (this._winResolve) {\n this._winResolve();\n this._winResolve = null;\n }\n }\n\n resize(width: number, height: number): void {\n if (!this._spine) return;\n const bounds = this._spine.getBounds();\n if (bounds.width > 0 && bounds.height > 0) {\n this._spine.scale.set(\n width / bounds.width,\n height / bounds.height,\n );\n }\n }\n\n protected override onDestroy(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.destroy();\n this._spine = null;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in speed profiles covering common slot game needs.
|
|
3
|
+
*
|
|
4
|
+
* Bounce values are tuned for the typical 120–200px symbol range. For larger
|
|
5
|
+
* or smaller symbols, register a custom profile or override `bounceDistance`.
|
|
6
|
+
* `bounceDuration` is the total time for the two-leg bounce (down then back).
|
|
7
|
+
*/
|
|
8
|
+
export declare const SpeedPresets: {
|
|
9
|
+
readonly NORMAL: {
|
|
10
|
+
readonly name: "normal";
|
|
11
|
+
readonly spinDelay: 100;
|
|
12
|
+
readonly spinSpeed: 30;
|
|
13
|
+
readonly stopDelay: 140;
|
|
14
|
+
readonly anticipationDelay: 450;
|
|
15
|
+
readonly bounceDistance: 56;
|
|
16
|
+
readonly bounceDuration: 600;
|
|
17
|
+
readonly accelerationEase: "power2.in";
|
|
18
|
+
readonly decelerationEase: "power2.out";
|
|
19
|
+
readonly accelerationDuration: 300;
|
|
20
|
+
readonly minimumSpinTime: 500;
|
|
21
|
+
};
|
|
22
|
+
readonly TURBO: {
|
|
23
|
+
readonly name: "turbo";
|
|
24
|
+
readonly spinDelay: 30;
|
|
25
|
+
readonly spinSpeed: 50;
|
|
26
|
+
readonly stopDelay: 0;
|
|
27
|
+
readonly anticipationDelay: 250;
|
|
28
|
+
readonly bounceDistance: 42;
|
|
29
|
+
readonly bounceDuration: 200;
|
|
30
|
+
readonly accelerationEase: "power2.in";
|
|
31
|
+
readonly decelerationEase: "power2.out";
|
|
32
|
+
readonly accelerationDuration: 200;
|
|
33
|
+
readonly minimumSpinTime: 300;
|
|
34
|
+
};
|
|
35
|
+
readonly SUPER_TURBO: {
|
|
36
|
+
readonly name: "superTurbo";
|
|
37
|
+
readonly spinDelay: 0;
|
|
38
|
+
readonly spinSpeed: 80;
|
|
39
|
+
readonly stopDelay: 0;
|
|
40
|
+
readonly anticipationDelay: 0;
|
|
41
|
+
readonly bounceDistance: 14;
|
|
42
|
+
readonly bounceDuration: 120;
|
|
43
|
+
readonly accelerationEase: "power1.in";
|
|
44
|
+
readonly decelerationEase: "power1.out";
|
|
45
|
+
readonly accelerationDuration: 50;
|
|
46
|
+
readonly minimumSpinTime: 100;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=SpeedPresets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpeedPresets.d.ts","sourceRoot":"","sources":["../../src/config/SpeedPresets.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCwB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Default values applied when not explicitly set by the builder. */
|
|
2
|
+
export declare const DEFAULTS: {
|
|
3
|
+
bufferSymbols: number;
|
|
4
|
+
symbolGap: {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
};
|
|
8
|
+
initialSpeed: string;
|
|
9
|
+
maxPoolPerKey: number;
|
|
10
|
+
zIndexStep: number;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=defaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,eAAO,MAAM,QAAQ;mBACC,MAAM;;WACL,MAAM;WAAU,MAAM;;kBACjB,MAAM;mBACX,MAAM;gBACR,MAAM;CAC1B,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Container, Ticker } from 'pixi.js';
|
|
2
|
+
/** Timing and animation profile for a speed mode. */
|
|
3
|
+
export interface SpeedProfile {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
/** Milliseconds between each reel starting to spin. */
|
|
6
|
+
readonly spinDelay: number;
|
|
7
|
+
/** Pixels per frame at full spin speed. */
|
|
8
|
+
readonly spinSpeed: number;
|
|
9
|
+
/** Milliseconds between each reel stopping. */
|
|
10
|
+
readonly stopDelay: number;
|
|
11
|
+
/** Milliseconds to hold anticipation phase. */
|
|
12
|
+
readonly anticipationDelay: number;
|
|
13
|
+
/** Pixels of overshoot when reel lands. */
|
|
14
|
+
readonly bounceDistance: number;
|
|
15
|
+
/** Milliseconds for bounce-back animation. */
|
|
16
|
+
readonly bounceDuration: number;
|
|
17
|
+
/** Optional GSAP ease string for acceleration. Default: 'power2.in'. */
|
|
18
|
+
readonly accelerationEase?: string;
|
|
19
|
+
/** Optional GSAP ease string for deceleration. Default: 'power2.out'. */
|
|
20
|
+
readonly decelerationEase?: string;
|
|
21
|
+
/** Milliseconds for acceleration phase. Default: 300. */
|
|
22
|
+
readonly accelerationDuration?: number;
|
|
23
|
+
/** Minimum spin time in ms before stop is allowed. Default: 500. */
|
|
24
|
+
readonly minimumSpinTime?: number;
|
|
25
|
+
}
|
|
26
|
+
/** Per-symbol configuration data. */
|
|
27
|
+
export interface SymbolData {
|
|
28
|
+
/** Relative weight for random generation (higher = more frequent). */
|
|
29
|
+
weight: number;
|
|
30
|
+
/** Display layering order. Higher = in front. */
|
|
31
|
+
zIndex?: number;
|
|
32
|
+
/** If true, this symbol renders above the reel mask (for oversized animations). */
|
|
33
|
+
unmask?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** Configuration for the reel grid layout. */
|
|
36
|
+
export interface ReelGridConfig {
|
|
37
|
+
/** Number of reel columns. */
|
|
38
|
+
reelCount: number;
|
|
39
|
+
/** Number of visible symbol rows per reel. */
|
|
40
|
+
visibleRows: number;
|
|
41
|
+
/** Symbol width in pixels. */
|
|
42
|
+
symbolWidth: number;
|
|
43
|
+
/** Symbol height in pixels. */
|
|
44
|
+
symbolHeight: number;
|
|
45
|
+
/** Gap between symbols. Default: { x: 0, y: 0 }. */
|
|
46
|
+
symbolGap?: {
|
|
47
|
+
x: number;
|
|
48
|
+
y: number;
|
|
49
|
+
};
|
|
50
|
+
/** Number of buffer symbols above and below the visible area. Default: 1. */
|
|
51
|
+
bufferSymbols?: number;
|
|
52
|
+
}
|
|
53
|
+
/** Extra symbols above/below config per reel. */
|
|
54
|
+
export interface ReelExtraSymbols {
|
|
55
|
+
symbolsAbove: number;
|
|
56
|
+
symbolsBelow: number;
|
|
57
|
+
}
|
|
58
|
+
/** Offset modes for X-axis symbol positioning. */
|
|
59
|
+
export type OffsetXMode = 'none' | 'trapezoid';
|
|
60
|
+
/** Trapezoid perspective configuration. */
|
|
61
|
+
export interface TrapezoidConfig {
|
|
62
|
+
mode: 'trapezoid';
|
|
63
|
+
widthDifference: number;
|
|
64
|
+
topWidthFactor: number;
|
|
65
|
+
bottomWidthFactor: number;
|
|
66
|
+
}
|
|
67
|
+
/** No offset configuration. */
|
|
68
|
+
export interface NoOffsetConfig {
|
|
69
|
+
mode: 'none';
|
|
70
|
+
}
|
|
71
|
+
export type OffsetConfig = TrapezoidConfig | NoOffsetConfig;
|
|
72
|
+
/** 2D matrix type (reel × row). */
|
|
73
|
+
export type Matrix<T> = T[][];
|
|
74
|
+
/** Simple 2D position. */
|
|
75
|
+
export interface Position {
|
|
76
|
+
x: number;
|
|
77
|
+
y: number;
|
|
78
|
+
}
|
|
79
|
+
/** Mask configuration for the reel viewport. */
|
|
80
|
+
export interface MaskConfig {
|
|
81
|
+
mask: Container;
|
|
82
|
+
position: Position;
|
|
83
|
+
}
|
|
84
|
+
/** Full internal configuration assembled by the builder. */
|
|
85
|
+
export interface ReelSetInternalConfig {
|
|
86
|
+
grid: Required<ReelGridConfig>;
|
|
87
|
+
symbols: Record<string, SymbolData>;
|
|
88
|
+
speeds: Map<string, SpeedProfile>;
|
|
89
|
+
initialSpeed: string;
|
|
90
|
+
offset: OffsetConfig;
|
|
91
|
+
ticker: Ticker;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Container } from 'pixi.js';
|
|
2
|
+
import { Disposable } from '../utils/Disposable.js';
|
|
3
|
+
import { ReelSymbol } from '../symbols/ReelSymbol.js';
|
|
4
|
+
import { SymbolFactory } from '../symbols/SymbolFactory.js';
|
|
5
|
+
import { SymbolData } from '../config/types.js';
|
|
6
|
+
import { ReelMotion } from './ReelMotion.js';
|
|
7
|
+
import { StopSequencer } from './StopSequencer.js';
|
|
8
|
+
import { EventEmitter } from '../events/EventEmitter.js';
|
|
9
|
+
import { ReelEvents } from '../events/ReelEvents.js';
|
|
10
|
+
import { RandomSymbolProvider } from '../frame/RandomSymbolProvider.js';
|
|
11
|
+
import { ReelViewport } from './ReelViewport.js';
|
|
12
|
+
import { SpinningMode } from '../spin/modes/SpinningMode.js';
|
|
13
|
+
export interface ReelConfig {
|
|
14
|
+
reelIndex: number;
|
|
15
|
+
visibleRows: number;
|
|
16
|
+
bufferAbove: number;
|
|
17
|
+
bufferBelow: number;
|
|
18
|
+
symbolWidth: number;
|
|
19
|
+
symbolHeight: number;
|
|
20
|
+
symbolGapX: number;
|
|
21
|
+
symbolGapY: number;
|
|
22
|
+
symbolsData: Record<string, SymbolData>;
|
|
23
|
+
initialSymbols: string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* One vertical column of a slot board.
|
|
27
|
+
*
|
|
28
|
+
* A `Reel` owns:
|
|
29
|
+
* - the `ReelSymbol[]` currently on screen (a small buffer above the
|
|
30
|
+
* visible rows + the visible rows + a small buffer below — so symbols
|
|
31
|
+
* can fade in from off-screen cleanly)
|
|
32
|
+
* - the `ReelMotion` that adds a Y delta each tick and wraps symbols
|
|
33
|
+
* that scroll off the ends
|
|
34
|
+
* - a `StopSequencer` — the queue of target symbols the reel still has
|
|
35
|
+
* to land on before it can stop
|
|
36
|
+
*
|
|
37
|
+
* You generally do not touch a `Reel` directly. Drive the `ReelSet` and
|
|
38
|
+
* let it fan out. Reels are exposed on `reelSet.reels` so you can read
|
|
39
|
+
* the current grid (`reel.getSymbolAt(row)`) or listen to per-reel
|
|
40
|
+
* events (`phase:enter`, `landed`, `symbol:created`, ...).
|
|
41
|
+
*/
|
|
42
|
+
export declare class Reel implements Disposable {
|
|
43
|
+
readonly container: Container;
|
|
44
|
+
readonly events: EventEmitter<ReelEvents>;
|
|
45
|
+
readonly reelIndex: number;
|
|
46
|
+
/** Current symbols in order (top buffer → visible → bottom buffer). */
|
|
47
|
+
symbols: ReelSymbol[];
|
|
48
|
+
/** Current spin speed (pixels per frame). Set by phases. */
|
|
49
|
+
speed: number;
|
|
50
|
+
/** Current spinning mode. */
|
|
51
|
+
spinningMode: SpinningMode;
|
|
52
|
+
readonly motion: ReelMotion;
|
|
53
|
+
readonly stopSequencer: StopSequencer;
|
|
54
|
+
private _symbolFactory;
|
|
55
|
+
private _randomProvider;
|
|
56
|
+
private _viewport;
|
|
57
|
+
private _symbolsData;
|
|
58
|
+
private _visibleRows;
|
|
59
|
+
private _bufferAbove;
|
|
60
|
+
private _symbolWidth;
|
|
61
|
+
private _symbolHeight;
|
|
62
|
+
private _isDestroyed;
|
|
63
|
+
private _isStopping;
|
|
64
|
+
constructor(config: ReelConfig, symbolFactory: SymbolFactory, randomProvider: RandomSymbolProvider, viewport: ReelViewport);
|
|
65
|
+
get isDestroyed(): boolean;
|
|
66
|
+
get isStopping(): boolean;
|
|
67
|
+
set isStopping(value: boolean);
|
|
68
|
+
get bufferAbove(): number;
|
|
69
|
+
get bufferBelow(): number;
|
|
70
|
+
get visibleRows(): number;
|
|
71
|
+
/** Update reel for one frame. Called by SpinController via ticker. */
|
|
72
|
+
update(deltaMs: number): void;
|
|
73
|
+
/** Set the target frame for stopping. */
|
|
74
|
+
setStopFrame(frame: string[]): void;
|
|
75
|
+
/** Get visible symbol IDs (top to bottom, excluding buffers). */
|
|
76
|
+
getVisibleSymbols(): string[];
|
|
77
|
+
/** Get symbol at a visible row (0-indexed from top visible). */
|
|
78
|
+
getSymbolAt(visibleRow: number): ReelSymbol;
|
|
79
|
+
/** Notify all visible symbols that the reel has started spinning. */
|
|
80
|
+
notifySpinStart(): void;
|
|
81
|
+
/** Notify all visible symbols that the reel is about to stop (just before bounce). */
|
|
82
|
+
notifySpinEnd(): void;
|
|
83
|
+
/** Notify all visible symbols that the reel has landed on its target. */
|
|
84
|
+
notifyLanded(): void;
|
|
85
|
+
/** Snap all symbols to grid. */
|
|
86
|
+
snapToGrid(): void;
|
|
87
|
+
/** Place symbols immediately at target positions (for skip/turbo). */
|
|
88
|
+
placeSymbols(symbolIds: string[]): void;
|
|
89
|
+
/**
|
|
90
|
+
* Recompute `zIndex` for every symbol in the reel.
|
|
91
|
+
*
|
|
92
|
+
* Formula: `symbolData.zIndex ?? 0` (scaled by 100 to leave room for row
|
|
93
|
+
* ordering), plus the symbol's current array index — so bottom-row symbols
|
|
94
|
+
* render in front of top-row symbols and any symbol with a higher
|
|
95
|
+
* configured base zIndex (e.g. wild, bonus) renders above its neighbors.
|
|
96
|
+
*
|
|
97
|
+
* Called automatically after wraps, snaps, and direct placement. Call it
|
|
98
|
+
* manually after mutating `symbolsData.zIndex` at runtime.
|
|
99
|
+
*/
|
|
100
|
+
refreshZIndex(): void;
|
|
101
|
+
destroy(): void;
|
|
102
|
+
private _setupSymbolPositions;
|
|
103
|
+
private _onSymbolWrapped;
|
|
104
|
+
private _replaceSymbol;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=Reel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Reel.d.ts","sourceRoot":"","sources":["../../src/core/Reel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAGlE,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACxC,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,IAAK,YAAW,UAAU;IACrC,SAAgB,SAAS,EAAE,SAAS,CAAC;IACrC,SAAgB,MAAM,EAAE,YAAY,CAAC,UAAU,CAAC,CAAC;IACjD,SAAgB,SAAS,EAAE,MAAM,CAAC;IAElC,uEAAuE;IAChE,OAAO,EAAE,UAAU,EAAE,CAAC;IAE7B,4DAA4D;IACrD,KAAK,EAAE,MAAM,CAAK;IAEzB,6BAA6B;IACtB,YAAY,EAAE,YAAY,CAAsB;IAEvD,SAAgB,MAAM,EAAE,UAAU,CAAC;IACnC,SAAgB,aAAa,EAAE,aAAa,CAAC;IAE7C,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,SAAS,CAAe;IAChC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAS;gBAG1B,MAAM,EAAE,UAAU,EAClB,aAAa,EAAE,aAAa,EAC5B,cAAc,EAAE,oBAAoB,EACpC,QAAQ,EAAE,YAAY;IA2CxB,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,IAAI,UAAU,CAAC,KAAK,EAAE,OAAO,EAE5B;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,sEAAsE;IACtE,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAc7B,yCAAyC;IACzC,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAInC,iEAAiE;IACjE,iBAAiB,IAAI,MAAM,EAAE;IAQ7B,gEAAgE;IAChE,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU;IAI3C,qEAAqE;IACrE,eAAe,IAAI,IAAI;IAMvB,sFAAsF;IACtF,aAAa,IAAI,IAAI;IAMrB,yEAAyE;IACzE,YAAY,IAAI,IAAI;IAMpB,gCAAgC;IAChC,UAAU,IAAI,IAAI;IAKlB,sEAAsE;IACtE,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAgBvC;;;;;;;;;;OAUG;IACH,aAAa,IAAI,IAAI;IAQrB,OAAO,IAAI,IAAI;IAYf,OAAO,CAAC,qBAAqB;IAc7B,OAAO,CAAC,gBAAgB;IAcxB,OAAO,CAAC,cAAc;CA8BvB"}
|