Buscaminas (Minesweeper)
Implementación clásica del juego Buscaminas en Java con interfaz gráfica y patrón MVC
Descripción del Proyecto
Buscaminas es una implementación completa del clásico juego de lógica desarrollado en Java con interfaz gráfica Swing. El proyecto demuestra la aplicación del patrón arquitectónico MVC (Modelo-Vista-Controlador) y principios de programación orientada a objetos.
El juego incluye múltiples niveles de dificultad, interfaz intuitiva, sistema de puntuación y todas las funcionalidades esperadas del Buscaminas tradicional, incluyendo marcado de banderas y revelado automático de áreas seguras.
Capturas de Pantalla
Tablero de juego principal
Selección de dificultad
Pantalla de victoria
Estado de game over
Tecnologías Utilizadas
Java
Lenguaje principal
Swing GUI
Interfaz gráfica nativa
MVC Pattern
Arquitectura limpia
Event Handling
Gestión de eventos
Custom Graphics
Gráficos personalizados
Game Logic
Algoritmos de juego
Características Principales
Múltiples Dificultades
Tres niveles de dificultad: Principiante (9x9), Intermedio (16x16) y Experto (30x16).
Sistema de Banderas
Marcado de celdas sospechosas con banderas usando clic derecho.
Revelado Automático
Apertura automática de áreas seguras adyacentes para mayor fluidez.
Cronómetro
Seguimiento de tiempo de juego con registro de mejores marcas.
Sistema de Puntuación
Cálculo de puntuación basado en tiempo y dificultad completada.
Reinicio Rápido
Función de reinicio inmediato para comenzar una nueva partida.
Arquitectura MVC
Modelo (Model)
public class MinesweeperModel {
private Cell[][] board;
private int rows, cols, mineCount;
private int revealedCells;
private GameState gameState;
public class Cell {
private boolean isMine;
private boolean isRevealed;
private boolean isFlagged;
private int adjacentMines;
// Constructor y métodos getter/setter
}
public void initializeBoard(int rows, int cols, int mines) {
this.rows = rows;
this.cols = cols;
this.mineCount = mines;
this.board = new Cell[rows][cols];
// Inicializar celdas
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
board[i][j] = new Cell();
}
}
placeMines();
calculateAdjacentMines();
}
private void placeMines() {
Random random = new Random();
int minesPlaced = 0;
while (minesPlaced < mineCount) {
int row = random.nextInt(rows);
int col = random.nextInt(cols);
if (!board[row][col].isMine()) {
board[row][col].setMine(true);
minesPlaced++;
}
}
}
}
Vista (View)
public class MinesweeperView extends JFrame {
private JButton[][] buttons;
private JLabel timeLabel, mineCountLabel;
private JPanel gamePanel;
private MinesweeperController controller;
public MinesweeperView() {
initializeUI();
}
private void initializeUI() {
setTitle("Buscaminas - ArceApps");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
// Panel superior con información
JPanel infoPanel = new JPanel(new FlowLayout());
mineCountLabel = new JLabel("Minas: 0");
timeLabel = new JLabel("Tiempo: 0");
infoPanel.add(mineCountLabel);
infoPanel.add(timeLabel);
// Panel de juego
gamePanel = new JPanel();
add(infoPanel, BorderLayout.NORTH);
add(gamePanel, BorderLayout.CENTER);
// Menú
createMenuBar();
}
public void createBoard(int rows, int cols) {
gamePanel.removeAll();
gamePanel.setLayout(new GridLayout(rows, cols));
buttons = new JButton[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
buttons[i][j] = new JButton();
buttons[i][j].setPreferredSize(new Dimension(30, 30));
buttons[i][j].setFont(new Font("Arial", Font.BOLD, 12));
final int row = i, col = j;
// Click izquierdo - revelar celda
buttons[i][j].addActionListener(e ->
controller.handleCellClick(row, col));
// Click derecho - colocar bandera
buttons[i][j].addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
controller.handleFlagToggle(row, col);
}
}
});
gamePanel.add(buttons[i][j]);
}
}
pack();
setLocationRelativeTo(null);
}
}
Controlador (Controller)
public class MinesweeperController {
private MinesweeperModel model;
private MinesweeperView view;
private Timer gameTimer;
private int elapsedTime;
public MinesweeperController(MinesweeperModel model, MinesweeperView view) {
this.model = model;
this.view = view;
this.view.setController(this);
}
public void handleCellClick(int row, int col) {
if (model.getGameState() != GameState.PLAYING) {
return;
}
if (model.isCellFlagged(row, col)) {
return; // No se puede revelar una celda marcada
}
if (model.isCellRevealed(row, col)) {
// Doble click en celda revelada con número
handleNumberClick(row, col);
return;
}
boolean firstClick = model.getRevealedCells() == 0;
if (firstClick) {
startTimer();
}
if (model.isCellMine(row, col)) {
// Game Over
revealAllMines();
model.setGameState(GameState.LOST);
stopTimer();
view.showGameOverDialog();
} else {
revealCell(row, col);
if (model.isGameWon()) {
model.setGameState(GameState.WON);
stopTimer();
view.showVictoryDialog(elapsedTime);
}
}
view.updateDisplay();
}
private void revealCell(int row, int col) {
if (row < 0 || row >= model.getRows() ||
col < 0 || col >= model.getCols() ||
model.isCellRevealed(row, col) ||
model.isCellFlagged(row, col)) {
return;
}
model.revealCell(row, col);
view.updateCellDisplay(row, col);
// Si la celda no tiene minas adyacentes, revelar celdas vecinas
if (model.getAdjacentMines(row, col) == 0) {
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
revealCell(row + i, col + j);
}
}
}
}
}
Lógica del Juego
Algoritmo de Generación de Minas
Las minas se colocan aleatoriamente en el tablero evitando la primera celda clicada para garantizar una experiencia de juego justa.
Cálculo de Números
Cada celda calcula automáticamente el número de minas en sus 8 celdas adyacentes usando un algoritmo eficiente de búsqueda.
Revelado en Cascada
Cuando se revela una celda sin minas adyacentes, el algoritmo automáticamente revela todas las celdas conectadas hasta encontrar números o bordes.
Desafíos y Soluciones
1. Arquitectura Escalable
Desafío: Separar claramente la lógica del juego de la interfaz.
Solución: Implementación estricta del patrón MVC con interfaces bien definidas.
2. Performance en Revelado Masivo
Desafío: Mantener fluidez cuando se revelan muchas celdas a la vez.
Solución: Algoritmo optimizado de flood-fill con prevención de stack overflow.
3. Interfaz Responsiva
Desafío: Crear una interfaz que se sienta nativa y responsiva.
Solución: Uso cuidadoso de Swing con eventos optimizados y feedback visual inmediato.