πŸ“„ ProgramaciΓ³n en Java – PrΓ‘cticas DAM

Videojuego por turnos (POO) y copia de ficheros binarios

POO Herencia & Polimorfismo Videojuegos Ficheros Binarios

πŸ“‹ Índice

  1. Videojuego por turnos con POO (Personaje, Guerrero, Mago, etc.)
  2. Copia y anΓ‘lisis de ficheros binarios (CopiaFicherosBinarios)
  3. Resumen de conceptos clave y relaciΓ³n con el temario
1
Videojuego por turnos con POO
"Herencia, polimorfismo y diseΓ±o orientado a objetos jugando a pegarse de tortas"

Esta prΓ‘ctica implementa un mini videojuego por turnos en Java, con varios tipos de personajes que se atacan hasta que uno queda a 0 de vida. El objetivo real (mΓ‘s allΓ‘ de matar orcos) es practicar: clases abstractas, herencia, polimorfismo, encapsulaciΓ³n, composiciΓ³n y un bucle de juego sencillo.

1.1 Estructura general del proyecto

Las clases importantes del paquete videojuego son:

πŸ”΄ Personaje (abstract)
└── πŸ”΅ Guerrero
└── πŸ”΅ Mago
└── πŸ”΅ Arquero
└── πŸ”΅ Enemigo
πŸ”΄ Pocion
πŸ”΄ Combate
πŸ”΄ Main

La idea es que Personaje define lo comΓΊn de todos los personajes (vida, ataque base, pociones, mΓ©todos como atacar, defender, etc.), y las subclases (Guerrero, Mago, Arquero, Enemigo) aΓ±aden comportamiento especΓ­fico.

πŸ’‘ Recuerda: en POO se intenta que el cΓ³digo que es igual para todos estΓ© en la clase padre, y solo lo diferente se programe en cada clase hija.

1.2 Clase abstracta Personaje

La clase Personaje es abstracta, lo que significa que:

  • No se puede instanciar directamente (new Personaje(...) estΓ‘ prohibido).
  • Define mΓ©todos abstractos que las clases hijas deben implementar (getTipo(), habilidadEspecial(...), actuar(...)).
JAVA// Clase base de todos los personajes
public abstract class Personaje {
    // Atributos privados (encapsulaciΓ³n)
    private String nombre;
    private int vida;
    private int vidamax;
    private int ataquebase;
    private int pociones;
    private boolean defendiendo;
    private java.util.List<Pocion> inventarioPociones;

    // Constructor: se llama desde las clases hijas con super(...)
    public Personaje(String nombre, int vidamax, int ataquebase) {
        this.nombre = nombre;
        this.vida = vidamax;
        this.vidamax = vidamax;
        this.ataquebase = ataquebase;
        this.pociones = 3;  // por ejemplo
        this.inventarioPociones = new java.util.ArrayList<>();
    }

    // MΓ©todo que comprueba si el personaje sigue vivo
    public boolean estaVivo() {
        return this.vida > 0;
    }

    // MΓ©todo de ataque genΓ©rico con posible crΓ­tico
    public void atacar(Personaje objetivo) {
        java.util.Random azar = new java.util.Random();
        int danioFinal = ataquebase;

        // 20% de probabilidad de crΓ­tico
        if (azar.nextInt(100) < 20) {
            danioFinal *= 2;
            System.out.println(nombre + " hace un GOLPE CRÍTICO de " + danioFinal);
        }

        // Si el objetivo se defiende, reduce el daΓ±o
        if (objetivo.getDefendiendo()) {
            danioFinal /= 2;
        }

        objetivo.setVida(Math.max(objetivo.getVida() - danioFinal, 0));
    }

    // MΓ©todos abstractos que las hijas deben implementar
    public abstract String getTipo();
    public abstract void habilidadEspecial(Personaje objetivo);
    public abstract void actuar(Personaje objetivo);
}
πŸ“Œ EncapsulaciΓ³n: todos los atributos son private y se accede por getters/setters. Esto protege el estado del objeto y evita que el resto del programa lo toque directamente a lo loco.
πŸ’‘ Polimorfismo: desde fuera trabajas con variables de tipo Personaje, pero en tiempo de ejecuciΓ³n pueden ser Guerrero, Mago, Enemigo, etc. Cada uno sobrescribe sus mΓ©todos.

1.3 Herencia: Guerrero, Mago, Arquero, Enemigo

