Una forma sencilla y rápida de aprender JAVA, observando y deduciendo cómo se comporta el lenguaje a través de ejemplos prácticos.

Archivo del blog

domingo, 13 de abril de 2025

Proyecto Ajedrez I.4. Lógica Central del Juego.

Antes de empezar como de costubre mostraremos el avance e indicaremos el punto en el que nos encontramos dentro del proyecto:

 1 Primer esbozo del tablero de ajedrez (2018)
 2 Explicación normativa y reglas del juego (2024)
 3 Interfaz Gráfica Principal (2025)
   .AjedrezGrafico.java
   .PanelTablero.java
   .SplashScreen.java
   .GuiUtils.java
 4 Ventanas de Diálogo (2025)
   .ConfiguracionDialog.java
   .ElegirLadoDialog.java
   .PromocionDialog.java
   .ConfirmarFinalizarDialog.java
   .ConfirmarCierreAppDialog.java

>5 Lógica Central del Juego
   .MotorJuego.java
   .Tablero.java
   .Movimiento.java
   .EstadoJuego.java
   .Posicion.java


En este post toca centrarnos en el punto 5 que representa la lógica central del juego.


Código Java 1 (MotorJuego.java):

package ajedrez_gui;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

/**
 * Controla la lógica principal del juego de ajedrez: gestiona el tablero,
 * turnos, movimientos, estados y reglas.
 */

public class MotorJuego {

    private Tablero tablero;
    private boolean turnoBlancas;
    private int contador50Movimientos; // Medios turnos para regla 50 mov
    private Map<String, Integer> historialPosiciones; // FEN -> count para triple rep
    private EstadoJuego estadoActual;
    private List<String> historialNotacion; // Notación para UI
    private Movimiento movimientoPendientePromocion; // Si se espera elección de promoción
    private final Random random = new Random(); // Para mov. CPU

    public MotorJuego() {
        iniciarNuevaPartida();
    }

    // Inicializa o resetea el juego
    public final void iniciarNuevaPartida() {
        tablero = new Tablero();
        turnoBlancas = true;
        contador50Movimientos = 0;
        historialPosiciones = new HashMap<>();
        historialNotacion = new ArrayList<>();
        movimientoPendientePromocion = null;
        registrarPosicionActual(); // Registrar posición inicial
        actualizarEstadoJuego(true); // Determinar estado inicial
        System.out.println("MotorJuego: Nueva partida iniciada. Estado: " + estadoActual);
    }

    // --- Getters ---
    public Tablero getTablero() {
        return tablero;
    }

    public boolean esTurnoBlancas() {
        return turnoBlancas;
    }

    public EstadoJuego getEstadoActual() {
        return estadoActual;
    }

    public List<String> getHistorialNotacion() {
        return new ArrayList<>(historialNotacion);
    } // Copia defensiva

    // Genera movimientos legales para el jugador actual
    public List<Movimiento> getMovimientosLegales() {
        if (estadoActual.esFinDePartida() || estadoActual == EstadoJuego.PROMOCION_REQUIERIDA) {
            return Collections.emptyList();
        }
        return tablero.generarMovimientosLegales(turnoBlancas);
    }

    // Intenta realizar un movimiento validándolo y ejecutándolo
    public EstadoJuego intentarMover(Posicion origen, Posicion destino) {
        if (estadoActual.esFinDePartida() || estadoActual == EstadoJuego.PROMOCION_REQUIERIDA) {
            System.err.println("MotorJuego: Intento de mover en estado inválido: " + estadoActual);
            return estadoActual;
        }
        if (origen == null || destino == null || !origen.esValida() || !destino.esValida()) {
            System.err.println("MotorJuego: Coordenadas inválidas.");
            return EstadoJuego.MOVIMIENTO_INVALIDO;
        }

        Pieza piezaEnOrigen = tablero.obtenerPieza(origen);
        if (piezaEnOrigen == null || piezaEnOrigen.esBlanca() != turnoBlancas) {
            return EstadoJuego.MOVIMIENTO_INVALIDO; // Pieza incorrecta o vacía
        }

        // Buscar el movimiento en la lista de legales
        List<Movimiento> movimientosLegales = tablero.generarMovimientosLegales(turnoBlancas);
        Movimiento movimientoSeleccionado = encontrarMovimientoEnLista(movimientosLegales, origen, destino);

        if (movimientoSeleccionado == null) {
            return EstadoJuego.MOVIMIENTO_INVALIDO; // No es un movimiento legal
        }

        // --- Movimiento legal encontrado ---
        Pieza piezaMovidaOriginal = movimientoSeleccionado.getPiezaMovida();
        Pieza piezaCapturada = movimientoSeleccionado.getPiezaCapturada();

        // Ejecutar en el tablero (mueve piezas, actualiza flags)
        movimientoSeleccionado.ejecutar();
        // Establecer posible casilla en passant para el siguiente turno
        tablero.setEnPassant(movimientoSeleccionado.obtenerNuevoEnPassant());

        // Verificar si resultó en promoción
        if (movimientoSeleccionado.esPromocion()) {
            this.movimientoPendientePromocion = movimientoSeleccionado;
            estadoActual = EstadoJuego.PROMOCION_REQUIERIDA;
            System.out.println("MotorJuego: Promoción requerida en " + destino);
            return estadoActual;
        } else {
            // Finalizar turno normalmente
            finalizarTurno(movimientoSeleccionado, piezaMovidaOriginal, piezaCapturada, null);
            return estadoActual;
        }
    }

    // Completa una promoción pendiente
    public EstadoJuego completarPromocion(char tipoPieza) {
        if (estadoActual != EstadoJuego.PROMOCION_REQUIERIDA || movimientoPendientePromocion == null) {
            System.err.println("MotorJuego: completarPromocion llamado incorrectamente.");
            return estadoActual == EstadoJuego.PROMOCION_REQUIERIDA ? EstadoJuego.ERROR_INTERNO : estadoActual;
        }

        boolean eraBlanca = movimientoPendientePromocion.getPiezaMovida().esBlanca();
        Posicion destinoPromocion = movimientoPendientePromocion.getDestino();
        char piezaCharMayus = Character.toUpperCase(tipoPieza);

        // Crear nueva pieza con switch expression y pattern matching (implícito)
        Pieza nuevaPieza = switch (piezaCharMayus) {
            case 'R' ->
                new Torre(eraBlanca);
            case 'B' ->
                new Alfil(eraBlanca);
            case 'N' ->
                new Caballo(eraBlanca);
            // case 'Q' -> new Dama(eraBlanca); // Cubierto por default
            default -> {
                piezaCharMayus = 'Q'; // Asegurar Q si es inválido
                yield new Dama(eraBlanca);
            }
        };

        tablero.setPieza(destinoPromocion, nuevaPieza); // Poner la pieza nueva
        System.out.println("MotorJuego: Peón promocionado a " + nuevaPieza.getClass().getSimpleName() + " (" + piezaCharMayus + ")");

        // Finalizar el turno pasando la información de promoción
        finalizarTurno(
                movimientoPendientePromocion,
                movimientoPendientePromocion.getPiezaMovida(), // El Peón original
                movimientoPendientePromocion.getPiezaCapturada(), // Si hubo captura
                piezaCharMayus // El tipo de pieza promocionada
        );

        movimientoPendientePromocion = null; // Limpiar
        return estadoActual;
    }

