ai/rules/connect4-rules.js


/**
 * Regel-Definitionen für Connect 4 (Standard und 3D).
 * Implementiert "elementary" und "advanced" Strategiebäume.
 * @fileoverview
 * @author Alexander Wolf
 */

const Connect4RulesLibrary = {
    
    // --- UTILS ---
    utils: {
        /**
         * Checks if a move leads to a win for the specified player.
         * @param {Connect4Regular|Connect43D} game 
         * @param {number} move 
         * @param {number} player 
         */
        canWin: (game, move, player) => {
            const sim = game.clone();
            sim.currentPlayer = player; 
            sim.makeMove(move);
            return sim.winner === player;
        },

        /**
         * Checks if opponent can win on their NEXT turn if we make 'myMove'.
         * (Lookahead 1)
         */
        givesOpponentWin: (game, myMove, me) => {
            const sim = game.clone();
            sim.currentPlayer = me;
            sim.makeMove(myMove);
            
            if (sim.winner !== NONE) return false;

            const opp = sim.currentPlayer;
            const oppMoves = sim.getAllValidMoves();
            
            for (let om of oppMoves) {
                const sim2 = sim.clone();
                sim2.makeMove(om);
                if (sim2.winner === opp) return true;
            }
            return false;
        },

        /**
         * Zählt wie viele Gewinndrohungen ein Spieler hat.
         * (Spalten, in denen ein Stein zum Sieg führen würde)
         */
        countThreats: (game, player) => {
            let threats = 0;
            for (const m of game.getAllValidMoves()) {
                if (Connect4RulesLibrary.utils.canWin(game, m, player)) threats++;
            }
            return threats;
        }
    },

    // --- ATOMIC RULES (Regular) ---
    regular: {
        win: new AtomicRule("Siegzug", "Gewinne sofort wenn möglich", (game) => {
            for (let m of game.getAllValidMoves()) {
                if (Connect4RulesLibrary.utils.canWin(game, m, game.currentPlayer)) return m;
            }
            return null;
        }),

        block: new AtomicRule("Blocken", "Verhindere Sieg des Gegners", (game) => {
            const opp = game.currentPlayer === PLAYER1 ? PLAYER2 : PLAYER1;
            for (let m of game.getAllValidMoves()) {
                if (Connect4RulesLibrary.utils.canWin(game, m, opp)) return m;
            }
            return null;
        }),

        preferCenter: new AtomicRule("Zentrum", "Spiele in die mittlere Spalte", (game) => {
            const center = Math.floor(game.cols / 2);
            const moves = game.getAllValidMoves();
            if (moves.includes(center)) return center;
            return null;
        }),
        
        avoidBadMoves: new AtomicRule("Sicher spielen", "Vermeide Züge, die dem Gegner den Sieg schenken", (game) => {
            const moves = game.getAllValidMoves();
            if (moves.length === 1) return moves[0];

            const safeMoves = moves.filter(m => !Connect4RulesLibrary.utils.givesOpponentWin(game, m, game.currentPlayer));
            
            if (safeMoves.length > 0) {
                return safeMoves[Math.floor(Math.random() * safeMoves.length)];
            }
            return null;
        }),

        setupThreat: new AtomicRule("Aufbau", "Baue eine Drohung auf (3er mit Lücke)", (game) => {
            const me = game.currentPlayer;
            const moves = game.getAllValidMoves();
            // Suche Zug, der uns eine neue Drohung gibt, ohne dem Gegner zu helfen
            let bestMove = null;
            let bestScore = -Infinity;
            for (const m of moves) {
                if (Connect4RulesLibrary.utils.givesOpponentWin(game, m, me)) continue;
                // Zähle unsere Drohungen nach dem Zug
                const sim = game.clone();
                sim.currentPlayer = me;
                sim.makeMove(m);
                const myThreats = Connect4RulesLibrary.utils.countThreats(sim, me);
                if (myThreats > bestScore) {
                    bestScore = myThreats;
                    bestMove = m;
                }
            }
            return bestScore > 0 ? bestMove : null;
        }),

        preferNearCenter: new AtomicRule("Nahe Mitte", "Spalten nahe der Mitte bevorzugen", (game) => {
            const moves = game.getAllValidMoves();
            if (moves.length === 0) return null;
            const center = game.cols / 2;
            moves.sort((a, b) => Math.abs(a - center) - Math.abs(b - center));
            return moves[0];
        })
    },

    // --- ATOMIC RULES (3D) ---
    d3: {
        win: new AtomicRule("3D Sieg", "Gewinne 3D sofort", (game) => {
            for (let m of game.getAllValidMoves()) {
                if (Connect4RulesLibrary.utils.canWin(game, m, game.currentPlayer)) return m;
            }
            return null;
        }),

        block: new AtomicRule("3D Block", "Verhindere 3D Sieg", (game) => {
            const opp = game.currentPlayer === PLAYER1 ? PLAYER2 : PLAYER1;
            for (let m of game.getAllValidMoves()) {
                if (Connect4RulesLibrary.utils.canWin(game, m, opp)) return m;
            }
            return null;
        }),
        
        preferCenterPole: new AtomicRule("Zentrum-Säulen", "Spiele nahe der Mitte des Boards (XZ)", (game) => {
            const moves = game.getAllValidMoves();
            if (moves.length === 0) return null;
            
            const s = game.size;
            const center = (s-1)/2;
            
            moves.sort((a, b) => {
                const ax = a % s, az = Math.floor(a/s);
                const bx = b % s, bz = Math.floor(b/s);
                const da = Math.abs(ax-center) + Math.abs(az-center);
                const db = Math.abs(bx-center) + Math.abs(bz-center);
                return da - db;
            });
            
            const limit = Math.min(moves.length, 3);
            return moves[Math.floor(Math.random() * limit)];
        }),

        avoidBadMoves3D: new AtomicRule("3D Sicher", "Vermeide 3D-Züge die zum Verlust führen", (game) => {
            const moves = game.getAllValidMoves();
            if (moves.length <= 1) return moves[0] || null;
            const safeMoves = moves.filter(m => !Connect4RulesLibrary.utils.givesOpponentWin(game, m, game.currentPlayer));
            return safeMoves.length > 0 ? safeMoves[Math.floor(Math.random() * safeMoves.length)] : null;
        })
    },

    // --- TREE BUILDER ---
    /**
     * Erstellt Entscheidungsbaum für Connect4.
     * @param {string} variant - 'regular' | '3d'
     * @param {string} [level='advanced'] - 'elementary' | 'advanced' (Aliases: 'simple'→elementary, 'complex'→advanced)
     * @returns {DecisionTree}
     */
    createTree: (variant, level) => {
        // Alias-Mapping für Rückwärtskompatibilität
        const normalizedLevel = (level === 'simple') ? 'elementary'
            : (level === 'complex') ? 'advanced'
            : (level || 'advanced');

        const rules = (variant === '3d') ? Connect4RulesLibrary.d3 : Connect4RulesLibrary.regular;
        const randomFallback = new AtomicRule("Zufall", "Fallback", (game) => {
            const moves = game.getAllValidMoves();
            return moves.length > 0 ? moves[Math.floor(Math.random() * moves.length)] : null;
        });

        if (normalizedLevel === 'elementary') {
            // Elementary: Win → Block → Center → Random (flache Liste)
            const root = new RuleGroup("Wurzel", "Einfache Prioritätsliste");
            root.add(rules.win.clone());
            root.add(rules.block.clone());
            root.add((variant === '3d') ? rules.preferCenterPole.clone() : rules.preferCenter.clone());
            root.add(randomFallback);
            return new DecisionTree(`C4-${variant}-elementary`, root);
        }

        // ═══ Advanced: ConditionNode-basierte Verzweigungen ═══
        const root = new RuleGroup("Wurzel", "Strategischer Entscheidungsbaum");

        // 1. Existenz: Win / Block
        const survival = new RuleGroup("Existenz");
        survival.add(rules.win.clone());
        survival.add(rules.block.clone());
        root.add(survival);

        if (variant === '3d') {
            // 3D: Bedingung → Zentrum frei?
            const centerCheck = new ConditionNode(
                "Zentrum frei?",
                "Sind die zentralen Säulen verfügbar?",
                (game) => {
                    const s = game.size;
                    const center = (s - 1) / 2;
                    const moves = game.getAllValidMoves();
                    return moves.some(m => {
                        const x = m % s, z = Math.floor(m / s);
                        return Math.abs(x - center) <= 0.5 && Math.abs(z - center) <= 0.5;
                    });
                },
                // THEN: Zentrum besetzen
                rules.preferCenterPole.clone(),
                // ELSE: Sicher spielen
                rules.avoidBadMoves3D.clone()
            );
            root.add(centerCheck);
        } else {
            // Regular: Bedingung → Unter Druck? (Gegner droht)
            const pressureCheck = new ConditionNode(
                "Unter Druck?",
                "Hat der Gegner aufgebaute Drohungen?",
                (game) => {
                    const opp = game.currentPlayer === PLAYER1 ? PLAYER2 : PLAYER1;
                    return Connect4RulesLibrary.utils.countThreats(game, opp) >= 1;
                },
                // THEN: Defensiv — sichere Züge, Gegner-Drohungen neutralisieren
                new RuleGroup("🛡️ Defensiv", "", [
                    rules.avoidBadMoves.clone(),
                    rules.preferCenter.clone()
                ]),
                // ELSE: Offensiv — eigene Drohungen aufbauen
                new RuleGroup("⚔️ Offensiv", "", [
                    rules.setupThreat.clone(),
                    rules.preferCenter.clone(),
                    rules.preferNearCenter.clone()
                ])
            );
            root.add(pressureCheck);
        }

        // Fallback
        root.add(randomFallback);

        return new DecisionTree(`C4-${variant}-advanced`, root);
    }
};