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.5: Representación de las Piezas.

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 (2025)
   .MotorJuego.java
   .Tablero.java
   .Movimiento.java
   .EstadoJuego.java
   .Posicion.java

>6 Representación de las Piezas
   .Pieza.java
   .Peon.java
   .Caballo.java
   .Alfil.java
   .Torre.java
   .Dama.java
   .Rey.java



Con este último paso, en el que se han implementado las clases de las piezas, hemos alcanzado el punto en el que la aplicación está lista para ser compilada y utilizada para jugar.


Código Java 1 (Pieza.java):

package ajedrez_gui;

/**
 * Clase base abstracta y sellada para todas las piezas de ajedrez. Define
 * propiedades comunes y métodos abstractos/protegidos.
 */

public abstract sealed class Pieza permits Alfil, Caballo, Dama, Peon, Rey, Torre {

    protected final boolean esBlanca; // Color de la pieza
    protected boolean haMovido;      // Flag para reglas especiales (enroque, mov peón)

    public Pieza(boolean esBlanca) {
        this.esBlanca = esBlanca;
        this.haMovido = false;
    }

    // Getters y Setters
    public boolean esBlanca() {
        return esBlanca;
    }

    public boolean haMovido() {
        return haMovido;
    }

    public void setMovida(boolean movida) {
        this.haMovido = movida;
    }

    /**
     * Verifica si un movimiento es válido según las reglas básicas de la pieza,
     * sin considerar jaques propios.
     *
     * @param origen Posición actual.
     * @param destino Posición deseada.
     * @param tablero Estado actual del tablero.
     * @return true si el movimiento básico es válido.
     */

    public abstract boolean esMovimientoBasicoValido(Posicion origen, Posicion destino, Tablero tablero);

    /**
     * Verifica si una casilla está vacía o contiene una pieza enemiga.
     */

    protected boolean esCasillaVaciaOEnemiga(Posicion pos, Tablero tablero) {
        if (!pos.esValida()) {
            return false;
        }
        Pieza piezaDestino = tablero.obtenerPieza(pos);
        return piezaDestino == null || piezaDestino.esBlanca() != this.esBlanca;
    }

    /**
     * Verifica si el camino recto (horizontal/vertical) está libre hasta la
     * casilla destino (exclusive).
     */

    protected boolean esCaminoLibreRecto(Posicion origen, Posicion destino, Tablero tablero) {
        int dx = Integer.compare(destino.x(), origen.x());
        int dy = Integer.compare(destino.y(), origen.y());
        if (dx != 0 && dy != 0) {
            return false; // No es recto
        }
        if (dx == 0 && dy == 0) {
            return true;  // Mismo origen/destino (camino trivialmente libre)
        }
        int x = origen.x() + dx;
        int y = origen.y() + dy;
        while (x != destino.x() || y != destino.y()) {
            Posicion actual = new Posicion(x, y);
            // if (!actual.esValida()) return false; // No debería pasar si destino es válido
            if (tablero.obtenerPieza(actual) != null) {
                return false; // Bloqueado
            }
            x += dx;
            y += dy;
        }
        return true; // Camino libre
    }

    /**
     * Verifica si el camino diagonal está libre hasta la casilla destino
     * (exclusive).
     */

    protected boolean esCaminoLibreDiagonal(Posicion origen, Posicion destino, Tablero tablero) {
        int dx_total = destino.x() - origen.x();
        int dy_total = destino.y() - origen.y();
        if (Math.abs(dx_total) != Math.abs(dy_total) || dx_total == 0) {
            return false; // No es diagonal
        }
        int dx_step = Integer.compare(dx_total, 0);
        int dy_step = Integer.compare(dy_total, 0);
        int x = origen.x() + dx_step;
        int y = origen.y() + dy_step;

        while (x != destino.x() || y != destino.y()) {
            Posicion actual = new Posicion(x, y);
            // if (!actual.esValida()) return false;
            if (tablero.obtenerPieza(actual) != null) {
                return false; // Bloqueado
            }
            x += dx_step;
            y += dy_step;
        }
        return true; // Camino libre
    }

    /**
     * Crea una copia de la pieza con su estado actual.
     */

    public abstract Pieza copiar();

    /**
     * Devuelve la representación estándar de la pieza (mayúscula/minúscula).
     * P/p, N/n, B/b, R/r, Q/q, K/k.
     */

