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:
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.
Suscribirse a:
Enviar comentarios (Atom)
Con la tecnología de Blogger.
No hay comentarios:
Publicar un comentario