Ir al contenido principal

Proyecto: Juego sencillo en Java

Introducción

Un aspecto fascinante de la programación es la posibilidad de crear juegos, donde podemos aplicar muchos de los conceptos que hemos aprendido hasta ahora en un contexto divertido y creativo. Los juegos constituyen un excelente ejercicio práctico ya que combinan múltiples aspectos de la programación: gestión de estados, interacción con el usuario, lógica condicional y, en algunos casos, representación gráfica. En este artículo, desarrollaremos un juego de "Adivina el número" con una interfaz por consola y luego evolucionaremos hacia una versión gráfica sencilla utilizando JavaFX.

A través de este proyecto práctico, reforzaremos conceptos fundamentales de Java como clases, objetos, control de flujo, manejo de excepciones y entrada/salida, mientras creamos algo entretenido que puede ser expandido y personalizado posteriormente.

Planificación del juego

Antes de empezar a programar, es importante definir qué queremos construir. Para nuestro juego "Adivina el número":

  • Objetivo del juego: El programa generará un número aleatorio entre 1 y 100, y el jugador deberá adivinarlo en la menor cantidad de intentos posible.
  • Mecánica: El jugador introduce un número y recibe pistas sobre si es mayor o menor que el número a adivinar.
  • Final del juego: El juego termina cuando el jugador adivina el número o decide rendirse.

Componentes necesarios

Para desarrollar este juego necesitaremos implementar:

  1. Generación de números aleatorios
  2. Gestión de la entrada del usuario
  3. Lógica del juego (comparaciones, conteo de intentos)
  4. Interfaz de usuario (primero por consola, luego gráfica)

Versión por consola

Comencemos con la implementación de la versión por consola del juego.

Estructura del proyecto

// AdivinaNumero.java
import java.util.Random;
import java.util.Scanner;

public class AdivinaNumero {
    private int numeroSecreto;
    private int intentos;
    private boolean juegoTerminado;
    private final Scanner scanner;
    
    public AdivinaNumero() {
        // Inicializamos las variables
        Random random = new Random();
        this.numeroSecreto = random.nextInt(100) + 1; // Número entre 1 y 100
        this.intentos = 0;
        this.juegoTerminado = false;
        this.scanner = new Scanner(System.in);
    }
    
    public void jugar() {
        System.out.println("¡Bienvenido a Adivina el Número!");
        System.out.println("He pensado un número entre 1 y 100.");
        System.out.println("Intenta adivinarlo en la menor cantidad de intentos posible.");
        
        while (!juegoTerminado) {
            System.out.print("\nIntroduce un número (o escribe 'rendirse' para terminar): ");
            String entrada = scanner.nextLine();
            
            // Comprobar si el jugador se rinde
            if (entrada.equalsIgnoreCase("rendirse")) {
                System.out.println("Te has rendido. El número era: " + numeroSecreto);
                juegoTerminado = true;
                continue;
            }
            
            // Procesar la entrada numérica
            try {
                int numeroUsuario = Integer.parseInt(entrada);
                procesarIntento(numeroUsuario);
            } catch (NumberFormatException e) {
                System.out.println("Por favor, introduce un número válido o escribe 'rendirse'.");
            }
        }
        
        scanner.close();
    }
    
    private void procesarIntento(int numeroUsuario) {
        intentos++;
        
        if (numeroUsuario < 1 || numeroUsuario > 100) {
            System.out.println("El número debe estar entre 1 y 100.");
            return;
        }
        
        if (numeroUsuario < numeroSecreto) {
            System.out.println("El número secreto es MAYOR.");
        } else if (numeroUsuario > numeroSecreto) {
            System.out.println("El número secreto es MENOR.");
        } else {
            System.out.println("¡CORRECTO! Has adivinado el número en " + intentos + " intentos.");
            juegoTerminado = true;
        }
    }
    
