/**
* @fileoverview Schema-Definition und Validierung für Heuristik-Konfigurationen.
* Unterstützt Profile v1–v7 (baseline, positional, aggressive, defensive, offensive, local, global).
* Validierung clampt Range-Verletzungen mit Warning statt throw (§5 ENGINEERING_CONVENTIONS).
* @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',
'v4_defensive', 'v5_offensive', 'v6_local', 'v7_global'
],
weightDefinitions: {
// ── Terminal-Bewertungen ──
// Skalierung: Regular=1000, 3D=10000, Ultimate/C4=100000
// Regel: Taktische Werte (fork, pressure) ≤ 1/10 von win
win: { type: 'number', default: 1000, min: 1, max: 10000000 },
loss: { type: 'number', default: -1000, min: -10000000, max: -1 },
// draw skaliert mit dem Spieltyp: Regular=±100, 3D=±1000, Ultimate/C4=±10000
// Bereich ±100000 deckt alle Varianten ab (Regular draw 0-100 << win 1000)
draw: { type: 'number', default: 0, min: -100000, max: 100000 },
// ── Linien-Bewertungen ──
oneInLine: { type: 'number', default: 1, min: 0, max: 100 },
twoInLine: { type: 'number', default: 10, min: 0, max: 500 },
threeInLine: { type: 'number', default: 100, min: 0, max: 5000 },
fourInRow: { type: 'number', default: 500, min: 0, max: 100000 },
// ── Gegner-Linien (negativ) ──
opponentTwoInLine: { type: 'number', default: -10, min: -500, max: 0 },
opponentThreeInLine: { type: 'number', default: -90, min: -5000, max: 0 },
// ── Positionelle Gewichte ──
centerBonus: { type: 'number', default: 20, min: 0, max: 500 },
centerWeight: { type: 'number', default: 3, min: 0, max: 50 },
spatialControl: { type: 'number', default: 2, min: 0, max: 100 },
cornerBonus: { type: 'number', default: 3, min: 0, max: 200 },
edgeBonus: { type: 'number', default: 1, min: 0, max: 100 },
// ── Taktische Gewichte ──
forkBonus: { type: 'number', default: 50, min: 0, max: 500 },
opponentForkPenalty: { type: 'number', default: -50, min: -500, max: 0 },
pressureWeight: { type: 'number', default: 15, min: 0, max: 200 },
blockUrgency: { type: 'number', default: 80, min: 0, max: 500 },
threatMultiplier: { type: 'number', default: 5, min: 0, max: 50 },
setupBonus: { type: 'number', default: 15, min: 0, max: 200 },
// ── Strukturelle Gewichte ──
connectivityBonus: { type: 'number', default: 8, min: 0, max: 100 },
heightPenalty: { type: 'number', default: -2, min: -100, max: 0 },
mobilityWeight: { type: 'number', default: 1, min: 0, max: 100 },
// ── Ultimate TTT spezifisch ──
macroWeight: { type: 'number', default: 50, min: 0, max: 1000 },
boardWonBonus: { type: 'number', default: 20, min: 0, max: 500 }
}
};
/**
* 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) {
if (typeof DebugConfig !== 'undefined' && typeof DEBUG_DOMAINS !== 'undefined') {
DebugConfig.log(DEBUG_DOMAINS.AI_HEURISTICS, 'warn',
`Parameter '${key}' hat falschen Typ '${typeof value}', erwartet '${definition.type}'. Verwende Default.`);
}
validatedWeights[key] = definition.default;
continue;
}
let clamped = value;
if (definition.min !== undefined && value < definition.min) {
clamped = definition.min;
if (typeof DebugConfig !== 'undefined' && typeof DEBUG_DOMAINS !== 'undefined') {
DebugConfig.log(DEBUG_DOMAINS.AI_HEURISTICS, 'warn',
`Parameter '${key}': ${value} < min ${definition.min}, clamped auf ${clamped}.`);
}
}
if (definition.max !== undefined && value > definition.max) {
clamped = definition.max;
if (typeof DebugConfig !== 'undefined' && typeof DEBUG_DOMAINS !== 'undefined') {
DebugConfig.log(DEBUG_DOMAINS.AI_HEURISTICS, 'warn',
`Parameter '${key}': ${value} > max ${definition.max}, clamped auf ${clamped}.`);
}
}
validatedWeights[key] = clamped;
}
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
};
}