perf(ai): optimize AI decision speed — fast clone, reduced search, time budget

SCOPONE-0005 iteration 8

- Replace JSON.parse/stringify deepClone with hand-written cloneState() (~10-50x faster)
- Export cloneState from engine.ts, use in applyMove and generateSamples
- Master: reduce samples 24→10 (14 endgame), depth 8→6 (8 endgame)
- Add 1.5s time budget with early termination in masterMove and alphaBeta
- evaluateFast(): single-pass pile scan, zero array allocations at leaf nodes
- Simplified move ordering in alphaBeta (captures-first, no per-move eval)
- Skip countScopaThreats when tableSum >= 11 (impossible to clear)
- Remove unused calcPrimiera import from ai.ts
This commit is contained in:
Giancarmine Salucci
2026-03-31 23:34:22 +02:00
parent a045efd798
commit 185f7c36c7
2 changed files with 147 additions and 79 deletions

View File

@@ -126,7 +126,7 @@ export function applyMove(
card: Card,
captureChoice?: Card[]
): { nextState: GameState; capture: Capture | null; isScopa: boolean } {
const state2 = deepClone(state);
const state2 = cloneState(state);
const player = state2.players[playerIdx];
// Remove card from hand
@@ -294,6 +294,43 @@ export function getScoreBreakdown(state: GameState): ScoreBreakdown {
// Helpers
// ---------------------------------------------------------------------------
function cloneCard(c: Card): Card {
return { suit: c.suit, value: c.value, id: c.id };
}
function clonePlayer(p: Player): Player {
return {
index: p.index,
hand: p.hand.map(cloneCard),
pile: p.pile.map(cloneCard),
scope: p.scope,
isHuman: p.isHuman,
name: p.name,
};
}
function cloneTeamScore(ts: TeamScore): TeamScore {
return { ...ts };
}
export function cloneState(state: GameState): GameState {
return {
players: [
clonePlayer(state.players[0]),
clonePlayer(state.players[1]),
clonePlayer(state.players[2]),
clonePlayer(state.players[3]),
],
table: state.table.map(cloneCard),
currentPlayer: state.currentPlayer,
roundOver: state.roundOver,
gameOver: state.gameOver,
teamScores: [cloneTeamScore(state.teamScores[0]), cloneTeamScore(state.teamScores[1])],
lastCapturTeam: state.lastCapturTeam,
roundNumber: state.roundNumber,
};
}
function deepClone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}