    // Lógica que se ejecuta al final de cada turno válido
    // Recibe el Movimiento para generar notación O-O / O-O-O

    private void finalizarTurno(Movimiento movimientoRealizado, Pieza piezaMovida, Pieza piezaCapturada, Character promocionChar) {

        // Evaluar estado del *oponente* después del movimiento
        boolean oponenteEnJaque = tablero.estaEnJaque(!turnoBlancas);
        List<Movimiento> movimientosOponente = tablero.generarMovimientosLegales(!turnoBlancas);
        boolean esJaqueMate = oponenteEnJaque && movimientosOponente.isEmpty();//
        String sufijoNotacion = "";
        if (esJaqueMate) {
            sufijoNotacion = "++"; // Mate
        } else if (oponenteEnJaque) {
            sufijoNotacion = "+"; // Jaque
        }
        // Actualizar contador 50 mov y historial posiciones (reset si peón o captura)
        if (piezaMovida instanceof Peon || piezaCapturada != null) {
            contador50Movimientos = 0;
            historialPosiciones.clear(); // Resetear historial FEN
        } else {
            contador50Movimientos++;
        }

        registrarPosicionActual(); // Registrar FEN *antes* de cambiar turno

        // --- Generar Notación ---
        String notacionFinal;
        if (movimientoRealizado.esEnroqueCorto()) {
            notacionFinal = "O-O" + sufijoNotacion;
        } else if (movimientoRealizado.esEnroqueLargo()) {
            notacionFinal = "O-O-O" + sufijoNotacion;
        } else {
            // Notación de coordenadas para otros movimientos
            Posicion origen = movimientoRealizado.getOrigen();
            Posicion destino = movimientoRealizado.getDestino();
            if (origen != null && destino != null) {
                String capturaStr = (piezaCapturada != null) ? "x" : "-";
                // Usar notación simple (pieza opcional, coordenadas)
                // String piezaStr = obtenerNotacionPiezaSimple(piezaMovida); // Opcional: SAN completo
                String piezaStr = (piezaMovida instanceof Peon) ? "" : piezaMovida.toString().toUpperCase(); // K, Q, R, B, N (o nada para peón)

                String promoStr = (promocionChar != null) ? "=" + promocionChar : "";
                // Formato: e2-e4, Nf3, Bx
                // Aquí usamos un formato más explícito: Pe2-e4, Ng1-f3, Bf1xc4, Pe7-e8=Q++

                notacionFinal = piezaMovida.toString().toUpperCase() // P, N, B, R, Q, K
                        + origen.toString()
                        + capturaStr
                        + destino.toString()
                        + promoStr
                        + sufijoNotacion;

                // Quitar P de peón si no captura
                if (piezaMovida instanceof Peon && piezaCapturada == null) {
                    notacionFinal = origen.toString() + capturaStr + destino.toString() + promoStr + sufijoNotacion;
                }
                // Quitar guion si no captura y no es peón
                if (piezaCapturada == null && !(piezaMovida instanceof Peon)) {
                    notacionFinal = notacionFinal.replace("-", "");
                }
                // Usar 'x' para captura de peón
                if (piezaMovida instanceof Peon && piezaCapturada != null) {
                    notacionFinal = origen.toString().charAt(0) + "x" + destino.toString() + promoStr + sufijoNotacion;
                }

            } else {
                System.err.println("MotorJuego WARN: finalizarTurno con origen/destino nulos.");
                notacionFinal = "???-???" + sufijoNotacion;
            }
        }

        historialNotacion.add(notacionFinal); // Añadir al historial
        System.out.println("MotorJuego: Movimiento registrado: " + notacionFinal);

        // Cambiar turno
        turnoBlancas = !turnoBlancas;

        // Actualizar estado para el NUEVO jugador
        actualizarEstadoJuego(false);
    }

    // Actualiza el estado del juego basado en las reglas
    private void actualizarEstadoJuego(boolean esInicioPartida) {
        List<Movimiento> movimientosLegales = tablero.generarMovimientosLegales(turnoBlancas);
        boolean reyActualEnJaque = tablero.estaEnJaque(turnoBlancas);

        // 1. Fin de partida por falta de movimientos
        if (movimientosLegales.isEmpty()) {
            estadoActual = reyActualEnJaque
                    ? (turnoBlancas ? EstadoJuego.JAQUEMATE_GANA_NEGRAS : EstadoJuego.JAQUEMATE_GANA_BLANCAS) // Mate
                    : EstadoJuego.AHOGADO; // Ahogado
        } // 2. Fin de partida por reglas de empate
        else if (contador50Movimientos >= 100) { // 50 mov = 100 medios turnos
            estadoActual = EstadoJuego.EMPATE_50_MOV;
        } else if (historialPosiciones.getOrDefault(tablero.getPosicionHash(turnoBlancas), 0) >= 3) {
            estadoActual = EstadoJuego.EMPATE_TRIPLE_REP;
        } else if (tablero.esMaterialInsuficiente()) {
            estadoActual = EstadoJuego.EMPATE_MATERIAL_INSUF;
        } // 3. Juego continúa: determinar si es jaque o turno normal
        else {
            estadoActual = reyActualEnJaque
                    ? (turnoBlancas ? EstadoJuego.JAQUE_BLANCAS : EstadoJuego.JAQUE_NEGRAS) // Jaque
                    : (turnoBlancas ? EstadoJuego.TURNO_BLANCAS : EstadoJuego.TURNO_NEGRAS); // Turno normal
        }
        // System.out.println("MotorJuego: Nuevo estado: " + estadoActual); // Debug
    }

    // Busca un movimiento en la lista por origen y destino
    private Movimiento encontrarMovimientoEnLista(List<Movimiento> movimientos, Posicion origen, Posicion destino) {
        if (movimientos == null || origen == null || destino == null) {
            return null;
        }
        for (var mov : movimientos) { // Usar var
            if (mov.getOrigen().equals(origen) && mov.getDestino().equals(destino)) {
                // Devuelve el primero encontrado. Si hay múltiples (promoción), se elige el primero.
                return mov;
            }
        }
        return null;
    }

