@energy8platform/game-engine 0.23.0 → 0.24.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/dist/core.cjs.js +2 -2
- package/dist/core.cjs.js.map +1 -1
- package/dist/core.d.ts +3 -3
- package/dist/core.esm.js +2 -2
- package/dist/core.esm.js.map +1 -1
- package/dist/host.cjs.js +35 -10
- package/dist/host.cjs.js.map +1 -1
- package/dist/host.d.ts +22 -9
- package/dist/host.esm.js +24 -11
- package/dist/host.esm.js.map +1 -1
- package/dist/index.cjs.js +2 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.esm.js +2 -2
- package/dist/index.esm.js.map +1 -1
- package/dist/shell.cjs.js +4 -4
- package/dist/shell.d.ts +1 -1
- package/dist/shell.esm.js +1 -1
- package/dist/slot.cjs.js +226 -75
- package/dist/slot.cjs.js.map +1 -1
- package/dist/slot.d.ts +137 -19
- package/dist/slot.esm.js +224 -76
- package/dist/slot.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/core/GameApplication.ts +3 -3
- package/src/host/createSlotGame.ts +6 -3
- package/src/host/index.ts +11 -1
- package/src/host/shellConfig.ts +22 -12
- package/src/host/types.ts +13 -2
- package/src/shell/index.ts +4 -3
- package/src/slot/cascade/TumbleController.ts +6 -3
- package/src/slot/config/ReelSystemConfig.ts +19 -0
- package/src/slot/features/extra.ts +1 -2
- package/src/slot/features/symbols.ts +10 -10
- package/src/slot/features/types.ts +13 -5
- package/src/slot/features/wilds.ts +1 -1
- package/src/slot/grid/AnimatedSymbol.ts +20 -11
- package/src/slot/grid/ReelGrid.ts +80 -41
- package/src/slot/grid/SymbolCell.ts +14 -7
- package/src/slot/grid/SymbolView.ts +2 -2
- package/src/slot/grid/geometry.ts +155 -0
- package/src/slot/index.ts +3 -0
- package/src/slot/motion/SpinEngine.ts +1 -1
- package/src/slot/system/ReelSystem.ts +10 -0
- package/src/types.ts +1 -1
package/dist/slot.esm.js
CHANGED
|
@@ -452,22 +452,23 @@ class SpriteAnimation {
|
|
|
452
452
|
}
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
+
const dims = (size) => typeof size === 'number' ? { width: size, height: size } : size;
|
|
455
456
|
/** Built-in SymbolView: a static base sprite with optional idle/win spritesheet frames. */
|
|
456
457
|
class AnimatedSymbol extends Container {
|
|
457
458
|
_base;
|
|
458
459
|
_anim = null;
|
|
459
460
|
_textures;
|
|
460
|
-
|
|
461
|
+
_w = 0;
|
|
462
|
+
_h = 0;
|
|
461
463
|
_fps;
|
|
462
464
|
constructor(config) {
|
|
463
465
|
super();
|
|
464
466
|
this._textures = config.textures;
|
|
465
|
-
this._size = config.size;
|
|
466
467
|
this._fps = config.fps ?? 24;
|
|
467
468
|
this._base = new Sprite(config.textures.base);
|
|
468
469
|
this._base.anchor.set(0.5);
|
|
469
470
|
this.addChild(this._base);
|
|
470
|
-
this.resize(
|
|
471
|
+
this.resize(config.size);
|
|
471
472
|
}
|
|
472
473
|
setTextures(t) {
|
|
473
474
|
this._textures = t;
|
|
@@ -475,12 +476,14 @@ class AnimatedSymbol extends Container {
|
|
|
475
476
|
this.showStatic();
|
|
476
477
|
}
|
|
477
478
|
resize(size) {
|
|
478
|
-
|
|
479
|
-
this.
|
|
480
|
-
this.
|
|
479
|
+
const { width, height } = dims(size);
|
|
480
|
+
this._w = width;
|
|
481
|
+
this._h = height;
|
|
482
|
+
this._base.width = width;
|
|
483
|
+
this._base.height = height;
|
|
481
484
|
if (this._anim) {
|
|
482
|
-
this._anim.width =
|
|
483
|
-
this._anim.height =
|
|
485
|
+
this._anim.width = width;
|
|
486
|
+
this._anim.height = height;
|
|
484
487
|
}
|
|
485
488
|
}
|
|
486
489
|
showStatic() {
|
|
@@ -510,8 +513,8 @@ class AnimatedSymbol extends Container {
|
|
|
510
513
|
this._base.visible = false;
|
|
511
514
|
const a = SpriteAnimation.create(frames, { loop, autoPlay: true, onComplete });
|
|
512
515
|
a.anchor.set(0.5);
|
|
513
|
-
a.width = this.
|
|
514
|
-
a.height = this.
|
|
516
|
+
a.width = this._w;
|
|
517
|
+
a.height = this._h;
|
|
515
518
|
a.animationSpeed = this._fps / 60;
|
|
516
519
|
this.addChild(a);
|
|
517
520
|
this._anim = a;
|
|
@@ -525,9 +528,11 @@ const DEFAULT_STYLE = {
|
|
|
525
528
|
removed: { color: 0x223047, alpha: 0.12 },
|
|
526
529
|
fresh: { color: 0xffffff, alpha: 0.6 },
|
|
527
530
|
};
|
|
531
|
+
const cellDims = (size) => typeof size === 'number' ? { width: size, height: size } : size;
|
|
528
532
|
class SymbolCell extends Container {
|
|
529
533
|
__uiComponent = true;
|
|
530
|
-
|
|
534
|
+
_w;
|
|
535
|
+
_h;
|
|
531
536
|
_resolve;
|
|
532
537
|
_style;
|
|
533
538
|
_frame;
|
|
@@ -539,7 +544,9 @@ class SymbolCell extends Container {
|
|
|
539
544
|
frameStyleKey = 'idle';
|
|
540
545
|
constructor(config) {
|
|
541
546
|
super();
|
|
542
|
-
|
|
547
|
+
const { width, height } = cellDims(config.size);
|
|
548
|
+
this._w = width;
|
|
549
|
+
this._h = height;
|
|
543
550
|
this._resolve = config.resolve;
|
|
544
551
|
this._style = { ...DEFAULT_STYLE, ...(config.frameStyle ?? {}) };
|
|
545
552
|
this._frame = new Graphics();
|
|
@@ -567,7 +574,7 @@ class SymbolCell extends Container {
|
|
|
567
574
|
}
|
|
568
575
|
const v = this._resolve(data.symbol);
|
|
569
576
|
if (v) {
|
|
570
|
-
v.resize?.(this.
|
|
577
|
+
v.resize?.({ width: this._w, height: this._h });
|
|
571
578
|
this.addChildAt(v, 1); // above frame, below badges
|
|
572
579
|
this._view = v;
|
|
573
580
|
}
|
|
@@ -608,7 +615,7 @@ class SymbolCell extends Container {
|
|
|
608
615
|
const s = this._style[key];
|
|
609
616
|
this._frame.clear();
|
|
610
617
|
this._frame
|
|
611
|
-
.roundRect(-this.
|
|
618
|
+
.roundRect(-this._w / 2, -this._h / 2, this._w, this._h, this._style.radius)
|
|
612
619
|
.fill({ color: s.color, alpha: s.alpha });
|
|
613
620
|
// store the colour as tint for cheap inspection/testing
|
|
614
621
|
this._frame.tint = s.color;
|
|
@@ -621,7 +628,7 @@ class SymbolCell extends Container {
|
|
|
621
628
|
if (!value || value <= 1)
|
|
622
629
|
return;
|
|
623
630
|
this._multBadge = this._badge(`×${value}`, 0xffd24a);
|
|
624
|
-
this._multBadge.position.set(this.
|
|
631
|
+
this._multBadge.position.set(this._w / 2 - 12, -this._h / 2 + 12);
|
|
625
632
|
this._badges.addChild(this._multBadge);
|
|
626
633
|
}
|
|
627
634
|
_setBonus(value) {
|
|
@@ -632,7 +639,7 @@ class SymbolCell extends Container {
|
|
|
632
639
|
if (!value || value <= 0)
|
|
633
640
|
return;
|
|
634
641
|
this._bonusBadge = this._badge(`+${value}`, 0x7ad7ff);
|
|
635
|
-
this._bonusBadge.position.set(-this.
|
|
642
|
+
this._bonusBadge.position.set(-this._w / 2 + 12, -this._h / 2 + 12);
|
|
636
643
|
this._badges.addChild(this._bonusBadge);
|
|
637
644
|
}
|
|
638
645
|
_badge(label, color) {
|
|
@@ -644,17 +651,110 @@ class SymbolCell extends Container {
|
|
|
644
651
|
}
|
|
645
652
|
}
|
|
646
653
|
|
|
654
|
+
// packages/game-engine/src/slot/grid/geometry.ts
|
|
655
|
+
//
|
|
656
|
+
// Pure geometry resolver for the reel grid. Turns the (backward-compatible) grid config
|
|
657
|
+
// — a square `cellSize` + single `gap`, optionally overridden by rectangular / per-strip
|
|
658
|
+
// dimensions and per-strip gaps — into a fully-resolved, per-reel layout.
|
|
659
|
+
//
|
|
660
|
+
// Coordinate convention (unchanged from the original square grid):
|
|
661
|
+
// - `cellPosition` returns CELL-CENTRE coordinates.
|
|
662
|
+
// - Cell (0,0)'s centre sits at local x = 0. Reels extend to the right.
|
|
663
|
+
// - Variable-height reels are vertically CENTRED about a shared centre line, so the
|
|
664
|
+
// tallest reel's row 0 sits at y = 0 (matching the old Megaways behaviour).
|
|
665
|
+
//
|
|
666
|
+
// See docs/reels-analysis-and-design.md §6.
|
|
667
|
+
const perReelSize = (spec, w, h) => {
|
|
668
|
+
if (typeof spec === 'number')
|
|
669
|
+
return [spec, spec];
|
|
670
|
+
if (spec && typeof spec === 'object')
|
|
671
|
+
return [spec.width, spec.height];
|
|
672
|
+
return [w, h];
|
|
673
|
+
};
|
|
674
|
+
const gapAt = (g, i, base) => Array.isArray(g) ? (g[i] ?? base) : (g ?? base);
|
|
675
|
+
/** Resolve a grid config into a fully-populated per-reel geometry. */
|
|
676
|
+
function resolveGeometry(g) {
|
|
677
|
+
const cols = Math.max(0, g.cols);
|
|
678
|
+
const rowsPerReel = g.rowsPerReel && g.rowsPerReel.length === cols
|
|
679
|
+
? g.rowsPerReel.slice()
|
|
680
|
+
: Array.from({ length: cols }, () => g.rows);
|
|
681
|
+
const maxRows = cols ? Math.max(1, ...rowsPerReel) : 0;
|
|
682
|
+
const baseW = g.cellWidth ?? g.cellSize;
|
|
683
|
+
const baseH = g.cellHeight ?? g.cellSize;
|
|
684
|
+
const baseGap = g.gap ?? 0;
|
|
685
|
+
const cellW = [];
|
|
686
|
+
const cellH = [];
|
|
687
|
+
const rowGap = [];
|
|
688
|
+
for (let c = 0; c < cols; c++) {
|
|
689
|
+
const [w, h] = perReelSize(g.cellSizePerReel?.[c], baseW, baseH);
|
|
690
|
+
cellW[c] = w;
|
|
691
|
+
cellH[c] = h;
|
|
692
|
+
rowGap[c] = gapAt(g.rowGap, c, baseGap);
|
|
693
|
+
}
|
|
694
|
+
const colGap = [];
|
|
695
|
+
for (let i = 0; i < Math.max(0, cols - 1); i++)
|
|
696
|
+
colGap[i] = gapAt(g.colGap, i, baseGap);
|
|
697
|
+
// Horizontal: reel 0 centre at x = 0, then accumulate half-widths + between-reel gaps.
|
|
698
|
+
const colX = [];
|
|
699
|
+
if (cols)
|
|
700
|
+
colX[0] = 0;
|
|
701
|
+
for (let c = 1; c < cols; c++)
|
|
702
|
+
colX[c] = colX[c - 1] + cellW[c - 1] / 2 + colGap[c - 1] + cellW[c] / 2;
|
|
703
|
+
// Vertical: each reel's rows span (rows-1)*step; centre every reel about a shared line so
|
|
704
|
+
// the tallest reel's row 0 stays at y = 0 (parity with the old uniform Megaways layout).
|
|
705
|
+
const span = rowsPerReel.map((rr, c) => Math.max(0, rr - 1) * (cellH[c] + rowGap[c]));
|
|
706
|
+
const halfMaxSpan = cols ? Math.max(...span) / 2 : 0;
|
|
707
|
+
const yOff = span.map((s) => halfMaxSpan - s / 2);
|
|
708
|
+
// Bounding box.
|
|
709
|
+
let leftX = 0;
|
|
710
|
+
let rightX = 0;
|
|
711
|
+
let topY = 0;
|
|
712
|
+
let bottomY = 0;
|
|
713
|
+
for (let c = 0; c < cols; c++) {
|
|
714
|
+
leftX = Math.min(leftX, colX[c] - cellW[c] / 2);
|
|
715
|
+
rightX = Math.max(rightX, colX[c] + cellW[c] / 2);
|
|
716
|
+
const reelTop = yOff[c] - cellH[c] / 2;
|
|
717
|
+
topY = Math.min(topY, reelTop);
|
|
718
|
+
bottomY = Math.max(bottomY, reelTop + rowsPerReel[c] * cellH[c] + Math.max(0, rowsPerReel[c] - 1) * rowGap[c]);
|
|
719
|
+
}
|
|
720
|
+
const gridW = rightX - leftX;
|
|
721
|
+
const gridH = bottomY - topY;
|
|
722
|
+
return {
|
|
723
|
+
cols,
|
|
724
|
+
rowsPerReel,
|
|
725
|
+
maxRows,
|
|
726
|
+
cellW,
|
|
727
|
+
cellH,
|
|
728
|
+
rowGap,
|
|
729
|
+
colGap,
|
|
730
|
+
colX,
|
|
731
|
+
yOff,
|
|
732
|
+
gridW,
|
|
733
|
+
gridH,
|
|
734
|
+
centerX: (leftX + rightX) / 2,
|
|
735
|
+
centerY: (topY + bottomY) / 2,
|
|
736
|
+
leftX,
|
|
737
|
+
topY,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
/** Cell-centre position from a resolved geometry. */
|
|
741
|
+
function cellPositionOf(geom, col, row) {
|
|
742
|
+
return {
|
|
743
|
+
x: geom.colX[col] ?? 0,
|
|
744
|
+
y: (geom.yOff[col] ?? 0) + row * ((geom.cellH[col] ?? 0) + (geom.rowGap[col] ?? 0)),
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
647
748
|
/**
|
|
648
|
-
* A grid of `SymbolCell`s. Supports uniform grids
|
|
649
|
-
*
|
|
749
|
+
* A grid of `SymbolCell`s. Supports uniform grids, variable-height reels (Megaways), and
|
|
750
|
+
* rectangular / per-strip cell sizes with per-strip gaps. All layout flows through a single
|
|
751
|
+
* resolved geometry (see grid/geometry.ts); every consumer reads positions via `cellPosition`
|
|
752
|
+
* and dimensions via `cellSize(col)` rather than assuming a square cell.
|
|
650
753
|
*/
|
|
651
754
|
class ReelGrid extends Container {
|
|
652
755
|
__uiComponent = true;
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
_maxRows;
|
|
656
|
-
_cellSize;
|
|
657
|
-
_gap;
|
|
756
|
+
_cfg;
|
|
757
|
+
_geom;
|
|
658
758
|
_cells = [];
|
|
659
759
|
_cellLayer = new Container();
|
|
660
760
|
_resolve;
|
|
@@ -662,39 +762,35 @@ class ReelGrid extends Container {
|
|
|
662
762
|
_mask = null;
|
|
663
763
|
constructor(config) {
|
|
664
764
|
super();
|
|
665
|
-
this.
|
|
666
|
-
this._rowsPerReel =
|
|
667
|
-
config.rowsPerReel && config.rowsPerReel.length === config.cols
|
|
668
|
-
? config.rowsPerReel.slice()
|
|
669
|
-
: Array.from({ length: config.cols }, () => config.rows);
|
|
670
|
-
this._maxRows = Math.max(1, ...this._rowsPerReel);
|
|
671
|
-
this._cellSize = config.cellSize;
|
|
672
|
-
this._gap = config.gap ?? 0;
|
|
765
|
+
this._cfg = { ...config };
|
|
673
766
|
this._resolve = config.resolve;
|
|
674
767
|
this._frameStyle = config.frameStyle;
|
|
675
|
-
|
|
768
|
+
this._geom = resolveGeometry(config);
|
|
769
|
+
if (config.decoration?.texture) {
|
|
676
770
|
const pad = config.decoration.padding ?? 0;
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
deco.height = h;
|
|
683
|
-
deco.position.set(-pad - this._cellSize / 2, -pad - this._cellSize / 2);
|
|
684
|
-
this.addChild(deco);
|
|
685
|
-
}
|
|
771
|
+
const deco = new Sprite(config.decoration.texture);
|
|
772
|
+
deco.width = this._geom.gridW + pad * 2;
|
|
773
|
+
deco.height = this._geom.gridH + pad * 2;
|
|
774
|
+
deco.position.set(this._geom.leftX - pad, this._geom.topY - pad);
|
|
775
|
+
this.addChild(deco);
|
|
686
776
|
}
|
|
687
777
|
this.addChild(this._cellLayer);
|
|
688
778
|
this._buildCells();
|
|
689
779
|
if (config.mask)
|
|
690
780
|
this._applyMask();
|
|
691
781
|
}
|
|
782
|
+
get _cols() {
|
|
783
|
+
return this._geom.cols;
|
|
784
|
+
}
|
|
785
|
+
get _rowsPerReel() {
|
|
786
|
+
return this._geom.rowsPerReel;
|
|
787
|
+
}
|
|
692
788
|
_buildCells() {
|
|
693
789
|
for (let c = 0; c < this._cols; c++) {
|
|
694
790
|
this._cells[c] = [];
|
|
695
791
|
for (let r = 0; r < this._rowsPerReel[c]; r++) {
|
|
696
792
|
const cell = new SymbolCell({
|
|
697
|
-
size: this.
|
|
793
|
+
size: this.cellSize(c),
|
|
698
794
|
resolve: this._resolve,
|
|
699
795
|
frameStyle: this._frameStyle,
|
|
700
796
|
});
|
|
@@ -705,10 +801,19 @@ class ReelGrid extends Container {
|
|
|
705
801
|
}
|
|
706
802
|
}
|
|
707
803
|
}
|
|
804
|
+
/** Per-strip mask: one window per reel sized to that reel's own cell width × column height. */
|
|
708
805
|
_applyMask() {
|
|
709
|
-
const
|
|
710
|
-
const
|
|
711
|
-
|
|
806
|
+
const g = this._geom;
|
|
807
|
+
const m = new Graphics();
|
|
808
|
+
for (let c = 0; c < this._cols; c++) {
|
|
809
|
+
const rows = this._rowsPerReel[c];
|
|
810
|
+
const w = g.cellW[c];
|
|
811
|
+
const h = rows * g.cellH[c] + Math.max(0, rows - 1) * g.rowGap[c];
|
|
812
|
+
const x = g.colX[c] - w / 2;
|
|
813
|
+
const y = g.yOff[c] - g.cellH[c] / 2;
|
|
814
|
+
m.rect(x, y, w, h);
|
|
815
|
+
}
|
|
816
|
+
m.fill(0xffffff);
|
|
712
817
|
this._cellLayer.mask = m;
|
|
713
818
|
this.addChild(m);
|
|
714
819
|
this._mask = m;
|
|
@@ -718,10 +823,15 @@ class ReelGrid extends Container {
|
|
|
718
823
|
}
|
|
719
824
|
/** Tallest reel's row count (the grid's visual height in rows). */
|
|
720
825
|
get rows() {
|
|
721
|
-
return this.
|
|
826
|
+
return this._geom.maxRows;
|
|
827
|
+
}
|
|
828
|
+
/** Cell dimensions (px) for a reel. Rectangular / per-strip aware. */
|
|
829
|
+
cellSize(col) {
|
|
830
|
+
return { width: this._geom.cellW[col] ?? 0, height: this._geom.cellH[col] ?? 0 };
|
|
722
831
|
}
|
|
723
|
-
|
|
724
|
-
|
|
832
|
+
/** The fully-resolved grid geometry (read-only view). */
|
|
833
|
+
get geometry() {
|
|
834
|
+
return this._geom;
|
|
725
835
|
}
|
|
726
836
|
rowsOf(col) {
|
|
727
837
|
return this._rowsPerReel[col] ?? 0;
|
|
@@ -729,12 +839,13 @@ class ReelGrid extends Container {
|
|
|
729
839
|
get rowsPerReel() {
|
|
730
840
|
return this._rowsPerReel.slice();
|
|
731
841
|
}
|
|
732
|
-
/** Cell centre position. Variable-height reels are centred
|
|
842
|
+
/** Cell centre position. Variable-height reels are centred about a shared centre line. */
|
|
733
843
|
cellPosition(col, row) {
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
844
|
+
return cellPositionOf(this._geom, col, row);
|
|
845
|
+
}
|
|
846
|
+
/** Grid bounding-box centre in local coords. */
|
|
847
|
+
center() {
|
|
848
|
+
return { x: this._geom.centerX, y: this._geom.centerY };
|
|
738
849
|
}
|
|
739
850
|
getCell(col, row) {
|
|
740
851
|
return this._cells[col][row];
|
|
@@ -752,8 +863,8 @@ class ReelGrid extends Container {
|
|
|
752
863
|
return;
|
|
753
864
|
this._cellLayer.removeChildren().forEach((c) => c.destroy());
|
|
754
865
|
this._cells = [];
|
|
755
|
-
this.
|
|
756
|
-
this.
|
|
866
|
+
this._cfg = { ...this._cfg, rowsPerReel: rowsPerReel.slice() };
|
|
867
|
+
this._geom = resolveGeometry(this._cfg);
|
|
757
868
|
this._buildCells();
|
|
758
869
|
if (this._mask) {
|
|
759
870
|
this._mask.destroy();
|
|
@@ -762,8 +873,19 @@ class ReelGrid extends Container {
|
|
|
762
873
|
this._applyMask();
|
|
763
874
|
}
|
|
764
875
|
}
|
|
765
|
-
|
|
766
|
-
|
|
876
|
+
/**
|
|
877
|
+
* Re-resolve geometry after a base cell-size change and reposition cells. Accepts a square
|
|
878
|
+
* scalar (updates `cellSize`) or explicit `{width,height}`. Per-strip overrides still apply.
|
|
879
|
+
* (Cell frames are not re-drawn here — a geometry change routes through a full rebuild upstream.)
|
|
880
|
+
*/
|
|
881
|
+
resize(size) {
|
|
882
|
+
if (typeof size === 'number') {
|
|
883
|
+
this._cfg = { ...this._cfg, cellSize: size, cellWidth: undefined, cellHeight: undefined };
|
|
884
|
+
}
|
|
885
|
+
else {
|
|
886
|
+
this._cfg = { ...this._cfg, cellWidth: size.width, cellHeight: size.height };
|
|
887
|
+
}
|
|
888
|
+
this._geom = resolveGeometry(this._cfg);
|
|
767
889
|
for (let c = 0; c < this._cols; c++) {
|
|
768
890
|
for (let r = 0; r < this._rowsPerReel[c]; r++) {
|
|
769
891
|
const { x, y } = this.cellPosition(c, r);
|
|
@@ -1136,6 +1258,10 @@ function effectiveRowsPerReel(grid) {
|
|
|
1136
1258
|
return grid.rowsPerReel.slice();
|
|
1137
1259
|
return Array.from({ length: grid.cols }, () => grid.rows);
|
|
1138
1260
|
}
|
|
1261
|
+
/** Resolve a `GridConfig` into a fully-populated per-reel geometry (rectangular / per-strip aware). */
|
|
1262
|
+
function resolveGridGeometry(grid) {
|
|
1263
|
+
return resolveGeometry(grid);
|
|
1264
|
+
}
|
|
1139
1265
|
/** Total ways-to-win for a ways/megaways grid (product of per-reel heights). */
|
|
1140
1266
|
function waysCount(grid) {
|
|
1141
1267
|
return effectiveRowsPerReel(grid).reduce((a, b) => a * b, 1);
|
|
@@ -1495,7 +1621,7 @@ class SpinEngine {
|
|
|
1495
1621
|
tape.x = base.x;
|
|
1496
1622
|
const landingStart = tapeLen - rows;
|
|
1497
1623
|
for (let i = 0; i < tapeLen; i++) {
|
|
1498
|
-
const cell = new SymbolCell({ size: this._grid.cellSize, resolve: this._resolve });
|
|
1624
|
+
const cell = new SymbolCell({ size: this._grid.cellSize(p.reel), resolve: this._resolve });
|
|
1499
1625
|
const sym = i >= landingStart
|
|
1500
1626
|
? (p.landing[i - landingStart]?.symbol ?? null)
|
|
1501
1627
|
: (pool[i % pool.length] ?? null);
|
|
@@ -1820,8 +1946,10 @@ class TumbleController {
|
|
|
1820
1946
|
cell.alpha = 1;
|
|
1821
1947
|
}
|
|
1822
1948
|
this._undim();
|
|
1823
|
-
// 3. gravity: survivors slide down, new cells drop from above
|
|
1824
|
-
const
|
|
1949
|
+
// 3. gravity: survivors slide down, new cells drop from above (per-reel row step)
|
|
1950
|
+
const rowStepOf = (col) => this._grid.rowsOf(col) > 1
|
|
1951
|
+
? this._grid.cellPosition(col, 1).y - this._grid.cellPosition(col, 0).y
|
|
1952
|
+
: this._grid.cellSize(col).height;
|
|
1825
1953
|
const slides = this._cfg.gravity ? (step.drops ?? this.deriveDrops(step)) : [];
|
|
1826
1954
|
const anims = [];
|
|
1827
1955
|
for (const d of slides) {
|
|
@@ -1841,7 +1969,7 @@ class TumbleController {
|
|
|
1841
1969
|
cell.setState({ fresh: true });
|
|
1842
1970
|
cell.alpha = 1;
|
|
1843
1971
|
cell.scale.set(1);
|
|
1844
|
-
cell.position.set(home.x, home.y -
|
|
1972
|
+
cell.position.set(home.x, home.y - rowStepOf(n.col) * (this._grid.rowsOf(n.col) + 1));
|
|
1845
1973
|
const idx = (perCol[n.col] = (perCol[n.col] ?? 0) + 1);
|
|
1846
1974
|
anims.push((async () => {
|
|
1847
1975
|
await Tween.delay(idx * 28 * f);
|
|
@@ -1905,17 +2033,26 @@ class TumbleController {
|
|
|
1905
2033
|
function cellCenter(grid, col, row) {
|
|
1906
2034
|
return grid.cellPosition(col, row);
|
|
1907
2035
|
}
|
|
1908
|
-
|
|
1909
|
-
|
|
2036
|
+
/** Vertical row-to-row step for a reel (falls back to the reel's cell height for 1-row reels). */
|
|
2037
|
+
function rowStepOf(grid, col = 0) {
|
|
2038
|
+
if (grid.rowsOf(col) > 1)
|
|
2039
|
+
return grid.cellPosition(col, 1).y - grid.cellPosition(col, 0).y;
|
|
2040
|
+
return grid.cellSize(col).height;
|
|
2041
|
+
}
|
|
2042
|
+
/** Horizontal reel-to-reel step at a boundary (falls back to the reel's cell width for 1-col grids). */
|
|
2043
|
+
function colStepOf(grid, col = 0) {
|
|
2044
|
+
if (col + 1 < grid.cols)
|
|
2045
|
+
return grid.cellPosition(col + 1, 0).x - grid.cellPosition(col, 0).x;
|
|
2046
|
+
return grid.cellSize(col).width;
|
|
1910
2047
|
}
|
|
1911
2048
|
/** A glowing ring drawn around a cell. Returns a disposer. */
|
|
1912
2049
|
function glowRing(fx, grid, col, row, color) {
|
|
1913
2050
|
if (fx.destroyed)
|
|
1914
2051
|
return () => { };
|
|
1915
2052
|
const { x, y } = cellCenter(grid, col, row);
|
|
1916
|
-
const
|
|
2053
|
+
const { width, height } = grid.cellSize(col);
|
|
1917
2054
|
const g = new Graphics()
|
|
1918
|
-
.roundRect(x -
|
|
2055
|
+
.roundRect(x - width / 2, y - height / 2, width, height, 10)
|
|
1919
2056
|
.stroke({ color, width: 3, alpha: 0.9 });
|
|
1920
2057
|
fx.addChild(g);
|
|
1921
2058
|
return () => g.destroy();
|
|
@@ -1958,7 +2095,7 @@ async function dropCell(grid, cell, col, row, ms = 280) {
|
|
|
1958
2095
|
if (cell.destroyed)
|
|
1959
2096
|
return;
|
|
1960
2097
|
const home = grid.cellPosition(col, row);
|
|
1961
|
-
cell.position.set(home.x, home.y - rowStepOf(grid) * (row + 2));
|
|
2098
|
+
cell.position.set(home.x, home.y - rowStepOf(grid, col) * (row + 2));
|
|
1962
2099
|
cell.alpha = 1;
|
|
1963
2100
|
cell.scale.set(1);
|
|
1964
2101
|
await Tween.to(cell, { 'position.y': home.y }, ms, easingByName('easeOutBounce'));
|
|
@@ -2023,7 +2160,7 @@ const StickySymbols = {
|
|
|
2023
2160
|
glowRing(ctx.fx, ctx.grid, p.col, p.row, f.ringColor);
|
|
2024
2161
|
await pulseCell(cell, 1.2, 260);
|
|
2025
2162
|
const { x, y } = cellCenter(ctx.grid, p.col, p.row);
|
|
2026
|
-
await floatLabel(ctx.fx, x, y - ctx.grid.cellSize / 2, f.durationSpins ? `STICKY ${f.durationSpins}` : 'STICKY', f.ringColor, 600 + i * 50);
|
|
2163
|
+
await floatLabel(ctx.fx, x, y - ctx.grid.cellSize(p.col).height / 2, f.durationSpins ? `STICKY ${f.durationSpins}` : 'STICKY', f.ringColor, 600 + i * 50);
|
|
2027
2164
|
}));
|
|
2028
2165
|
},
|
|
2029
2166
|
};
|
|
@@ -2164,14 +2301,15 @@ const GiantSymbol = {
|
|
|
2164
2301
|
const view = ctx.resolve(sym);
|
|
2165
2302
|
if (!view)
|
|
2166
2303
|
return;
|
|
2167
|
-
const
|
|
2304
|
+
const cs = ctx.grid.cellSize(anchorCol);
|
|
2305
|
+
const stepX = colStepOf(ctx.grid, anchorCol);
|
|
2306
|
+
const step = rowStepOf(ctx.grid, anchorCol);
|
|
2168
2307
|
const tl = cellCenter(ctx.grid, anchorCol, 0);
|
|
2169
2308
|
const giant = new Container();
|
|
2170
2309
|
giant.addChild(view);
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
giant.position.set(tl.x + ((w - 1) * step) / 2, tl.y + ((h - 1) * step) / 2);
|
|
2310
|
+
// span w×h cells (footprint ignores gaps, matching the previous single-cell unit)
|
|
2311
|
+
view.resize?.({ width: cs.width * w, height: cs.height * h });
|
|
2312
|
+
giant.position.set(tl.x + ((w - 1) * stepX) / 2, tl.y + ((h - 1) * step) / 2);
|
|
2175
2313
|
// hide covered cells
|
|
2176
2314
|
for (let c = anchorCol; c < anchorCol + w; c++)
|
|
2177
2315
|
for (let r = 0; r < h; r++)
|
|
@@ -2203,8 +2341,9 @@ const SplitSymbol = {
|
|
|
2203
2341
|
await Promise.all(left.map(async (p, i) => {
|
|
2204
2342
|
await Tween.delay((i % 6) * 30);
|
|
2205
2343
|
const { x, y } = cellCenter(ctx.grid, p.col, p.row);
|
|
2344
|
+
const cs = ctx.grid.cellSize(p.col);
|
|
2206
2345
|
await pulseCell(ctx.grid.getCell(p.col, p.row), 1.12, 200);
|
|
2207
|
-
await floatLabel(ctx.fx, x +
|
|
2346
|
+
await floatLabel(ctx.fx, x + cs.width / 3, y - cs.height / 3, `×${f.factor}`, 0x9b5cff, 600);
|
|
2208
2347
|
}));
|
|
2209
2348
|
},
|
|
2210
2349
|
};
|
|
@@ -2257,8 +2396,7 @@ const MultiplierSymbols = {
|
|
|
2257
2396
|
const { x, y } = cellCenter(ctx.grid, p.col, p.row);
|
|
2258
2397
|
await floatLabel(ctx.fx, x, y, `×${values[spots.indexOf(p)]}`, 0xffd24a, 500);
|
|
2259
2398
|
}));
|
|
2260
|
-
const
|
|
2261
|
-
const cy = (ctx.grid.rows * ctx.grid.cellSize) / 2 - ctx.grid.cellSize / 2;
|
|
2399
|
+
const { x: cx, y: cy } = ctx.grid.center();
|
|
2262
2400
|
await floatLabel(ctx.fx, cx, cy, `×${capped}`, 0xffe24a, 900);
|
|
2263
2401
|
},
|
|
2264
2402
|
};
|
|
@@ -2464,7 +2602,12 @@ function createReelSystem(opts) {
|
|
|
2464
2602
|
rows: config.grid.rows,
|
|
2465
2603
|
rowsPerReel: config.grid.rowsPerReel ?? effectiveRowsPerReel(config.grid),
|
|
2466
2604
|
cellSize: config.grid.cellSize,
|
|
2605
|
+
cellWidth: config.grid.cellWidth,
|
|
2606
|
+
cellHeight: config.grid.cellHeight,
|
|
2607
|
+
cellSizePerReel: config.grid.cellSizePerReel,
|
|
2467
2608
|
gap: config.grid.gap,
|
|
2609
|
+
colGap: config.grid.colGap,
|
|
2610
|
+
rowGap: config.grid.rowGap,
|
|
2468
2611
|
resolve,
|
|
2469
2612
|
frameStyle: config.grid.frameStyle,
|
|
2470
2613
|
mask: config.grid.mask,
|
|
@@ -2485,8 +2628,13 @@ function createReelSystem(opts) {
|
|
|
2485
2628
|
return (a.cols !== b.cols ||
|
|
2486
2629
|
a.rows !== b.rows ||
|
|
2487
2630
|
a.cellSize !== b.cellSize ||
|
|
2631
|
+
a.cellWidth !== b.cellWidth ||
|
|
2632
|
+
a.cellHeight !== b.cellHeight ||
|
|
2488
2633
|
a.gap !== b.gap ||
|
|
2489
2634
|
a.mask !== b.mask ||
|
|
2635
|
+
JSON.stringify(a.cellSizePerReel) !== JSON.stringify(b.cellSizePerReel) ||
|
|
2636
|
+
JSON.stringify(a.colGap) !== JSON.stringify(b.colGap) ||
|
|
2637
|
+
JSON.stringify(a.rowGap) !== JSON.stringify(b.rowGap) ||
|
|
2490
2638
|
JSON.stringify(a.rowsPerReel) !== JSON.stringify(b.rowsPerReel) ||
|
|
2491
2639
|
(a.decoration?.padding ?? 0) !== (b.decoration?.padding ?? 0));
|
|
2492
2640
|
}
|
|
@@ -2776,5 +2924,5 @@ class MultiplierAccumulator {
|
|
|
2776
2924
|
}
|
|
2777
2925
|
}
|
|
2778
2926
|
|
|
2779
|
-
export { AnimatedSymbol, AnticipationController, BigWinOverlay, CascadeController, CountUpDisplay, DEFAULT_REEL_CONFIG, EASING_BY_NAME, FEATURES, FEATURE_KEYS, FEATURE_LIST, INTENSITY_SCALE, MultiplierAccumulator, PRESETS, PRESET_LIST, ReelGrid, ReelSpinController, SpinEngine, SymbolCell, TumbleController, createReelSystem, easingByName, effectiveRowsPerReel, mergeReelConfig, pickTier, resolveReelConfig, tierIndexAtValue, valueAt, waysCount };
|
|
2927
|
+
export { AnimatedSymbol, AnticipationController, BigWinOverlay, CascadeController, CountUpDisplay, DEFAULT_REEL_CONFIG, EASING_BY_NAME, FEATURES, FEATURE_KEYS, FEATURE_LIST, INTENSITY_SCALE, MultiplierAccumulator, PRESETS, PRESET_LIST, ReelGrid, ReelSpinController, SpinEngine, SymbolCell, TumbleController, cellPositionOf, createReelSystem, easingByName, effectiveRowsPerReel, mergeReelConfig, pickTier, resolveGeometry, resolveGridGeometry, resolveReelConfig, tierIndexAtValue, valueAt, waysCount };
|
|
2780
2928
|
//# sourceMappingURL=slot.esm.js.map
|