ai/heuristics/config/heuristic-config-schema.js

/**
 * @fileoverview Schema-Definition und Validierung für Heuristik-Konfigurationen.
 * Unterstützt Profile (v1_baseline, v2_positional, v3_aggressive) pro Game-Variante.
 * @author Alexander Wolf
 */

const HEURISTIC_CONFIG_SCHEMA = {
    required: ['game', 'variant'],
    games: ['ttt', 'connect4', 'knights-tour', 'chess-variant', 'nim', 'generic'],
    variants: {
        ttt: ['regular', '3d', 'ultimate'],
        connect4: ['regular', '3d'],
        'knights-tour': ['warnsdorf'],
        'chess-variant': ['pawn-chess', 'anti-chess'],
        nim: ['standard'],
        generic: ['winLoss']
    },
    /** Erlaubte Profilnamen für differenzierte Heuristik-Varianten */
    profiles: ['v1_baseline', 'v2_positional', 'v3_aggressive'],
    weightDefinitions: {
        win: { type: 'number', default: 1000, min: 1, max: 1000000 },
        loss: { type: 'number', default: -1000, min: -1000000, max: -1 },
        draw: { type: 'number', default: 0, min: -100, max: 100 },
        twoInLine: { type: 'number', default: 10, min: 0, max: 100000 },
        oneInLine: { type: 'number', default: 1, min: 0, max: 100000 },
        threeInLine: { type: 'number', default: 100, min: 0, max: 100000 },
        fourInRow: { type: 'number', default: 10000, min: 0, max: 1000000 },
        opponentTwoInLine: { type: 'number', default: -10, max: 0 },
        opponentThreeInLine: { type: 'number', default: -90, max: 0 },
        centerBonus: { type: 'number', default: 20, min: 0, max: 100000 },
        centerWeight: { type: 'number', default: 3, min: 0, max: 100000 },
        spatialControl: { type: 'number', default: 2, min: 0, max: 100000 },
        macroWeight: { type: 'number', default: 50, min: 0, max: 100000 },
        boardWonBonus: { type: 'number', default: 20, min: 0, max: 100000 },
        mobilityWeight: { type: 'number', default: 1, min: 0, max: 100000 },
        cornerBonus: { type: 'number', default: 3, min: 0, max: 100000 },
        edgeBonus: { type: 'number', default: 1, min: 0, max: 100000 },
        forkBonus: { type: 'number', default: 50, min: 0, max: 100000 },
        opponentForkPenalty: { type: 'number', default: -50, min: -100000, max: 0 },
        pressureWeight: { type: 'number', default: 15, min: 0, max: 100000 },
        blockUrgency: { type: 'number', default: 80, min: 0, max: 100000 },
        threatMultiplier: { type: 'number', default: 5, min: 0, max: 100000 },
        connectivityBonus: { type: 'number', default: 8, min: 0, max: 100000 },
        heightPenalty: { type: 'number', default: -2, min: -100000, max: 0 },
        setupBonus: { type: 'number', default: 15, min: 0, max: 100000 }
    }
};

/**
 * Validiert eine Heuristik-Konfiguration gegen das zentrale Schema.
 * Unterstützt optionales `profile`-Feld und `description`-Feld.
 * @param {Object} config - Rohkonfiguration.
 * @returns {Object} Validierte Konfiguration mit normalisierten Gewichten.
 */
function validateHeuristicConfig(config) {
    for (const field of HEURISTIC_CONFIG_SCHEMA.required) {
        if (config[field] === undefined || config[field] === null || config[field] === '') {
            throw new Error(`Pflichtfeld '${field}' fehlt in Heuristik-Config.`);
        }
    }

    if (!HEURISTIC_CONFIG_SCHEMA.games.includes(config.game)) {
        throw new Error(`Unbekanntes Spiel: '${config.game}'.`);
    }

    const allowedVariants = HEURISTIC_CONFIG_SCHEMA.variants[config.game] || [];
    if (!allowedVariants.includes(config.variant)) {
        throw new Error(`Unbekannte Variante '${config.variant}' für Spiel '${config.game}'.`);
    }

    const inputWeights = config.weights || {};
    const validatedWeights = {};

    for (const [key, definition] of Object.entries(HEURISTIC_CONFIG_SCHEMA.weightDefinitions)) {
        if (!(key in inputWeights)) {
            validatedWeights[key] = definition.default;
            continue;
        }

        const value = inputWeights[key];

        if (typeof value !== definition.type) {
            throw new Error(`Parameter '${key}' muss vom Typ '${definition.type}' sein.`);
        }

        if (definition.min !== undefined && value < definition.min) {
            throw new Error(`Parameter '${key}' ist kleiner als Minimum ${definition.min}.`);
        }

        if (definition.max !== undefined && value > definition.max) {
            throw new Error(`Parameter '${key}' ist größer als Maximum ${definition.max}.`);
        }

        validatedWeights[key] = value;
    }

    for (const key of Object.keys(inputWeights)) {
        if (!HEURISTIC_CONFIG_SCHEMA.weightDefinitions[key]) {
            if (typeof DebugConfig !== 'undefined' && typeof DEBUG_DOMAINS !== 'undefined') {
                DebugConfig.log(DEBUG_DOMAINS.AI_HEURISTICS, 'warn', 'Unknown heuristic weight ignored', { key });
            }
        }
    }

    return {
        ...config,
        name: config.name || `${config.game}-${config.variant}`,
        profile: config.profile || null,
        description: config.description || '',
        weights: validatedWeights
    };
}