ai/rules/rule-structure.js

/**
 * @fileoverview Datenstrukturen für das Regelsystem.
 * Definiert die Bausteine für den Entscheidungsbaum (Composite Pattern).
 * @author Alexander Wolf
 */

/**
 * Abstrakte Basisklasse für alle Regel-Knoten.
 */
class RuleNode {
    /**
     * @param {string} name - Anzeigename der Regel.
     * @param {string} description - Tooltip/Beschreibung.
     */
    constructor(name, description = "") {
        this.name = name;
        this.description = description;
        this.active = true; // Kann per UI deaktiviert werden
    }

    /**
     * Evaluiert den Knoten gegen einen Spielzustand.
     * Muss von Unterklassen implementiert werden.
     * @param {GameState} gameState 
     * @returns {Object|null} { move, reason } oder null
     */
    evaluate(gameState) { throw new Error("Abstract method"); }
}

/**
 * Eine atomare Regel, die einen Zug vorschlägt (Blatt im Baum).
 */
class AtomicRule extends RuleNode {
    /**
     * @param {string} name 
     * @param {string} description 
     * @param {function(GameState): (number|Object|null)} logicFn - Gibt Zug oder null zurück.
     */
    constructor(name, description, logicFn) {
        super(name, description);
        this.logicFn = logicFn;
    }

    evaluate(gameState) {
        if (!this.active) return null;
        const move = this.logicFn(gameState);
        return (move !== null) ? { move, reason: this.name, node: this } : null;
    }

    /**
     * Erzeugt eine tiefe Kopie der Regel.
     * @returns {AtomicRule} Geklonte Regelinstanz.
     */
    clone() {
        const c = new AtomicRule(this.name, this.description, this.logicFn);
        c.active = this.active;
        return c;
    }
}

/**
 * Eine Gruppe von Regeln. Geht die Kinder der Reihe nach durch (Priorität).
 * Das erste Kind, das einen Zug liefert, gewinnt.
 */
class RuleGroup extends RuleNode {
    /**
     * @param {string} name - Anzeigename der Gruppe.
     * @param {string} [description=""] - Beschreibung der Gruppe.
     * @param {RuleNode[]} [children=[]] - Unterknoten.
     */
    constructor(name, description = "", children = []) {
        super(name, description);
        this.children = children;
    }

    /**
     * Fügt einen Kindknoten an und gibt die Gruppe für Chaining zurück.
     * @param {RuleNode} node - Regelknoten.
     * @returns {RuleGroup} Die aktuelle Gruppe.
     */
    add(node) {
        this.children.push(node);
        return this;
    }

    /**
     * Evaluiert die Gruppe in Prioritätsreihenfolge.
     * @param {GameState} gameState - Zu bewertender Zustand.
     * @returns {Object|null} Erster gültiger Entscheid oder null.
     */
    evaluate(gameState) {
        if (!this.active) return null;
        for (const child of this.children) {
            const result = child.evaluate(gameState);
            if (result) return result;
        }
        return null;
    }

    /**
     * Erzeugt eine tiefe Kopie inklusive aller Kinder.
     * @returns {RuleGroup} Geklonte Gruppe.
     */
    clone() {
        const c = new RuleGroup(this.name, this.description, this.children.map(child => child.clone()));
        c.active = this.active;
        return c;
    }
}

/**
 * Ein Verzweigungsknoten (If-Then-Else).
 * Ermöglicht echte Entscheidungsbäume statt nur Listen.
 */
class ConditionNode extends RuleNode {
    /**
     * @param {string} name 
     * @param {string} description 
     * @param {function(GameState): boolean} conditionFn - Prüft Bedingung.
     * @param {RuleNode} thenNode - Wird ausgeführt, wenn true.
     * @param {RuleNode} elseNode - Wird ausgeführt, wenn false.
     */
    constructor(name, description, conditionFn, thenNode, elseNode) {
        super(name, description);
        this.conditionFn = conditionFn;
        this.thenNode = thenNode;
        this.elseNode = elseNode;
    }

    /**
     * Evaluiert den passenden Zweig abhängig von der Bedingung.
     * @param {GameState} gameState - Zu bewertender Zustand.
     * @returns {Object|null} Ergebnis aus Then/Else-Zweig oder null.
     */
    evaluate(gameState) {
        if (!this.active) return null; // Ganze Verzweigung deaktivieren

        // Bedingung prüfen
        if (this.conditionFn(gameState)) {
            // JA-Zweig
            return this.thenNode ? this.thenNode.evaluate(gameState) : null;
        } else {
            // NEIN-Zweig
            return this.elseNode ? this.elseNode.evaluate(gameState) : null;
        }
    }

    /**
     * Erzeugt eine tiefe Kopie inklusive Then-/Else-Knoten.
     * @returns {ConditionNode} Geklonter Bedingungsknoten.
     */
    clone() {
        const c = new ConditionNode(
            this.name, 
            this.description, 
            this.conditionFn, 
            this.thenNode ? this.thenNode.clone() : null, 
            this.elseNode ? this.elseNode.clone() : null
        );
        c.active = this.active;
        return c;
    }
}

/**
 * Wrapper für den gesamten Baum.
 */
class DecisionTree {
    /**
     * @param {string} name - Anzeigename des Entscheidungsbaums.
     * @param {RuleNode} rootNode - Wurzelknoten.
     */
    constructor(name, rootNode) {
        this.name = name;
        this.root = rootNode;
    }

    /**
     * Liefert die Entscheidungsantwort des Baums.
     * @param {GameState} gameState - Zu bewertender Zustand.
     * @returns {Object|null} Regel-Entscheidung oder null.
     */
    getDecision(gameState) {
        if (!this.root) return null;
        return this.root.evaluate(gameState);
    }
}