games/tictactoe/renderer.js

/**
 * @fileoverview Renderer-Sammlung für alle Tic-Tac-Toe Varianten
 * 
 * Bietet spezialierte Render-Funktionen für die 3 TTT-Varianten:
 * - drawRegular: Klassisches 3x3 Board
 * - drawUltimate: 9x9 Board mit Makro/Mikro-Struktur  
 * - draw3DSlices: 3D Board als Schicht-Views
 * - drawIsoView: Isometrische 3D-Projektion
 * 
 * @namespace TTTRenderer
 * @author Alexander Wolf
 * @version 2.0
 */

const TTTRenderer = {
    /**
     * Prüft, ob Renderer-Debug ausgegeben werden soll.
     * @param {'debug'|'warn'|'error'|'critical'} level
     * @returns {boolean}
     */
    _shouldLog(level = 'debug') {
        if (typeof window === 'undefined' || !window.DebugConfig || !window.DEBUG_DOMAINS) {
            return level === 'error' || level === 'critical';
        }
        return window.DebugConfig.shouldLog(window.DEBUG_DOMAINS.TTT_RENDERER, level);
    },

    /**
     * Schreibt einen Debug-Logeintrag des Renderers.
     * @param {'debug'|'warn'|'error'|'critical'} level
     * @param {...any} args
     */
    _log(level, ...args) {
        if (!this._shouldLog(level)) return;
        if (level === 'warn') {
            DebugConfig.log(DEBUG_DOMAINS.GAMES_TTT_REGULAR, "warn", ...args);
            return;
        }
        if (level === 'error' || level === 'critical') {
            DebugConfig.log(DEBUG_DOMAINS.GAMES_TTT_REGULAR, "error", ...args);
            return;
        }
        DebugConfig.log(DEBUG_DOMAINS.GAMES_TTT_REGULAR, "debug", ...args);
    },
    
    /**
     * Zeichnet das klassische 3x3 Board.
     * @param {HTMLCanvasElement} canvas - Canvas-Element zum Zeichnen
     * @param {TTTRegularBoard} game - Spiel-Zustand mit Grid und Winner
     * @returns {void}
     */
    drawRegular(canvas, game) {
        this._log('debug', '🖌️ TTTRenderer.drawRegular() aufgerufen');
        this._log('debug', '   - Canvas:', canvas.width, 'x', canvas.height);
        this._log('debug', '   - game:', game);
        this._log('debug', '   - game.grid:', game?.grid);
        
        if (!canvas || !game) {
            this._log('error', '❌ FEH LER: Canvas oder game ist null/undefined!');
            return;
        }
        
        const ctx = canvas.getContext('2d');
        const w = canvas.width, h = canvas.height;
        const s = w / 3;

        ctx.clearRect(0, 0, w, h);
        this._log('debug', '✅ Canvas gelöscht');
        
        // Gitterlinien
        ctx.strokeStyle = "#2c3e50"; 
        ctx.lineWidth = 6; 
        ctx.lineCap = "round";
        
        ctx.beginPath();
        ctx.moveTo(s, 10); ctx.lineTo(s, h-10); 
        ctx.moveTo(s*2, 10); ctx.lineTo(s*2, h-10);
        ctx.moveTo(10, s); ctx.lineTo(w-10, s); 
        ctx.moveTo(10, s*2); ctx.lineTo(w-10, s*2);
        ctx.stroke();
        this._log('debug', '✅ Gitterlinien gezeichnet');

        // Symbole
        for(let i=0; i<9; i++) {
            if(game.grid[i] === 0) continue;
            const cx = (i % 3) * s + s/2;
            const cy = Math.floor(i / 3) * s + s/2;
            this._log('debug', `   - Zeichne Symbol an Position ${i}: (${cx}, ${cy}) für Wert ${game.grid[i]}`);
            this._drawSymbol(ctx, cx, cy, s/3.5, game.grid[i]);
        }
        this._log('debug', '✅ Symbole gezeichnet');
    },

    /**
     * Zeichnet das Ultimate TTT Board (9x9 mit Makro/Mikro-Struktur).
     * @param {HTMLCanvasElement} canvas - Canvas-Element zum Zeichnen
     * @param {UltimateBoard} game - Ultimates Spiel mit macroBoard und grids Array
     * @returns {void}
     */
    drawUltimate(canvas, game) {
        const ctx = canvas.getContext('2d');
        const w = canvas.width, h = canvas.height;
        const bigS = w / 3;
        const smallS = bigS / 3;

        ctx.clearRect(0, 0, w, h);

        // 1. Highlights: Wo darf gespielt werden?
        if (game.winner === 0) {
            if (game.nextBoardIdx !== -1) {
                // Bestimmtes Board highlighten
                const bx = (game.nextBoardIdx % 3) * bigS;
                const by = Math.floor(game.nextBoardIdx / 3) * bigS;
                ctx.fillStyle = "#eafaed"; // Hellgrün
                ctx.fillRect(bx, by, bigS, bigS);
            } else {
                // Freie Wahl -> Alles leicht grün
                ctx.fillStyle = "#eafaed"; 
                ctx.fillRect(0,0,w,h);
            }
        }

        // 2. Makro-Board Status (Farbige Hintergründe für gewonnene Boards)
        for (let i=0; i<9; i++) {
            const bx = (i%3)*bigS;
            const by = Math.floor(i/3)*bigS;
            
            if (game.macroBoard[i] !== 0) {
                const winner = game.macroBoard[i];
                
                // Hintergrundfarbe transparent
                if (winner === 1) ctx.fillStyle = "rgba(52, 152, 219, 0.15)"; // Blau
                else if (winner === 2) ctx.fillStyle = "rgba(231, 76, 60, 0.15)"; // Rot
                else ctx.fillStyle = "rgba(127, 140, 141, 0.2)"; // Grau/Remis
                
                ctx.fillRect(bx, by, bigS, bigS);
                
                // Großes Symbol darüber zeichnen (sehr transparent)
                ctx.save();
                ctx.globalAlpha = 0.3; 
                this._drawSymbol(ctx, bx+bigS/2, by+bigS/2, bigS/3, winner, 15);
                ctx.restore();
            }
        }

        // 3. Kleines Gitter (Dünn)
        ctx.lineWidth = 1; 
        ctx.strokeStyle = "#bdc3c7";
        ctx.beginPath();
        for (let i=1; i<9; i++) {
            if (i%3===0) continue; 
            ctx.moveTo(i*smallS, 0); ctx.lineTo(i*smallS, h);
            ctx.moveTo(0, i*smallS); ctx.lineTo(w, i*smallS);
        }
        ctx.stroke();

        // 4. Großes Gitter (Dick)
        ctx.lineWidth = 4; 
        ctx.strokeStyle = "#2c3e50";
        ctx.beginPath();
        for (let i=1; i<=2; i++) {
            ctx.moveTo(i*bigS, 0); ctx.lineTo(i*bigS, h);
            ctx.moveTo(0, i*bigS); ctx.lineTo(w, i*bigS);
        }
        ctx.stroke();

        // 5. Kleine Spielsteine
        for (let b=0; b<9; b++) {
            for (let s=0; s<9; s++) {
                if (game.boards[b][s] === 0) continue;
                const bx = (b%3)*bigS; 
                const by = Math.floor(b/3)*bigS;
                const sx = (s%3)*smallS; 
                const sy = Math.floor(s/3)*smallS;
                
                this._drawSymbol(ctx, bx+sx+smallS/2, by+sy+smallS/2, smallS/3, game.boards[b][s], 3);
            }
        }
    },

    /**
     * Zeichnet die 2D-Schnittebenen (Slices) für das 3D-Spiel.
     * @param {HTMLCanvasElement} canvas 
     * @param {TTT3DBoard} game 
     * @param {string} axis - 'x', 'y' oder 'z'.
     */
    draw3DSlices(canvas, game, axis) {
        const ctx = canvas.getContext('2d');
        const w = canvas.width;
        const h = canvas.height;
        const s = game.size; 

        ctx.clearRect(0,0,w,h);
        
        // Layout-Konstanten (Müssen mit Controller-Hit-Detection übereinstimmen!)
        const padding = 20;
        const availW = w - (padding*2);
        const availH = h - (padding*2);
        // Boxgröße so berechnen, dass s Boxen nebeneinander passen
        const boxSize = Math.min(availW / s, availH);
        const gap = boxSize * 0.1;
        const boardSize = boxSize - gap;
        
        const totalW = s * boxSize;
        const startX = (w - totalW) / 2 + gap/2;
        const startY = (h - boardSize) / 2 + 10;

        ctx.textAlign = "center"; 
        ctx.textBaseline = "middle";

        const sliceName = (axis === 'z') ? 'Z' : (axis === 'y') ? 'Y' : 'X';
        
        for (let k = 0; k < s; k++) {
            const ox = startX + k * boxSize;
            const oy = startY;

            // Label
            ctx.fillStyle = "#2c3e50"; 
            ctx.font = "bold 14px sans-serif";
            ctx.fillText(`${sliceName}${k+1}`, ox + boardSize/2, oy - 15);

            // Hintergrund
            ctx.fillStyle = "#ecf0f1"; 
            ctx.fillRect(ox, oy, boardSize, boardSize);
            ctx.strokeStyle = "#bdc3c7"; 
            ctx.lineWidth = 2; 
            ctx.strokeRect(ox, oy, boardSize, boardSize);

            // Gitterlinien
            const cellS = boardSize / s;
            ctx.beginPath();
            for(let i=1; i<s; i++) {
                ctx.moveTo(ox + i*cellS, oy); ctx.lineTo(ox + i*cellS, oy + boardSize);
                ctx.moveTo(ox, oy + i*cellS); ctx.lineTo(ox + boardSize, oy + i*cellS);
            }
            ctx.stroke();

            // Inhalte
            for(let r=0; r<s; r++) { 
                for(let c=0; c<s; c++) { 
                    // Koordinaten Mapping
                    let x, y, z;
                    if (axis === 'z') { z = k; y = r; x = c; } 
                    else if (axis === 'y') { y = k; x = c; z = (s - 1) - r; } 
                    else { x = k; y = c; z = (s - 1) - r; }

                    const idx = z*(s*s) + y*s + x;
                    const val = game.grid[idx];

                    if (val !== 0) {
                        const cx = ox + c*cellS + cellS/2;
                        const cy = oy + r*cellS + cellS/2;
                        this._drawSymbol(ctx, cx, cy, cellS/3.5, val, 3);
                    }
                }
            }
        }
    },

    /**
     * Zeichnet die isometrische 3D Ansicht.
     * @param {HTMLCanvasElement} canvas 
     * @param {TTT3DBoard} game 
     */
    drawIsoView(canvas, game) {
        if (!canvas) return;
        const ctx = canvas.getContext('2d');
        const w = canvas.width, h = canvas.height;
        const s = game.size;

        ctx.clearRect(0,0,w,h);

        let boardSize = w * 0.55; 
        if (s === 4) boardSize = w * 0.45;

        const offX = boardSize * 0.2;
        const offY = -boardSize * 0.4;
        
        const startX = (w - (boardSize + (s-1)*offX))/2 + 20;
        const startY = h - 50;

        ctx.font = "bold 12px sans-serif";
        ctx.textAlign = "right"; 
        ctx.textBaseline = "middle";

        // Zeichnen von hinten (z=0) nach vorne (z=s-1)
        for (let z=0; z<s; z++) {
            const ox = startX + z * offX;
            const oy = startY + z * offY;

            // Transparenz für Tiefeneffekt
            ctx.save();
            // Hintere Ebenen transparenter
            ctx.globalAlpha = 0.4 + (0.6 * (z+1)/s);

            // Label
            ctx.fillStyle = "#7f8c8d";
            ctx.fillText(`z${z+1}`, ox - 10, oy + 20);

            // Ebene Boden
            ctx.fillStyle = "rgba(52, 152, 219, 0.05)"; 
            ctx.fillRect(ox, oy - boardSize, boardSize, boardSize);
            ctx.strokeStyle = "rgba(44, 62, 80, 0.2)"; 
            ctx.lineWidth = 1; 
            ctx.strokeRect(ox, oy - boardSize, boardSize, boardSize);

            // Gitterlinien
            const cellS = boardSize / s;
            ctx.beginPath();
            for(let i=1; i<s; i++) {
                ctx.moveTo(ox + i*cellS, oy); ctx.lineTo(ox + i*cellS, oy - boardSize);
                ctx.moveTo(ox, oy - i*cellS); ctx.lineTo(ox + boardSize, oy - i*cellS);
            }
            ctx.stroke();

            // Steine
            for(let y=0; y<s; y++) {
                for(let x=0; x<s; x++) {
                    const idx = z*(s*s) + y*s + x;
                    const val = game.grid[idx];
                    
                    if (val !== 0) {
                        const cx = ox + x*cellS + cellS/2;
                        const cy = (oy - boardSize) + y*cellS + cellS/2;
                        const r = cellS/3.5;

                        // Kleiner Schatten
                        ctx.fillStyle = "rgba(0,0,0,0.1)";
                        ctx.beginPath(); ctx.arc(cx+2, cy+2, r, 0, Math.PI*2); ctx.fill();

                        this._drawSymbol(ctx, cx, cy, r, val, 2);
                    }
                }
            }
            ctx.restore();
        }
    },

    /**
     * Interner Helfer: Zeichnet Kreis (1) oder Kreuz (2).
     */
    _drawSymbol(ctx, x, y, r, player, lw=5) {
        ctx.lineWidth = lw;
        if (player === 1) { // Blau
            ctx.strokeStyle = "#3498db"; 
            ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI*2); ctx.stroke();
        } else if (player === 2) { // Rot
            ctx.strokeStyle = "#e74c3c"; 
            ctx.beginPath(); 
            ctx.moveTo(x-r, y-r); ctx.lineTo(x+r, y+r);
            ctx.moveTo(x+r, y-r); ctx.lineTo(x-r, y+r); 
            ctx.stroke();
        }
    }
};