From 238c744e93c9e7321fd6232ead7adc0214e874fb Mon Sep 17 00:00:00 2001 From: Brian Fertig Date: Sun, 17 May 2026 14:44:45 -0600 Subject: [PATCH] refactor(phase10): manage staging buttons via dedicated lifecycle methods - Extract button creation and destruction into `_createStagingButtons` and `_destroyStagingButtons`. - Ensure buttons are properly destroyed when staging is cleared, submitted, or when turn conditions change. - Prevent memory leaks and stale UI state by centralizing button lifecycle management. - Add piece click sound effect in Backgammon on landing. --- public/src/games/backgammon/BackgammonGame.js | 5 ++- public/src/games/phase10/Phase10Game.js | 40 ++++++++++++++----- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/public/src/games/backgammon/BackgammonGame.js b/public/src/games/backgammon/BackgammonGame.js index 7a58043..8cd6f81 100644 --- a/public/src/games/backgammon/BackgammonGame.js +++ b/public/src/games/backgammon/BackgammonGame.js @@ -802,7 +802,10 @@ export default class BackgammonGame extends Phaser.Scene { container.x = to.x; container.y = to.y; // Squash-and-stretch on landing - this.tweens.add({ targets: container, scaleX: 1.3, scaleY: 0.7, duration: 60, yoyo: true, ease: 'Quad.easeOut', onComplete }); + this.tweens.add({ targets: container, scaleX: 1.3, scaleY: 0.7, duration: 60, yoyo: true, ease: 'Quad.easeOut', onComplete: () => { + playSound(this, SFX.PIECE_CLICK); + onComplete(); + }}); }, }); } diff --git a/public/src/games/phase10/Phase10Game.js b/public/src/games/phase10/Phase10Game.js index 3c9c955..71db75e 100644 --- a/public/src/games/phase10/Phase10Game.js +++ b/public/src/games/phase10/Phase10Game.js @@ -152,6 +152,8 @@ export default class Phase10Game extends Phaser.Scene { this.dragState = null; this.potentialDrag = null; this.stagingGroups = null; + this.submitBtn = null; + this.clearBtn = null; } create() { @@ -761,16 +763,6 @@ export default class Phase10Game extends Phaser.Scene { cx += gw + GROUP_GAP; } - const btnX = panelX + panelW + 160; - const submitBtn = new Button(this, btnX, laidStart.y - 18, 'Submit Phase', () => this.submitStagingLaydown(), { - width: 200, height: 44, fontSize: 18, - }).setDepth(D.ui); - this.transientObjs.push(submitBtn); - - const clearBtn = new Button(this, btnX, laidStart.y + 34, 'Clear', () => this.clearStagingLaydown(), { - variant: 'ghost', width: 200, height: 34, fontSize: 14, - }).setDepth(D.ui); - this.transientObjs.push(clearBtn); } submitStagingLaydown() { @@ -798,6 +790,7 @@ export default class Phase10Game extends Phaser.Scene { return; } this.stagingGroups = null; + this._destroyStagingButtons(); this.gs = next; this.selectedHandIdx = null; this.clearHighlights(); @@ -954,6 +947,7 @@ export default class Phase10Game extends Phaser.Scene { this.matchOver = false; if (this.dragState) this.endCardDragImmediate(); this.stagingGroups = null; + this._destroyStagingButtons(); this.clearAllCardObjs(); this.clearHighlights(); this.selectedHandIdx = null; @@ -1241,6 +1235,7 @@ export default class Phase10Game extends Phaser.Scene { const eligible = this.isLocalTurn() && this.gs.drawnThisTurn && !player.laidDown; if (!eligible) { this.stagingGroups = null; + this._destroyStagingButtons(); return; } if (!this.stagingGroups) { @@ -1251,9 +1246,34 @@ export default class Phase10Game extends Phaser.Scene { count: g.count, slots: new Array(g.count).fill(null), })); + this._createStagingButtons(); } } + _createStagingButtons() { + this._destroyStagingButtons(); + const { laidStart } = slotLayout('bottom'); + let totalW = 0; + for (let i = 0; i < this.stagingGroups.length; i++) { + if (i > 0) totalW += GROUP_GAP; + totalW += this.stagingGroups[i].count * (LAIDOWN_CARD_W * 0.75) + (LAIDOWN_CARD_W * 0.25); + } + const panelX = laidStart.x - 14; + const panelW = totalW + 28; + const btnX = panelX + panelW + 160; + this.submitBtn = new Button(this, btnX, laidStart.y - 18, 'Submit Phase', () => this.submitStagingLaydown(), { + width: 200, height: 44, fontSize: 18, + }).setDepth(D.ui); + this.clearBtn = new Button(this, btnX, laidStart.y + 34, 'Clear', () => this.clearStagingLaydown(), { + variant: 'ghost', width: 200, height: 34, fontSize: 14, + }).setDepth(D.ui); + } + + _destroyStagingButtons() { + if (this.submitBtn) { this.submitBtn.destroy(); this.submitBtn = null; } + if (this.clearBtn) { this.clearBtn.destroy(); this.clearBtn = null; } + } + // ── Local input ───────────────────────────────────────────────────────── isLocalTurn() {