    public static void main(String[] args) {
        AdivinaNumero juego = new AdivinaNumero();
        juego.jugar();
    }
}

Explicación del código

  1. Atributos de la clase:

    • numeroSecreto: El número que el jugador debe adivinar
    • intentos: Contador de intentos realizados
    • juegoTerminado: Estado del juego
    • scanner: Objeto para leer la entrada del usuario
  2. Constructor:

    • Inicializamos el número secreto de forma aleatoria con la clase Random
    • Configuramos los valores iniciales para las demás variables
  3. Método jugar():

    • Muestra las instrucciones iniciales
    • Implementa el bucle principal del juego
    • Procesa la entrada del usuario y maneja la opción de rendirse
  4. Método procesarIntento():

    • Incrementa el contador de intentos
    • Valida que el número esté dentro del rango permitido
    • Compara el número del usuario con el número secreto
    • Proporciona pistas o indica si el jugador ha ganado
  5. Método main():

    • Punto de entrada del programa, crea una instancia del juego y lo inicia

Probando el juego

Para probar el juego, simplemente compila y ejecuta la clase AdivinaNumero:

javac AdivinaNumero.java
java AdivinaNumero

Deberías ver la bienvenida y poder comenzar a introducir números para intentar adivinar. El programa te proporcionará pistas sobre si tu número es mayor o menor que el número secreto.

Mejorando el juego: niveles de dificultad

Añadamos la posibilidad de seleccionar diferentes niveles de dificultad que cambiarán el rango de números.

// AdivinaNumeroMejorado.java
import java.util.Random;
import java.util.Scanner;

public class AdivinaNumeroMejorado {
    private int numeroSecreto;
    private int intentos;
    private boolean juegoTerminado;
    private final Scanner scanner;
    private int rangoMaximo;
    
    public AdivinaNumeroMejorado() {
        this.scanner = new Scanner(System.in);
        this.intentos = 0;
        this.juegoTerminado = false;
    }
    
    public void iniciar() {
        System.out.println("¡Bienvenido a Adivina el Número!");
        seleccionarDificultad();
        
        // Generar número aleatorio según la dificultad
        Random random = new Random();
        this.numeroSecreto = random.nextInt(rangoMaximo) + 1;
        
        jugar();
    }
    
    private void seleccionarDificultad() {
        System.out.println("\nSelecciona el nivel de dificultad:");
        System.out.println("1. Fácil (1-50)");
        System.out.println("2. Normal (1-100)");
        System.out.println("3. Difícil (1-200)");
        System.out.print("Introduce tu elección (1-3): ");
        
        try {
            int opcion = Integer.parseInt(scanner.nextLine());
            switch (opcion) {
                case 1:
                    rangoMaximo = 50;
                    System.out.println("Has seleccionado nivel Fácil (1-50)");
                    break;
                case 2:
                    rangoMaximo = 100;
                    System.out.println("Has seleccionado nivel Normal (1-100)");
                    break;
                case 3:
                    rangoMaximo = 200;
                    System.out.println("Has seleccionado nivel Difícil (1-200)");
                    break;
                default:
                    System.out.println("Opción no válida. Seleccionando nivel Normal por defecto.");
                    rangoMaximo = 100;
            }
        } catch (NumberFormatException e) {
            System.out.println("Entrada no válida. Seleccionando nivel Normal por defecto.");
            rangoMaximo = 100;
        }
    }
    
    public void jugar() {
        System.out.println("\nHe pensado un número entre 1 y " + rangoMaximo + ".");
        System.out.println("Intenta adivinarlo en la menor cantidad de intentos posible.");
        
        while (!juegoTerminado) {
            System.out.print("\nIntroduce un número (o escribe 'rendirse' para terminar): ");
            String entrada = scanner.nextLine();
            
            if (entrada.equalsIgnoreCase("rendirse")) {
                System.out.println("Te has rendido. El número era: " + numeroSecreto);
                juegoTerminado = true;
                continue;
            }
            
            try {
                int numeroUsuario = Integer.parseInt(entrada);
                procesarIntento(numeroUsuario);
            } catch (NumberFormatException e) {
                System.out.println("Por favor, introduce un número válido o escribe 'rendirse'.");
            }
        }
        
        preguntarNuevaPartida();
    }
    