    // Registra el FEN simplificado de la posición actual
    private void registrarPosicionActual() {
        String hash = tablero.getPosicionHash(turnoBlancas);
        historialPosiciones.put(hash, historialPosiciones.getOrDefault(hash, 0) + 1);
        // System.out.println("Posición registrada: " + hash + " Count: " + historialPosiciones.get(hash)); // Debug
    }

    // Realiza un movimiento aleatorio para la CPU
    public EstadoJuego realizarMovimientoAleatorioCPU() {
        if (estadoActual.esFinDePartida() || estadoActual == EstadoJuego.PROMOCION_REQUIERIDA) {
            return estadoActual;
        }
        List<Movimiento> movimientosLegales = getMovimientosLegales();
        if (movimientosLegales.isEmpty()) {
            actualizarEstadoJuego(false); // Forzar actualización si no hay movs
            System.err.println("MotorJuego WARN: CPU sin movimientos legales. Estado: " + estadoActual);
            return estadoActual;
        }

        Movimiento movCPU = movimientosLegales.get(random.nextInt(movimientosLegales.size()));
        System.out.println("MotorJuego: CPU elige mover: " + movCPU);

        EstadoJuego resultado = intentarMover(movCPU.getOrigen(), movCPU.getDestino());

        // Completar promoción si es necesario (CPU siempre elige Dama)
        if (resultado == EstadoJuego.PROMOCION_REQUIERIDA) {
            System.out.println("MotorJuego: CPU promociona a Dama (Q).");
            resultado = completarPromocion('Q');
        }
        return resultado;
    }
}


Código Java 2 (Tablero.java):

package ajedrez_gui;

import java.util.ArrayList;
import java.util.List;

/**
 * Representa el tablero de ajedrez 8x8. Contiene el estado de las piezas,
 * derechos de enroque y casilla en passant. Proporciona métodos para manipular
 * y consultar el estado del tablero.
 */

public final class Tablero {

    private final Pieza[][] casillas; // Array 2D para las piezas
    public static final int TAMANO_TABLERO = 8;
    // Constantes para filas clave (base 0)
    public static final int FILA_INICIAL_BLANCAS = 0; // Rango '1'
    public static final int FILA_PEONES_BLANCOS = 1; // Rango '2'
    public static final int FILA_PEONES_NEGROS = 6; // Rango '7'
    public static final int FILA_INICIAL_NEGRAS = 7; // Rango '8'

    // Estado de los derechos de enroque
    private boolean puedeEnrocarBlancoCorto;
    private boolean puedeEnrocarBlancoLargo;
    private boolean puedeEnrocarNegroCorto;
    private boolean puedeEnrocarNegroLargo;

    // Casilla objetivo para captura En Passant (si existe)
    private Posicion enPassantTarget;

    // Constructor principal: inicializa el tablero a la posición estándar
    public Tablero() {
        casillas = new Pieza[TAMANO_TABLERO][TAMANO_TABLERO];
        iniciarPosicionEstandar();
    }

    // Constructor para copia (usado internamente)
    private Tablero(Pieza[][] casillasExistentes, boolean bCorto, boolean bLargo, boolean nCorto, boolean nLargo, Posicion epTarget) {
        this.casillas = new Pieza[TAMANO_TABLERO][TAMANO_TABLERO];
        for (int i = 0; i < TAMANO_TABLERO; i++) {
            for (int j = 0; j < TAMANO_TABLERO; j++) {
                // Copiar cada pieza (Pieza.copiar() devuelve una nueva instancia)
                this.casillas[i][j] = (casillasExistentes[i][j] != null) ? casillasExistentes[i][j].copiar() : null;
            }
        }
        // Copiar estado de enroque y en passant
        this.puedeEnrocarBlancoCorto = bCorto;
        this.puedeEnrocarBlancoLargo = bLargo;
        this.puedeEnrocarNegroCorto = nCorto;
        this.puedeEnrocarNegroLargo = nLargo;
        this.enPassantTarget = epTarget; // Posicion es record (inmutable), se puede copiar referencia
    }

    // --- Getters y Setters para estado ---
    public Posicion getEnPassant() {
        return enPassantTarget;
    }

    public void setEnPassant(Posicion nuevoEnPassant) {
        this.enPassantTarget = nuevoEnPassant;
    }

    // Coloca las piezas en la posición inicial estándar
    public void iniciarPosicionEstandar() {
        // Limpiar tablero
        for (int i = 0; i < TAMANO_TABLERO; i++) {
            for (int j = 0; j < TAMANO_TABLERO; j++) {
                casillas[i][j] = null;
            }
        }
        // Colocar piezas blancas
        casillas[FILA_INICIAL_BLANCAS][0] = new Torre(true);  // a1
        casillas[FILA_INICIAL_BLANCAS][1] = new Caballo(true);// b1
        casillas[FILA_INICIAL_BLANCAS][2] = new Alfil(true);  // c1
        casillas[FILA_INICIAL_BLANCAS][3] = new Dama(true);   // d1
        casillas[FILA_INICIAL_BLANCAS][4] = new Rey(true);    // e1
        casillas[FILA_INICIAL_BLANCAS][5] = new Alfil(true);  // f1
        casillas[FILA_INICIAL_BLANCAS][6] = new Caballo(true);// g1
        casillas[FILA_INICIAL_BLANCAS][7] = new Torre(true);  // h1
        for (int j = 0; j < TAMANO_TABLERO; j++) {
            casillas[FILA_PEONES_BLANCOS][j] = new Peon(true);// Fila 2
        }
        // Colocar piezas negras
        casillas[FILA_INICIAL_NEGRAS][0] = new Torre(false);  // a8
        casillas[FILA_INICIAL_NEGRAS][1] = new Caballo(false); // b8
        casillas[FILA_INICIAL_NEGRAS][2] = new Alfil(false);  // c8
        casillas[FILA_INICIAL_NEGRAS][3] = new Dama(false);   // d8
        casillas[FILA_INICIAL_NEGRAS][4] = new Rey(false);    // e8
        casillas[FILA_INICIAL_NEGRAS][5] = new Alfil(false);  // f8
        casillas[FILA_INICIAL_NEGRAS][6] = new Caballo(false); // g8
        casillas[FILA_INICIAL_NEGRAS][7] = new Torre(false);  // h8
        for (int j = 0; j < TAMANO_TABLERO; j++) {
            casillas[FILA_PEONES_NEGROS][j] = new Peon(false); // Fila 7
        }

        // Inicializar derechos de enroque y en passant
        inicializarEnroque();
        enPassantTarget = null;
    }

