/**
* @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);
}
}