Cada clase hija extiende Personaje y aΓ±ade sus propios atributos y comportamiento. Por ejemplo, Guerrero tiene estamina, Mago tiene mana, Arquero tiene flechas, etc.

JAVA// Ejemplo simplificado de Guerrero
public class Guerrero extends Personaje {
    private int estamina;

    public Guerrero(String nombre, int vidamax,
                     int ataquebase, int estamina) {
        super(nombre, vidamax, ataquebase);
        this.estamina = estamina;
    }

    @Override
    public String getTipo() {
        return "Guerrero";
    }

    @Override
    public void habilidadEspecial(Personaje objetivo) {
        // Ejemplo: gastar estamina para hacer mΓ‘s daΓ±o
        if (estamina >= 10) {
            estamina -= 10;
            System.out.println(getNombre() + " usa GOLPE BRUTAL sobre " + objetivo.getNombre());
            objetivo.setVida(Math.max(objetivo.getVida() - 30, 0));
        } else {
            System.out.println(getNombre() + " estΓ‘ cansado, usa ataque normal.");
            atacar(objetivo);
        }
    }

    @Override
    public void actuar(Personaje objetivo) {
        // Decide si usar habilidad especial o ataque normal
        if (estamina >= 10) habilidadEspecial(objetivo);
        else atacar(objetivo);
    }
}

Mago

Tiene atributo mana y habilidades mΓ‘gicas. Suele gastar mana para lanzar hechizos mΓ‘s potentes, por ejemplo curarse o hacer bastante daΓ±o.

Arquero

Tiene atributo flechas. Puede hacer ataques a distancia mΓ‘s fuertes mientras tenga flechas. Cuando se queda sin flechas, se ve obligado a atacar de forma mΓ‘s bΓ‘sica.

⚠️ Detalle importante: aunque el código concreto de cada habilidad sea distinto, desde fuera siempre se invoca igual: personaje.habilidadEspecial(objetivo). Eso es polimorfismo en acción.

1.4 Enemigo: IA simple basada en la vida

La clase Enemigo tambiΓ©n extiende Personaje, pero su mΓ©todo actuar no se decide por un jugador humano, sino por una lΓ³gica interna sencilla:

JAVA// Parte del mΓ©todo actuar en Enemigo
public void actuar(Personaje objetivo) {
    double porcentajeVida =
        (double) this.getVida() / this.getVidamax();

    // Si la vida baja del 30%, se defiende
    if (porcentajeVida < 0.3) {
        this.defender();
        System.out.println(getNombre() + " estΓ‘ herido y se pone en modo defensivo.");
    } else {
        // Si no, ataca normalmente
        this.atacar(objetivo);
    }
}

Con esto se consigue que el enemigo tenga un comportamiento mΓ­nimamente inteligente sin necesidad de un sistema de IA complejo.

1.5 Pocion y el inventario del personaje

La clase Pocion es muy sencilla, pero muestra un concepto clave: composiciΓ³n. Un Personaje tiene una lista de Pocion, es decir, estΓ‘ formado tambiΓ©n por esos objetos.

JAVA// Clase Pocion
public class Pocion {
    private int curacion = 20;

    public void usar(Personaje objetivo) {
        int vidaActual = objetivo.getVida();
        int vidaMax = objetivo.getVidamax();
        objetivo.setVida(Math.min(vidaActual + curacion, vidaMax));
    }
}

En Personaje hay algo como:

JAVA// Uso del inventario de pociones
public void usarPocion() {
    if (!inventarioPociones.isEmpty()) {
        Pocion p = inventarioPociones.remove(0);  // coge la primera pociΓ³n
        p.usar(this);                            // se aplica sobre este personaje
    } else {
        System.out.println(nombre + " no tiene pociones.");
    }
}
⚠️ Importante: el método usar de Pocion recibe un Personaje como parÑmetro. Esto permite usar la misma clase Pocion para cualquier tipo de personaje (Guerrero, Mago, Enemigo...) sin duplicar código.

1.6 Combate y el flujo de ejecuciΓ³n

La clase Combate orquesta el intercambio de golpes entre dos personajes: controla los turnos hasta que uno muere.

JAVA// Bucle principal de combate
public class Combate {
    private Personaje p1, p2;
    private int turnos = 1;

    public Combate(Personaje p1, Personaje p2) {
        this.p1 = p1;
        this.p2 = p2;
    }