    // Obtiene la pieza en una posición dada
    public Pieza obtenerPieza(Posicion pos) {
        return (pos != null && pos.esValida()) ? casillas[pos.x()][pos.y()] : null;
    }

    // Coloca una pieza en una posición dada (sobrescribe si hay algo)
    public void setPieza(Posicion pos, Pieza pieza) {
        if (pos != null && pos.esValida()) {
            casillas[pos.x()][pos.y()] = pieza;
        } else {
            System.err.println("Error: Intento de setPieza en posición inválida: " + pos);
        }
    }

    // Encuentra la posición del rey de un color dado
    public Posicion encontrarRey(boolean esBlanco) {
        for (int i = 0; i < TAMANO_TABLERO; i++) {
            for (int j = 0; j < TAMANO_TABLERO; j++) {
                Pieza p = casillas[i][j];
                // Usar pattern matching for instanceof
                if (p instanceof Rey rey && rey.esBlanca() == esBlanco) {
                    return new Posicion(i, j);
                }
            }
        }
        // Esto no debería ocurrir en una partida normal
        System.err.println("Error Crítico: No se encontró el rey " + (esBlanco ? "blanco" : "negro") + "!");
        return null; // O lanzar una excepción
    }

    /**
     * Verifica si una casilla está siendo atacada por alguna pieza del color
     * especificado.
     *
     * @param pos La posición a verificar.
     * @param atacadoPorBlancas true si buscamos atacantes blancos, false si
     * negros.
     * @return true si la casilla está atacada, false en caso contrario.
     */

    public boolean estaAtacada(Posicion pos, boolean atacadoPorBlancas) {
        if (pos == null || !pos.esValida()) {
            return false;
        }

        for (int i = 0; i < TAMANO_TABLERO; i++) {
            for (int j = 0; j < TAMANO_TABLERO; j++) {
                Posicion origenAtacante = new Posicion(i, j);
                Pieza piezaAtacante = obtenerPieza(origenAtacante);

                // Si hay una pieza del color buscado
                if (piezaAtacante != null && piezaAtacante.esBlanca() == atacadoPorBlancas) {
                    // Verificar si su movimiento básico puede llegar a 'pos'
                    // Esto incluye camino libre para piezas deslizantes

                    if (piezaAtacante.esMovimientoBasicoValido(origenAtacante, pos, this)) {
                        // Caso especial Peón: esMovimientoBasicoValido puede ser true para avance,
                        // pero solo ataca en diagonal.

                        if (piezaAtacante instanceof Peon) {
                            // Ataca solo si el movimiento fue diagonal (cambio en y != 0)
                            if (pos.y() != origenAtacante.y()) {
                                return true; // Peón ataca diagonalmente
                            }
                            // Si dy == 0, era un avance, no un ataque. Continuar buscando.
                        } else {
                            // Para otras piezas, si esMovimientoBasicoValido es true, ataca.
                            return true;
                        }
                    }
                }
            }
        }
        return false; // Ninguna pieza de ese color ataca la posición
    }

    // Verifica si el rey de un color dado está en jaque
    public boolean estaEnJaque(boolean reyBlanco) {
        Posicion posRey = encontrarRey(reyBlanco);
        if (posRey == null) {
            // Si no se encuentra el rey, asumir jaque podría ser lo más seguro,
            // aunque indica un estado inválido del juego.

            System.err.println("Advertencia: Rey no encontrado al verificar jaque para " + (reyBlanco ? "blancas." : "negras."));
            return true;
        }
        // El rey está en jaque si su posición está atacada por el color opuesto
        return estaAtacada(posRey, !reyBlanco);
    }

    /**
     * Genera TODOS los movimientos pseudo-legales y luego los filtra para
     * asegurar que no dejan al propio rey en jaque.
     *
     * @param paraBlancas true si se generan movimientos para las blancas, false
     * para negras.
     * @return Una lista de objetos Movimiento legales.
     */

    public List<Movimiento> generarMovimientosLegales(boolean paraBlancas) {
        List<Movimiento> movimientosLegales = new ArrayList<>();

        for (int x1 = 0; x1 < TAMANO_TABLERO; x1++) {
            for (int y1 = 0; y1 < TAMANO_TABLERO; y1++) {
                Posicion origen = new Posicion(x1, y1);
                Pieza pieza = obtenerPieza(origen);

                // Si hay una pieza del color correcto
                if (pieza != null && pieza.esBlanca() == paraBlancas) {
                    // Iterar sobre todas las posibles casillas destino
                    for (int x2 = 0; x2 < TAMANO_TABLERO; x2++) {
                        for (int y2 = 0; y2 < TAMANO_TABLERO; y2++) {
                            Posicion destino = new Posicion(x2, y2);
                            if (origen.equals(destino)) {
                                continue; // No mover a la misma casilla
                            }
                            // Crear un Movimiento potencial basado en el tablero ACTUAL
                            // (determina si es captura, enroque, etc.)

                            Movimiento movPotencial = new Movimiento(this, origen, destino);

                            // 1. Validar si el movimiento es básico válido según la pieza
                            //    (incluye validación de enroque en Rey.esMovimientoBasicoValido)

                            if (pieza.esMovimientoBasicoValido(origen, destino, this)) {

                                // 2. Simular el movimiento en una COPIA del tablero
                                Tablero copiaTablero = this.copiar();
                                // Obtener la pieza de la COPIA y ejecutar el movimiento simulado
                                Pieza piezaEnCopiaOrigen = copiaTablero.obtenerPieza(origen);
                                if (piezaEnCopiaOrigen != null) {
                                    // Crear Movimiento sobre la copia para simular
                                    Movimiento movSimulado = new Movimiento(copiaTablero, origen, destino);
                                    movSimulado.ejecutar(); // Ejecuta en la copia
                                    // Importante: Actualizar EP en la copia si el mov lo crea

                                    copiaTablero.setEnPassant(movSimulado.obtenerNuevoEnPassant());

                                    // 3. Verificar si el rey del jugador quedó en jaque en la copia
                                    if (!copiaTablero.estaEnJaque(paraBlancas)) {
                                        // Si no quedó en jaque, el movimiento es legal
                                        // Añadir el movimiento POTENCIAL (basado en tablero original)
                                        movimientosLegales.add(movPotencial);
                                    }
                                    // else { Movimiento ilegal porque deja al rey en jaque }
                                } else {
                                    System.err.println("Error simulación: Pieza desapareció en copia?");
                                }
                            }
                        }
                    }
                }
            }
        }
        return movimientosLegales;
    }

