feat: enhance Craps roll results with sequential badges and fireworks

Adds animated "Win!" or "Lose" badges that appear sequentially for each player after a roll, accompanied by sound effects. Introduces a colorful fireworks particle animation for wins. Adjusts dice landing positions for better visibility and removes redundant emotion/sound calls in favor of the new sequential result display.
This commit is contained in:
Brian Fertig 2026-05-21 00:12:02 -06:00
parent 327d3d7019
commit f8d406ab4e
1 changed files with 81 additions and 9 deletions

View File

@ -259,7 +259,7 @@ export default class CrapsGame extends Phaser.Scene {
const ox = SEAT_X[this.gs.shooterIndex]; const ox = SEAT_X[this.gs.shooterIndex];
const oy = SEAT_Y - 60; const oy = SEAT_Y - 60;
return Promise.all(this.diceGfx.map((g, i) => return Promise.all(this.diceGfx.map((g, i) =>
this.throwOneDie(g, finalDice[i], ox + (i ? 34 : -34), oy, i))); this.throwOneDie(g, finalDice[i], ox + (i ? 44 : -44), oy, i)));
} }
throwOneDie(g, face, ox, oy, idx) { throwOneDie(g, face, ox, oy, idx) {
@ -270,7 +270,7 @@ export default class CrapsGame extends Phaser.Scene {
const wallX = TABLE.x + TABLE.w - 38 + (Math.random() * 24 - 12); // upper-right corner wall const wallX = TABLE.x + TABLE.w - 38 + (Math.random() * 24 - 12); // upper-right corner wall
const wallY = TABLE.y + 44 + Math.random() * 22; const wallY = TABLE.y + 44 + Math.random() * 22;
const landX = LAND.x + (idx ? 32 : -32) + (Math.random() * 20 - 10); const landX = LAND.x + (idx ? 50 : -50) + (Math.random() * 14 - 7);
const landY = LAND.y + (Math.random() * 24 - 12); const landY = LAND.y + (Math.random() * 24 - 12);
const outMs = 320 + idx * 28; const outMs = 320 + idx * 28;
const backMs = 540 + idx * 44; const backMs = 540 + idx * 44;
@ -696,16 +696,12 @@ export default class CrapsGame extends Phaser.Scene {
if (res.pointEstablished) this.movePuck(res.pointEstablished); if (res.pointEstablished) this.movePuck(res.pointEstablished);
else if (res.newComeOut) this.movePuck(null); else if (res.newComeOut) this.movePuck(null);
// Animate per-player net outcome. // Chip animations fire immediately for all players.
for (let i = 0; i < this.gs.players.length; i++) { for (let i = 0; i < this.gs.players.length; i++) {
const delta = this.gs.players[i].lastDelta; const delta = this.gs.players[i].lastDelta;
if (delta > 0) { this.animateChips(i, true, delta); this.portraits[i]?.playEmotion?.('happy'); } if (delta > 0) this.animateChips(i, true, delta);
else if (delta < 0) { this.animateChips(i, false, -delta); this.portraits[i]?.playEmotion?.('upset'); } else if (delta < 0) this.animateChips(i, false, -delta);
} }
const humanDelta = this.gs.players[0].lastDelta;
if (humanDelta > 0) playSound(this, SFX.CASINO_WIN);
else if (humanDelta < 0) playSound(this, SFX.CASINO_LOSE);
this.setStatus(this.outcomeMessage(res, total)); this.setStatus(this.outcomeMessage(res, total));
this.renderHumanBets(); this.renderHumanBets();
this.renderAiWagers(); this.renderAiWagers();
@ -713,6 +709,9 @@ export default class CrapsGame extends Phaser.Scene {
this.updateBalances(); this.updateBalances();
this.persistChips(); this.persistChips();
// Win/lose badges + emotions play sequentially left to right.
this.showRollResults();
this.time.delayedCall(1400, () => { this.time.delayedCall(1400, () => {
this.animating = false; this.animating = false;
this.diceGfx.forEach((g) => g.setVisible(true)); // leave dice resting on the felt this.diceGfx.forEach((g) => g.setVisible(true)); // leave dice resting on the felt
@ -752,6 +751,79 @@ export default class CrapsGame extends Phaser.Scene {
return `Rolled ${total}.`; return `Rolled ${total}.`;
} }
// ── Win / lose result badges (sequential left → right) ────────────────────
showRollResults() {
const entries = this.gs.players
.map((p, i) => ({ i, delta: p.lastDelta }))
.filter(e => e.delta !== 0)
.sort((a, b) => SEAT_X[a.i] - SEAT_X[b.i]);
entries.forEach(({ i, delta }, idx) => {
this.time.delayedCall(idx * 420, () => {
this.portraits[i]?.playEmotion?.(delta > 0 ? 'happy' : 'upset');
this.animatePlayerResult(i, delta);
});
});
}
animatePlayerResult(pi, delta) {
const isWin = delta > 0;
const x = SEAT_X[pi];
const textY = SEAT_Y - PORTRAIT_R - 58;
const badge = this.add.text(x, textY, isWin ? 'Win!' : 'Lose', {
fontFamily: '"Julius Sans One"',
fontSize: isWin ? '52px' : '44px',
color: isWin ? '#f5d020' : '#e05c5c',
fontStyle: 'bold',
stroke: '#000000', strokeThickness: 5,
shadow: isWin ? { offsetX: 0, offsetY: 0, color: '#f5d020', blur: 24, fill: true } : undefined,
}).setOrigin(0.5).setAlpha(0).setScale(1.4).setDepth(D.modal);
this.tweens.add({ targets: badge, alpha: 1, scaleX: 1, scaleY: 1, duration: 200, ease: 'Back.Out' });
playSound(this, isWin ? SFX.CASINO_WIN : SFX.CASINO_LOSE);
if (isWin) this.animateFireworks(x, textY);
this.time.delayedCall(isWin ? 1200 : 900, () => {
this.tweens.add({
targets: badge, alpha: 0, y: textY - 30,
duration: 350, ease: 'Power2',
onComplete: () => badge.destroy(),
});
});
}
animateFireworks(cx, cy) {
const palette = [0xf5d020, 0xff8c00, 0x5cb85c, 0x4a90d9, 0xff69b4, 0xffffff, 0x00e5ff];
for (let burst = 0; burst < 3; burst++) {
this.time.delayedCall(burst * 380, () => {
if (!this.scene.isActive('CrapsGame')) return;
const bx = cx + (Math.random() - 0.5) * 140;
const by = cy - 10 + (Math.random() - 0.5) * 80;
for (let i = 0; i < 10; i++) {
const angle = (i / 10) * Math.PI * 2 + Math.random() * 0.3;
const dist = 65 + Math.random() * 65;
const color = palette[Math.floor(Math.random() * palette.length)];
const dot = this.add.graphics().setDepth(D.modal + 5);
dot.fillStyle(color, 1);
dot.fillCircle(0, 0, 4 + Math.random() * 3);
dot.x = bx; dot.y = by;
this.tweens.add({
targets: dot,
x: bx + Math.cos(angle) * dist,
y: by + Math.sin(angle) * dist,
alpha: 0, scaleX: 0.1, scaleY: 0.1,
duration: 700 + Math.random() * 400,
delay: Math.random() * 100,
ease: 'Power2',
onComplete: () => dot.destroy(),
});
}
});
}
}
// ── Chip win/loss animation (aggregated per player) ───────────────────────── // ── Chip win/loss animation (aggregated per player) ─────────────────────────
animateChips(playerIndex, toPlayer, amount) { animateChips(playerIndex, toPlayer, amount) {
const seatX = SEAT_X[playerIndex]; const seatX = SEAT_X[playerIndex];