Introducción
En este enlace puedes encontrar una presentación explicando el código, y a continuación dispones de una infografía y un vídeo explicativo.

Todo el código
import java.util.Random;
import java.util.Scanner;
/**
* Juego de "Hundir la Flota" (Versión un jugador).
* El objetivo es encontrar todos los barcos enemigos ocultos en el tablero
* antes de que se agoten los intentos (misiles) disponibles.
*
* Este ejemplo refuerza el uso de matrices para ocultar información (niebla de guerra)
* y la gestión de estados mediante Enums.
*/
public class Main {
// --- ENUMS: CONFIGURACIÓN Y ESTÉTICA ---
/**
* Define los tipos de barcos y sus longitudes.
*/
enum TipoBarco {
PORTAAVIONES(5),
ACORAZADO(4),
CRUCERO(3),
SUBMARINO(3),
DESTRUCTOR(2);
private final int longitud;
TipoBarco(int longitud) {
this.longitud = longitud;
}
public int getLongitud() {
return longitud;
}
}
/**
* Enum que define los colores usando formato RGB (Red, Green, Blue).
* Esto permite usar cualquier color de los 16 millones disponibles.
*/
enum EstadoCasilla {
// Símbolo y color en formato RGB
AGUA ( "~", 0, 180, 255), // Azul cielo (agua)
BARCO ( "B", 160, 32, 240), // Púrpura (barco sin impacto)
TOCADO ( "X", 255, 0, 0), // Rojo puro (impacto)
FALLO ( "o", 180, 180, 180); // Gris claro (fallo)
private final String simbolo;
private final String codigoColor;
private static final String RESET = "\u001B[0m";
/**
* Constructor que acepta valores RGB (0-255).
* Convierte los números a la secuencia ANSI TrueColor automáticamente.
*/
EstadoCasilla(String simbolo, int r, int g, int b) {
this.simbolo = simbolo;
// La secuencia mágica para RGB es: \u001B[38;2;R;G;Bm
this.codigoColor = String.format("\u001B[38;2;%d;%d;%dm", r, g, b);
}
@Override
public String toString() {
return codigoColor + simbolo + RESET;
}
}
// --- CONSTANTES Y ESTADO GLOBAL ---
static final int DIMENSION = 10;
static final int NO_ENCONTRADO = -1;
// Configuración de dificultad
static final int MUNICION_MAXIMA = 50;
static final int IMPACTOS_NECESARIOS = calcularPuntosTotales();
// Matriz del tablero y herramientas
static EstadoCasilla[][] oceano = new EstadoCasilla[DIMENSION][DIMENSION];
static Scanner teclado = new Scanner(System.in);
static Random radar = new Random();
/**
* Hilo principal de ejecución.
* Gestiona el bucle de juego, el control de turnos y las condiciones de victoria/derrota.
* @param args Argumentos de consola (no utilizados).
*/
public static void main(String[] args) {
System.out.println("--- HUNDIR LA FLOTA ---");
System.out.println("Tablero: " + DIMENSION + "x" + DIMENSION);
System.out.println("Regla especial: Los barcos no pueden tocarse entre sí.");
System.out.println("Objetivo: " + IMPACTOS_NECESARIOS + " impactos.");
System.out.println("Munición: " + MUNICION_MAXIMA + " misiles.");
// 1. Preparación
inicializarOceano();
colocarFlotaCompleta();
// 2. Variables de estado
int aciertos = 0;
int misilesRestantes = MUNICION_MAXIMA;
boolean juegoTerminado = false;
// 3. Bucle Principal
while (!juegoTerminado) {
imprimirOceano(false); // false = Modo Niebla de Guerra
System.out.println("------------------------------------------------");
System.out.println("Misiles: " + misilesRestantes + " | Aciertos: " + aciertos + "/" + IMPACTOS_NECESARIOS);
// Turno de juego
boolean impacto = realizarDisparo();
misilesRestantes--;
// Feedback inmediato
if (impacto) {
aciertos++;
System.out.println(">>> ¡IMPACTO CONFIRMADO! <<<");
} else {
System.out.println(">>> Agua. Sin rastro del enemigo. <<<");
}
// Comprobación de fin de partida
if (aciertos == IMPACTOS_NECESARIOS) {
juegoTerminado = true;
imprimirOceano(true); // Revelamos el mapa
System.out.println("\n¡VICTORIA! Has desmantelado la flota enemiga.");
} else if (misilesRestantes == 0) {
juegoTerminado = true;
imprimirOceano(true);
System.out.println("\n¡MUNICIÓN AGOTADA! Retirada táctica.");
}
}
}
// --- LÓGICA DE JUEGO ---
/**
* Calcula dinámicamente cuántos aciertos hacen falta para ganar.
* @return Total de casillas ocupadas por barcos.
*/
static int calcularPuntosTotales() {
int suma = 0;
for (TipoBarco barco : TipoBarco.values()) {
suma += barco.getLongitud();
}
return suma;
}
/**
* Gestiona la interacción con el usuario para realizar un disparo.
* Verifica que la coordenada sea válida y no se haya disparado antes allí.
* @return true si el disparo acierta en un barco, false si falla.
*/
static boolean realizarDisparo() {
boolean disparoValido = false;
int fila = NO_ENCONTRADO;
int col = NO_ENCONTRADO;
while (!disparoValido) {
fila = pedirCoordenada("Fila (0-" + (DIMENSION - 1) + "): ");
col = pedirCoordenada("Columna (0-" + (DIMENSION - 1) + "): ");
if (oceano[fila][col] == EstadoCasilla.TOCADO || oceano[fila][col] == EstadoCasilla.FALLO) {
System.out.println("Ya has disparado en esa zona. Elige otra.");
} else {
disparoValido = true;
}
}
if (oceano[fila][col] == EstadoCasilla.BARCO) {
oceano[fila][col] = EstadoCasilla.TOCADO;
return true;
} else {
oceano[fila][col] = EstadoCasilla.FALLO;
return false;
}
}
// --- ALGORITMOS DE COLOCACIÓN (IA) ---
/**
* Recorre el catálogo de barcos y delega la colocación de cada uno.
*/
static void colocarFlotaCompleta() {
for (TipoBarco barco : TipoBarco.values()) {
colocarBarcoAleatorio(barco);
}
}
/**
* Intenta colocar un barco en una posición aleatoria.
* Si la posición elegida no es válida (choca, se toca con otro o se sale), repite el intento.
* @param barco El tipo de barco a colocar.
*/
static void colocarBarcoAleatorio(TipoBarco barco) {
boolean colocado = false;
while (!colocado) {
int fila = radar.nextInt(DIMENSION);
int col = radar.nextInt(DIMENSION);
boolean horizontal = radar.nextBoolean();
if (esPosicionValida(fila, col, barco.getLongitud(), horizontal)) {
pintarBarcoEnMatriz(fila, col, barco.getLongitud(), horizontal);
colocado = true;
}
}
}
/**
* Verifica si el barco cabe y cumple la "Regla del Aire".
* La regla del aire implica que no solo las casillas del barco deben estar libres,
* sino también todas las casillas adyacentes (incluyendo diagonales).
* @param f Fila inicial.
* @param c Columna inicial.
* @param longitud Tamaño del barco.
* @param horizontal Orientación (true = horizontal, false = vertical).
* @return true si el barco y su perímetro están libres.
*/
static boolean esPosicionValida(int f, int c, int longitud, boolean horizontal) {
// 1. Calculamos las dimensiones que ocupará el barco
int anchoBarco = horizontal ? longitud : 1;
int altoBarco = horizontal ? 1 : longitud;
// 2. Validar límites del tablero (Si se sale, devolvemos false)
if (f + altoBarco > DIMENSION || c + anchoBarco > DIMENSION) {
return false;
}
// 3. Definir el "Marco de Seguridad" (Barco + 1 casilla alrededor)
// Usamos Math.max/min para no salirnos de los bordes (0 y 9)
int filaInicio = Math.max(0, f - 1);
int colInicio = Math.max(0, c - 1);
int filaFin = Math.min(DIMENSION - 1, f + altoBarco);
int colFin = Math.min(DIMENSION - 1, c + anchoBarco);
// 4. Escanear esa área buscando obstáculos
for (int i = filaInicio; i <= filaFin; i++) {
for (int j = colInicio; j <= colFin; j++) {
if (oceano[i][j] != EstadoCasilla.AGUA) {
return false; // Colisión detectada (barco o vecino)
}
}
}
return true; // Todo limpio
}
/**
* Escribe el barco en la matriz una vez validada la posición.
*/
static void pintarBarcoEnMatriz(int f, int c, int longitud, boolean horizontal) {
for (int i = 0; i < longitud; i++) {
if (horizontal) {
oceano[f][c + i] = EstadoCasilla.BARCO;
} else {
oceano[f + i][c] = EstadoCasilla.BARCO;
}
}
}
// --- UTILIDADES Y VISTA ---
/**
* Limpia el tablero llenándolo de agua.
*/
static void inicializarOceano() {
for (int f = 0; f < DIMENSION; f++) {
for (int c = 0; c < DIMENSION; c++) {
oceano[f][c] = EstadoCasilla.AGUA;
}
}
}
/**
* Dibuja el tablero en consola.
* Utiliza la lógica de "Niebla de Guerra".
* @param revelarTodo true para mostrar la ubicación de los barcos (Game Over).
*/
static void imprimirOceano(boolean revelarTodo) {
System.out.println();
// Cabecera de columnas
System.out.print(" ");
for (int c = 0; c < DIMENSION; c++) {
System.out.print(c + " ");
}
System.out.println();
for (int f = 0; f < DIMENSION; f++) {
System.out.print(f + "| "); // Índice de fila
for (int c = 0; c < DIMENSION; c++) {
EstadoCasilla actual = oceano[f][c];
// Si es un barco y estamos jugando, lo mostramos como agua
if (actual == EstadoCasilla.BARCO && !revelarTodo) {
System.out.print(EstadoCasilla.AGUA + " ");
} else {
System.out.print(actual + " ");
}
}
System.out.println("|");
}
}
/**
* Pide un número entero al usuario de forma segura.
* @param mensaje Texto a mostrar.
* @return Un entero validado dentro del rango del tablero.
*/
static int pedirCoordenada(String mensaje) {
int valor = NO_ENCONTRADO;
boolean valido = false;
while (!valido) {
System.out.print(mensaje);
if (teclado.hasNextInt()) {
valor = teclado.nextInt();
if (valor >= 0 && valor < DIMENSION) {
valido = true;
} else {
System.out.println("Error: El número debe estar entre 0 y " + (DIMENSION - 1));
}
} else {
teclado.next(); // Limpiar el buffer
System.out.println("Error: Debes introducir un número entero.");
}
}
return valor;
}
}