    @Override
    public abstract String toString();
}


Código Java 2 (Peon.java):

package ajedrez_gui;

/**
 * Representa la pieza Peón. Clase final que implementa la interfaz sellada
 * Pieza.
 */

public final class Peon extends Pieza {

    public Peon(boolean esBlanca) {
        super(esBlanca);
    }

    @Override
    public boolean esMovimientoBasicoValido(Posicion origen, Posicion destino, Tablero tablero) {
        if (!destino.esValida()) {
            return false;
        }

        int direccion = esBlanca() ? 1 : -1; // 1: blancas (arriba), -1: negras (abajo)
        int dx = destino.x() - origen.x(); // Cambio fila
        int dy = destino.y() - origen.y(); // Cambio columna
        Pieza piezaDestino = tablero.obtenerPieza(destino);

        // 1. Avance simple (1 casilla)
        if (dx == direccion && dy == 0 && piezaDestino == null) {
            return true;
        }

        // 2. Avance doble inicial (2 casillas)
        boolean enFilaInicial = (esBlanca() && origen.x() == Tablero.FILA_PEONES_BLANCOS)
                || (!esBlanca() && origen.x() == Tablero.FILA_PEONES_NEGROS);
        Posicion posIntermedia = new Posicion(origen.x() + direccion, origen.y());
        if (dx == 2 * direccion && dy == 0 && enFilaInicial
                && tablero.obtenerPieza(posIntermedia) == null && piezaDestino == null) {
            return true;
        }

        // 3. Captura normal (diagonal)
        if (dx == direccion && Math.abs(dy) == 1 && piezaDestino != null && piezaDestino.esBlanca() != this.esBlanca()) {
            return true;
        }

        // 4. Captura En Passant
        Posicion enPassantTarget = tablero.getEnPassant();
        if (enPassantTarget != null && destino.equals(enPassantTarget)
                && dx == direccion && Math.abs(dy) == 1) {
            // Validar que realmente haya un peón enemigo para capturar EP
            // (Aunque Movimiento.determinarDetalles lo hace, es bueno tenerlo aquí también)

            Posicion peonCapturadoPos = new Posicion(origen.x(), destino.y());
            Pieza peonACapturar = tablero.obtenerPieza(peonCapturadoPos);
            if (peonACapturar instanceof Peon && peonACapturar.esBlanca() != this.esBlanca()) {
                return true; // Movimiento hacia casilla EP es válido
            }
        }

        return false; // Ninguna regla básica se cumplió
    }

    @Override
    public Pieza copiar() {
        var copia = new Peon(this.esBlanca);
        copia.setMovida(this.haMovido);
        return copia;
    }

    @Override
    public String toString() {
        // Notación estándar: P para blanco, p para negro
        return esBlanca() ? "P" : "p";
    }
}


Código Java 3 (Caballo.java):

package ajedrez_gui;

/**
 * Representa la pieza Caballo. Se mueve en "L". Clase final que implementa la
 * interfaz sellada Pieza.
 */

public final class Caballo extends Pieza {

    public Caballo(boolean esBlanca) {
        super(esBlanca);
    }

    @Override
    public boolean esMovimientoBasicoValido(Posicion origen, Posicion destino, Tablero tablero) {
        if (!destino.esValida()) {
            return false;
        }
        // Movimiento en "L": 2 casillas en una dirección (x o y) y 1 en la perpendicular
        int dxAbs = Math.abs(destino.x() - origen.x());
        int dyAbs = Math.abs(destino.y() - origen.y());

        if ((dxAbs == 2 && dyAbs == 1) || (dxAbs == 1 && dyAbs == 2)) {
            // Caballo salta, no necesita camino libre, solo verificar casilla destino
            return esCasillaVaciaOEnemiga(destino, tablero);
        }
        return false;
    }

    @Override
    public Pieza copiar() {
        var copia = new Caballo(this.esBlanca);
        copia.setMovida(this.haMovido);
        return copia;
    }

    @Override
    public String toString() {
        // Notación estándar: N para blanco, n para negro (Knight)
        return esBlanca() ? "N" : "n";
    }
}


Código Java 4 (Alfil.java):

package ajedrez_gui;

