Juego Java Completado

Buscaminas (Minesweeper)

Implementación clásica del juego Buscaminas en Java con interfaz gráfica y patrón MVC

Buscaminas Game

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

Juego en progreso

Tablero de juego principal

Configuración

Selección de dificultad

Victoria

Pantalla de victoria

Game Over

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.