    private void procesarIntento(int numeroUsuario) {
        intentos++;
        
        if (numeroUsuario < 1 || numeroUsuario > rangoMaximo) {
            System.out.println("El número debe estar entre 1 y " + rangoMaximo + ".");
            return;
        }
        
        if (numeroUsuario < numeroSecreto) {
            System.out.println("El número secreto es MAYOR.");
        } else if (numeroUsuario > numeroSecreto) {
            System.out.println("El número secreto es MENOR.");
        } else {
            System.out.println("¡CORRECTO! Has adivinado el número en " + intentos + " intentos.");
            juegoTerminado = true;
        }
    }
    
    private void preguntarNuevaPartida() {
        System.out.print("\n¿Quieres jugar otra partida? (s/n): ");
        String respuesta = scanner.nextLine();
        
        if (respuesta.equalsIgnoreCase("s")) {
            // Reiniciar el juego
            this.intentos = 0;
            this.juegoTerminado = false;
            iniciar();
        } else {
            System.out.println("¡Gracias por jugar! ¡Hasta pronto!");
            scanner.close();
        }
    }
    
    public static void main(String[] args) {
        AdivinaNumeroMejorado juego = new AdivinaNumeroMejorado();
        juego.iniciar();
    }
}

Nuevas características

  1. Niveles de dificultad: Ahora el jugador puede seleccionar entre tres niveles que modifican el rango de números posibles.
  2. Reinicio del juego: Al finalizar una partida, se ofrece la posibilidad de jugar nuevamente.
  3. Mejor estructura: Hemos reorganizado el código para separar mejor las diferentes funcionalidades.

Versión con interfaz gráfica

Ahora vamos a evolucionar nuestro juego para crear una versión con interfaz gráfica utilizando JavaFX. Para este ejemplo, asegúrate de tener configurado JavaFX en tu entorno de desarrollo.

// AdivinaNumeroGUI.java
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import java.util.Random;

public class AdivinaNumeroGUI extends Application {
    
    private int numeroSecreto;
    private int intentos;
    private int rangoMaximo;
    private boolean juegoTerminado;
    
    // Componentes de la interfaz
    private TextField campoNumero;
    private Button botonAdivinar;
    private Button botonReiniciar;
    private Text mensajeResultado;
    private Text contadorIntentos;
    private ComboBox<String> comboNivel;
    
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Adivina el Número");
        
        // Configuración inicial
        rangoMaximo = 100; // Por defecto
        juegoTerminado = false;
        intentos = 0;
        generarNumeroSecreto();
        
        // Crear la interfaz
        GridPane grid = crearInterfaz();
        