/**
 * Representa la pieza Alfil. Se mueve en diagonal. Clase final que implementa
 * la interfaz sellada Pieza.
 */

public final class Alfil extends Pieza {

    public Alfil(boolean esBlanca) {
        super(esBlanca);
    }

    @Override
    public boolean esMovimientoBasicoValido(Posicion origen, Posicion destino, Tablero tablero) {
        if (!destino.esValida() || origen.equals(destino)) {
            return false;
        }
        // Movimiento diagonal: cambio absoluto en X debe ser igual al cambio absoluto en Y
        int dxAbs = Math.abs(destino.x() - origen.x());
        int dyAbs = Math.abs(destino.y() - origen.y());

        // Debe ser diagonal (dxAbs == dyAbs) y moverse al menos una casilla (dxAbs > 0)
        if (dxAbs == dyAbs) {
            // Verificar camino libre y si la casilla destino es válida (vacía o enemiga)
            return esCaminoLibreDiagonal(origen, destino, tablero) && esCasillaVaciaOEnemiga(destino, tablero);
        }
        return false;
    }

    @Override
    public Pieza copiar() {
        var copia = new Alfil(this.esBlanca);
        copia.setMovida(this.haMovido);
        return copia;
    }

    @Override
    public String toString() {
        // Notación estándar: B para blanco, b para negro (Bishop)
        return esBlanca() ? "B" : "b";
    }
}


Código Java 5 (Torre.java):

package ajedrez_gui;

/**
 * Representa la pieza Torre. Se mueve horizontal o verticalmente. Clase final
 * que implementa la interfaz sellada Pieza.
 */

public final class Torre extends Pieza {

    public Torre(boolean esBlanca) {
        super(esBlanca);
    }

    @Override
    public boolean esMovimientoBasicoValido(Posicion origen, Posicion destino, Tablero tablero) {
        if (!destino.esValida() || origen.equals(destino)) {
            return false;
        }
        // Movimiento debe ser puramente horizontal o vertical
        boolean esMovimientoRecto = (origen.x() == destino.x() && origen.y() != destino.y())
                || (origen.x() != destino.x() && origen.y() == destino.y());

        if (esMovimientoRecto) {
            // Verificar camino libre y casilla destino válida
            return esCaminoLibreRecto(origen, destino, tablero) && esCasillaVaciaOEnemiga(destino, tablero);
        }
        return false;
    }

    @Override
    public Pieza copiar() {
        var copia = new Torre(this.esBlanca);
        copia.setMovida(this.haMovido);
        return copia;
    }

    @Override
    public String toString() {
        // Notación estándar: R para blanco, r para negro (Rook)
        return esBlanca() ? "R" : "r";
    }
}


Código Java 6 (Dama.java):

package ajedrez_gui;

/**
 * Representa la pieza Dama (Reina). Se mueve como Torre y Alfil combinados.
 * Clase final que implementa la interfaz sellada Pieza.
 */

public final class Dama extends Pieza {

    public Dama(boolean esBlanca) {
        super(esBlanca);
    }

    @Override
    public boolean esMovimientoBasicoValido(Posicion origen, Posicion destino, Tablero tablero) {
        if (!destino.esValida() || origen.equals(destino)) {
            return false;
        }
        int dx = destino.x() - origen.x();
        int dy = destino.y() - origen.y();
        int dxAbs = Math.abs(dx);
        int dyAbs = Math.abs(dy);

        // Movimiento recto (como Torre)
        boolean esMovimientoRecto = (dx == 0 && dy != 0) || (dx != 0 && dy == 0);
        // Movimiento diagonal (como Alfil)
        boolean esMovimientoDiagonal = dxAbs == dyAbs; // dxAbs > 0 está implícito si no es recto y origen!=destino

        if (esMovimientoRecto) {
            return esCaminoLibreRecto(origen, destino, tablero) && esCasillaVaciaOEnemiga(destino, tablero);
        } else if (esMovimientoDiagonal) {
            return esCaminoLibreDiagonal(origen, destino, tablero) && esCasillaVaciaOEnemiga(destino, tablero);
        }

        return false; // No es ni recto ni diagonal
    }

    @Override
    public Pieza copiar() {
        var copia = new Dama(this.esBlanca);
        copia.setMovida(this.haMovido);
        return copia;
    }

