feat(SCOPONE-0005): complete iteration 0 — AI mastery levels, score bar fix, difficulty selection
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import Phaser from 'phaser';
|
||||
import { Card, PlayerIndex, GameState } from '../game/types';
|
||||
import { Card, PlayerIndex, GameState, Difficulty } from '../game/types';
|
||||
import {
|
||||
createInitialState, applyMove, findCaptures, getScoreBreakdown, teamOf, calcPrimiera
|
||||
} from '../game/engine';
|
||||
import { chooseMove } from '../game/ai';
|
||||
import { CardTracker } from '../game/card-tracker';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Layout constants
|
||||
@@ -46,13 +47,20 @@ export class GameScene extends Phaser.Scene {
|
||||
private state!: GameState;
|
||||
private cardImages: Map<string, Phaser.GameObjects.Image> = new Map();
|
||||
|
||||
// Difficulty & card tracker
|
||||
private difficulty: Difficulty = 'advanced';
|
||||
private tracker: CardTracker = new CardTracker();
|
||||
|
||||
// Active player highlight
|
||||
private activeHighlightRect: Phaser.GameObjects.Graphics | null = null;
|
||||
|
||||
// Live score bar texts
|
||||
private hudA!: { scope: Phaser.GameObjects.Text; cards: Phaser.GameObjects.Text;
|
||||
denari: Phaser.GameObjects.Text; prim: Phaser.GameObjects.Text;
|
||||
total: Phaser.GameObjects.Text };
|
||||
denari: Phaser.GameObjects.Text; sette: Phaser.GameObjects.Text;
|
||||
prim: Phaser.GameObjects.Text; total: Phaser.GameObjects.Text };
|
||||
private hudB!: { scope: Phaser.GameObjects.Text; cards: Phaser.GameObjects.Text;
|
||||
denari: Phaser.GameObjects.Text; prim: Phaser.GameObjects.Text;
|
||||
total: Phaser.GameObjects.Text };
|
||||
denari: Phaser.GameObjects.Text; sette: Phaser.GameObjects.Text;
|
||||
prim: Phaser.GameObjects.Text; total: Phaser.GameObjects.Text };
|
||||
private roundText!: Phaser.GameObjects.Text;
|
||||
|
||||
// Status bar
|
||||
@@ -90,11 +98,15 @@ export class GameScene extends Phaser.Scene {
|
||||
// Create
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
create(): void {
|
||||
create(data?: { difficulty?: Difficulty }): void {
|
||||
const W = this.scale.width;
|
||||
const H = this.scale.height;
|
||||
this.tableCenter = { x: W / 2, y: (H + SCOREBAR_H) / 2 + 10 };
|
||||
|
||||
// Read difficulty from scene data (MenuScene passes it)
|
||||
this.difficulty = data?.difficulty ?? 'advanced';
|
||||
this.tracker = new CardTracker();
|
||||
|
||||
this.generateParticleTextures();
|
||||
this.drawBackground(W, H);
|
||||
this.buildScoreBar(W);
|
||||
@@ -245,12 +257,12 @@ export class GameScene extends Phaser.Scene {
|
||||
this.roundText = mkTxt(W / 2, SCOREBAR_H / 2, 'Mano 1', '#ffd700', '16px');
|
||||
|
||||
// Column headers (shared, centered-ish)
|
||||
const cols = ['Scope', 'Carte', 'Denari', 'Primiera', 'TOTALE'];
|
||||
const xA = [240, 320, 410, 510, 620];
|
||||
const xB = [W - 240, W - 320, W - 410, W - 510, W - 620];
|
||||
const cols = ['Scope', 'Carte', 'Denari', '7Bello', 'Primiera', 'TOTALE'];
|
||||
const xA = [230, 295, 370, 445, 520, 610];
|
||||
const xB = [W - 230, W - 295, W - 370, W - 445, W - 520, W - 610];
|
||||
|
||||
cols.forEach((_, i) => {
|
||||
const label = ['Sc', 'Ca', 'De', 'Pr', 'Pt'][i];
|
||||
const label = ['Sc', 'Ca', 'De', '7B', 'Pr', 'Pt'][i];
|
||||
this.add.text(xA[i], SCOREBAR_H * 0.28, label, {
|
||||
fontFamily: 'monospace', fontSize: '10px', color: '#999999', resolution: 2,
|
||||
}).setOrigin(0.5).setDepth(9);
|
||||
@@ -264,15 +276,15 @@ export class GameScene extends Phaser.Scene {
|
||||
const mkB = (xi: number) => mkTxt(xB[xi], SCOREBAR_H * 0.72, '0', '#ffaaaa', '17px');
|
||||
|
||||
this.hudA = {
|
||||
scope: mkA(0), cards: mkA(1), denari: mkA(2), prim: mkA(3),
|
||||
total: this.add.text(xA[4], SCOREBAR_H * 0.72, '0', {
|
||||
scope: mkA(0), cards: mkA(1), denari: mkA(2), sette: mkA(3), prim: mkA(4),
|
||||
total: this.add.text(xA[5], SCOREBAR_H * 0.72, '0', {
|
||||
fontFamily: 'Georgia, serif', fontSize: '20px', color: '#aaffaa',
|
||||
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', {
|
||||
scope: mkB(0), cards: mkB(1), denari: mkB(2), sette: mkB(3), prim: mkB(4),
|
||||
total: this.add.text(xB[5], SCOREBAR_H * 0.72, '0', {
|
||||
fontFamily: 'Georgia, serif', fontSize: '20px', color: '#ffaaaa',
|
||||
stroke: '#000', strokeThickness: 2, resolution: 2,
|
||||
}).setOrigin(0.5).setDepth(9),
|
||||
@@ -294,6 +306,8 @@ export class GameScene extends Phaser.Scene {
|
||||
const den1 = pile1.filter(c => c.suit === 'denara').length;
|
||||
const prim0 = calcPrimiera(pile0);
|
||||
const prim1 = calcPrimiera(pile1);
|
||||
const sette0 = pile0.some(c => c.suit === 'denara' && c.value === 7);
|
||||
const sette1 = pile1.some(c => c.suit === 'denara' && c.value === 7);
|
||||
|
||||
const setAnim = (txt: Phaser.GameObjects.Text, val: string | number) => {
|
||||
const v = String(val);
|
||||
@@ -305,12 +319,14 @@ export class GameScene extends Phaser.Scene {
|
||||
setAnim(this.hudA.scope, scope0);
|
||||
setAnim(this.hudA.cards, pile0.length);
|
||||
setAnim(this.hudA.denari, den0);
|
||||
setAnim(this.hudA.sette, sette0 ? '✓' : '–');
|
||||
setAnim(this.hudA.prim, prim0 > 0 ? prim0 : '-');
|
||||
setAnim(this.hudA.total, t0.totalPoints);
|
||||
|
||||
setAnim(this.hudB.scope, scope1);
|
||||
setAnim(this.hudB.cards, pile1.length);
|
||||
setAnim(this.hudB.denari, den1);
|
||||
setAnim(this.hudB.sette, sette1 ? '✓' : '–');
|
||||
setAnim(this.hudB.prim, prim1 > 0 ? prim1 : '-');
|
||||
setAnim(this.hudB.total, t1.totalPoints);
|
||||
|
||||
@@ -408,12 +424,37 @@ export class GameScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private pulseLabel(playerIdx: PlayerIndex): void {
|
||||
// Reset all
|
||||
// Reset all labels
|
||||
for (const [idx, lbl] of this.playerLabels) {
|
||||
lbl.setAlpha(idx === playerIdx ? 1 : 0.5);
|
||||
lbl.setAlpha(idx === playerIdx ? 1 : 0.4);
|
||||
}
|
||||
// Pulse active
|
||||
|
||||
// Remove old highlight
|
||||
if (this.activeHighlightRect) {
|
||||
this.activeHighlightRect.destroy();
|
||||
this.activeHighlightRect = null;
|
||||
}
|
||||
|
||||
// Draw glow rectangle behind active player label
|
||||
const lbl = this.playerLabels.get(playerIdx)!;
|
||||
const bounds = lbl.getBounds();
|
||||
const pad = 6;
|
||||
const color = teamOf(playerIdx) === 0 ? 0x00ff44 : 0xff4444;
|
||||
const gfx = this.add.graphics().setDepth(1);
|
||||
gfx.fillStyle(color, 0.25);
|
||||
gfx.fillRoundedRect(bounds.x - pad, bounds.y - pad, bounds.width + pad * 2, bounds.height + pad * 2, 6);
|
||||
gfx.lineStyle(2, color, 0.8);
|
||||
gfx.strokeRoundedRect(bounds.x - pad, bounds.y - pad, bounds.width + pad * 2, bounds.height + pad * 2, 6);
|
||||
this.activeHighlightRect = gfx;
|
||||
|
||||
// Pulse the glow
|
||||
this.tweens.add({
|
||||
targets: gfx,
|
||||
alpha: { from: 1, to: 0.4 },
|
||||
duration: 600, yoyo: true, repeat: -1, ease: 'Sine.InOut',
|
||||
});
|
||||
|
||||
// Pulse the label
|
||||
this.tweens.add({
|
||||
targets: lbl,
|
||||
scaleX: 1.2, scaleY: 1.2,
|
||||
@@ -548,7 +589,7 @@ export class GameScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private doAIMove(playerIdx: PlayerIndex): void {
|
||||
const move = chooseMove(this.state, playerIdx);
|
||||
const move = chooseMove(this.state, playerIdx, this.difficulty, this.tracker);
|
||||
this.aiThinking = false;
|
||||
this.executeMove(playerIdx, move.card, move.capture);
|
||||
}
|
||||
@@ -750,6 +791,12 @@ export class GameScene extends Phaser.Scene {
|
||||
const oldState = this.state;
|
||||
this.state = nextState;
|
||||
|
||||
// Update card tracker
|
||||
this.tracker.trackPlay(card);
|
||||
if (captureResult) {
|
||||
this.tracker.trackCapture(captureResult.captured);
|
||||
}
|
||||
|
||||
const cardImg = this.cardImages.get(card.id)!;
|
||||
cardImg.setDepth(15);
|
||||
|
||||
@@ -1318,6 +1365,7 @@ export class GameScene extends Phaser.Scene {
|
||||
const startingPlayer = ((nextRound - 1) % 4) as PlayerIndex;
|
||||
for (const img of this.cardImages.values()) img.destroy();
|
||||
this.cardImages.clear();
|
||||
this.tracker.reset();
|
||||
this.state = createInitialState(startingPlayer);
|
||||
this.state.teamScores[0].totalPoints = totals[0];
|
||||
this.state.teamScores[1].totalPoints = totals[1];
|
||||
|
||||
Reference in New Issue
Block a user