        // Configurar la escena
        Scene scene = new Scene(grid, 400, 350);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    private GridPane crearInterfaz() {
        GridPane grid = new GridPane();
        grid.setAlignment(Pos.CENTER);
        grid.setHgap(10);
        grid.setVgap(10);
        grid.setPadding(new Insets(25, 25, 25, 25));
        
        // Título
        Text titulo = new Text("Adivina el Número");
        titulo.setFont(Font.font("Tahoma", FontWeight.BOLD, 20));
        grid.add(titulo, 0, 0, 2, 1);
        
        // Selección de nivel
        Label nivelLabel = new Label("Nivel de dificultad:");
        grid.add(nivelLabel, 0, 1);
        
        comboNivel = new ComboBox<>();
        comboNivel.getItems().addAll("Fácil (1-50)", "Normal (1-100)", "Difícil (1-200)");
        comboNivel.setValue("Normal (1-100)");
        comboNivel.setOnAction(e -> cambiarNivel());
        grid.add(comboNivel, 1, 1);
        
        // Instrucciones
        Text instrucciones = new Text("He pensado un número entre 1 y " + rangoMaximo + 
                                     ". ¡Intenta adivinarlo!");
        grid.add(instrucciones, 0, 2, 2, 1);
        
        // Campo para introducir número
        Label numeroLabel = new Label("Tu número:");
        grid.add(numeroLabel, 0, 3);
        
        campoNumero = new TextField();
        grid.add(campoNumero, 1, 3);
        
        // Botones
        HBox hbBotones = new HBox(10);
        botonAdivinar = new Button("Adivinar");
        botonAdivinar.setOnAction(e -> procesarIntento());
        
        botonReiniciar = new Button("Nueva partida");
        botonReiniciar.setOnAction(e -> reiniciarJuego());
        
        hbBotones.getChildren().addAll(botonAdivinar, botonReiniciar);
        hbBotones.setAlignment(Pos.CENTER);
        grid.add(hbBotones, 0, 4, 2, 1);
        
        // Mensaje de resultado
        mensajeResultado = new Text();
        mensajeResultado.setFont(Font.font("Tahoma", FontWeight.NORMAL, 14));
        grid.add(mensajeResultado, 0, 5, 2, 1);
        
        // Contador de intentos
        contadorIntentos = new Text("Intentos: 0");
        contadorIntentos.setFont(Font.font("Tahoma", FontWeight.NORMAL, 14));
        grid.add(contadorIntentos, 0, 6, 2, 1);
        
        return grid;
    }
    
    private void generarNumeroSecreto() {
        Random random = new Random();
        numeroSecreto = random.nextInt(rangoMaximo) + 1;
    }
    
    private void cambiarNivel() {
        String nivelSeleccionado = comboNivel.getValue();
        
        if (nivelSeleccionado.startsWith("Fácil")) {
            rangoMaximo = 50;
        } else if (nivelSeleccionado.startsWith("Normal")) {
            rangoMaximo = 100;
        } else {
            rangoMaximo = 200;
        }
        
        reiniciarJuego();
    }
    
    private void procesarIntento() {
        if (juegoTerminado) {
            mensajeResultado.setText("El juego ha terminado. Inicia una nueva partida.");
            mensajeResultado.setFill(Color.BLUE);
            return;
        }
        
        try {
            int numeroUsuario = Integer.parseInt(campoNumero.getText());
            intentos++;
            contadorIntentos.setText("Intentos: " + intentos);
            
            if (numeroUsuario < 1 || numeroUsuario > rangoMaximo) {
                mensajeResultado.setText("El número debe estar entre 1 y " + rangoMaximo);
                mensajeResultado.setFill(Color.ORANGE);
                return;
            }
            
            if (numeroUsuario < numeroSecreto) {
                mensajeResultado.setText("El número secreto es MAYOR");
                mensajeResultado.setFill(Color.RED);
            } else if (numeroUsuario > numeroSecreto) {
                mensajeResultado.setText("El número secreto es MENOR");
                mensajeResultado.setFill(Color.RED);
            } else {
                mensajeResultado.setText("¡CORRECTO! Has adivinado en " + intentos + " intentos");
                mensajeResultado.setFill(Color.GREEN);
                juegoTerminado = true;
            }
            
        } catch (NumberFormatException e) {
            mensajeResultado.setText("Por favor, introduce un número válido");
            mensajeResultado.setFill(Color.ORANGE);
        }
        
        campoNumero.clear();
        campoNumero.requestFocus();
    }
    
