Logica_Piezas_Comandante.js

import Pieza from '../Pieza.js';

/**
 * Clase que representa la pieza de Comandante en el juego.
 * @class Comandante
 * @extends Pieza
 * @memberof Logica
 */
class Comandante extends Pieza {
    /**
     * Constructor de la pieza Comandante.
     * @param {Tablero} tablero - tablero al que pertenece la pieza
     * @param {number} fil - fila
     * @param {number} col - columna
     * @param {string} jugador - 'J1' o 'J2'
     */
    constructor(tablero, fil, col, jugador) {
        super(tablero, 'Comandante', fil, col, jugador, 4, 3, 5);
        
        /**
         * Peso base del comandante (sin considerar amenazas o aliados)
         * @type {number}
         */
        this.pesoBase = 4;
    }

    /**
     * Determina el modo táctico del comandante (ATAQUE o DEFENSA) según las amenazas cercanas.
     * Analiza un área de 2 celdas alrededor y evalúa:
     * - Cantidad y tipo de piezas enemigas
     * - Presencia de artillería enemiga en rango
     * - Presencia del comandante enemigo
     * 
     * @returns {string} 'DEFENSA' si hay amenazas significativas, 'ATAQUE' en caso contrario
     */
    calculaModo() {
        let modo;
        
        // Calcular límites del área de análisis (2 celdas alrededor)
        let topLimit = this.fil - 2 < 0 ? 0 : this.fil - 2;
        let bottomLimit = this.fil + 2 > this.tablero.filas - 1 ? this.tablero.filas - 1 : this.fil + 2;
        let leftLimit = this.col - 2 < 0 ? 0 : this.col - 2;
        let rightLimit = this.col + 2 > this.tablero.columnas - 1 ? this.tablero.columnas - 1 : this.col + 2;

        let soldados = [];
        let caballerias = [];
        let artilleria = 0;
        let comandante = 0;
        let piezasEnRango = 0;
        
        // Escanear área alrededor del comandante
        for (let i = topLimit; i <= bottomLimit; i++) {
            for (let j = leftLimit; j <= rightLimit; j++) {
                if (!this.tablero.getCelda(i, j).estaVacia() && this.tablero.getCelda(i, j).getPieza().getJugador() === 'J1') {
                    const pieza = this.tablero.getCelda(i, j).getPieza();
                    let res = this.detectaTipo(this.tablero.getCelda(i, j), 'J1');
                    
                    // Clasificar piezas enemigas encontradas
                    switch (res) {
                        case 1: 
                            soldados.push(pieza);
                            break;
                        case 2: 
                            caballerias.push(pieza);
                            break;
                        case 4: 
                            comandante = 1;
                            break;
                    }
                    piezasEnRango++;
                }
            }
        }
        
        // Verificar si hay artillería enemiga en rango de disparo
        let artilleriaJ1 = this.encuentraArtilleria();
        if (artilleriaJ1 && artilleriaJ1.puedeDisparar()) {
            if (artilleriaJ1.getPosicion().col < this.col && artilleriaJ1.getPosicion().col + 4 >= this.col)
                artilleria = 1;
        }
        
        // Determinar modo según amenazas detectadas
        if (soldados.length > 2 || caballerias.length > 2 || artilleria == 1 || comandante == 1 || piezasEnRango > 3)
            modo = 'DEFENSA';
        else 
            modo = 'ATAQUE';
            
        return modo;
    }

    /**
     * Calcula el peso táctico del comandante basándose en su modo actual.
     * En DEFENSA: Usa BFS hasta 4 pasos para encontrar la mejor posición defensiva cerca de aliados
     * En ATAQUE: Busca enemigos en un radio de 2 celdas y selecciona el de mayor valor
     * 
     * @returns {Object} Objeto con dos propiedades:
     *   - peso {number}: Peso total calculado
     *   - bestCelda {Celda|null}: Celda objetivo seleccionada
     */
    calculaPeso() {
        const modo = this.calculaModo();
        let bestCelda = null;
        let bestPeso = -Infinity;

        const celdaInicial = this.tablero.getCelda(this.fil, this.col);

        if (modo === 'DEFENSA') {
            // Modo defensivo: BFS para encontrar mejor posición cerca de aliados
            const maxPasos = 4;
            const visitadasClaves = new Set();
            const queue = [];

            const pos0 = celdaInicial.getPosicion();
            const clave0 = `${pos0.fila},${pos0.col}`;
            visitadasClaves.add(clave0);
            queue.push({ celda: celdaInicial, pasos: 0 });

            // Evaluar posición actual
            bestCelda = celdaInicial;
            bestPeso = this.evaluarDefensa(celdaInicial);

            // BFS: explorar posiciones alcanzables en 4 movimientos
            while (queue.length > 0) {
                const { celda, pasos } = queue.shift();

                if (pasos === maxPasos) continue;

                // Obtener vecinos vacíos (el comandante se puede mover en 8 direcciones)
                const vecinosMovimiento = this.getVecinos(celda)
                    .filter(v => v.estaVacia());

                for (const destino of vecinosMovimiento) {
                    const pos = destino.getPosicion();
                    const clave = `${pos.fila},${pos.col}`;

                    if (visitadasClaves.has(clave)) continue;
                    visitadasClaves.add(clave);

                    // Encolar para continuar expandiendo
                    queue.push({ celda: destino, pasos: pasos + 1 });

                    // Evaluar valor defensivo de esta posición
                    const pesoCelda = this.evaluarDefensa(destino);

                    console.log(`   Evaluando (${pos.fila}, ${pos.col}): peso = ${pesoCelda}`);

                    if (pesoCelda > bestPeso) {
                        bestPeso = pesoCelda;
                        bestCelda = destino;
                        console.log(`   ✅ NUEVA MEJOR: (${pos.fila}, ${pos.col}) con peso ${pesoCelda}`);
                    }
                }
            }

            const finalPos = bestCelda.getPosicion();
            console.log(`   🎯 MEJOR CELDA: (${finalPos.fila}, ${finalPos.col}) con peso ${bestPeso}`);
        }

        if (modo === 'ATAQUE') {
            // Modo ofensivo: buscar enemigo de mayor valor en radio de 2 celdas
            let celdasVecinas = this.getVecinos(celdaInicial);
            
            // Primera capa: vecinos directos
            for (const vecino of celdasVecinas) {
                if (!vecino.estaVacia() && vecino.getPieza().getJugador() === 'J1') {
                    let res = this.detectaTipo(vecino);
                    if (res > bestPeso) {
                        bestPeso = res;
                        bestCelda = vecino;
                    }
                }
                
                // Segunda capa: vecinos de los vecinos
                const segVecinos = this.getVecinos(vecino);
                for (const segVecino of segVecinos) {
                    if (!segVecino.estaVacia() && segVecino.getPieza().getJugador() === 'J1') {
                        let res = this.detectaTipo(segVecino);
                        if (res > bestPeso) {
                            bestPeso = res;
                            bestCelda = segVecino;
                        }
                    }
                }
            }
        }

        console.log(`Comandante (${this.fil}, ${this.col}) en modo ${modo} elige celda objetivo: ${bestCelda ? `(${bestCelda.getPosicion().fila}, ${bestCelda.getPosicion().col})` : 'Ninguna'} con peso ${bestPeso + this.pesoBase}`);
        return { peso: (bestPeso + this.pesoBase), bestCelda: bestCelda };
    }