    public void iniciar() {
        while (p1.estaVivo() && p2.estaVivo()) {
            System.out.println("\n--- TURNO " + turnos + " ---");
            p1.mostrarEstado();
            p2.mostrarEstado();

            p1.actuar(p2);
            if (!p2.estaVivo()) break;

            p2.actuar(p1);

            p1.finturno();
            p2.finturno();
            turnos++;
        }

        System.out.println("\nGANADOR: " +
            (p1.estaVivo() ? p1.getNombre() : p2.getNombre()));
    }
}
πŸ“Œ Dentro del bucle while se ve el polimorfismo claramente: se llama a actuar sobre Personaje, pero el cΓ³digo ejecutado dependerΓ‘ de si el objeto real es Guerrero, Mago, Enemigo, etc.

1.7 Main: punto de entrada

JAVA
public class Main {
    public static void main(String[] args) {
        Personaje p1 = new Guerrero("MΓ‘ximo", 100, 15, 50);
        Personaje p2 = new Enemigo("Orco Carnicero", 120, 12);

        Combate duelo = new Combate(p1, p2);
        duelo.iniciar();
    }
}

AquΓ­ se ve la β€œfoto final”: se crean los objetos concretos, se construye el combate con ellos y se llama a iniciar() para ejecutar el bucle.

2
Copia y anΓ‘lisis de ficheros binarios
"Streams, buffers y la realidad poco glamourosa del I/O"

Esta prΓ‘ctica trabaja con ficheros binarios usando streams de bytes. AdemΓ‘s de copiar un archivo origen a un destino, tambiΓ©n:

2.1 Entrada por teclado y validaciones bΓ‘sicas

Al inicio, el programa pide al usuario las rutas de origen y destino:

JAVA
public class CopiaFicherosBinarios {
    public static void main(String[] args) {
        java.util.Scanner sc = new java.util.Scanner(System.in);

        try {
            System.out.println("Indica la ruta del archivo ORIGEN: ");
            String rutaOrigen = sc.nextLine().trim();

            System.out.println("Indica la ruta del archivo DESTINO: ");
            String rutaDestino = sc.nextLine().trim();

            java.io.File origen = new java.io.File(rutaOrigen);
            java.io.File destino = new java.io.File(rutaDestino);

            // Comprobaciones bΓ‘sicas
            if (!origen.exists()) {
                System.out.println("El archivo origen no existe.");
                return;
            }

            if (!origen.isFile()) {
                System.out.println("El origen no es un fichero vΓ‘lido.");
                return;
            }

            // (Opcional) comprobar extensiΓ³n .jpg, .bin, etc.
            // AquΓ­ irΓ­a la llamada al mΓ©todo de copia...

        } catch (java.io.IOException e) {
            System.out.println("Error de E/S: " + e.getMessage());
        }
    }
}
⚠️ Detalle típico de examen: siempre se debe comprobar que el fichero existe y que es un fichero (isFile()) antes de intentar abrirlo con streams. Si no, revientas a excepciones.

2.2 Copia usando FileInputStream y FileOutputStream

La copia real se hace leyendo bloques de bytes de un stream de entrada y escribiΓ©ndolos en el de salida. Usar un buffer mejora el rendimiento frente a leer byte a byte.

JAVA
private static void copiarFichero(java.io.File origen, java.io.File destino)
        throws java.io.IOException {

    try (java.io.FileInputStream fis = new java.io.FileInputStream(origen);
         java.io.FileOutputStream fos = new java.io.FileOutputStream(destino)) {

        byte[] buffer = new byte[4096];  // 4 KB por lectura
        int bytesLeidos;

        while ((bytesLeidos = fis.read(buffer)) != -1) {
            fos.write(buffer, 0, bytesLeidos);
        }
    }
}
πŸ’‘ El patrΓ³n es siempre el mismo:
  1. Abrir FileInputStream para el origen.
  2. Abrir FileOutputStream para el destino.
  3. Leer en un array de bytes (byte[]) dentro de un bucle while.
  4. Escribir al destino los bytes leΓ­dos.

2.3 Comprobar si los ficheros son idΓ©nticos

DespuΓ©s de copiar, se suele comprobar si ambos ficheros son iguales comparando byte a byte:

JAVA
private static boolean sonFicherosIguales(java.io.File f1, java.io.File f2)
        throws java.io.IOException {

    if (f1.length() != f2.length()) {
        return false;  // tamaΓ±os distintos β‡’ ya no son iguales
    }

    try (java.io.FileInputStream in1 = new java.io.FileInputStream(f1);
         java.io.FileInputStream in2 = new java.io.FileInputStream(f2)) {

        int b1, b2;
        do {
            b1 = in1.read();
            b2 = in2.read();
            if (b1 != b2) return false;
        } while (b1 != -1);
    }

    return true;
}

