refactor: overhaul 2048 merge animation and swap sound effects

- Replace PIECE_CLICK with alternating MASTERMIND_GLITCH sounds
- Refactor merge animation: redraw all tiles before merge phase instead
  of skipping merged positions
- Hide spawn tile early to prevent flash before spawn animation
- Simplify merge destination setup by removing redundant alpha reset
- Extract runMergePhase callback for cleaner merge/no-merge flow
This commit is contained in:
Brian Fertig 2026-06-14 12:55:14 -06:00
parent 4b78bf047f
commit a8e21a3bc6
1 changed files with 28 additions and 23 deletions

View File

@ -35,11 +35,12 @@ export default class Game2048 extends Phaser.Scene {
this.tileGraphics = [];
this.tileTexts = [];
this.busy = false;
this.gameOver = false;
this.wonAlready = false;
this.score = 0;
this.best = 0;
this.busy = false;
this.gameOver = false;
this.wonAlready = false;
this.score = 0;
this.best = 0;
this._sfxToggle = 0;
this.scoreValueText = null;
this.bestValueText = null;
@ -416,7 +417,8 @@ export default class Game2048 extends Phaser.Scene {
if (!newState) return;
this.busy = true;
playSound(this, SFX.PIECE_CLICK);
playSound(this, this._sfxToggle ? SFX.MASTERMIND_GLITCH_2 : SFX.MASTERMIND_GLITCH_1);
this._sfxToggle ^= 1;
this._animateMove(this.state, newState, () => {
this.state = newState;
@ -500,7 +502,7 @@ export default class Game2048 extends Phaser.Scene {
return;
}
// Build map: merge destination index → moving source containers currently at that position
// Build map: merge destination → moving source containers sitting at that position
const mergeSources = new Map();
for (const mv of movingEntries) {
if (!mergeSet.has(mv.toIdx)) continue;
@ -509,33 +511,35 @@ export default class Game2048 extends Phaser.Scene {
mergeSources.get(mv.toIdx).push({ cont: this.tileContainers[fr][fc], fromIdx: mv.fromIdx });
}
// Immediately update all tiles that aren't part of a merge transition
const skipIndices = new Set(newState.mergedAt);
for (const srcs of mergeSources.values()) {
for (const { fromIdx } of srcs) skipIndices.add(fromIdx);
}
this._redrawTilesExcept(newState, skipIndices);
// Count total source containers across all merges
let totalSrcs = 0;
for (const srcs of mergeSources.values()) totalSrcs += srcs.length;
if (totalSrcs === 0) {
// Restore every tile to its canonical position/value, then hand off to
// _showMergedTiles which overrides scale=0 on the merge destinations so
// the grow animation runs from nothing. Hiding the spawn tile here
// prevents it flashing visible before spawnPhase animates it in.
const runMergePhase = () => {
this._redrawAllTiles(newState);
if (newState.spawned !== null) {
const sr = Math.floor(newState.spawned / 4), sc = newState.spawned % 4;
this.tileContainers[sr][sc].setAlpha(0);
}
this._showMergedTiles(newState, spawnPhase);
return;
}
};
// Scale down all source containers simultaneously (they're at the merge position)
if (totalSrcs === 0) { runMergePhase(); return; }
// Scale down all merge-source containers simultaneously (they're at the merge position)
let scaledDown = 0;
const onAllScaledDown = () => {
// Reset every source to its home cell (invisible) before growing merged tiles
// Reset source containers to their home cells before the full redraw
for (const [, srcs] of mergeSources) {
for (const { cont, fromIdx } of srcs) {
const { cx, cy } = this._cellCenter(Math.floor(fromIdx / 4), fromIdx % 4);
cont.setAlpha(0).setScale(1).setPosition(cx, cy);
}
}
this._showMergedTiles(newState, spawnPhase);
runMergePhase();
};
for (const [, srcs] of mergeSources) {
@ -577,9 +581,10 @@ export default class Game2048 extends Phaser.Scene {
for (const idx of newState.mergedAt) {
const tr = Math.floor(idx / 4), tc = idx % 4;
const destCont = this.tileContainers[tr][tc];
this._paintTile(tr, tc, newState.grid[idx]);
// _redrawAllTiles already painted this tile at alpha=1 scale=1;
// collapse scale to 0 so the grow tween starts from nothing.
this._scorePopup(destCont.x, destCont.y - 40, newState.grid[idx]);
destCont.setAlpha(1).setScale(0);
destCont.setScale(0);
this.tweens.add({
targets: destCont, scaleX: 1.2, scaleY: 1.2,
duration: 200, ease: 'Back.easeOut',