    /**
     * Detecta el tipo de pieza en una celda y retorna su valor táctico.
     * Soldado=1, Caballería=2, Artillería=3, Comandante=4
     * 
     * @param {Celda} celda - Celda a analizar
     * @param {string} jugadorObjetivo - Jugador objetivo ('J1' o 'J2')
     * @returns {number} Valor táctico de la pieza (0 si está vacía o es del jugador contrario)
     */
    detectaTipo(celda, jugadorObjetivo = 'J1') {
        let peso = 0;

        if (!celda.estaVacia()) {
            const piezaJugador = celda.getPieza().getJugador();

            // Solo contar si es del jugador objetivo
            if (piezaJugador === jugadorObjetivo) {
                switch (celda.getPieza().getTipo()) {
                    case 'Soldado':
                        peso += 1;
                        break;
                    case 'Caballeria':
                        peso += 2;
                        break;
                    case 'Artilleria':
                        peso += 3;
                        break;
                    case 'Comandante':
                        if (jugadorObjetivo === 'J1')
                        peso += 4;
                        break;
                }
            }
        }
        return peso;
    }

    /**
     * Obtiene las 8 celdas vecinas de una celda dada (movimiento diagonal incluido).
     * El comandante puede moverse en todas las direcciones adyacentes.
     * 
     * @param {Celda} celda - Celda central
     * @returns {Array<Celda>} Array con las celdas vecinas válidas
     */
    getVecinos(celda) {
        const pos = celda.getPosicion();
        const fila = pos.fila;
        const col = pos.col;
        const res = [];

        // Iterar sobre las 8 direcciones (incluyendo diagonales)
        for (let df = -1; df <= 1; df++) {
            for (let dc = -1; dc <= 1; dc++) {
                if (df === 0 && dc === 0) continue;

                const nf = fila + df;
                const nc = col + dc;

                // Verificar límites del tablero
                if (nf < 0 || nf >= this.tablero.filas) continue;
                if (nc < 0 || nc >= this.tablero.columnas) continue;

                res.push(this.tablero.getCelda(nf, nc));
            }
        }

        return res;
    }

    /**
     * Evalúa el valor defensivo de una celda sumando el valor de todas las piezas aliadas vecinas.
     * 
     * @param {Celda} celda - Celda a evaluar
     * @returns {number} Suma del valor de todas las piezas aliadas vecinas menos el valor de las enemigas
     */
    evaluarDefensa(celda) {
        let peso = 0;
        const vecinos = this.getVecinos(celda);

        for (const v of vecinos) {
            if (v.estaVacia()) continue;

            const jugador = v.getPieza().getJugador();
            if (jugador === 'J2') {
                peso += this.detectaTipo(v, 'J2'); // Aliados suman
            } else if (jugador === 'J1') {
                peso -= this.detectaTipo(v, 'J1'); // Enemigos restan
            }
        }

        return peso;
    }

    /**
     * Busca la pieza de artillería enemiga en las primeras 3 columnas del tablero.
     * 
     * @returns {Pieza|undefined} Pieza de artillería encontrada o undefined
     */
    encuentraArtilleria() {
        for (let i = 0; i < this.tablero.filas - 1; i++) {
            for (let j = 0; j < 3; j++) {
                if (!this.tablero.getCelda(i, j).estaVacia() && this.tablero.getCelda(i, j).getPieza().getTipo() === 'Artilleria') {
                    return this.tablero.getCelda(i, j).getPieza();
                }
            }
        }
    }
}

export default Comandante;