    // --- Gestión de Enroque ---
    private void inicializarEnroque() {
        puedeEnrocarBlancoCorto = true;
        puedeEnrocarBlancoLargo = true;
        puedeEnrocarNegroCorto = true;
        puedeEnrocarNegroLargo = true;
    }

    public boolean puedeEnrocar(boolean esBlanco, boolean ladoCorto) {
        if (esBlanco) {
            return ladoCorto ? puedeEnrocarBlancoCorto : puedeEnrocarBlancoLargo;
        } else {
            return ladoCorto ? puedeEnrocarNegroCorto : puedeEnrocarNegroLargo;
        }
    }

    // Invalida derechos de enroque si se mueve el rey o una torre desde su casilla inicial
    public void actualizarEnroquePorMovimiento(Posicion origen, Pieza piezaMovida) {
        if (piezaMovida == null || !origen.esValida()) {
            return;
        }

        // Usar pattern matching for instanceof
        if (piezaMovida instanceof Rey) {
            if (piezaMovida.esBlanca()) {
                invalidarEnroqueBlanco();
            } else {
                invalidarEnroqueNegro();
            }
        } else if (piezaMovida instanceof Torre torre) {
            // Posiciones iniciales de las torres
            Posicion TR_BLANCA_LARGA = new Posicion(FILA_INICIAL_BLANCAS, 0); // a1
            Posicion TR_BLANCA_CORTA = new Posicion(FILA_INICIAL_BLANCAS, 7); // h1
            Posicion TR_NEGRA_LARGA = new Posicion(FILA_INICIAL_NEGRAS, 0); // a8
            Posicion TR_NEGRA_CORTA = new Posicion(FILA_INICIAL_NEGRAS, 7); // h8

            if (torre.esBlanca()) {
                if (origen.equals(TR_BLANCA_LARGA)) {
                    puedeEnrocarBlancoLargo = false;
                } else if (origen.equals(TR_BLANCA_CORTA)) {
                    puedeEnrocarBlancoCorto = false;
                }
            } else {
                if (origen.equals(TR_NEGRA_LARGA)) {
                    puedeEnrocarNegroLargo = false;
                } else if (origen.equals(TR_NEGRA_CORTA)) {
                    puedeEnrocarNegroCorto = false;
                }
            }
        }
    }

    // Invalida derechos de enroque si una torre es capturada en su casilla inicial
    public void actualizarEnroquePorCapturaTorre(Posicion posTorreCapturada) {
        if (posTorreCapturada == null || !posTorreCapturada.esValida()) {
            return;
        }

        // Posiciones iniciales de las torres (repetido para claridad)
        Posicion TR_BLANCA_LARGA = new Posicion(FILA_INICIAL_BLANCAS, 0); // a1
        Posicion TR_BLANCA_CORTA = new Posicion(FILA_INICIAL_BLANCAS, 7); // h1
        Posicion TR_NEGRA_LARGA = new Posicion(FILA_INICIAL_NEGRAS, 0); // a8
        Posicion TR_NEGRA_CORTA = new Posicion(FILA_INICIAL_NEGRAS, 7); // h8

        if (posTorreCapturada.equals(TR_BLANCA_LARGA)) {
            puedeEnrocarBlancoLargo = false;
        } else if (posTorreCapturada.equals(TR_BLANCA_CORTA)) {
            puedeEnrocarBlancoCorto = false;
        } else if (posTorreCapturada.equals(TR_NEGRA_LARGA)) {
            puedeEnrocarNegroLargo = false;
        } else if (posTorreCapturada.equals(TR_NEGRA_CORTA)) {
            puedeEnrocarNegroCorto = false;
        }
    }

    public void invalidarEnroqueBlanco() {
        puedeEnrocarBlancoCorto = false;
        puedeEnrocarBlancoLargo = false;
    }

    public void invalidarEnroqueNegro() {
        puedeEnrocarNegroCorto = false;
        puedeEnrocarNegroLargo = false;
    }

    // --- Utilidades ---
    // Crea una copia profunda del tablero y su estado

    public Tablero copiar() {
        return new Tablero(this.casillas,
                this.puedeEnrocarBlancoCorto, this.puedeEnrocarBlancoLargo,
                this.puedeEnrocarNegroCorto, this.puedeEnrocarNegroLargo,
                this.enPassantTarget);
    }

    /**
     * Genera una cadena FEN simplificada (solo posición, turno, enroque, EP).
     * Útil para detectar repeticiones de posición.
     *
     * @param turnoActualBlancas Indica a quién le toca mover.
     * @return String representando la posición en formato FEN simplificado.
     */

    public String getPosicionHash(boolean turnoActualBlancas) {
        var sb = new StringBuilder();
        // 1. Posición de piezas (de fila 8 a 1)
        for (int i = TAMANO_TABLERO - 1; i >= 0; i--) {
            int casillasVacias = 0;
            for (int j = 0; j < TAMANO_TABLERO; j++) {
                Pieza p = casillas[i][j];
                if (p == null) {
                    casillasVacias++;
                } else {
                    if (casillasVacias > 0) {
                        sb.append(casillasVacias);
                    }
                    casillasVacias = 0;
                    // Usar el toString de la pieza (ya da mayús/minús)
                    // Necesitamos KQRBNP estándar FEN

                    char piezaChar = switch (p) {
                        case Rey r ->
                            'K';
                        case Dama q ->
                            'Q';
                        case Torre t ->
                            'R';
                        case Alfil b ->
                            'B';
                        case Caballo k ->
                            'N'; // N for Knight
                        case Peon pawn ->
                            'P';
                        // default -> '?'; // No debería pasar con sealed
                    };
                    sb.append(p.esBlanca() ? piezaChar : Character.toLowerCase(piezaChar));
                }
            }
            if (casillasVacias > 0) {
                sb.append(casillasVacias);
            }
            if (i > 0) {
                sb.append('/'); // Separador de filas
            }
        }

        // 2. Turno ('w' o 'b')
        sb.append(turnoActualBlancas ? " w" : " b");

        // 3. Derechos de enroque ('KQkq' o '-')
        sb.append(" ");
        String enroques = "";
        if (puedeEnrocarBlancoCorto) {
            enroques += "K";
        }
        if (puedeEnrocarBlancoLargo) {
            enroques += "Q";
        }
        if (puedeEnrocarNegroCorto) {
            enroques += "k";
        }
        if (puedeEnrocarNegroLargo) {
            enroques += "q";
        }
        sb.append(enroques.isEmpty() ? "-" : enroques);

        // 4. Casilla En Passant ('e3', 'f6' o '-')
        sb.append(" ");
        sb.append(enPassantTarget != null ? enPassantTarget.toString() : "-");

        // 5 & 6. Contadores de 50 mov y número de jugada (omitidos para hash simple)
        // sb.append(" 0 1"); // Ejemplo

        return sb.toString();
    }