    @Override
    public String toString() {
        // Notación estándar: Q para blanco, q para negro (Queen)
        return esBlanca() ? "Q" : "q";
    }
}


Código Java 7 (Rey.java):

package ajedrez_gui;

/**
 * Representa la pieza Rey. Se mueve una casilla en cualquier dirección. Tiene
 * el movimiento especial de Enroque. Clase final que implementa la interfaz
 * sellada Pieza.
 */

public final class Rey extends Pieza {

    public Rey(boolean esBlanca) {
        super(esBlanca);
    }

    @Override
    public boolean esMovimientoBasicoValido(Posicion origen, Posicion destino, Tablero tablero) {
        if (!destino.esValida()) {
            return false;
        }

        int dx_abs = Math.abs(destino.x() - origen.x());
        int dy_abs = Math.abs(destino.y() - origen.y());

        // 1. Movimiento normal (1 casilla)
        if (dx_abs <= 1 && dy_abs <= 1 && (dx_abs != 0 || dy_abs != 0)) {
            return esCasillaVaciaOEnemiga(destino, tablero);
        }

        // 2. Movimiento de Enroque (2 casillas horizontalmente)
        // Solo posible si el rey no se ha movido

        if (!this.haMovido && dx_abs == 0 && dy_abs == 2) {
            // Determinar si es corto (derecha) o largo (izquierda)
            boolean ladoCorto = destino.y() > origen.y();
            // La validación completa del enroque (camino libre, no jaque, etc.)
            return validarEnroqueCompleto(origen, ladoCorto, tablero);
        }

        return false; // Ni movimiento normal ni enroque
    }

    /**
     * Valida todas las condiciones para un enroque. Llamado desde
     * esMovimientoBasicoValido.
     */

    private boolean validarEnroqueCompleto(Posicion posRey, boolean ladoCorto, Tablero tablero) {
        // 1. Verificar derechos de enroque (flags del tablero)
        if (!tablero.puedeEnrocar(this.esBlanca, ladoCorto)) {
            return false;
        }

        // 2. Rey no puede estar en jaque actualmente
        if (tablero.estaEnJaque(this.esBlanca)) {
            return false;
        }

        int fila = posRey.x();
        int colRey = posRey.y();
        int direccion = ladoCorto ? 1 : -1; // 1 para corto, -1 para largo
        int colTorre = ladoCorto ? 7 : 0; // Columna H o A

        // 3. Casillas entre rey y torre deben estar vacías
        int inicio = Math.min(colRey, colTorre) + 1;
        int fin = Math.max(colRey, colTorre);
        for (int col = inicio; col < fin; col++) {
            if (tablero.obtenerPieza(new Posicion(fila, col)) != null) {
                return false; // Camino bloqueado
            }
        }

        // 4. Casillas por las que pasa el rey no deben estar atacadas por el oponente
        Posicion casillaPaso1 = new Posicion(fila, colRey + direccion);
        Posicion casillaPaso2 = new Posicion(fila, colRey + 2 * direccion); // Destino final del rey
        if (tablero.estaAtacada(casillaPaso1, !this.esBlanca)
                || tablero.estaAtacada(casillaPaso2, !this.esBlanca)) {
            return false; // Rey pasa por o termina en jaque
        }

        // 5. Verificar si la torre está en su sitio y no se ha movido
        // (Esto es redundante si los flags de enroque se manejan correctamente, pero es una buena doble verificación)

        Posicion posTorre = new Posicion(fila, colTorre);
        Pieza piezaTorre = tablero.obtenerPieza(posTorre);
        if (!(piezaTorre instanceof Torre) || piezaTorre.esBlanca() != this.esBlanca || piezaTorre.haMovido()) {
            // System.err.println("Inconsistencia enroque: flag OK, pero torre movida/ausente.");
            return false; // No se puede enrocar si la torre no está o ya se movió
        }

        return true; // Todas las condiciones se cumplen
    }

    @Override
    public Pieza copiar() {
        var copia = new Rey(this.esBlanca);
        copia.setMovida(this.haMovido);
        return copia;
    }

    @Override
    public String toString() {
        // Notación estándar: K para blanco, k para negro (King)
        return esBlanca() ? "K" : "k";
    }
}


Resultado final:





No hay comentarios:

Publicar un comentario

Con la tecnología de Blogger.