**feat(triominoes): animate drawn tiles from pool to player portrait**

Add `animateDrawTile()` to tween a face-down triangle from the pool area to the drawing player's portrait over 1.2 seconds. Integrates the animation into both the human draw flow (`onPoolClick`) and the AI draw loop (`runAITurn`), ensuring visual feedback before the game state updates and the hand refreshes.
This commit is contained in:
Brian Fertig 2026-06-07 19:05:04 -06:00
parent a231d821ca
commit 74d5470d11
1 changed files with 51 additions and 4 deletions

View File

@ -527,6 +527,49 @@ export default class TriominoesGame extends Phaser.Scene {
} }
} }
// ─── Draw animation ─────────────────────────────────────────────────────
// Animate a face-down triangle from the pool to the player's portrait.
// Returns a promise that resolves when the animation completes.
animateDrawTile(seat) {
return new Promise((resolve) => {
const portrait = this.portraitCtrls[seat];
if (!portrait) { resolve(); return; }
const px = portrait.x;
const py = portrait.y;
const poolX = POOL_X;
const poolY = POOL_Y;
const size = HAND_BASE * 0.45;
const h = Math.round(size * 0.8660254);
const triPts = [[0, -h * 2 / 3], [size / 2, h / 3], [-size / 2, h / 3]];
const container = this.add.container(poolX, poolY).setDepth(DEPTH.drag);
const gfx = this.add.graphics();
// Face-down: dark fill, subtle border, small center dot.
gfx.fillStyle(COLORS.panel, 1);
gfx.fillTriangle(triPts[0][0], triPts[0][1], triPts[1][0], triPts[1][1], triPts[2][0], triPts[2][1]);
gfx.lineStyle(2, COLORS.accent, 0.9);
gfx.strokeTriangle(triPts[0][0], triPts[0][1], triPts[1][0], triPts[1][1], triPts[2][0], triPts[2][1]);
gfx.fillStyle(COLORS.accent, 0.35);
gfx.fillCircle(0, 0, Math.max(3, size * 0.06));
container.add(gfx);
container.setScale(1).setAngle(0);
this.tweens.add({
targets: container,
x: px,
y: py,
duration: 1200,
ease: 'Cubic.easeInOut',
onComplete: () => {
container.destroy();
resolve();
},
});
});
}
// ─── AI tile animation ────────────────────────────────────────────────── // ─── AI tile animation ──────────────────────────────────────────────────
// Create a small triangle at the opponent's portrait and tween it to the // Create a small triangle at the opponent's portrait and tween it to the
// board cell over 1.2 s, rotating and scaling as it goes. Returns a promise // board cell over 1.2 s, rotating and scaling as it goes. Returns a promise
@ -625,10 +668,13 @@ export default class TriominoesGame extends Phaser.Scene {
if (!this._poolActive) return; if (!this._poolActive) return;
this.showPoolClickable(false); this.showPoolClickable(false);
playSound(this, SFX.CARD_DEAL); playSound(this, SFX.CARD_DEAL);
// Animate tile from pool → player's portrait.
this.animateDrawTile(0).then(() => {
this.gs = drawTile(this.gs); this.gs = drawTile(this.gs);
this.refresh(); this.refresh();
// Re-evaluate: a drawn tile may now be playable. // Re-evaluate: a drawn tile may now be playable.
this.time.delayedCall(260, () => this.beginHumanTurn()); this.time.delayedCall(260, () => this.beginHumanTurn());
});
} }
showPoolClickable(on) { showPoolClickable(on) {
@ -656,6 +702,7 @@ export default class TriominoesGame extends Phaser.Scene {
if (canDraw(this.gs) && this.gs.players[seat].draws < MAX_DRAWS) { if (canDraw(this.gs) && this.gs.players[seat].draws < MAX_DRAWS) {
await this.delay(360); await this.delay(360);
playSound(this, SFX.CARD_DEAL); playSound(this, SFX.CARD_DEAL);
await this.animateDrawTile(seat);
this.gs = drawTile(this.gs); this.gs = drawTile(this.gs);
this.refresh(); this.refresh();
continue; continue;