fix(SCOPONE-0004): improve UI text readability and score bar contrast

- MenuScene: add resolution: 2 to all text styles for crisp rendering
- GameScene score bar: solid opaque background, brighter column headers (#999999), visible gold separator (alpha 0.7), resolution: 2
- GameScene player labels: reposition to viewport edges (South bottom, West left, East right), North after cards near table
- Add resolution: 2 to all player label text styles
This commit is contained in:
Giancarmine Salucci
2026-03-31 21:34:18 +02:00
parent 54a55b9269
commit 6c01044c71
3 changed files with 33 additions and 16 deletions

View File

@@ -87,6 +87,18 @@ The `findCaptures()` in `engine.ts` correctly implements:
- Sum subsets via power set enumeration ✓
- `applyMove()` auto-captures when possible ✓
### SCOPONE-0004: Phaser Text Resolution for Crisp Rendering (2026-03-31)
**Source**: Phaser 3 API docs (Context7 — `/websites/phaser_io_api-documentation`)
Phaser `Text` objects render to an internal Canvas. When the game uses `Scale.FIT` (scaling 1280×720 to a larger display), the text canvas is rasterized at 1× and then upscaled by CSS, causing blur.
**Fix**: Set the `resolution` property on each `TextStyle` or call `text.setResolution(value)`. A value of `2` doubles the internal canvas size, producing sharper text on high-DPI and scaled displays at the cost of more memory. The style property `resolution: 0` (default) uses the game's resolution (defaults to 1).
**Recommended**: Use `resolution: 2` in all text style objects for critical UI text (menu, score bar, labels). Avoid higher values to limit memory impact.
**Available since**: Phaser 3.12.0 (current project uses ^3.87.0).
### SCOPONE-0003: findCaptures() Change Analysis (2026-03-31)
**Current behavior** (engine.ts lines 43-62): When direct matches exist, `findCaptures()` bundles ALL into one `Card[]` and returns immediately (`results.push([...directMatches]); return results`). Sum captures are never computed when direct matches exist.

View File

@@ -222,23 +222,23 @@ export class GameScene extends Phaser.Scene {
private buildScoreBar(W: number): void {
// [trueref: fillRoundedRect] Panel background
const bg = this.add.graphics().setDepth(8);
bg.fillStyle(0x050e07, 0.92);
bg.fillStyle(0x050e07);
bg.fillRect(0, 0, W, SCOREBAR_H);
bg.lineStyle(1, 0xffd700, 0.35);
bg.lineStyle(1, 0xffd700, 0.7);
bg.lineBetween(0, SCOREBAR_H, W, SCOREBAR_H);
const mkTxt = (x: number, y: number, val: string, color = '#ffffff', size = '15px') =>
this.add.text(x, y, val, { fontFamily: 'monospace', fontSize: size, color })
this.add.text(x, y, val, { fontFamily: 'monospace', fontSize: size, color, resolution: 2 })
.setOrigin(0.5).setDepth(9);
// Left side — Team A
this.add.text(10, SCOREBAR_H / 2, 'TEAM A (Tu + Compagno)', {
fontFamily: 'serif', fontSize: '13px', color: '#aaffaa',
fontFamily: 'serif', fontSize: '13px', color: '#aaffaa', resolution: 2,
}).setOrigin(0, 0.5).setDepth(9);
// Right side — Team B
this.add.text(W - 10, SCOREBAR_H / 2, 'TEAM B (AI Ovest + AI Est)', {
fontFamily: 'serif', fontSize: '13px', color: '#ffaaaa',
fontFamily: 'serif', fontSize: '13px', color: '#ffaaaa', resolution: 2,
}).setOrigin(1, 0.5).setDepth(9);
// Center — Round
@@ -252,10 +252,10 @@ export class GameScene extends Phaser.Scene {
cols.forEach((_, i) => {
const label = ['Sc', 'Ca', 'De', 'Pr', 'Pt'][i];
this.add.text(xA[i], SCOREBAR_H * 0.28, label, {
fontFamily: 'monospace', fontSize: '10px', color: '#666666',
fontFamily: 'monospace', fontSize: '10px', color: '#999999', resolution: 2,
}).setOrigin(0.5).setDepth(9);
this.add.text(xB[i], SCOREBAR_H * 0.28, label, {
fontFamily: 'monospace', fontSize: '10px', color: '#666666',
fontFamily: 'monospace', fontSize: '10px', color: '#999999', resolution: 2,
}).setOrigin(0.5).setDepth(9);
});
@@ -267,14 +267,14 @@ export class GameScene extends Phaser.Scene {
scope: mkA(0), cards: mkA(1), denari: mkA(2), prim: mkA(3),
total: this.add.text(xA[4], SCOREBAR_H * 0.72, '0', {
fontFamily: 'Georgia, serif', fontSize: '20px', color: '#aaffaa',
stroke: '#000', strokeThickness: 2,
stroke: '#000', strokeThickness: 2, resolution: 2,
}).setOrigin(0.5).setDepth(9),
};
this.hudB = {
scope: mkB(0), cards: mkB(1), denari: mkB(2), prim: mkB(3),
total: this.add.text(xB[4], SCOREBAR_H * 0.72, '0', {
fontFamily: 'Georgia, serif', fontSize: '20px', color: '#ffaaaa',
stroke: '#000', strokeThickness: 2,
stroke: '#000', strokeThickness: 2, resolution: 2,
}).setOrigin(0.5).setDepth(9),
};
}
@@ -391,17 +391,18 @@ export class GameScene extends Phaser.Scene {
// ---------------------------------------------------------------------------
private buildPlayerLabels(W: number, H: number): void {
const defs: Array<{ idx: PlayerIndex; x: number; y: number; color: string; txt: string }> = [
{ idx: 0, x: W / 2, y: H - CH_H - 56, color: '#aaffaa', txt: 'Tu [Team A]' },
{ idx: 1, x: CH_A + 14, y: H / 2 + SCOREBAR_H / 2 - 60, color: '#ffaaaa', txt: 'AI\nOvest\n[B]' },
{ idx: 2, x: W / 2, y: SCOREBAR_H + 18, color: '#aaffaa', txt: 'Compagno [Team A]' },
{ idx: 3, x: W - CH_A - 14, y: H / 2 + SCOREBAR_H / 2 - 60, color: '#ffaaaa', txt: 'AI\nEst\n[B]' },
const defs: Array<{ idx: PlayerIndex; x: number; y: number; color: string;
txt: string; originX: number; originY: number }> = [
{ idx: 0, x: W / 2, y: H - 8, color: '#aaffaa', txt: 'Tu [Team A]', originX: 0.5, originY: 1 },
{ idx: 1, x: 6, 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 + 34, color: '#aaffaa', txt: 'Compagno [Team A]', originX: 0.5, originY: 0 },
{ idx: 3, x: W - 6, y: H / 2 + SCOREBAR_H / 2, color: '#ffaaaa', txt: 'AI\nEst\n[B]', originX: 1, originY: 0.5 },
];
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',
}).setOrigin(0.5).setDepth(2);
stroke: '#000', strokeThickness: 1, align: 'center', resolution: 2,
}).setOrigin(d.originX, d.originY).setDepth(2);
this.playerLabels.set(d.idx, lbl);
}
}

View File

@@ -19,12 +19,14 @@ export class MenuScene extends Phaser.Scene {
color: '#ffd700',
stroke: '#000000',
strokeThickness: 4,
resolution: 2,
}).setOrigin(0.5);
this.add.text(W / 2, H * 0.32, '2 vs 2 · Tu + Compagno vs 2 AI', {
fontFamily: 'serif',
fontSize: '22px',
color: '#ccffcc',
resolution: 2,
}).setOrigin(0.5);
// Rules summary
@@ -39,6 +41,7 @@ export class MenuScene extends Phaser.Scene {
fontFamily: 'serif',
fontSize: '18px',
color: '#ffffff',
resolution: 2,
}).setOrigin(0.5);
});
@@ -49,6 +52,7 @@ export class MenuScene extends Phaser.Scene {
fontFamily: 'Georgia, serif',
fontSize: '22px',
color: '#1a5c2a',
resolution: 2,
}).setOrigin(0.5);
btn.on('pointerover', () => btn.setFillStyle(0xffec6e));