Este tutorial aborda la construcción de una aplicación gráfica de rompecabezas deslizante usando Java Swing. El desarrollo se enfoca en la aplicación de conceptos de programación orientada a objetos, como la herencia, para crear interfaces de usuario interactivas y gestionar la lógica del juego.
La Herencia en el Desarrollo de GUI
La herencia es un pilar fundamental en la programación orientada a objetos. Permite que una clase (subclase) herede atributos y métodos de otra clase (superclase), promoviendo la reutilización de código y un diseño jerárquico. En el contexto de las interfaces gráficas con Java Swing, es una práctica común extender la clase JFrame para crear ventanas personalizadas, aprovcehando todo el comportamiento base que esta proporciona.
Por ejemplo, al crear una ventana de inicio de sesión, podemos definir nuestra propia clase que hereda de JFrame. Esto nos da acceso directo a métodos como setBounds(), setLayout() o setVisible(), que son heredados de la jerarquía de clases de Swing, donde Component es una superclase común.
Implementación del Rompecabezas Gráfico
El proyecto consiste en una cuadrícula 4x4 donde se desordena una imagen dividida en 15 fichas, dejando un espacio vacío. El jugador debe reordenar las fichas moviendo el espacio vacío hasta recomponer la imagen original.
Configuración de la Ventana Principal
La ventana principle se define mediante una clase que hereda de JFrame. Su constructor inicializa las propiedades básicas de la ventana y dibuja los componentes de la interfaz.
import javax.swing.*;
import java.awt.event.*;
import java.util.Random;
public class PuzzleWindow extends JFrame {
private int[][] tileMatrix = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0}
};
private int emptyRow, emptyCol;
private JPanel gridPanel;
public PuzzleWindow() {
configureWindow();
shuffleTiles();
drawComponents();
this.setVisible(true);
}
private void configureWindow() {
this.setSize(960, 565);
this.setTitle("Puzzle Gráfico");
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setAlwaysOnTop(true);
this.setLayout(null);
}
// Métodos para dibujar componentes y lógica del juego...
}
Dibujado de la Interfaz y Mezcla de Fichas
La interfaz se compone de un panel que contiene la cuadrícula de imágenes, botones de dirección para mover el espacio vacío, y botones de acción como "ayuda" y "reiniciar". La lógica para mezclar las fichas recorre la matriz intercambiando elementos aleatoriamente. Posteriormente, se localiza la posición del espacio vacío (representado por el valor 0) para registrar su fila y columna iniciales.
private void shuffleTiles() {
Random randomGenerator = new Random();
for (int row = 0; row < tileMatrix.length; row++) {
for (int col = 0; col < tileMatrix[row].length; col++) {
int randRow = randomGenerator.nextInt(tileMatrix.length);
int randCol = randomGenerator.nextInt(tileMatrix[randRow].length);
// Intercambio de elementos
int tempValue = tileMatrix[row][col];
tileMatrix[row][col] = tileMatrix[randRow][randCol];
tileMatrix[randRow][randCol] = tempValue;
}
}
locateEmptyTile();
}
private void locateEmptyTile() {
for (int row = 0; row < tileMatrix.length; row++) {
for (int col = 0; col < tileMatrix[row].length; col++) {
if (tileMatrix[row][col] == 0) {
emptyRow = row;
emptyCol = col;
return;
}
}
}
}
Lógica de Movimeinto y Eventos
El movimiento se implementa mediante el intercambio de la posición del espacio vacío con una ficha adyacente, seguido de un redibujado del panel de imágenes. Cada botón de dirección tiene un manejador de eventos que verifica los límites de la matriz antes de realizar el intercambio. Tras cada movimiento, se comprueba si la configuración actual coincide con la imagen original (la solución).
private void addEventListeners() {
JButton upBtn = new JButton("^");
upBtn.addActionListener(e -> moveEmptyTile(1, 0));
JButton leftBtn = new JButton("<");
leftBtn.addActionListener(e -> moveEmptyTile(0, 1));
JButton downBtn = new JButton("v");
downBtn.addActionListener(e -> moveEmptyTile(-1, 0));
JButton rightBtn = new JButton(">");
rightBtn.addActionListener(e -> moveEmptyTile(0, -1));
// Añadir botones al panel...
}
private void moveEmptyTile(int rowDelta, int colDelta) {
int targetRow = emptyRow + rowDelta;
int targetCol = emptyCol + colDelta;
if (isValidPosition(targetRow, targetCol)) {
// Intercambiar valores en la matriz
tileMatrix[emptyRow][emptyCol] = tileMatrix[targetRow][targetCol];
tileMatrix[targetRow][targetCol] = 0;
emptyRow = targetRow;
emptyCol = targetCol;
repaintGrid();
checkVictory();
}
}
private boolean isValidPosition(int row, int col) {
return row >= 0 && row < 4 && col >= 0 && col < 4;
}
Funcionalidades de Ayuda y Reinicio
La función de ayuda establece directamente la matriz de fichas en su estado resuelto y desactiva los botones de movimiento. La función de reinicio restaura la matriz a su orden inicial y vuelve a mezclarla aleatoriamente, reactivando los controles para iniciar una nueva partida.
private void revealSolution() {
tileMatrix = new int[][]{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0}
};
locateEmptyTile();
repaintGrid();
setDirectionButtonsEnabled(false);
}
private void resetGame() {
tileMatrix = new int[][]{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0}
};
shuffleTiles();
repaintGrid();
setDirectionButtonsEnabled(true);
}
private void repaintGrid() {
gridPanel.removeAll();
for (int row = 0; row < tileMatrix.length; row++) {
for (int col = 0; col < tileMatrix[row].length; col++) {
int tileId = tileMatrix[row][col];
String imagePath = "images/" + tileId + ".png";
JLabel tileLabel = new JLabel(new ImageIcon(imagePath));
tileLabel.setBounds(col * 90, row * 90, 90, 90);
gridPanel.add(tileLabel);
}
}
gridPanel.revalidate();
gridPanel.repaint();
}
La clase principal de la aplicación instancia la ventana del rompecabezas para iniciar el programa.
public class PuzzleApp {
public static void main(String[] args) {
new PuzzleWindow();
}
}