    /**
     * Verifica si hay material insuficiente en el tablero para que cualquier
     * bando pueda forzar un jaque mate. Simplificado: K vs K, K vs K+B, K vs
     * K+N, K+B vs K+B (mismo color)
     *
     * @return true si el material es insuficiente.
     */

    public boolean esMaterialInsuficiente() {
        int caballosBlancos = 0, alfilesBlancosCasillaClara = 0, alfilesBlancosCasillaOscura = 0;
        int caballosNegros = 0, alfilesNegrosCasillaClara = 0, alfilesNegrosCasillaOscura = 0;
        boolean hayPiezasMayoresOPeones = false;

        for (int i = 0; i < TAMANO_TABLERO; i++) {
            for (int j = 0; j < TAMANO_TABLERO; j++) {
                Pieza p = casillas[i][j];
                if (p == null || p instanceof Rey) {
                    continue; // Ignorar reyes y casillas vacías
                }
                // Usar pattern matching for instanceof
                if (p instanceof Peon || p instanceof Dama || p instanceof Torre) {
                    hayPiezasMayoresOPeones = true;
                    break; // Suficiente para saber que no es material insuficiente
                }

                boolean esCasillaClara = (i + j) % 2 != 0; // A1(0,0) es oscura, B1(0,1) es clara

                if (p.esBlanca()) {
                    if (p instanceof Caballo) {
                        caballosBlancos++;
                    } else if (p instanceof Alfil) {
                        if (esCasillaClara) {
                            alfilesBlancosCasillaClara++;
                        } else {
                            alfilesBlancosCasillaOscura++;
                        }
                    }
                } else { // Pieza negra
                    if (p instanceof Caballo) {
                        caballosNegros++;
                    } else if (p instanceof Alfil) {
                        if (esCasillaClara) {
                            alfilesNegrosCasillaClara++;
                        } else {
                            alfilesNegrosCasillaOscura++;
                        }
                    }
                }
            }
            if (hayPiezasMayoresOPeones) {
                break;
            }
        }

        // Si hay peones, damas o torres, no es insuficiente
        if (hayPiezasMayoresOPeones) {
            return false;
        }

        // Contar piezas menores totales
        int alfilesBlancos = alfilesBlancosCasillaClara + alfilesBlancosCasillaOscura;
        int alfilesNegros = alfilesNegrosCasillaClara + alfilesNegrosCasillaOscura;
        int totalPiezasMenoresBlancas = caballosBlancos + alfilesBlancos;
        int totalPiezasMenoresNegras = caballosNegros + alfilesNegros;

        // K vs K
        if (totalPiezasMenoresBlancas == 0 && totalPiezasMenoresNegras == 0) {
            return true;
        }

        // K vs K + (N o B) -> Insuficiente
        if ((totalPiezasMenoresBlancas == 1 && totalPiezasMenoresNegras == 0)
                || (totalPiezasMenoresBlancas == 0 && totalPiezasMenoresNegras == 1)) {
            return true;
        }

        // K + B vs K + B (mismo color de casilla) -> Insuficiente
        if (caballosBlancos == 0 && caballosNegros == 0 && alfilesBlancos == 1 && alfilesNegros == 1) {
            boolean ambosClaros = alfilesBlancosCasillaClara == 1 && alfilesNegrosCasillaClara == 1;
            boolean ambosOscuros = alfilesBlancosCasillaOscura == 1 && alfilesNegrosCasillaOscura == 1;
            if (ambosClaros || ambosOscuros) {
                return true;
            }
        }

        // Otros casos (K+N vs K, K+N vs K+N, K+2N vs K, etc.) se consideran suficientes
        // (aunque algunas sean tablas teóricas, las reglas FIDE no las marcan como empate inmediato).

        return false;
    }

    // Método simple para mostrar tablero en consola (para debug)
    public void mostrarTableroConsola() {
        System.out.println("\n   a b c d e f g h");
        System.out.println("  +-----------------+");
        for (int i = TAMANO_TABLERO - 1; i >= 0; i--) { // De fila 8 a 1
            System.out.print((i + 1) + " | ");
            for (int j = 0; j < TAMANO_TABLERO; j++) { // De columna a a h
                Pieza p = casillas[i][j];
                System.out.print((p == null ? "." : p.toString()) + " "); // Usa Pieza.toString()
            }
            System.out.println("| " + (i + 1));
        }
        System.out.println("  +-----------------+");
        System.out.println("   a b c d e f g h\n");
    }
}


Código Java 3 (Movimiento.java):

package ajedrez_gui;

/**
 * Representa un movimiento potencial o ejecutado en el tablero. Contiene
 * información sobre origen, destino, piezas involucradas y tipos especiales
 * (enroque, promo, EP).
 */

public class Movimiento {

    private final Tablero tablero; // Tablero de referencia
    private final Posicion origen;
    private final Posicion destino;
    private final Pieza piezaMovida;
    private final Pieza piezaCapturada;
    private final boolean esEnroqueCorto;
    private final boolean esEnroqueLargo;
    private final boolean esPromocion;
    private final boolean esCapturaEnPassant;
    private final Posicion enPassantPrevio; // Casilla EP *antes* de este movimiento

    // Record interno para agrupar los detalles calculados

    private record DetallesMovimiento(
            Pieza piezaCapturada,
            boolean esEnroqueCorto,
            boolean esEnroqueLargo,
            boolean esPromocion,
            boolean esCapturaEnPassant
            ) {

    }

    // Constructor principal: Calcula detalles al crear
    public Movimiento(Tablero tablero, Posicion origen, Posicion destino) {
        this.tablero = tablero;
        this.origen = origen;
        this.destino = destino;
        this.piezaMovida = tablero.obtenerPieza(origen);
        this.enPassantPrevio = tablero.getEnPassant();

        DetallesMovimiento detalles = determinarDetallesMovimiento(tablero, origen, destino, piezaMovida, enPassantPrevio);
        this.piezaCapturada = detalles.piezaCapturada();
        this.esEnroqueCorto = detalles.esEnroqueCorto();
        this.esEnroqueLargo = detalles.esEnroqueLargo();
        this.esPromocion = detalles.esPromocion();
        this.esCapturaEnPassant = detalles.esCapturaEnPassant();
    }

