/* --- FILE: js/ai/agents/minimax-agent.js --- */
/**
* @fileoverview
* Agent für Minimax-Algorithmus mit Alpha-Beta Pruning.
* Berechnet optimale Züge durch baumartige Spielzustandssimulation.
*/
/**
* Agent, der Minimax nutzt.
* @class MinimaxAgent
* @extends Agent
*/
class MinimaxAgent extends Agent {
/**
* Erstellt einen neuen Minimax-Agenten.
* @param {Object} config - Konfigurationsobjekt.
* @param {string} [config.name="Minimax"] - Name des Agenten.
* @param {number} [config.maxDepth=3] - Suchtiefe.
* @param {boolean} [config.useAlphaBeta=true] - Ob Alpha-Beta genutzt werden soll.
* @param {function} [config.heuristicFn] - Bewertungsfunktion. Falls null, wird winLoss genutzt.
*/
constructor(config = {}) {
super(config.name || "Minimax KI");
/**
* Maximale Suchtiefe.
* @type {number}
*/
this.maxDepth = config.maxDepth || DEFAULT_MAX_DEPTH;
/**
* Flag für Alpha-Beta Pruning.
* @type {boolean}
*/
this.useAlphaBeta = config.useAlphaBeta !== false;
/**
* Die Bewertungsfunktion.
* @type {function(GameState, number): number}
*/
if (config.heuristicFn) {
this.heuristicFn = config.heuristicFn;
} else if (typeof HeuristicRegistry !== 'undefined' && HeuristicRegistry.has('generic', 'winLoss')) {
this.heuristicFn = HeuristicRegistry.get('generic', 'winLoss').evaluate.bind(
HeuristicRegistry.get('generic', 'winLoss')
);
} else {
this.heuristicFn = (gameState, player) => {
if (gameState.winner === player) return 1000;
if (gameState.winner !== NONE && gameState.winner !== DRAW) return -1000;
return 0;
};
}
/**
* Die verwendete Minimax-Engine.
* @type {MinimaxEngine}
*/
this.engine = new MinimaxEngine({
heuristicFn: this.heuristicFn,
maxDepth: this.maxDepth,
useAlphaBeta: this.useAlphaBeta,
captureTrace: false // Im Spielbetrieb brauchen wir kein Trace
});
}
/**
* Berechnet den besten Zug.
* @param {GameState} gameState - Der aktuelle Spielzustand.
* @returns {Object|null} Der berechnete Zug mit Begründung.
*/
getAction(gameState) {
// 🔴 NEURALGISCH: Agent-Aufruf (Phase 3)
DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, 'debug',
'MinimaxAgent.getAction() called', {
validMoves: gameState.getAllValidMoves ? gameState.getAllValidMoves().length : 0,
maxDepth: this.maxDepth,
useAlphaBeta: this.useAlphaBeta,
heuristicFn: this.heuristicFn.name || 'unknown'
});
// Safety: Wenn Spiel schon vorbei, null
if (gameState.winner !== NONE) {
DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, 'warn',
'MinimaxAgent: Game already over', { winner: gameState.winner });
return null;
}
if (gameState.getAllValidMoves().length === 0) {
DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, 'error',
'MinimaxAgent: No valid moves available', { boardState: 'blocked' });
return null;
}
// Engine starten
const result = this.engine.findBestMove(gameState);
if (result.move === null) {
// Fallback (sollte bei korrekter Engine nicht passieren, außer Spiel ist voll)
DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, 'error',
'MinimaxAgent: Engine returned no move', {
nodesVisited: result.nodesVisited,
boardState: 'blocked_or_error'
});
return null;
}
// 🔴 NEURALGISCH: Best Move Result (Phase 3)
DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, 'debug',
'MinimaxAgent: Best move found', {
move: result.move,
score: result.score,
nodesVisited: result.nodesVisited,
depthSearched: this.maxDepth
});
return {
move: result.move,
reason: `Score: ${result.score} (Tiefe ${this.maxDepth})`
};
}
}