ai/heuristics/registry.js

/**
 * @fileoverview Zentrale Registry für Heuristiken.
 * Kapselt Registrierung und Lookup von Heuristik-Instanzen.
 * @author Alexander Wolf
 */

class HeuristicRegistry {
    static _registry = new Map();
    static _constructors = new Map();

    /**
     * @param {BaseHeuristic} heuristic
     */
    static register(heuristic) {
        if (!(heuristic instanceof BaseHeuristic)) {
            throw new Error('Nur BaseHeuristic-Instanzen können registriert werden.');
        }

        if (HeuristicRegistry._registry.has(heuristic.id)) {
            DebugConfig.log(DEBUG_DOMAINS.AI_HEURISTICS, 'warn', 'Heuristic already registered, overwriting', {
                id: heuristic.id
            });
        }

        HeuristicRegistry._registry.set(heuristic.id, heuristic);
        DebugConfig.log(DEBUG_DOMAINS.AI_HEURISTICS, 'debug', 'Heuristic registered', {
            id: heuristic.id
        });
    }

    /**
     * Ruft eine registrierte Heuristik ab.
     * Unterstützt sowohl `get('ttt', 'regular')` als auch `get('ttt:regular:v2_positional')`.
     * @param {string} game - Spielname oder vollständiger Composite-Key.
     * @param {string} [variant] - Variante (optional bei Composite-Key).
     * @returns {BaseHeuristic}
     */
    static get(game, variant) {
        // Composite-Key: Wenn game bereits ':' enthält und kein variant explizit übergeben, direkt verwenden
        const id = (variant !== undefined) ? `${game}:${variant}` : (game.includes(':') ? game : `${game}:regular`);
        const heuristic = HeuristicRegistry._registry.get(id);

        if (!heuristic) {
            DebugConfig.log(DEBUG_DOMAINS.AI_HEURISTICS, 'error', 'Heuristic not found', {
                id,
                available: [...HeuristicRegistry._registry.keys()]
            });
            throw new Error(`Heuristik '${id}' nicht registriert.`);
        }

        return heuristic;
    }

    /**
     * Prüft ob eine Heuristik registriert ist.
     * Unterstützt sowohl `has('ttt', 'regular')` als auch `has('ttt:regular:v2_positional')`.
     * @param {string} game - Spielname oder vollständiger Composite-Key.
     * @param {string} [variant] - Variante (optional bei Composite-Key).
     * @returns {boolean}
     */
    static has(game, variant) {
        const id = (variant !== undefined) ? `${game}:${variant}` : (game.includes(':') ? game : `${game}:regular`);
        return HeuristicRegistry._registry.has(id);
    }

    /**
     * @param {string} game
     * @returns {BaseHeuristic[]}
     */
    static getByGame(game) {
        return [...HeuristicRegistry._registry.values()].filter((heuristic) => heuristic.game === game);
    }

    /**
     * @returns {BaseHeuristic[]}
     */
    static getAll() {
        return [...HeuristicRegistry._registry.values()];
    }

    /**
     * @param {Object} config
     * @returns {BaseHeuristic}
     */
    static createFromConfig(config) {
        const Constructor = HeuristicRegistry._constructors.get(config.game);
        if (!Constructor) {
            throw new Error(`Kein Heuristik-Konstruktor für Spiel '${config.game}' registriert.`);
        }

        const instance = new Constructor(config);
        HeuristicRegistry.register(instance);
        return instance;
    }

    /**
     * @param {string} game
     * @param {Function} Constructor - A BaseHeuristic subclass constructor
     */
    static registerConstructor(game, Constructor) {
        HeuristicRegistry._constructors.set(game, Constructor);
    }
}

/**
 * Generic fallback heuristic for terminal-only evaluation.
 */
class GenericWinLossHeuristic extends BaseHeuristic {
    constructor(config = {}) {
        super({ game: 'generic', variant: 'winLoss', ...config });
    }

    evaluate(gameState, player) {
        const terminal = this.checkTerminal(gameState, player);
        return terminal !== null ? terminal : 0;
    }
}

if (!HeuristicRegistry.has('generic', 'winLoss')) {
    HeuristicRegistry.register(new GenericWinLossHeuristic());
}