games/connect4/3d-controller.js

/**
 * @fileoverview Controller für 3D Connect4 (4x4x4 Cube)
 * 
 * Extends BaseGameController mit 3D Connect4-Support und Multi-Panel Rendering.
 * Visualisiert 4 unterschiedliche Perspektiven des Spielwürfels.
 * 
 * @class Connect43DController
 * @extends BaseGameController
 */
class Connect43DController extends BaseGameController {
    constructor() {
        super('connect4-3d', 'gameCanvas');
    }

    createGame() {
        return new Connect43D(4);
    }

    reset() {
        this.canvas.width = 800; // Wider for 4 panels
        this.canvas.height = 500; // Taller for 2 rows of views
        super.reset();
    }

    drawGame() {
        Connect4Renderer.draw3D(this.canvas, this.game);
    }

    coordsToMove(mx, my) {
        const w = this.canvas.width;
        const s = this.game.size;
        
        const pad = 10;
        const boardW = (w - pad * (s + 1)) / s;
        const boardH = boardW;

        // Match renderer logic (topY = 40)
        const topY = 40; 
        const secondY = topY + boardH + 60; // Offset for Side View

        // Check Top View (Front View)
        if (my >= topY && my <= topY + boardH) {
            for (let z = 0; z < s; z++) {
                 const startX = pad + z * (boardW + pad);
                 if (mx >= startX && mx <= startX + boardW) {
                     const x = Math.floor((mx - startX) / (boardW / s));
                     if (x >= 0 && x < s) {
                         // Move is x + z*size. In Top view we select X and Slice Z (now Y)
                         // Wait, if "Ebene Y=..." is the slice, then z loop is Y?
                         // The renderer loop: for (let z = 0; z < s; z++) -> Label "Ebene Y={z+1}"
                         // So 'z' var is the new 'y' dim.
                         // But game logic uses x + z*size for a column. Check game logic.
                         // Standard Connect4 3D: 16 columns (4x4 grid on floor).
                         // Pieces stack up in Y (Height).
                         // The input usually selects the column (x, z).
                         
                         // If we are looking "von vorne" at slices.
                         // If standard is X (width), Y (height), Z (depth).
                         // "Ebene Y=1" means Slice at Depth 1? No, usually Y is height.
                         // If "Ebene Y=1" means Depth=1, then user renamed Z to Y.
                         // Let's assume the move is still (x, z).
                         
                         // In Top/Front view: we click on a slice.
                         // slice index 'z' (loop var).
                         // grid x 'x'.
                         // Move = x + z * size.
                         return x + z * s;
                     }
                 }
            }
        }
        
        // Check Side View
        if (my >= secondY && my <= secondY + boardH) {
             for (let x = 0; x < s; x++) {
                 const startX = pad + x * (boardW + pad);
                 if (mx >= startX && mx <= startX + boardW) {
                     // In Side View (X Slices), we see Z (Depth, now Y?) vs Y (Height, now Z?)
                     // If we click here, we are selecting a column?
                     // A move is defined by picking a vertical column (x, z).
                     // In Side view of slice X, horizontal axis is Z.
                     // So we need to compute 'z' from mouse X inside the grid.
                     
                     const z = Math.floor((mx - startX) / (boardW / s));
                     if (z >= 0 && z < s) {
                         // We found x (loop var) and z (calculated).
                         return x + z * s;
                     }
                 }
             }
        }
        
        return null;
    }

    createAIAgent(type) {
        if (type === 'random') {
            return new RandomAgent();
        } else if (type === 'rule_simple' || type === 'rule_elementary') {
            return new RuleBasedAgent(Connect4RulesLibrary.createTree('3d', 'elementary'));
        } else if (type === 'rule_complex' || type === 'rule_advanced') {
            return new RuleBasedAgent(Connect4RulesLibrary.createTree('3d', 'advanced'));
        } else if (type.startsWith('minimax')) {
            const profileMap = { minimax: 'v1_baseline', minimax_positional: 'v2_positional', minimax_aggressive: 'v3_aggressive' };
            const profile = profileMap[type] || 'v1_baseline';
            const regKey = `connect4:3d:${profile}`;
            const heuristicFn = (typeof HeuristicRegistry !== 'undefined' && HeuristicRegistry.has(regKey))
                ? HeuristicRegistry.get(regKey).evaluate.bind(HeuristicRegistry.get(regKey))
                : (typeof HeuristicRegistry !== 'undefined' && HeuristicRegistry.has('connect4', '3d'))
                    ? HeuristicRegistry.get('connect4', '3d').evaluate.bind(HeuristicRegistry.get('connect4', '3d'))
                    : ((gameState, player) => {
                        if (gameState.winner === player) return 100000;
                        if (gameState.winner !== NONE && gameState.winner !== DRAW) return -100000;
                        return 0;
                    });
            return new MinimaxAgent({
                name: `Minimax 3D (${profile})`,
                maxDepth: 3,
                useAlphaBeta: true,
                heuristicFn
            });
        }
        return new RandomAgent();
    }
}