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