feat(catan): add visual and audio feedback for card theft by robber

- Detect when the robber steals a resource from player 0 by comparing resource counts before and after the move.
- Display a temporary "CARD STOLEN" banner with the stolen resource name and robber's name.
- Play a lose sound effect to provide immediate audio feedback.
- Apply this notification logic consistently across all three locations where the robber is moved (AI turn, player turn, and initial setup).
This commit is contained in:
Brian Fertig 2026-05-23 17:34:55 -06:00
parent 67500636cb
commit 58c4edf8fd
1 changed files with 30 additions and 0 deletions

View File

@ -1389,7 +1389,12 @@ export default class CatanGame extends Phaser.Scene {
this.renderAll(); await this.delay(400); this.renderAll(); await this.delay(400);
const m = AI.chooseRobberMove(this.gs, seat); const m = AI.chooseRobberMove(this.gs, seat);
const preRobberHex = this.gs.robberHex; const preRobberHex = this.gs.robberHex;
const preRes0roll = { ...this.gs.players[0].resources };
this.gs = L.moveRobber(this.gs, m.hexId, m.targetSeat); this.gs = L.moveRobber(this.gs, m.hexId, m.targetSeat);
if (m.targetSeat === 0) {
const stolen = RESOURCE_TYPES.find(r => this.gs.players[0].resources[r] < preRes0roll[r]);
if (stolen) this._notifyStolenCard(stolen, this.pname(seat));
}
await this.animateRobber(preRobberHex, m.hexId); await this.animateRobber(preRobberHex, m.hexId);
this.renderAll(); await this.delay(400); this.renderAll(); await this.delay(400);
} }
@ -1412,9 +1417,14 @@ export default class CatanGame extends Phaser.Scene {
await this.delay(450); await this.delay(450);
const m = AI.chooseRobberMove(this.gs, seat); const m = AI.chooseRobberMove(this.gs, seat);
const preRobberHex = this.gs.robberHex; const preRobberHex = this.gs.robberHex;
const preRes0 = { ...this.gs.players[0].resources };
this.gs = L.moveRobber(this.gs, m.hexId, m.targetSeat); this.gs = L.moveRobber(this.gs, m.hexId, m.targetSeat);
const animPromise = this.animateRobber(preRobberHex, m.hexId); const animPromise = this.animateRobber(preRobberHex, m.hexId);
if (m.targetSeat != null) this.opponentPortraits[seat]?.playEmotion?.('happy'); if (m.targetSeat != null) this.opponentPortraits[seat]?.playEmotion?.('happy');
if (m.targetSeat === 0) {
const stolen = RESOURCE_TYPES.find(r => this.gs.players[0].resources[r] < preRes0[r]);
if (stolen) this._notifyStolenCard(stolen, this.pname(seat));
}
await animPromise; await animPromise;
this.renderAll(); this.renderAll();
await this.delay(450); await this.delay(450);
@ -1444,7 +1454,12 @@ export default class CatanGame extends Phaser.Scene {
if (this.gs.phase === 'moveRobber') { if (this.gs.phase === 'moveRobber') {
const m = AI.chooseRobberMove(this.gs, seat); const m = AI.chooseRobberMove(this.gs, seat);
const preRobberHex = this.gs.robberHex; const preRobberHex = this.gs.robberHex;
const preRes0action = { ...this.gs.players[0].resources };
this.gs = L.moveRobber(this.gs, m.hexId, m.targetSeat); this.gs = L.moveRobber(this.gs, m.hexId, m.targetSeat);
if (m.targetSeat === 0) {
const stolen = RESOURCE_TYPES.find(r => this.gs.players[0].resources[r] < preRes0action[r]);
if (stolen) this._notifyStolenCard(stolen, this.pname(seat));
}
await this.animateRobber(preRobberHex, m.hexId); await this.animateRobber(preRobberHex, m.hexId);
} }
this.renderAll(); this.renderAll();
@ -1779,6 +1794,21 @@ export default class CatanGame extends Phaser.Scene {
this.statusText.setText(msg); this.statusText.setText(msg);
} }
_notifyStolenCard(resource, robberName) {
playSound(this, SFX.CASINO_LOSE);
const label = (RESOURCE_INFO[resource]?.label ?? resource).toUpperCase();
const txt = this.add.text(GAME_WIDTH / 2, 855,
`CARD STOLEN: ${label} BY ${robberName.toUpperCase()}`, {
fontFamily: 'Righteous', fontSize: '38px', color: '#ffd700',
stroke: '#000000', strokeThickness: 5,
}
).setOrigin(0.5).setDepth(D.banner + 2);
this.tweens.add({
targets: txt, alpha: 0, delay: 3000, duration: 500,
onComplete: () => txt.destroy(),
});
}
showTurnBanner(text) { showTurnBanner(text) {
const banner = this.add.text(1000, 120, text, { const banner = this.add.text(1000, 120, text, {
fontFamily: 'Righteous', fontSize: '34px', color: COLORS.textHex, fontFamily: 'Righteous', fontSize: '34px', color: COLORS.textHex,