feat(SCOPONE-0008): complete iteration 0 improve ai rules
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import Phaser from 'phaser';
|
||||
import { Card, PlayerIndex, GameState, Difficulty } from '../game/types';
|
||||
import {
|
||||
createInitialState, applyMove, findCaptures, getScoreBreakdown, teamOf, calcPrimiera
|
||||
createInitialState, applyMove, findCaptures, getScoreBreakdown, teamOf, calcPrimiera, getMatchOutcome
|
||||
} from '../game/engine';
|
||||
import { chooseMove } from '../game/ai';
|
||||
import { chooseMove, AIDecisionProgress } from '../game/ai';
|
||||
import { CardTracker } from '../game/card-tracker';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -25,8 +25,6 @@ const CH_H = 645 * CARD_SCALE_HUMAN; // card height for human ≈ 106
|
||||
const CW_A = 402 * CARD_SCALE_AI; // card width for AI ≈ 50
|
||||
const CH_A = 645 * CARD_SCALE_AI; // card height for AI ≈ 81
|
||||
|
||||
const AI_DELAY = 1100; // ms — think bar fills over this time
|
||||
|
||||
// Scorebar height at top
|
||||
const SCOREBAR_H = 54;
|
||||
|
||||
@@ -76,8 +74,6 @@ export class GameScene extends Phaser.Scene {
|
||||
|
||||
// Think bar
|
||||
private thinkBar!: Phaser.GameObjects.Graphics;
|
||||
private thinkTween: Phaser.Tweens.Tween | null = null;
|
||||
private thinkProgress = 0;
|
||||
|
||||
// Player label containers (pulsed on active turn)
|
||||
private playerLabels: Map<PlayerIndex, Phaser.GameObjects.Text> = new Map();
|
||||
@@ -128,7 +124,8 @@ export class GameScene extends Phaser.Scene {
|
||||
|
||||
this.input.once('pointerdown', () => this.startMusic());
|
||||
|
||||
this.state = createInitialState();
|
||||
const startingPlayer = Phaser.Math.Between(0, 3) as PlayerIndex;
|
||||
this.state = createInitialState(startingPlayer);
|
||||
this.dealAnimation(() => {
|
||||
this.updateScoreBar();
|
||||
this.nextTurn();
|
||||
@@ -374,39 +371,34 @@ export class GameScene extends Phaser.Scene {
|
||||
this.thinkBar = this.add.graphics().setDepth(11).setVisible(false);
|
||||
}
|
||||
|
||||
private showThinkBar(playerIdx: PlayerIndex): void {
|
||||
this.thinkProgress = 0;
|
||||
private showThinkBar(playerIdx: PlayerIndex, remainingRatio = 1): void {
|
||||
this.thinkBar.setVisible(true);
|
||||
this.thinkTween?.stop();
|
||||
this.drawThinkBar(playerIdx, remainingRatio);
|
||||
}
|
||||
|
||||
private updateThinkBar(playerIdx: PlayerIndex, progress: AIDecisionProgress): void {
|
||||
this.drawThinkBar(playerIdx, 1 - progress.progress);
|
||||
}
|
||||
|
||||
private drawThinkBar(playerIdx: PlayerIndex, remainingRatio: number): void {
|
||||
const W = this.scale.width;
|
||||
const tg = this.thinkBar;
|
||||
const color = (playerIdx === 0 || playerIdx === 2) ? 0x44ff88 : 0xff5555;
|
||||
const clampedRatio = Phaser.Math.Clamp(remainingRatio, 0, 1);
|
||||
const width = clampedRatio * W;
|
||||
|
||||
const tweenTarget = { v: 0 };
|
||||
this.thinkTween = this.tweens.add({
|
||||
targets: tweenTarget,
|
||||
v: 1,
|
||||
duration: AI_DELAY - 80,
|
||||
ease: 'Linear',
|
||||
onUpdate: () => {
|
||||
tg.clear();
|
||||
const w = tweenTarget.v * W;
|
||||
tg.fillStyle(0x000000, 0.4);
|
||||
tg.fillRect(0, SCOREBAR_H, W, 4);
|
||||
tg.fillStyle(color, 0.85);
|
||||
tg.fillRect(0, SCOREBAR_H, w, 4);
|
||||
// Glow tip
|
||||
tg.fillStyle(0xffffff, 0.6);
|
||||
tg.fillRect(w - 6, SCOREBAR_H, 6, 4);
|
||||
},
|
||||
onComplete: () => { tg.clear(); tg.setVisible(false); },
|
||||
});
|
||||
tg.clear();
|
||||
tg.fillStyle(0x000000, 0.4);
|
||||
tg.fillRect(0, SCOREBAR_H, W, 4);
|
||||
if (width <= 0) return;
|
||||
|
||||
tg.fillStyle(color, 0.85);
|
||||
tg.fillRect(0, SCOREBAR_H, width, 4);
|
||||
tg.fillStyle(0xffffff, 0.6);
|
||||
tg.fillRect(Math.max(0, width - 6), SCOREBAR_H, Math.min(6, width), 4);
|
||||
}
|
||||
|
||||
private hideThinkBar(): void {
|
||||
this.thinkTween?.stop();
|
||||
this.thinkTween = null;
|
||||
this.thinkBar.clear();
|
||||
this.thinkBar.setVisible(false);
|
||||
}
|
||||
@@ -602,21 +594,47 @@ export class GameScene extends Phaser.Scene {
|
||||
this.pulseLabel(cur);
|
||||
|
||||
if (player.isHuman) {
|
||||
this.hideThinkBar();
|
||||
this.enableHumanInteraction();
|
||||
} else {
|
||||
this.aiThinking = true;
|
||||
this.showThinkBar(cur);
|
||||
this.time.delayedCall(AI_DELAY, () => {
|
||||
this.hideThinkBar();
|
||||
this.doAIMove(cur);
|
||||
});
|
||||
this.showThinkBar(cur, 1);
|
||||
void this.doAIMove(cur);
|
||||
}
|
||||
}
|
||||
|
||||
private doAIMove(playerIdx: PlayerIndex): void {
|
||||
const move = chooseMove(this.state, playerIdx, this.difficulty, this.tracker);
|
||||
this.aiThinking = false;
|
||||
this.executeMove(playerIdx, move.card, move.capture);
|
||||
private async doAIMove(playerIdx: PlayerIndex): Promise<void> {
|
||||
const turnState = this.state;
|
||||
|
||||
try {
|
||||
const move = await chooseMove(
|
||||
this.state,
|
||||
playerIdx,
|
||||
this.difficulty,
|
||||
this.tracker,
|
||||
(progress) => {
|
||||
if (!this.scene.isActive('GameScene') || this.state !== turnState) return;
|
||||
this.updateThinkBar(playerIdx, progress);
|
||||
}
|
||||
);
|
||||
|
||||
if (!this.scene.isActive('GameScene')) return;
|
||||
if (this.state !== turnState || this.state.currentPlayer !== playerIdx || this.state.roundOver) return;
|
||||
|
||||
this.hideThinkBar();
|
||||
this.aiThinking = false;
|
||||
this.executeMove(playerIdx, move.card, move.capture);
|
||||
} catch (error) {
|
||||
console.error('AI move failed', error);
|
||||
if (this.scene.isActive('GameScene') && this.state === turnState) {
|
||||
this.setStatus('Errore durante la mossa AI');
|
||||
}
|
||||
} finally {
|
||||
if (this.scene.isActive('GameScene') && this.state === turnState) {
|
||||
this.hideThinkBar();
|
||||
this.aiThinking = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1316,7 +1334,8 @@ export class GameScene extends Phaser.Scene {
|
||||
}).setOrigin(0.5).setDepth(32);
|
||||
});
|
||||
|
||||
const gameOver = t0.totalPoints >= 11 || t1.totalPoints >= 11;
|
||||
const outcome = getMatchOutcome(this.state.teamScores);
|
||||
const gameOver = !outcome.continueMatch;
|
||||
const btnLabel = gameOver ? 'Fine Partita' : 'Prossima Mano';
|
||||
|
||||
const btnG = this.add.graphics().setDepth(32);
|
||||
@@ -1344,7 +1363,8 @@ export class GameScene extends Phaser.Scene {
|
||||
const H = this.scale.height;
|
||||
const t0 = this.state.teamScores[0];
|
||||
const t1 = this.state.teamScores[1];
|
||||
const win = t0.totalPoints >= t1.totalPoints;
|
||||
const outcome = getMatchOutcome(this.state.teamScores);
|
||||
const win = outcome.winner === 0;
|
||||
this.stopMusic();
|
||||
|
||||
// Victory confetti
|
||||
@@ -1392,11 +1412,13 @@ export class GameScene extends Phaser.Scene {
|
||||
private startNewRound(): void {
|
||||
const totals = this.state.teamScores.map(t => t.totalPoints);
|
||||
const nextRound = (this.state.roundNumber ?? 1) + 1;
|
||||
const startingPlayer = ((nextRound - 1) % 4) as PlayerIndex;
|
||||
const matchStartingPlayer = this.state.matchStartingPlayer;
|
||||
const startingPlayer = ((matchStartingPlayer + 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.matchStartingPlayer = matchStartingPlayer;
|
||||
this.state.teamScores[0].totalPoints = totals[0];
|
||||
this.state.teamScores[1].totalPoints = totals[1];
|
||||
this.state.roundNumber = nextRound;
|
||||
|
||||
Reference in New Issue
Block a user