ai/agents/agent-profiles.js

/**
 * @fileoverview Vorkonfigurierte Agent-Profile für die Arena.
 * Definiert verschiedene Konfigurationen für MiniMax, RuleBased und Random Agenten.
 * @author Alexander Wolf
 */

/**
 * Suchtiefe für einfaches Profil
 * @constant {number}
 */
const PROFILE_DEPTH_EASY = 2;
/**
 * Suchtiefe für mittleres Profil
 * @constant {number}
 */
const PROFILE_DEPTH_MEDIUM = 3;
/**
 * Suchtiefe für schweres Profil
 * @constant {number}
 */
const PROFILE_DEPTH_HARD = 4;

const AgentProfiles = {
    // ============ MINIMAX PROFILE ============

    minimaxCautious: {
        name: "Minimax (Vorsichtig)",
        description: "Minimax mit Suchtiefe 2 - schnell aber weniger optimal",
        type: "minimax",
        config: {
            maxDepth: PROFILE_DEPTH_EASY,
            useAlphaBeta: true,
            heuristic: "winLoss"
        }
    },

    minimaxBalanced: {
        name: "Minimax (Ausgewogen)",
        description: "Minimax mit Suchtiefe 3 - gutes Gleichgewicht",
        type: "minimax",
        config: {
            maxDepth: PROFILE_DEPTH_MEDIUM,
            useAlphaBeta: true,
            heuristic: "winLoss"
        }
    },

    minimaxAggressive: {
        name: "Minimax (Aggressiv)",
        description: "Minimax mit Suchtiefe 4 - sehr stark aber langsam",
        type: "minimax",
        config: {
            maxDepth: PROFILE_DEPTH_HARD,
            useAlphaBeta: true,
            heuristic: "winLoss"
        }
    },

    minimaxHeuristicCentered: {
        name: "Minimax (Zentraler Fokus)",
        description: "Minimax Tiefe 3 mit Zentralitäts-Heuristik",
        type: "minimax",
        config: {
            maxDepth: PROFILE_DEPTH_MEDIUM,
            useAlphaBeta: true,
            heuristic: "centerControl"
        }
    },

    minimaxHeuristicMobility: {
        name: "Minimax (Mobilität)",
        description: "Minimax Tiefe 3 mit Beweglichkeits-Heuristik",
        type: "minimax",
        config: {
            maxDepth: PROFILE_DEPTH_MEDIUM,
            useAlphaBeta: true,
            heuristic: "mobility"
        }
    },

    // ============ RULE-BASED PROFILE ============

    ruleBasedConservative: {
        name: "Regel-KI (Konservativ)",
        description: "Defensive Regelstruktur mit Fokus auf Blockieren",
        type: "ruleBased",
        config: {
            strategy: "defensive"
        }
    },

    ruleBasedAggressive: {
        name: "Regel-KI (Offensiv)",
        description: "Aggressive Regelstruktur mit Fokus auf Gewinnen",
        type: "ruleBased",
        config: {
            strategy: "offensive"
        }
    },

    ruleBasedBalanced: {
        name: "Regel-KI (Ausgewogen)",
        description: "Ausgewogene Regelstruktur - offensiv und defensiv",
        type: "ruleBased",
        config: {
            strategy: "balanced"
        }
    },

    // ============ RANDOM PROFILE ============

    random: {
        name: "Zufalls-KI",
        description: "Wählt komplett zufällig - Baseline für Vergleiche",
        type: "random",
        config: {}
    },

    // ============ SPEZIELLE VERGLEICHE ============

    minimaxDebugger: {
        name: "Minimax (Debugger)",
        description: "Minimax mit Trace-Ausgabe (nur für Analyze)",
        type: "minimax",
        config: {
            maxDepth: PROFILE_DEPTH_EASY,
            useAlphaBeta: true,
            heuristic: "winLoss",
            captureTrace: true
        }
    }
};

/**
 * Factory-Funktion zum Erstellen von Agenten aus Profilen.
 * @param {string} profileKey - Schlüssel aus AgentProfiles
 * @param {boolean} [customConfig=false] - Ob benutzerdefinierte Konfiguration erlaubt ist
 * @returns {Agent|null} Der erstellte Agent oder null
 */
function createAgentFromProfile(profileKey, customConfig = null) {
    if (!profileKey) {
        DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, "error", "createAgentFromProfile: profileKey ist undefined/null");
        return null;
    }

    const profile = AgentProfiles[profileKey];
    if (!profile) {
        DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, "error", `createAgentFromProfile: Profile nicht gefunden: ${profileKey}`);
        DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, "error", "Verfügbare Profile:", Object.keys(AgentProfiles));
        return null;
    }

    const config = customConfig ? { ...profile.config, ...customConfig } : profile.config;

    DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, "debug", `[createAgentFromProfile] Erstelle Agent vom Typ: ${profile.type}, Config:`, config);

    let agent = null;

    switch (profile.type) {
        case "minimax":
            agent = createMinimaxAgent(profile.name, config);
            break;

        case "ruleBased":
            agent = createRuleBasedAgent(profile.name, config);
            break;

        case "random":
            agent = new RandomAgent(profile.name);
            break;

        default:
            DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, "error", `createAgentFromProfile: Unbekannter Agent-Typ: ${profile.type}`);
            return null;
    }

    if (!agent) {
        DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, "error", `createAgentFromProfile: Agent-Erstellung fehlgeschlagen für ${profileKey}`);
        return null;
    }

    DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, "debug", `✓ Agent erstellt: ${agent.name} (Typ: ${agent.constructor.name})`);
    return agent;
}