De este modo se asegura que no solo se ha copiado β€œalgo”, sino que el contenido es exactamente igual.

2.4 Informe de copia y uso de fecha/hora

La prΓ‘ctica tambiΓ©n escribe un log en texto usando BufferedWriter y FileWriter, aΓ±adiendo fecha y estadΓ­sticas de la copia.

JAVA
private static void escribirInforme(
        java.io.File origen, java.io.File destino,
        boolean sonIguales, String textoAnalisis)
        throws java.io.IOException {

    String salto = System.lineSeparator();
    java.time.LocalDateTime ahora = java.time.LocalDateTime.now();
    java.time.format.DateTimeFormatter formato =
        java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    String marcaTiempo = ahora.format(formato);

    try (java.io.BufferedWriter bw =
             new java.io.BufferedWriter(
                 new java.io.FileWriter("informe_copia.txt", true))) {

        bw.write("========== COPIA DE FICHEROS ==========" + salto);
        bw.write("Fecha y hora: " + marcaTiempo + salto);
        bw.write("Origen: " + origen.getAbsolutePath() + salto);
        bw.write("Destino: " + destino.getAbsolutePath() + salto);
        bw.write("ΒΏFicheros idΓ©nticos?: " + sonIguales + salto);
        bw.write("EstadΓ­sticas:" + salto);
        bw.write(textoAnalisis + salto + salto);
    }
}
πŸ“Œ AquΓ­ se ven varias cosas tΓ­picas de prΓ‘ctica: uso de LocalDateTime, DateTimeFormatter, BufferedWriter con modo append, y composiciΓ³n de cadenas con saltos de lΓ­nea.
3
Resumen de conceptos y relaciΓ³n con el temario
"QuΓ© estΓ‘s practicando realmente con estas chorradas de ejemplo"

Las dos prΓ‘cticas no son aleatorias: estΓ‘n elegidas para cubrir bloques tΓ­picos de la asignatura de ProgramaciΓ³n (y de la vida real, para variar). AquΓ­ tienes una visiΓ³n comparativa.

3.1 Tabla comparativa de conceptos

Concepto PrΓ‘ctica videojuego PrΓ‘ctica ficheros binarios
Clases y objetos Personaje, Guerrero, Mago, Combate, etc. CopiaFicherosBinarios, uso de File, Scanner, streams.
Herencia Guerrero, Mago, Arquero, Enemigo extienden Personaje. No hay herencia relevante, es mΓ‘s procedural.
Polimorfismo Llamadas a actuar() y habilidadEspecial() sobre referencias Personaje. No se usa, pero sΓ­ se practica sobrecarga de responsabilidades en funciones.
ComposiciΓ³n Personaje tiene un List<Pocion>. La clase usa varios objetos: File, streams, BufferedWriter.
Manejo de errores Validar vida β‰₯ 0, controlar estados como defendiendo, etc. try/catch de IOException, validaciΓ³n de rutas y existencia de ficheros.
E/S Principalmente consola (mensajes de combate). Lectura y escritura binaria + log de texto.

3.2 Checklist mental para examen / prΓ‘cticas

1. POO bΓ‘sica: ser capaz de definir una clase con atributos privados, constructor, getters/setters y algΓΊn mΓ©todo con lΓ³gica (no solo toString).
2. Herencia y polimorfismo: crear una clase base y varias hijas, sobrescribir mΓ©todos y usar referencias del tipo de la clase padre.
3. DiseΓ±o por responsabilidad: que cada clase haga β€œlo suyo”: Combate controla turnos, Personaje su estado, Pocion la curaciΓ³n, etc.
4. Ficheros binarios: abrir streams, usar un buffer de bytes, copiar contenido y cerrar recursos.
5. ValidaciΓ³n y errores: comprobar existencia de ficheros, capturar IOException, controlar estados lΓ­mite (vida 0, sin pociones, sin flechas...).
6. Documentar mentalmente: ser capaz de explicar en voz alta β€œquΓ© hace cada mΓ©todo” sin tener que leerlo diez veces.
πŸ’‘ Recuerda: si entiendes realmente estas dos prΓ‘cticas, no solo has pasado una tarea de clase; has cubierto casi todo el bloque de POO + ficheros bΓ‘sicos que se suele evaluar en un examen largo.
GuΓ­a de repaso β€” ProgramaciΓ³n en Java Β· [Actualiza la fecha aquΓ­]