    // Constructor privado (para copiar)
    private Movimiento(Tablero tablero, Posicion origen, Posicion destino, Pieza piezaMovida, Pieza piezaCapturada,
            boolean esEnroqueCorto, boolean esEnroqueLargo, boolean esPromocion, boolean esCapturaEnPassant,
            Posicion enPassantPrevio) {
        this.tablero = tablero;
        this.origen = origen;
        this.destino = destino;
        this.piezaMovida = piezaMovida;
        this.piezaCapturada = piezaCapturada;
        this.esEnroqueCorto = esEnroqueCorto;
        this.esEnroqueLargo = esEnroqueLargo;
        this.esPromocion = esPromocion;
        this.esCapturaEnPassant = esCapturaEnPassant;
        this.enPassantPrevio = enPassantPrevio;
    }

    // Determina los detalles (captura, enroque, promo, EP) basado en el estado actual
    private static DetallesMovimiento determinarDetallesMovimiento(
            Tablero tableroActual, Posicion origen, Posicion destino,
            Pieza piezaMovida, Posicion enPassantTargetActual) {

        Pieza capturada = null;
        boolean enroqueC = false, enroqueL = false, promocion = false, capturaEP = false;

        if (piezaMovida == null) {
            return new DetallesMovimiento(null, false, false, false, false);
        }

        // --- Lógica por tipo de pieza (usando pattern matching) ---
        if (piezaMovida instanceof Rey) {
            int dy = destino.y() - origen.y();
            int dx = destino.x() - origen.x();
            if (dx == 0 && Math.abs(dy) == 2) { // Movimiento de enroque
                enroqueC = dy == 2;
                enroqueL = dy == -2;
                capturada = null;
            } else { // Movimiento normal de rey
                capturada = tableroActual.obtenerPieza(destino);
            }
        } else if (piezaMovida instanceof Peon peon) {
            int filaPromocion = peon.esBlanca() ? Tablero.FILA_INICIAL_NEGRAS : Tablero.FILA_INICIAL_BLANCAS;
            if (destino.x() == filaPromocion) {
                promocion = true;
                capturada = tableroActual.obtenerPieza(destino); // Puede ser promo con captura
            }

            int dx_peon = destino.x() - origen.x();
            int dy_peon_abs = Math.abs(destino.y() - origen.y());

            // Check En Passant
            if (enPassantTargetActual != null && destino.equals(enPassantTargetActual) && dy_peon_abs == 1) {
                int dirCorrecta = peon.esBlanca() ? 1 : -1;
                if (dx_peon == dirCorrecta) {
                    Posicion peonCapturadoPos = new Posicion(origen.x(), destino.y());
                    Pieza peonACapturar = tableroActual.obtenerPieza(peonCapturadoPos);
                    if (peonACapturar instanceof Peon && peonACapturar.esBlanca() != peon.esBlanca()) {
                        capturaEP = true;
                        capturada = peonACapturar; // Captura el peón al lado
                    }
                }
            }

            // Check Captura Normal (si no fue EP)
            if (!capturaEP && dy_peon_abs == 1 && dx_peon == (peon.esBlanca() ? 1 : -1)) {
                Pieza piezaEnDestino = tableroActual.obtenerPieza(destino);
                // Si hay pieza y no es promoción (ya cubierta), es captura normal
                if (piezaEnDestino != null && !promocion) {
                    capturada = piezaEnDestino;
                }
            }
        } else { // Otras piezas (Torre, Alfil, Dama, Caballo)
            capturada = tableroActual.obtenerPieza(destino);
        }

        if (enroqueC || enroqueL) {
            capturada = null; // Asegurar que enroque no marque captura
        }
        return new DetallesMovimiento(capturada, enroqueC, enroqueL, promocion, capturaEP);
    }

    // Ejecuta el movimiento modificando el tablero
    public void ejecutar() {
        if (piezaMovida == null || !origen.esValida() || !destino.esValida()) {
            System.err.println("Movimiento.ejecutar(): Intento inválido.");
            return;
        }

        // 1. Actualizar derechos de enroque (por mover rey/torre o capturar torre)
        tablero.actualizarEnroquePorMovimiento(origen, piezaMovida);
        Pieza piezaEnDestinoAntes = tablero.obtenerPieza(destino);
        if (piezaEnDestinoAntes instanceof Torre) {
            tablero.actualizarEnroquePorCapturaTorre(destino);
        }

        // 2. Mover la pieza principal
        tablero.setPieza(destino, piezaMovida);
        tablero.setPieza(origen, null);

        // 3. Ejecutar acciones especiales (enroque, EP)
        if (esEnroqueCorto || esEnroqueLargo) {
            int fila = origen.x();
            int torreOrigenY = esEnroqueCorto ? 7 : 0;
            int torreDestinoY = esEnroqueCorto ? 5 : 3;
            Posicion torreOrigenPos = new Posicion(fila, torreOrigenY);
            Posicion torreDestinoPos = new Posicion(fila, torreDestinoY);
            Pieza torre = tablero.obtenerPieza(torreOrigenPos);
            tablero.setPieza(torreDestinoPos, torre);
            tablero.setPieza(torreOrigenPos, null);
            if (torre != null) {
                torre.setMovida(true); // Marcar torre como movida
            }            // System.out.println("Movimiento.ejecutar(): Enroque ejecutado.");
        } else if (esCapturaEnPassant) {
            Posicion posPeonCapturado = new Posicion(origen.x(), destino.y());
            tablero.setPieza(posPeonCapturado, null); // Eliminar peón capturado EP
            // System.out.println("Movimiento.ejecutar(): Captura EP realizada en " + posPeonCapturado);

        }

        // 4. Marcar la pieza principal como movida
        piezaMovida.setMovida(true);

        // 5. El target En Passant se actualiza externamente (en MotorJuego)
    }

    // Determina si este movimiento crea una nueva casilla para En Passant
    public Posicion obtenerNuevoEnPassant() {
        // Solo si un peón avanzó dos casillas
        if (piezaMovida instanceof Peon && Math.abs(destino.x() - origen.x()) == 2) {
            int epFila = (origen.x() + destino.x()) / 2;
            int epColumna = origen.y();
            return new Posicion(epFila, epColumna);
        }
        return null; // No se crea casilla EP
    }

    // --- Getters ---
    public Posicion getOrigen() {
        return origen;
    }

    public Posicion getDestino() {
        return destino;
    }

    public Pieza getPiezaMovida() {
        return piezaMovida;
    }

    public Pieza getPiezaCapturada() {
        return piezaCapturada;
    }

    public boolean esEnroque() {
        return esEnroqueCorto || esEnroqueLargo;
    }

    public boolean esEnroqueCorto() {
        return esEnroqueCorto;
    }

    public boolean esEnroqueLargo() {
        return esEnroqueLargo;
    }

    public boolean esPromocion() {
        return esPromocion;
    }

    public boolean esCapturaEnPassant() {
        return esCapturaEnPassant;
    }