    private void reiniciarJuego() {
        juegoTerminado = false;
        intentos = 0;
        generarNumeroSecreto();
        
        campoNumero.clear();
        mensajeResultado.setText("");
        contadorIntentos.setText("Intentos: 0");
        
        campoNumero.setDisable(false);
        botonAdivinar.setDisable(false);
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

Explicación de la versión gráfica

  1. Componentes de JavaFX:

    • GridPane: Como contenedor principal para organizar los elementos
    • TextField: Para que el usuario introduzca su número
    • Button: Para las acciones "Adivinar" y "Nueva partida"
    • ComboBox: Para seleccionar el nivel de dificultad
    • Text: Para mostrar mensajes y resultados
  2. Estructura del código:

    • start(): Método principal que configura la aplicación
    • crearInterfaz(): Construye la interfaz gráfica
    • procesarIntento(): Maneja la lógica del juego
    • cambiarNivel() y reiniciarJuego(): Gestionan el cambio de configuración
  3. Características adicionales:

    • Retroalimentación visual con colores según el resultado
    • Interfaz intuitiva con botones y campos de texto
    • Mayor interactividad con selección de nivel mediante menú desplegable

Ejecución de la versión gráfica

Para ejecutar la versión gráfica, necesitas tener JavaFX configurado en tu entorno. La forma de ejecutar dependerá de tu configuración:

javac --module-path /ruta/a/javafx-sdk/lib --add-modules javafx.controls,javafx.fxml AdivinaNumeroGUI.java
java --module-path /ruta/a/javafx-sdk/lib --add-modules javafx.controls,javafx.fxml AdivinaNumeroGUI

Ampliaciones posibles

Una vez que tienes la base del juego funcionando, puedes considerar estas mejoras:

  1. Sistema de puntuación: Almacenar las mejores puntuaciones (menos intentos) en un archivo.
  2. Mejoras visuales: Añadir animaciones, efectos de sonido, o imágenes.
  3. Diferentes modos de juego: Por ejemplo, con límite de tiempo o número máximo de intentos.
  4. Multijugador: Permitir que dos jugadores compitan por turnos.

Ejemplo: Añadir un sistema de puntuación

// Fragmento para guardar puntuaciones
private void guardarPuntuacion(String nombre, int intentos, String nivel) {
    try (FileWriter fw = new FileWriter("puntuaciones.txt", true);
         BufferedWriter bw = new BufferedWriter(fw);
         PrintWriter out = new PrintWriter(bw)) {
        out.println(nombre + "," + intentos + "," + nivel + "," + new Date());
    } catch (IOException e) {
        System.err.println("Error al guardar la puntuación: " + e.getMessage());
    }
}

// Fragmento para cargar puntuaciones
private List<String[]> cargarPuntuaciones() {
    List<String[]> puntuaciones = new ArrayList<>();
    try (BufferedReader br = new BufferedReader(new FileReader("puntuaciones.txt"))) {
        String linea;
        while ((linea = br.readLine()) != null) {
            puntuaciones.add(linea.split(","));
        }
    } catch (IOException e) {
        System.err.println("Error al cargar las puntuaciones: " + e.getMessage());
    }
    return puntuaciones;
}

Resumen

En este proyecto, hemos creado un juego "Adivina el número" en Java, primero con una interfaz de consola y luego con una interfaz gráfica utilizando JavaFX. A lo largo del desarrollo, hemos aplicado numerosos conceptos importantes de Java:

  • Programación orientada a objetos: creando clases con atributos y métodos
  • Manejo de entrada/salida: tanto por consola como con interfaz gráfica
  • Control de flujo: con condicionales para la lógica del juego
  • Gestión de excepciones: para manejar entradas incorrectas
  • Interfaz de usuario: desarrollo de una UI interactiva con JavaFX

Este proyecto sirve como excelente punto de partida para explorar el desarrollo de juegos en Java. Puedes expandirlo con las sugerencias mencionadas o incluso aventurarte a crear juegos más complejos utilizando los mismos principios fundamentales. ¡La programación de juegos es una forma divertida y efectiva de mejorar tus habilidades de programación en Java!