fix(SCOPONE-0011): complete iteration 0 - tune ai and ui

This commit is contained in:
Giancarmine Salucci
2026-04-10 23:29:51 +02:00
parent 45dfbf65e5
commit 38f675eda5
5 changed files with 275 additions and 53 deletions

View File

@@ -37,10 +37,11 @@ const CH_A = 645 * CARD_SCALE_AI; // card height for AI ≈ 81
const SCOREBAR_H = 54;
const AI_MIN_THINK_MS = 1000;
const MOVE_OUTCOME_STATUS_MS = 2000;
const PLAYED_CARD_TRAVEL_MS = 400;
const CAPTURE_COLLAPSE_MS = 480;
const PLAYED_CARD_TRAVEL_MS = 320;
const CAPTURE_COLLAPSE_MS = 360;
const CAPTURE_COLLAPSE_DELAY_MS = 60;
const NON_CAPTURE_TABLE_TWEEN_MS = 560;
const NON_CAPTURE_TABLE_TWEEN_MS = 420;
const RELAYOUT_TWEEN_MS = 120;
// Player positions:
// 0 = South (human, bottom), 1 = West (AI, left, rotated -90°)
@@ -93,7 +94,7 @@ export class GameScene extends Phaser.Scene {
private thinkBar!: Phaser.GameObjects.Graphics;
// Player label containers (pulsed on active turn)
private playerLabels: Map<PlayerIndex, Phaser.GameObjects.Text> = new Map();
private playerLabels: Map<PlayerIndex, Phaser.GameObjects.Container> = new Map();
// Interaction state
private selectedCard: Card | null = null;
@@ -388,6 +389,7 @@ export class GameScene extends Phaser.Scene {
this.statusText = this.add.text(W / 2, H - CH_H - 36, '', {
fontFamily: 'serif', fontSize: '17px', color: '#ffffff',
stroke: '#000', strokeThickness: 2,
resolution: 2,
}).setOrigin(0.5).setDepth(10);
}
@@ -524,23 +526,59 @@ export class GameScene extends Phaser.Scene {
return `Problema durante la mossa di ${this.state.players[playerIdx].name}.`;
}
private withHiResText(
style: Phaser.Types.GameObjects.Text.TextStyle,
): Phaser.Types.GameObjects.Text.TextStyle {
return {
...style,
resolution: style.resolution ?? 2,
};
}
private createPlayerNameplate(
x: number,
y: number,
text: string,
color: string,
fillColor: number,
strokeColor: number,
): Phaser.GameObjects.Container {
const label = this.add.text(0, 0, text, this.withHiResText({
fontFamily: 'serif',
fontSize: '11px',
color,
stroke: '#000',
strokeThickness: 1,
align: 'center',
})).setOrigin(0.5);
const padX = 12;
const padY = 4;
const width = label.width + padX * 2;
const height = label.height + padY * 2;
const background = this.add.graphics();
background.fillStyle(fillColor, 0.92);
background.fillRoundedRect(-width / 2, -height / 2, width, height, 9);
background.lineStyle(1, strokeColor, 0.7);
background.strokeRoundedRect(-width / 2, -height / 2, width, height, 9);
return this.add.container(x, y, [background, label]).setDepth(18);
}
// ---------------------------------------------------------------------------
// Player labels (pulse on active turn)
// ---------------------------------------------------------------------------
private buildPlayerLabels(W: number, H: number): void {
const defs: Array<{ idx: PlayerIndex; x: number; y: number; color: string;
txt: string; originX: number; originY: number }> = [
{ idx: 0, x: W / 2, y: H - CH_H - 28, color: '#aaffaa', txt: 'Tu [Team A]', originX: 0.5, originY: 1 },
{ idx: 1, x: CH_A + 20, y: H / 2 + SCOREBAR_H / 2, color: '#ffaaaa', txt: 'AI\nOvest\n[B]', originX: 0, originY: 0.5 },
{ idx: 2, x: W / 2, y: SCOREBAR_H + CH_A + 44, color: '#aaffaa', txt: 'Compagno [Team A]', originX: 0.5, originY: 0 },
{ idx: 3, x: W - CH_A - 20, y: H / 2 + SCOREBAR_H / 2, color: '#ffaaaa', txt: 'AI\nEst\n[B]', originX: 1, originY: 0.5 },
fillColor: number; strokeColor: number; txt: string }> = [
{ idx: 0, x: W / 2, y: H - 9, color: '#dfffe5', fillColor: 0x0b2410, strokeColor: 0x4aa86a, txt: 'Tu [Team A]' },
{ idx: 1, x: CH_A + 58, y: H / 2 + SCOREBAR_H / 2, color: '#ffe0e0', fillColor: 0x260d0d, strokeColor: 0xb36a6a, txt: 'AI Ovest [B]' },
{ idx: 2, x: W / 2, y: SCOREBAR_H + 11, color: '#dfffe5', fillColor: 0x0b2410, strokeColor: 0x4aa86a, txt: 'Compagno [Team A]' },
{ idx: 3, x: W - CH_A - 58, y: H / 2 + SCOREBAR_H / 2, color: '#ffe0e0', fillColor: 0x260d0d, strokeColor: 0xb36a6a, txt: 'AI Est [B]' },
];
for (const d of defs) {
const lbl = this.add.text(d.x, d.y, d.txt, {
fontFamily: 'serif', fontSize: '12px', color: d.color,
stroke: '#000', strokeThickness: 1, align: 'center', resolution: 2,
}).setOrigin(d.originX, d.originY).setDepth(2);
const lbl = this.createPlayerNameplate(d.x, d.y, d.txt, d.color, d.fillColor, d.strokeColor);
this.playerLabels.set(d.idx, lbl);
}
}
@@ -594,7 +632,7 @@ export class GameScene extends Phaser.Scene {
const activeLbl = this.playerLabels.get(playerIdx)!;
this.tweens.add({
targets: activeLbl,
scaleX: 1.2, scaleY: 1.2,
scaleX: 1.08, scaleY: 1.08,
duration: 300, yoyo: true, ease: 'Sine.InOut',
});
}
@@ -929,6 +967,7 @@ export class GameScene extends Phaser.Scene {
const btn = this.add.zone(W / 2, y, 360, 28).setInteractive({ useHandCursor: true }).setDepth(21);
const txt = this.add.text(W / 2, y, `Prendi: ${label}`, {
fontFamily: 'serif', fontSize: '14px', color: color.text,
resolution: 2,
}).setOrigin(0.5).setDepth(21);
btn.on('pointerdown', () => this.confirmMove(this.selectedCard!, cap));
(bg as any)._captureBtn = true;
@@ -1113,7 +1152,15 @@ export class GameScene extends Phaser.Scene {
const positions = this.getHandPositions(playerIdx, hand.length);
hand.forEach((card, i) => {
const img = this.cardImages.get(card.id);
if (img) this.tweens.add({ targets: img, x: positions[i].x, y: positions[i].y, duration: 160 });
if (img) {
this.tweens.add({
targets: img,
x: positions[i].x,
y: positions[i].y,
duration: RELAYOUT_TWEEN_MS,
ease: 'Cubic.Out',
});
}
});
}
@@ -1123,7 +1170,15 @@ export class GameScene extends Phaser.Scene {
const positions = this.getTablePositions(table.length);
table.forEach((card, i) => {
const img = this.cardImages.get(card.id);
if (img?.visible) this.tweens.add({ targets: img, x: positions[i].x, y: positions[i].y, duration: 160 });
if (img?.visible) {
this.tweens.add({
targets: img,
x: positions[i].x,
y: positions[i].y,
duration: RELAYOUT_TWEEN_MS,
ease: 'Cubic.Out',
});
}
});
}
@@ -1196,12 +1251,14 @@ export class GameScene extends Phaser.Scene {
const txt = this.add.text(W / 2, H / 2, 'SCOPA!', {
fontFamily: 'Georgia, serif', fontSize: '108px', color: '#ffd700',
stroke: '#000000', strokeThickness: 10,
resolution: 2,
}).setOrigin(0.5).setDepth(50).setAlpha(0).setScale(0.2);
const sub = this.add.text(W / 2, H / 2 + 110, player.name, {
fontFamily: 'serif', fontSize: '32px',
color: isTeamA ? '#aaffaa' : '#ffaaaa',
stroke: '#000', strokeThickness: 3,
resolution: 2,
}).setOrigin(0.5).setDepth(50).setAlpha(0);
this.tweens.add({
@@ -1257,6 +1314,7 @@ export class GameScene extends Phaser.Scene {
const txt = this.add.text(W / 2, H / 2 - 60, '7♦', {
fontFamily: 'Georgia, serif', fontSize: '64px', color: '#ffd700',
stroke: '#000', strokeThickness: 7,
resolution: 2,
}).setOrigin(0.5).setDepth(50).setAlpha(0).setScale(0.4);
this.tweens.add({
@@ -1497,10 +1555,11 @@ export class GameScene extends Phaser.Scene {
];
lines.forEach(([line, color], i) => {
this.add.text(W / 2, H / 2 - 190 + i * 34, line, {
this.add.text(W / 2, H / 2 - 190 + i * 34, line, this.withHiResText({
fontFamily: i === 0 ? 'Georgia, serif' : 'monospace',
fontSize: i === 0 ? '28px' : '16px', color,
}).setOrigin(0.5).setDepth(32);
fontSize: i === 0 ? '28px' : '16px',
color,
})).setOrigin(0.5).setDepth(32);
});
const outcome = getMatchOutcome(this.state.teamScores);
@@ -1514,6 +1573,7 @@ export class GameScene extends Phaser.Scene {
.setInteractive({ useHandCursor: true }).setDepth(33);
this.add.text(W / 2, H / 2 + 207, btnLabel, {
fontFamily: 'Georgia, serif', fontSize: '20px', color: '#0a2e10',
resolution: 2,
}).setOrigin(0.5).setDepth(34);
btnG.on('pointerover', () => { btnG.clear(); btnG.fillStyle(0xffec6e, 1); btnG.fillRoundedRect(W / 2 - 110, H / 2 + 185, 220, 44, 10); });
@@ -1556,13 +1616,16 @@ export class GameScene extends Phaser.Scene {
this.add.text(W / 2, H / 2 - 110, 'PARTITA CONCLUSA', {
fontFamily: 'Georgia, serif', fontSize: '44px', color: '#ffd700',
stroke: '#000', strokeThickness: 6,
resolution: 2,
}).setOrigin(0.5).setDepth(42);
this.add.text(W / 2, H / 2 - 30, win ? 'Vince la tua squadra' : 'Vincono gli avversari', {
fontFamily: 'serif', fontSize: '26px',
color: win ? '#aaffaa' : '#ffaaaa',
resolution: 2,
}).setOrigin(0.5).setDepth(42);
this.add.text(W / 2, H / 2 + 35, `${t0.totalPoints}${t1.totalPoints}`, {
fontFamily: 'Georgia, serif', fontSize: '50px', color: '#ffd700',
resolution: 2,
}).setOrigin(0.5).setDepth(42);
const bz = this.add.zone(W / 2, H / 2 + 115, 230, 48)
@@ -1572,6 +1635,7 @@ export class GameScene extends Phaser.Scene {
drawBtn(0xffd700);
this.add.text(W / 2, H / 2 + 115, 'NUOVA PARTITA', {
fontFamily: 'Georgia, serif', fontSize: '21px', color: '#0a2e10',
resolution: 2,
}).setOrigin(0.5).setDepth(44);
bz.on('pointerover', () => drawBtn(0xffec6e));
bz.on('pointerout', () => drawBtn(0xffd700));