    public Posicion getEnPassantPrevio() {
        return enPassantPrevio;
    }

    // Crea una copia superficial (mismas referencias a piezas/tablero)
    public Movimiento copiar() {
        return new Movimiento(this.tablero, this.origen, this.destino,
                this.piezaMovida, this.piezaCapturada,
                this.esEnroqueCorto, this.esEnroqueLargo, this.esPromocion, this.esCapturaEnPassant,
                this.enPassantPrevio);
    }

    // Representación textual para debug (incluye O-O/O-O-O)
    @Override
    public String toString() {
        if (esEnroqueCorto) {
            return "O-O";
        }
        if (esEnroqueLargo) {
            return "O-O-O";
        }

        String piezaStr = (piezaMovida != null) ? piezaMovida.toString() : "?";
        String origenStr = (origen != null) ? origen.toString() : "??";
        String destinoStr = (destino != null) ? destino.toString() : "??";
        String capturaStr = (piezaCapturada != null) ? "x" : "-";
        String promoStr = esPromocion ? "=?" : "";
        String epStr = esCapturaEnPassant ? " e.p." : "";

        return piezaStr.toUpperCase() + origenStr + capturaStr + destinoStr + promoStr + epStr;
    }
}


Código Java 4 (EstadoJuego.java):

package ajedrez_gui;

/**
 * Enumera los posibles estados del juego de ajedrez, incluyendo turnos, jaques,
 * finales y estados especiales.
 */

public enum EstadoJuego {
    // Estados de turno
    TURNO_BLANCAS("TURNO BLANCAS"),
    TURNO_NEGRAS("TURNO NEGRAS"),
    // Estados de jaque (no final)
    JAQUE_BLANCAS("JAQUE!"), // Rey blanco amenazado
    JAQUE_NEGRAS("JAQUE!"), // Rey negro amenazado

    // Estados de fin de partida: Jaque Mate
    JAQUEMATE_GANA_NEGRAS("JAQUEMATE!!"), // Blancas pierden
    JAQUEMATE_GANA_BLANCAS("JAQUEMATE!!"), // Negras pierden

    // Estados de fin de partida: Empate
    AHOGADO("EMPATE AHOGADO"),
    EMPATE_50_MOV("EMPATE 50 MOV"),
    EMPATE_TRIPLE_REP("EMPATE X3 REP"),
    EMPATE_MATERIAL_INSUF("EMPATE MATERIAL"),
    // Estados especiales/intermedios
    PROMOCION_REQUIERIDA("PROMOCION PEON"), // Esperando elección de pieza
    MOVIMIENTO_INVALIDO("MOV NO VALIDO"), // Intento de movimiento ilegal
    ERROR_INTERNO("ERROR INTERNO"), // Error inesperado
    EN_CURSO("En curso");               // Estado genérico inicial/visual

    private final String mensaje; // Mensaje corto para UI

    EstadoJuego(String mensaje) {
        // Validar longitud máxima
        if (mensaje.length() > 16) {
            System.err.println("Advertencia: Mensaje de estado > 16 chars: " + mensaje);
        }
        this.mensaje = mensaje;
    }

    // Devuelve el mensaje asociado
    public String getMensaje() {
        return mensaje;
    }

    // Indica si el estado representa el final de la partida
    public boolean esFinDePartida() {
        return switch (this) {
            case JAQUEMATE_GANA_BLANCAS, JAQUEMATE_GANA_NEGRAS, AHOGADO, EMPATE_50_MOV, EMPATE_TRIPLE_REP, EMPATE_MATERIAL_INSUF ->
                true;
            default ->
                false;
        };
    }

    // Indica si es específicamente Jaque Mate
    public boolean esJaqueMate() {
        return this == JAQUEMATE_GANA_BLANCAS || this == JAQUEMATE_GANA_NEGRAS;
    }

    // Indica si es específicamente un Empate
    public boolean esEmpate() {
        return switch (this) {
            case AHOGADO, EMPATE_50_MOV, EMPATE_TRIPLE_REP, EMPATE_MATERIAL_INSUF ->
                true;
            default ->
                false;
        };
    }

    // Indica si es un Jaque (pero no mate)
    public boolean esJaque() {
        return this == JAQUE_BLANCAS || this == JAQUE_NEGRAS;
    }
}


Código Java 5 (Posicion.java):

package ajedrez_gui;

/**
 * Representa una posición (casilla) en el tablero de ajedrez usando coordenadas
 * (x, y) base 0. Implementado como un `record` para inmutabilidad y métodos
 * automáticos. x = fila (0='1', 7='8'), y = columna (0='a', 7='h').
 */

public record Posicion(int x, int y) {

    // Constructor canónico y getters (x(), y()) generados automáticamente.
    /**
     * Verifica si la posición está dentro de los límites del tablero (8x8).
     *
     * @return true si 0 <= x < 8 y 0 <= y < 8.
     */

    public boolean esValida() {
        return x >= 0 && x < Tablero.TAMANO_TABLERO && y >= 0 && y < Tablero.TAMANO_TABLERO;
    }

    /**
     * Convierte la posición a notación algebraica estándar (e.g., "a1", "h8").
     *
     * @return La notación como String, o "??" si es inválida.
     */

    @Override
    public String toString() {
        if (!esValida()) {
            return "??";
        }
        char file = (char) ('a' + y); // Columna 0..7 -> a..h
        char rank = (char) ('1' + x); // Fila 0..7 -> 1..8
        return "" + file + rank;
    }

    /**
     * Crea una Posicion desde notación algebraica (e.g., "e4", "H8").
     * Case-insensitive.
     *
     * @param notacionAlgebraica La notación (String).
     * @return La Posicion correspondiente, o null si la notación es inválida.
     */

    public static Posicion desdeNotacion(String notacionAlgebraica) {
        if (notacionAlgebraica == null || !notacionAlgebraica.matches("^[a-hA-H][1-8]$")) {
            // System.err.println("Error: Formato de notación inválido: " + notacionAlgebraica);
            return null;
        }
        String lowerNotacion = notacionAlgebraica.toLowerCase();
        int y = lowerNotacion.charAt(0) - 'a'; // a=0, h=7
        int x = Character.getNumericValue(lowerNotacion.charAt(1)) - 1; // 1=0, 8=7

        // Validar rangos (redundante con esValida pero bueno tenerlo)
        if (x < 0 || x >= Tablero.TAMANO_TABLERO || y < 0 || y >= Tablero.TAMANO_TABLERO) {
            // System.err.println("Error: Coordenadas fuera de rango: " + notacionAlgebraica);
            return null;
        }
        return new Posicion(x, y);
    }

}


No hay comentarios:

Publicar un comentario

Con la tecnología de Blogger.