/**
 * Erstellt einen Minimax-Agenten mit gegebener Konfiguration.
 * @private
 */
function createMinimaxAgent(name, config) {
    try {
        let heuristicFn = (gameState, player) => {
            if (gameState.winner === player) return 1000;
            if (gameState.winner !== NONE && gameState.winner !== DRAW) return -1000;
            return 0;
        };

        if (typeof HeuristicRegistry !== 'undefined' && HeuristicRegistry.has('generic', 'winLoss')) {
            heuristicFn = HeuristicRegistry.get('generic', 'winLoss').evaluate.bind(
                HeuristicRegistry.get('generic', 'winLoss')
            );
        }

        // Heuristik-Funktion auswählen
        if (config.heuristic) {
            if (typeof HeuristicRegistry !== 'undefined') {
                const mapping = {
                    winLoss: ['generic', 'winLoss'],
                    regularTTT: ['ttt', 'regular'],
                    ttt3d: ['ttt', '3d'],
                    threeDTTT: ['ttt', '3d'],
                    ultimateTTT: ['ttt', 'ultimate'],
                    connect4: ['connect4', 'regular'],
                    connect43d: ['connect4', '3d']
                };

                const mapped = mapping[config.heuristic];
                if (mapped && HeuristicRegistry.has(mapped[0], mapped[1])) {
                    heuristicFn = HeuristicRegistry.get(mapped[0], mapped[1]).evaluate.bind(
                        HeuristicRegistry.get(mapped[0], mapped[1])
                    );
                }
            }

        }

        const agent = new MinimaxAgent({
            name: name,
            maxDepth: config.maxDepth || PROFILE_DEPTH_MEDIUM,
            useAlphaBeta: config.useAlphaBeta !== false,
            heuristicFn: heuristicFn,
            captureTrace: config.captureTrace || false
        });

        if (!agent) {
            DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, "error", "createMinimaxAgent: MinimaxAgent-Konstruktor returned null");
            return null;
        }

        DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, "debug", `  ✓ MinimaxAgent erstellt: ${agent.name}, Depth=${config.maxDepth}, Heuristic=${config.heuristic}`);
        return agent;
    } catch (error) {
        DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, "error", "createMinimaxAgent: Fehler:", error.message);
        DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, "error", "Stack:", error.stack);
        return null;
    }
}

/**
 * Erstellt einen Regel-Agenten mit gegebener Konfiguration.
 * @private
 */
function createRuleBasedAgent(name, config) {
    try {
        // Verschiedene Regelstrukturen bauen - basierend auf existierender TTTRulesLibrary
        let tree;

        switch (config.strategy) {
            case "offensive":
                // Offensive: Priorität auf Gewinnen
                tree = new RuleGroup(name || "Regel-KI (Offensiv)");
                tree.add(TTTRulesLibrary.basics.win);
                if (TTTRulesLibrary.regular) {
                    tree.add(TTTRulesLibrary.regular.fork);
                    tree.add(TTTRulesLibrary.regular.blockFork);
                    tree.add(TTTRulesLibrary.regular.center);
                }
                tree.add(TTTRulesLibrary.basics.block);
                tree.add(TTTRulesLibrary.basics.random);
                break;

            case "defensive":
                // Defensive: Priorität auf Blocken
                tree = new RuleGroup(name || "Regel-KI (Konservativ)");
                tree.add(TTTRulesLibrary.basics.block);
                tree.add(TTTRulesLibrary.basics.win);
                if (TTTRulesLibrary.regular) {
                    tree.add(TTTRulesLibrary.regular.blockFork);
                    tree.add(TTTRulesLibrary.regular.corner);
                }
                tree.add(TTTRulesLibrary.basics.random);
                break;

            case "balanced":
            default:
                // Balanced: Gemischte Strategien
                tree = new RuleGroup(name || "Regel-KI (Ausgewogen)");
                tree.add(TTTRulesLibrary.basics.win);
                tree.add(TTTRulesLibrary.basics.block);
                if (TTTRulesLibrary.regular) {
                    tree.add(TTTRulesLibrary.regular.fork);
                    tree.add(TTTRulesLibrary.regular.blockFork);
                    tree.add(TTTRulesLibrary.regular.center);
                    tree.add(TTTRulesLibrary.regular.corner);
                }
                tree.add(TTTRulesLibrary.basics.random);
                break;
        }

        const agent = new RuleBasedAgent(tree);
        if (!agent) {
            DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, "error", "createRuleBasedAgent: RuleBasedAgent-Konstruktor returned null");
            return null;
        }

        DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, "debug", `  ✓ RuleBasedAgent erstellt: ${agent.name}, Strategy=${config.strategy}`);
        return agent;
    } catch (error) {
        DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, "error", "createRuleBasedAgent: Fehler:", error.message);
        DebugConfig.log(DEBUG_DOMAINS.AI_AGENTS, "error", "Stack:", error.stack);
        return null;
    }
}

/**
 * Gibt alle verfügbaren Profile als Array zurück.
 * @returns {Array<Object>}
 */
function getAvailableProfiles() {
    return Object.entries(AgentProfiles).map(([key, profile]) => ({
        key,
        name: profile.name,
        description: profile.description,
        type: profile.type
    }));
}

/**
 * Filtert Profile nach Typ.
 * @param {string} type - "minimax", "ruleBased", "random"
 * @returns {Array<Object>}
 */
function getProfilesByType(type) {
    return getAvailableProfiles().filter(p => p.type === type);
}