Foros aprenderaprogramar.com

Aprender a programar => C, C++, C#, Java, Visual Basic, HTML, PHP, CSS, Javascript, Ajax, Joomla, MySql y más => Mensaje iniciado por: MirMiriam en 27 de Agosto 2021, 21:01

Título: Problema Batalla RPG por turnos con java
Publicado por: MirMiriam en 27 de Agosto 2021, 21:01
Hola a todos, llevo poco tiempo con la programación y estoy con un proyecto, se trata de un juego tipo RPG por turnos basado en el señor de los anillos.
Las características que tiene son las siguientes:

Mi problema es que no consigo que el programa finalice, ya que no acumula cuando un personaje ataca a otro, creo que mi función vida() calcula la vida de los héroes y las bestias, pero los héroes y las bestias que están al inicio de la función y los que se atacaron son las copias que tienes en los arreglos por lo tanto en si tus héroes y bestias originales nunca se atacaron y tienen toda la vida, pero, no sé como solucionarlo.

Adjunto el código que tengo:

Clase Ejercito:

Código: [Seleccionar]
public class Ejercito {
    String nombre;
    int puntosVida;
    int nivelResistencia;


    public Ejercito(String nombre, int puntosVida, int nivelResistencia) {
        this.nombre = nombre;
        this.puntosVida = puntosVida;
        this.nivelResistencia = nivelResistencia;

    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public int getPuntosVida() {
        return puntosVida;
    }

    public void setPuntosVida(int puntosVida) {
        this.puntosVida = puntosVida;
    }

    public int getNivelResistencia() {
        return nivelResistencia;
    }

    public void setNivelResistencia(int nivelResistencia) {
        this.nivelResistencia = nivelResistencia;
    }

Clase Heroes:

Código: [Seleccionar]
package com.tokioschool.Batalla.domain;

import java.util.List;

public class Heroes extends Ejercito {
    int dado1;
    int dado2;

    public Heroes(String nombre, int puntosVida, int nivelResistencia) {
        super(nombre, puntosVida, nivelResistencia);

    }

    public int mayorResultado(){
        dado1=(int) (Math.random()*(100+1));
        dado2=(int) (Math.random()*(100+1));
        System.out.println("primer dado:"+dado1);
        System.out.println("segundo dado:"+dado2);

        return Math.max(dado1, dado2);
    }

    public int atacar(Bestias bestias) {
        int ataque = mayorResultado();
        int resultado = bestias.getPuntosVida() - ataque;
        boolean vid = getPuntosVida() >= 0;
        String men = "Las tiradas no superan la armadura de la bestia, pasa el turno";
        do {

            if (ataque > bestias.getNivelResistencia()) {
                System.out.println(bestias.getPuntosVida() + " - " + ataque + "=" + resultado);
                System.out.println("Datos actualizados de " + bestias.getNombre() + " (Vida=" + resultado + " Armadura=" + bestias.getNivelResistencia() + ")");
                return resultado;
            } else
                System.out.println(men);
            return 0;
        }while (bestias.getPuntosVida() >=0);

    }

    public boolean vida( List<Heroes> heroes){
        boolean vidas = getPuntosVida()<=0;
        if(!vidas);
        System.out.println(getNombre()+" ha sido eliminado");
        return vidas;
    }

    public String toString(){
        return "Lucha entre "+getNombre()+" (Vida="+getPuntosVida()+" Armadura="+getNivelResistencia()+")";
    }

}

Clase Bestias

Código: [Seleccionar]
public class Bestias extends Ejercito {

    int dado;

    public Bestias(String nombre, int puntosVida, int nivelResistencia) {
        super(nombre, puntosVida, nivelResistencia);

    }

    public int getNivelResistencia(){
        return nivelResistencia;
    }

    public int tirarDadosBestias(){
        return dado =(int) (Math.random()*(90+1));
    }

    public int atacarBestias(Heroes heroe) {
        int at = tirarDadosBestias();
        int result = heroe.getPuntosVida() - at;
        String mensaje = "La tirada no ha superado la armadura del heroe, pasa el turno";
        do{
            if (at > heroe.getNivelResistencia()) {
                System.out.println("Turno de las bestias, el resultado del dado es :"+at);
                System.out.println("Datos actualizados de: " + heroe.getNombre() + " (Vida=" + result + " Armadura=" + heroe.getNivelResistencia() + ")");
                return result;
            } else
                System.out.println(mensaje);
            return 0;
        }while(heroe.getPuntosVida()>=0);

    }

    public boolean vida( List<Bestias> bestias){
        boolean vidas = getPuntosVida()<=0;
        if(vidas);
        System.out.println(getNombre()+" ha sido eliminado");
        return vidas;
    }

    public String toString(){
        return " y "+getNombre()+" (Vida="+getPuntosVida()+" Armadura="+getNivelResistencia()+")";
    }

}

Clase Batalla

Código: [Seleccionar]
import java.util.List;

public class Batalla {

    private static Heroes[] ejHero;
    private static Bestias[] ejeBestia;

    public Batalla() {
        ejHero = new Heroes[5];
        ejeBestia = new Bestias[4];
    }

    public static void batalla(List<Heroes> heroes, List<Bestias> bestias) {

        Heroes[] he = new Heroes[heroes.size()];
        he = heroes.toArray(he);

        Bestias[] be = new Bestias[bestias.size()];
        be = bestias.toArray(be);

        do {

            System.out.println(he[0].toString() + be[0].toString());
            System.out.println(he[0].atacar(be[0]));
            System.out.println(be[0].atacarBestias(he[0]));


            System.out.println(he[1].toString() + be[1].toString());
            System.out.println(he[1].atacar(be[1]));
            System.out.println(be[1].atacarBestias(he[1]));

            System.out.println(he[2].toString() + be[2].toString());
            System.out.println(he[2].atacar(be[2]));
            System.out.println(be[2].atacarBestias(he[2]));

            System.out.println(he[3].toString() + be[3].toString());
            System.out.println(he[3].atacar(be[3]));
            System.out.println(be[3].atacarBestias(he[3]));

            System.out.println(he[4].toString() + " se queda en guardia");


        } while (!he[0].vida(heroes) || !be[0].vida(bestias));

    }

}

Clase Principal:

Código: [Seleccionar]
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main (String args[]){
        List<Heroes> ejerHero = new ArrayList<>();
        List<Bestias> ejerBestias = new ArrayList<>();

        ejerHero.add(new Elfos("Legolas", 150, 30));
        ejerHero.add(new Humano("Aragorn", 150, 50));
        ejerHero.add(new Humano("Boromir",100, 60));
        ejerHero.add(new Humano("Gandalf",300, 10));
        ejerHero.add(new Hobbits("Frodo", 20, 10));

        ejerBestias.add(new Orcos("Lurtz", 200, 60));
        ejerBestias.add(new Orcos("Shagrat", 220, 50));
        ejerBestias.add(new Trasgo("Uglúk", 120, 30));
        ejerBestias.add(new Trasgo("Mauhúr", 100, 30));

        Batalla.batalla(ejerHero, ejerBestias);

    }
}

Agradecería cualquier ayuda, gracias de antemano!
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: Kabuto en 28 de Agosto 2021, 14:15
Hola.
Veo varias inconsistencias:

Aquí al método vida(), le pasa un List<Heroes>, pero si te fijas no estás haciendo nada con él.
Citar
    public boolean vida(List<Heroes> heroes){
        boolean vidas = getPuntosVida()<=0;
        if(!vidas);
        System.out.println(getNombre()+" ha sido eliminado");
        return vidas;
    }

Si lo quitas, verás que todo funciona exactamente igual, por tanto es innecesario.
Código: [Seleccionar]
    public boolean vida(){
        boolean vidas = getPuntosVida()<=0;
        if(vidas);
        System.out.println(getNombre()+" ha sido eliminado");
        return vidas;
    }

En la clase Batalla ocurre algo parecido.
Declaras como atributos dos Ejercitos
Citar
public class Batalla {

    private static Heroes[] ejHero;
    private static Bestias[] ejeBestia;

    public Batalla() {
        ejHero = new Heroes[5];
        ejeBestia = new Bestias[4];
    }

Pero luego no se usan para nada, porque el único método que tiene esta clase, trabaja con unos List<> que recibe por argumentos. No hace nada con estos atributos declarados en la clase:
Citar
    public static void batalla(List<Heroes> heroes, List<Bestias> bestias) {

        Heroes[] he = new Heroes[heroes.size()];
        he = heroes.toArray(he);

        Bestias[] be = new Bestias[bestias.size()];
        be = bestias.toArray(be);

        do {

            System.out.println(he[0].toString() + be[0].toString());
            System.out.println(he[0].atacar(be[0]));
            System.out.println(be[0].atacarBestias(he[0]));


            System.out.println(he[1].toString() + be[1].toString());
            System.out.println(he[1].atacar(be[1]));
            System.out.println(be[1].atacarBestias(he[1]));

            System.out.println(he[2].toString() + be[2].toString());
            System.out.println(he[2].atacar(be[2]));
            System.out.println(be[2].atacarBestias(he[2]));

            System.out.println(he[3].toString() + be[3].toString());
            System.out.println(he[3].atacar(be[3]));
            System.out.println(be[3].atacarBestias(he[3]));

            System.out.println(he[4].toString() + " se queda en guardia");


        } while (!he[0].vida() || !be[0].vida());

    }
Luego, en ese método, la condición del while no me queda clara:
Código: [Seleccionar]
while (!he[0].vida() || !be[0].vida())
Citar
Hacer Mientras el primer Heroe no tenga vida o la primera Bestia no tenga vida

¿No sería mientras tengan vida?
¿Y por qué solo primer Heroe o primera Bestia?(los que ocupan posición
Supongo que esto lo haces por este punto del enuciado:
Citar
En el momento en que un personaje llegue a un nivel de vida igual o inferior a cero se producirá su muerte por lo que se eliminará de su posición y se desplazarán todos sus compañeros en posiciones posteriores para cubrir la baja
En teoría, mientras van muriendo combatientes, se van desplazando de forma que la posición
Pero en tu código este desplazamiento no está contemplado, así que esa condición para el bucle no se cumplirá correctamente.
Además, tu código está pensado para ejércitos de un tamaño fijo.
La "batalla" ha de adaptarse para poder recibir desde el principio ejércitos de diferentes tamaños y tener en cuenta que los ejércitos verán alterado este tamaño durante el combate.

Creo que hay que volver al principio y reformular todo el concepto.
De hecho, lo primero que he visto y me ha chirriado ha sido esto:
Código: [Seleccionar]
public class Heroes extends Ejercito¿Heroes y Bestias han de ser hijas(herederas) de Ejercito?
¿Eso tiene sentido?

En POO es muy habitual cometer el error de usar herencia cuando no corresponde. Y en realidad es fácil evitar este error. Basta con hacerse la pregunta adecuada.

Cuando decimos que una clase hereda de otra, estamos diciendo que esa clase "es una"... lo que sea la otra clase.

Ejemplos.
Si yo digo:
Código: [Seleccionar]
public class Alumno extends PersonaLo que estoy diciendo, es que un Alumno es una Persona
¿Tiene sentido esa afirmación? Sí la tiene, por tanto la herencia es correcta.

Si yo digo:
Código: [Seleccionar]
public class Perro extends AnimalLo que estoy diciendo, es que un Perro es un Animal
¿Tiene sentido esa afirmación? Totalmente, así que la herencia está justificada.

Si yo digo:
Código: [Seleccionar]
public class Heroes extends EjercitoLo que estoy diciendo es que un Heroe (o Bestia), es un Ejército
¿Tiene sentido esa afirmación?
Pues no, un Héroe no es un Ejército.
Un Ejército está compuesto de Héroes, eso sí es cierto. Pero un Héroe, no es un Ejército.

Por lo tanto, aquí una "relación de herencia" no es aplicable.
Lo que se puede aplicar es una "relación de composición".
Un Ejército se compone de uno o varios Héroes o Bestias.
Eso suena mucho mejor, ¿verdad?
¿Y cómo se hace una "relación de composición"?

Pues en este caso hay que reformular la clase Ejército para que sea ella quien tenga como atributo una colección de Héroes o Bestias.
Que sea ella quien controle si en sus filas todavía quedan guerreros con vida o no.
Que sea ella quien se encargue de hacer que los guerreros desplacen sus posiciones para ocupar el puesto de sus compañeros caídos (esto es automático si usamos un ArrayList).

Yo lo haría de este modo:
Código: [Seleccionar]
import java.util.ArrayList;

public class Ejercito {

public ArrayList<Soldado> soldados;

public Ejercito() {
soldados = new ArrayList<Soldado>();
}

public void reclutarSoldado(Soldado soldado) {
soldados.add(soldado);
}

public Soldado getSoldado(int pos) {
try {
return soldados.get(pos);
}
catch(Exception e) {
return null; //No hay soldado en esa posición
}
}

public void comprobarEjercito() {
int soldadoMuerto = -1; //Posicion de posible soldado muerto
//Comprobamos si hay alguna baja
for (int pos = 0; pos < soldados.size(); pos++)
if (soldados.get(pos).estaMuerto()) //Baja confirmada
soldadoMuerto = pos; //Guardamos posicion
/*
* Para evitar inconsistencias, hay que eliminar al soldado
* muerto DESPUÉS del bucle for.
* Si lo eliminasemos DENTRO del bucle for, mientras aún se está
* recorriendo, se podría producir una excepcion porque el indice
* que usa el bucle FOR podría ser inconsistente con el nuevo
* tamaño del ArrayList tras eliminar al soldado.
*/
if (soldadoMuerto != -1) //Se encontró una baja, hay que eliminar
soldados.remove(soldadoMuerto);
//Al eliminar del ArrayList, automáticamente suben una posición los soldados posteriores
}

public boolean esDerrotado() {
//Si ya no quedan soldado, el ejercito ha sido derrotado
return soldados.size() == 0;
}
}

Un Ejercito tiene un grupo de Soldados en un ArrayList.
Necesitamos una clase común entre Heroes y Bestias, para que Ejercito pueda trabajar tanto con unos como con otros..., porque en realidad trabajará con Soldados.

Así que Ejercito gestionará un ArrayList de Soldados. Métodos
- para reclutar Soldados, es decir, insertar Soldados en el ArrayList
- otro para retornar un Soldado de una posición en concreto, esto se usará en los combates por turnos. Como es posible que nos soliciten una posición donde no queda un soldado vivo, controlamos esta excepción y retornaremos null
- otro método comprobará si hay algún soldado muerto y lo eliminará de la lista
- por último, un método que indica si el ejército ha sido derrotado, es decir, ya no quedan soldados en sus filas.

Continuo en el siguiente mensaje.
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: Kabuto en 28 de Agosto 2021, 14:38
Bien, tenemos la clase Ejercito.

Veamos la clase Soldado.
Esta será una superclase, que hará de nexo común entre Heroes y Bestias.
Será abstracta, es decir, no podremos crear un Soldado. Se podrán crear Heroes y Bestias (que son Soldados), pero no un Soldado que no pertenezca a un bando o al otro.

Tendrá los atributos comunes: nombre, vida y resistencia.
Tendrá los métodos comunes: getNombre(), getResistencia(), toString()....

Tendrá el método que indica como gestionar recibir el daño de un ataque, porque esto es igual tanto para Heroes como para Bestias.
Pero NO tendrá el método que gestiona como producir el daño de ataque, ya que esto es distinto para Heroes(mejor de dos dados) y para Bestias(un solo dado)

Si tendrá la "firma abstracta" de dicho método, es decir, no tendrá el código. Pero si "obligará" a que sus clases hijas, Heroes y Bestias, tengan que implementar y sobreescribir este método

Código: [Seleccionar]
public abstract class Soldado {

private String nombre;
private int puntosVida;
private int nivelResistencia;

public Soldado(String nombre, int puntosVida, int nivelResistencia) {
this.nombre = nombre;
this.puntosVida = puntosVida;
this.nivelResistencia = nivelResistencia;
}

public String getNombre() {
return nombre;
}

public boolean estaMuerto() {
return puntosVida <= 0;
}

public int getNivelResistencia() {
return nivelResistencia;
}

public void recibirAtaque(int ataque) {
if (ataque > nivelResistencia)
puntosVida -= ataque - nivelResistencia;

if (puntosVida < 0)
puntosVida = 0; //Evitamos valores negativos
}

@Override
public String toString() {
return String.format("%s (Vida=%d Armadura=%d)", nombre, puntosVida, nivelResistencia);
}

//Este método lo han de sobreescribir Heroe y Bestia, porque será distinto para cada uno
public abstract int atacar();

}

Ahora vamos a ver la clase Heroe.
Esta clase hereda de Soldado.
Un Heroe es un Soldado. ¿Tiene sentido decir eso? Sí lo tiene, así que aprobamos la "relación de herencia"  ;)

Al heredar de Soldado, esta clase estará obligada a sobreescribir el método atacar(), siendo esto lo único que le va a distinguir de la clase Bestia
Código: [Seleccionar]
import java.util.Random;

public class Heroe extends Soldado {

public Heroe(String nombre, int puntosVida, int nivelResistencia) {
super(nombre, puntosVida, nivelResistencia);
}

@Override
public int atacar() {
// El ataque será el mejor lanzamiento entre dos dados de 0 a 100
Random dado = new Random();
int tirada1 = dado.nextInt(101);
int tirada2 = dado.nextInt(101);
System.out.println("Primer dado: " + tirada1);
System.out.println("Segundo dado: " + tirada2);
return Math.max(tirada1, tirada2);
}

}

Ahora la clase Bestia, que viene a ser lo mismo que Heroe, solo varía el código del método que se usa para atacar()
Código: [Seleccionar]
import java.util.Random;

public class Bestia extends Soldado{

public Bestia(String nombre, int puntosVida, int nivelResistencia) {
super(nombre, puntosVida, nivelResistencia);
}

@Override
public int atacar() {
//Único lanzamiento de un dado entre 0 y 90
Random dado = new Random();
int tirada = dado.nextInt(91);
System.out.println("Resultado del dado es: " + tirada);
return tirada;
}

}

Luego vendrían las subclases Humano, Elfo, Orco.... y todas las que queramos imaginar.
No vale la pena que las ponga aquí todas, simplemente lo que han de hacer es heredar de Heroe, o de Bestia, según corresponda:
Código: [Seleccionar]
public class Trasgo extends Bestia {

public Trasgo(String nombre, int puntosVida, int nivelResistencia) {
super(nombre, puntosVida, nivelResistencia);
}

}

Bien, pasamos a lo importante.
La clase Batalla.
Siguiendo un poco el enfoque que tú le diste, este clase tendrá un método estático que recibirá los dos Ejercitos que se van a enfrentar.
Cada Ejercito tiene su propio ArrayList de Soldados Heroes o Soldados Bestia, así que no se necesita declarar ningún otro ArrayList, ni array primitivo, ni nada...

El proceso de batalla sería que, mientras ningún ejercito haya sido derrotado...
... pues hacemos una ronda de turnos.
La duración de esta ronda depende del tamaño del ejército más grande. Los ejércitos cambiarán de tamaño durante la ejecución del programa, así que tras cada ronda de turnos, se vuelve a calcular su duración.

En cada turno, se le pide a cada Ejercito un Soldado que ocupen la misma posición.
Puede ocurrir que algún Ejercito no tenga un Soldado disponible en esa posición, entonces no hay combate y el Soldado que sí está disponible "queda en guardia".

Puede ocurrir que no haya soldado disponible en ninguno de los dos ejércitos, en ese caso, simplemente se pone fin a esta ronda de turnos.

Y si tenemos la suerte de dispone de ambos soldados, pues comienza el combate.
Primero ataca el Heroe. Si la Bestia sobrevive, pues hace su turno de ataque.

Tras esto, cada ejercito actualiza sus filas, es decir, comprueba si hay algún muerto que eliminar de su ArrayList.
Y tras esto, se pasa al siguiente turno con los siguientes soldados

Al terminar los turnos, se inicia una nueva ronda, cuya duración se calcula de nuevo según el ejército con más soldados.

Todo esto terminará cuando alguno de los Ejercitos, haya sido derrotado

Para poder ver paso a paso que ocurre en pantalla, uso un Scanner para pausar la ejecución del programa hasta que se pulse la tecla INTRO.

Código: [Seleccionar]
import java.util.Scanner;

public class Batalla {

private static Scanner sc = new Scanner(System.in);

public static void batallar(Ejercito heroes, Ejercito bestias) {

//Mientras ningun ejército haya sido derrotado....
while(!heroes.esDerrotado() && !bestias.esDerrotado()) {

//Calculamos el total de turnos, según el ejército más grande
int turnosTotal;
if (heroes.soldados.size() >= bestias.soldados.size())
turnosTotal = heroes.soldados.size();
else
turnosTotal = bestias.soldados.size();

//Comienza una ronda de turnos
for (int turno = 0; turno < turnosTotal; turno++) {
//Seleccionamos combatientes
Heroe heroe = (Heroe) heroes.getSoldado(turno);
Bestia bestia = (Bestia) bestias.getSoldado(turno);
//Comprobamos que ninguno sea null
if (heroe == null && bestia == null)
//¿Ambos son null?Entonces esta ronda de turnos ha terminado
break;
else if (heroe == null)
//No hay Heroe, Bestia queda en guardia
System.out.println(bestia.getNombre() + " queda en guardia");
else if (bestia == null)
//No hay Bestia, Heroe queda en guardia
System.out.println(heroe.getNombre() + " queda en guardia");
else {
//Ninguno es null, comienza el combate
System.out.println("Lucha entre " + heroe + " y " + bestia);
//Turno heroe
System.out.println("Turno de " + heroe.getNombre());
int ataqueH = heroe.atacar();
bestia.recibirAtaque(ataqueH);
System.out.println("Datos Actualizados de " + bestia);
if (bestia.estaMuerto())
System.out.println(bestia.getNombre() + " ha muerto.");
else {
//Turno bestia
System.out.println("Turno de " + bestia.getNombre());
int ataqueB = bestia.atacar();
heroe.recibirAtaque(ataqueB);
System.out.println("Datos Actualizados de " + heroe);
if (heroe.estaMuerto())
System.out.println(heroe.getNombre() + " ha muerto.");
}
}
//Turno combate finalizado, ejercitos actualizan sus filas
heroes.comprobarEjercito();
bestias.comprobarEjercito();
pausa();
//Y se inicia el siguiente turno
}
}

//Las rondas de turnos han finalizado porque algún ejército ha sido derrotado. Comprobamos
if (heroes.esDerrotado())
System.out.println("Han ganado las Bestias. Soldados restantes: " + bestias.soldados.size());
else
System.out.println("Han ganado los Heroes. Soldados restantes: " + heroes.soldados.size());
}

private static void pausa() {
System.out.println("\n\t\tPulse INTRO para continuar...\n");
sc.nextLine();
}

}


Y listo.
Ahora solo queda hacer una clase Main, declarar dos Ejercitos, reclutar algunos Soldados.., y hacer que se maten entre ellos.. ::)
Código: [Seleccionar]
public class Main {

    public static void main (String args[]){
   
        Ejercito heroes = new Ejercito();
        Ejercito bestias = new Ejercito();

        heroes.reclutarSoldado(new Elfos("Legolas", 150, 30));
        heroes.reclutarSoldado(new Humano("Aragorn", 150, 50));
        heroes.reclutarSoldado(new Humano("Boromir",100, 60));
        heroes.reclutarSoldado(new Humano("Gandalf",300, 10));
        heroes.reclutarSoldado(new Hobbits("Frodo", 20, 10));

        bestias.reclutarSoldado(new Orcos("Lurtz", 200, 60));
        bestias.reclutarSoldado(new Orcos("Shagrat", 220, 50));
        bestias.reclutarSoldado(new Trasgo("Uglúk", 120, 30));
        bestias.reclutarSoldado(new Trasgo("Mauhúr", 100, 30));

        Batalla.batallar(heroes, bestias);

    }
}

Si algo no ha quedado claro, por favor, no dudes en preguntar, y repreguntar.

Un saludo.
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: MirMiriam en 28 de Agosto 2021, 20:57
!Muchísimas gracias de verdad, porque estaba muy perdida!

La verdad pensaba que la herencia lo había comprendido mejor pero me ha resultado mucho más útil y sencillo de entender con la manera que indicas.

Una última pregunta si quisiera hacer por ejemplo que un Elfo incrementara su ataque en X puntos si se enfrenta a un Orco, ¿se podría realizar llamando de nuevo al método atacar() dentro de un método específico para esa clase Elfo o es mejor de otra manera?

Muchas gracias de nuevo  ;D
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: Kabuto en 28 de Agosto 2021, 23:51
Mmhhh.. para hacer lo que dices...

El problema es que tal y como yo he diseñado la batalla, los Soldados no pueden saber a quién se están enfrentando.
El que ataca genera un valor de daño, y este valor se lo enviamos al que defiende para que evalúe si es suficiente para producirle daño o no.
Código: [Seleccionar]
int ataqueH = heroe.atacar();
bestia.recibirAtaque(ataqueH);
Pero no hay comunicación directa entre ellos, no saben si están atacando a un Orco, a un Goblin, o que...

Pero bueno, podría modificarse para que el método atacar(), exija recibir entre paréntesis al enemigo que se está atacando.
Y ahora, en lugar de retornar el valor de ataque en un int, dicho valor se aplicará directamente al enemigo
Comenzamos indicando esta exigencia en la declaración abstracta de este método, en la clase Soldado.
Marco los cambios en negrita
Citar
//Este método lo han de sobreescribir Heroe y Bestia, porque será distinto para cada uno
   public abstract void atacar(Soldado enemigo);

Al hacer este cambio, ahora Java nos obliga a que, al menos, las clases Heroe y Bestia se adapten a esta exigencia.
Lo que haremos será cambiar el código para que ya no se retorne ningún valor, si no que el daño se aplicará al "enemigo" recibido entre paréntesis.

La clase Heroe queda con estos cambios:

Citar
import java.util.Random;

public class Heroe extends Soldado {

   public Heroe(String nombre, int puntosVida, int nivelResistencia) {
      super(nombre, puntosVida, nivelResistencia);
   }

   @Override
   public void atacar(Soldado enemigo) {
      // El ataque será el mejor lanzamiento entre dos dados de 0 a 100
      Random dado = new Random();
      int tirada1 = dado.nextInt(101);
      int tirada2 = dado.nextInt(101);
      System.out.println("Primer dado: " + tirada1);
      System.out.println("Segundo dado: " + tirada2);
      int maximo =  Math.max(tirada1, tirada2);
      enemigo.recibirAtaque(maximo);

   }

}

Y la clase Bestia queda así:

Citar
import java.util.Random;

public class Bestia extends Soldado{

   public Bestia(String nombre, int puntosVida, int nivelResistencia) {
      super(nombre, puntosVida, nivelResistencia);
   }

   @Override
   public void atacar(Soldado enemigo) {
      //Único lanzamiento de un dado entre 0 y 90
      Random dado = new Random();
      int tirada = dado.nextInt(91);
      System.out.println("Resultado del dado es: " + tirada);
      enemigo.recibirAtaque(tirada);
   }

}

Y por último modificamos ligeramente el método de la clase Batalla.
Ahora ya no capturamos el daño generado para pasárselo al enemigo a ver si le hemos hecho pupa, todo esto ya ocurre dentro del método atacar() de cada Soldado.
Es decir, las líneas que indiqué al principio:
Código: [Seleccionar]
int ataqueH = heroe.atacar();
bestia.recibirAtaque(ataqueH);
Ahora se reducen a esto:
Código: [Seleccionar]
heroe.atacar(bestia);
Así que la clase Batalla queda así:

Citar
public class Batalla {
   
   private static Scanner sc = new Scanner(System.in);
   
   public static void batallar(Ejercito heroes, Ejercito bestias) {
      
      //Mientras ningun ejército haya sido derrotado....
      while(!heroes.esDerrotado() && !bestias.esDerrotado()) {

         //Calculamos el total de turnos, según el ejército más grande
         int turnosTotal;
         if (heroes.soldados.size() >= bestias.soldados.size())
            turnosTotal = heroes.soldados.size();
         else
            turnosTotal = bestias.soldados.size();
         
         //Comienza una ronda de turnos
         for (int turno = 0; turno < turnosTotal; turno++) {
            //Seleccionamos combatientes
            Heroe heroe = (Heroe) heroes.getSoldado(turno);
            Bestia bestia = (Bestia) bestias.getSoldado(turno);
            //Comprobamos que ninguno sea null
            if (heroe == null && bestia == null)
               //¿Ambos son null?Entonces esta ronda de turnos ha terminado
               break;
            else if (heroe == null)
               //No hay Heroe, Bestia queda en guardia
               System.out.println(bestia.getNombre() + " queda en guardia");
            else if (bestia == null)
               //No hay Bestia, Heroe queda en guardia
               System.out.println(heroe.getNombre() + " queda en guardia");
            else {
               //Ninguno es null, comienza el combate
               System.out.println("Lucha entre " + heroe + " y " + bestia);
               //Turno heroe
               System.out.println("Turno de " + heroe.getNombre());
               heroe.atacar(bestia);
               System.out.println("Datos Actualizados de " + bestia);
               if (bestia.estaMuerto())
                  System.out.println(bestia.getNombre() + " ha muerto.");
               else {
                  //Turno bestia
                  System.out.println("Turno de " + bestia.getNombre());
                  bestia.atacar(heroe);
                  System.out.println("Datos Actualizados de " + heroe);
                  if (heroe.estaMuerto())
                     System.out.println(heroe.getNombre() + " ha muerto.");
               }
            }
            //Turno combate finalizado, ejercitos actualizan sus filas
            heroes.comprobarEjercito();
            bestias.comprobarEjercito();
            pausa();
            //Y se inicia el siguiente turno
         }
      }
      
      //Las rondas de turnos han finalizado porque algún ejército ha sido derrotado. Comprobamos
      if (heroes.esDerrotado())
         System.out.println("Han ganado las Bestias. Soldados restantes: " + bestias.soldados.size());
      else
         System.out.println("Han ganado los Heroes. Soldados restantes: " + heroes.soldados.size());
   }

   private static void pausa() {
      System.out.println("\n\t\tPulse INTRO para continuar...\n");
      sc.nextLine();
   }
   
}


Si ahora, con estos cambios, ejecutamos el programa. Va a funcionar exactamente igual que antes.
Pero ahora sí hemos conseguido que el Soldado que ataca, pueda saber a quién está atacando.
Y esto abre la puerta a conseguir lo que tu pedías, poder añadir reglas específicas de combate según la subclase de cada Soldado.

Por ejemplo: Queremos que un Elfo aumente su ataque al enfrentarse un Orco

Bien, ahora mismo, las clases Elfo, Orco, Humano, etc... son muy simples. No aportan nada.

Código: [Seleccionar]
public class Elfos extends Heroe {

public Elfos(String nombre, int puntosVida, int nivelResistencia) {
super(nombre, puntosVida, nivelResistencia);
}

}

Pero podemos pedirle que sobreescriban el método atacar() para cambiar las reglas de ataque.

Por ejemplo, podemos hacer que un Elfo incremente su ataque un 50% (1.5 veces) si se enfrenta a un Orco.


Citar
public class Elfos extends Heroe {

   public Elfos(String nombre, int puntosVida, int nivelResistencia) {
      super(nombre, puntosVida, nivelResistencia);
   }

   @Override
   public void atacar(Soldado enemigo) {
      if (enemigo instanceof Orcos) {
         //Regla específica cuando un Elfo ataca un Orco
         Random dado = new Random();
         int tirada1 = dado.nextInt(101);
         int tirada2 = dado.nextInt(101);
         System.out.println("Primer dado: " + tirada1);
         System.out.println("Segundo dado: " + tirada2);
         int maximo =  Math.max(tirada1, tirada2);
         System.out.println("*****¡¡El odio élfico hacia los Orcos incrementa el ataque en 1.5x!!*****");
         maximo *= 1.5;
         System.out.println("Valor de ataque resultante: " + maximo);
         enemigo.recibirAtaque(maximo);
         
      }
      else //Si no es Orco, se aplica la regla general de los Heroes
         super.atacar(enemigo);
   }

}

Entonces ahora, al combatir, veremos como Légolas puede hacer ataques letales al enfrentarse con Orcos:
Citar
Lucha entre Legolas (Vida=53 Armadura=30) y Shagrat (Vida=31 Armadura=50)
Turno de Legolas
Primer dado: 17
Segundo dado: 95
*****¡¡El odio élfico hacia los Orcos incrementa el ataque en 1.5x!!*****
Valor de ataque resultante: 142
Datos Actualizados de Shagrat (Vida=0 Armadura=50)
Shagrat ha muerto.

En cambio si se enfrenta a un Goblin, atacará como cualquier otro Heroe
Citar
Lucha entre Legolas (Vida=150 Armadura=30) y Uglúk (Vida=120 Armadura=30)
Turno de Legolas
Primer dado: 21
Segundo dado: 74
Datos Actualizados de Uglúk (Vida=76 Armadura=30)
Turno de Uglúk
Resultado del dado es: 30
Datos Actualizados de Legolas (Vida=150 Armadura=30)

Y de esta manera, podemos personalizar las reglas de ataque según cada Heroe y cada Bestia  ;)
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: MirMiriam en 29 de Agosto 2021, 00:11
!!Muchísimas gracias de verdad!! No sé como agradecerte todo lo que me has ayudado.

 ;D ;D ;D ;D
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: MirMiriam en 14 de Enero 2022, 21:16
Buenas noches de nuevo, Estoy intentando aplicar la librería swing al código con el que me ayudaron y no lo consigo, ya que al ir a empezar no si si es mejor hacer varias clases JPanel una para los Heroes y otra para las Bestias y dentro de ellas aplicar una estructura sencilla donde elegir el nombre, el tipo de héroe o bestia (hobbit, humano, Orco, etc), la vida y la armadura para luego dar un botón de lucha donde aparecerá el texto que ya teníamos.

Os Agradecería mucho si me pudieras ayudar de nuevo.

Un saludo.
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: Kabuto en 15 de Enero 2022, 00:38
Mmmmhh.. Recomiendo primero hacer un dibujo, ya sea con Paint o con papel y lápiz, haciendo un boceto aproximado de lo que se quiere conseguir tener en pantalla.
No tiene que ser una obra de arte, bastan unos garabatos que ayuden a visualizar lo que se tiene en mente.

A partir de ahí es más fácil identificar donde será necesario un panel, un botón,...
Que luego sobre la marcha, se van tomando decisiones y quizá se acaba diseñando algo muy distinto a lo dibujado, pero el boceto al menos ya de un punto de partida.

No se cómo tienes pensado diseñarlo, pero es muy posible que puedas escribir una única clase JPanel, de la que crear dos objetos (dos paneles) y que sirva por igual para Heroes y Bestias en lo que respecta a la "Vista".
Lo que cambiará serán los datos (el "Modelo") que le pases a cada panel, es decir, a uno le pasarás Heroes y al otro Bestias
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: MirMiriam en 15 de Enero 2022, 14:13
Pues en un principio he pensado en algo como la imagen que adjunto y una vez que le pinches en el botón de lucha que aparezca un cuadre con el desglose de la batalla.
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: Kabuto en 15 de Enero 2022, 23:53
OK.

Pues como dije, para los dos paneles superiores en principio bastaría con escribir una única clase JPanel.
Solo cambiará el texto de algún JLabel y que uno será para Heroes y otro para Bestias, que en realidad todos son clase Soldado.

Lo mismo con los paneles inferiores, escribiendo una sola clase podrías crear ambos paneles.

En la parte inferior, quizás se podría añadir otro panel con un JTextArea donde se irían mostrando los textos del combate
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: MirMiriam en 16 de Enero 2022, 00:52
Muchas gracias, pero perdona que te pregunte, otra vez, porque no lo entiendo del todo y por confirmar, en la clase por ejemplo PanelArriba, ¿en el constructor llamo a la clase soldado y como de ella se desglosa Heroes y bestias ya podría aplicar un JList?

Perdona de nuevo, porque aunque veo que swing no es difícil al tener un código ya hecho me es más complicado pasarlo a una interfaz gráfica y me está costando más de lo que esperaba.
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: Kabuto en 16 de Enero 2022, 02:49
Coger un código escrito para el "modo consola" y adaptarlo a una GUI, sí, es más complicado de lo que parece, porque el flujo del programa es muy distinto, así como la forma de organizarlo.

Además, no se que herramientas vas a usar para crearlo.
Es decir, en los centros de enseñanza es habitual enseñar Swing creando interfaces directamente con el asistente de Netbeans (o el equivalente para otro IDE), con la opción JFrame Form, donde indicas donde quieres colocar un componente swing y Netbeans te escribe todo el código necesario (y también el innecesario).
Esto permite crear de forma rápida interfaces, pero implica que el programador se desentienda de la estructura del código y en mi modesta opinión (no soy ni maestro, ni profesional ni nada...) luego cuesta más encontrar una forma coherente de combinar y relacionar la Vista (lo que se ve en pantalla, botones, campos, etiquetas...) con el Modelo (lo que no se ve, los ArrayList, variables, métodos, clases, etc..)

Yo las interfaces las hago escribiendo código a mano y es cierto que se requiere más tiempo de aprendizaje conseguir crear interfaces medio decentes..., pero al tener total control sobre el código escrito, luego es más fácil relacionar Vista y Modelo.

No se cuál de estos dos métodos vas a emplear tú, pero en cualquier caso...

...el panel superior, o los paneles superiores (yo uniría dos paneles creados a partir de una misma clase) su función es crear Soldados para los Ejercitos.

El código debería tener en algún sitio dos objetos Ejercitos ya declarados e inicializados al comenzar el programa.
Estos Ejercitos comienza sin Soldados, los cuáles se irán creando con el panel superior y añadiendo al Ejercito correspondiente según si son Heroes o Bestias.

Si el panel superior va a ser creado como clase independiente del resto del programa, necesitamos de algún modo que tenga acceso a esos Ejercitos para que cuando se pulse el botón Añadir pueda agregar Soldados a los Ejercitos.

Una opción es pasar por constructor una referencia a esos Ejercitos, los cuales seguramente vamos a querer tener declarados en la clase JFrame principal.
Así, los cambios que hagamos en los Ejercitos desde la clase del panel superior, se verán reflejados en los Ejercitos declarados en la clase principal.


Otra opción, es escribir el código del ActionListener del botón "Añadir" como una subclase escrita en la clase JFrame principal, donde se tendría acceso a los Ejercitops ahí declarados.
Luego, este ActionListener se lo transferimos al botón del panel superior. Así, aunque este panel superior no tenga referencia o acceso a los Ejercitos, no importará, porque su ActionListener, que es quien recogerá los datos introducidos y creará el nuevo Soldado, si tiene acceso a los Ejercitos.



No se si me estoy explicando bien, a ver si mañana puedo escribir algún ejemplo.
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: MirMiriam en 16 de Enero 2022, 08:21
Pues voy a utilizar el IDE de IntellJ IDEA, pero no aplicando un interfaz directamente sino escribiendo código ya que también soy de la opinión que es mejor escribir el código desde cero, pero estoy con el problema de que cuando voy a empezar me surgen tantas dudas  que me bloqueo y no consigo escribir ni una sola línea de código.

Te agradecería mucho un pequeño ejemplo de lo que comentas porque no lo entiendo bien, muchísimas gracias por la ayuda.
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: Kabuto en 16 de Enero 2022, 13:45
Es normal bloquearse  al principio ;)
Y también seguir caminos que llevan a callejones sin salida, pero no te preocupes, forma parte del proceso de aprendizaje.

Lo ideal es centrarte primero en crear la Vista, sin preocuparte demasiado por el Modelo.
Es decir, haz una interfaz con sus campos y botones, que de momento, no van a tener ninguna función.

Luego ya irás integrando el Modelo. Y sí, puede que durante ese proceso veas la necesidad de reescribir o desechar parte del código escrito para la Vista. Pero así va esto.

A ver, voy a empezar un proyecto de cero.
En el diseño que propones, tanto para la parte de arriba como para la de abajo, yo crearía dos paneles a partir de una única clase.
Lo señalado en rojo serían dos paneles, pero de una misma clase.
Y lo mismo para lo señalado en verde.
(https://i.ibb.co/LgJvGKp/Ejemplo-batalla-paneles.jpg)

Veamos la clase para los paneles superiores.
En lo que respecta a la Vista, entre ambos paneles solo cambia el titulo del borde del panel y las opciones del JComboBox. Así que estos datos se pueden indicar por el constructor

Para maquetar los componentes, puedes usar el/los layouts que tú prefieras.

Yo (de momento) creo que voy a usar un BoxLayout vertical, donde apilar paneles unos sobre otros.
Estos paneles los haré con una subclase que reciba por constructor el texto para un JLabel y el componente que acompañará

Clase PanelCrearSoldado
Código: [Seleccionar]
public class PanelCrearSoldado extends JPanel {

private JTextField jtNombre;
private JComboBox<String> jcTipo;
private JTextField jtVida;
private JTextField jtArmadura;
private JButton btAnadir;

public PanelCrearSoldado(String titulo, String[] tipos) {
//Inicializamos componentes
jtNombre = new JTextField();
jcTipo = new JComboBox<String>(tipos);
jtVida = new JTextField();
jtArmadura = new JTextField();
btAnadir = new JButton("Añadir");

//Layout de "cajas" verticales
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

/*
* Cada "caja" apilada verticalmente será un panel
* de clase PanelConLabel
*/
add(new PanelConLabel("Nombre:", jtNombre));
add(new PanelConLabel("Tipo:", jcTipo));
add(new PanelConLabel("Vida:", jtVida));
add(new PanelConLabel("Armadura:", jtArmadura));

//Boton añadir
add(btAnadir);

//Combinamos dos bordes, uno titulado y otro vacío para crear algo de relleno
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(15, 15, 15, 15),
BorderFactory.createTitledBorder(titulo)));

}

private class PanelConLabel extends JPanel {

public PanelConLabel(String texto, JComponent componente) {
//Layout tipo grilla, será una fila con dos columnas
setLayout(new GridLayout(1,2));
//En la primera columna, la etiqueta
add(new JLabel(texto));
//En la segunda columna, el componente que acompaña la etiqueta
add(componente);
}
}

}

Estaría bien poder ver cómo ha quedado, así que vamos a crear ahora la clase principal, que será el JFrame que contenga toda la interfaz.
De momento mostraremos solo el panel que hemos desarrollado, a ver que aspecto tiene.
Código: [Seleccionar]
public class BatallaRPG extends JFrame {

private PanelCrearSoldado crearHeroes;

public BatallaRPG() {

crearHeroes = new PanelCrearSoldado("Heroes", new String[] {"Elfo", "Humano", "Hobbit"});

setContentPane(crearHeroes);

setTitle("Batalla RPG");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
}

public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new BatallaRPG();
}
});
}
}

Veamos qué se nos dibuja en pantalla:
(https://i.ibb.co/fQZ1G34/imagen-2022-01-16-124010.png)

Bueno, no es una belleza, pero básicamente es el diseño que buscábamos. Necesita arreglillos.
Más separación entre las "cajas" apiladas, podemos hacerlo inflándolas un poco añadiendo algo de borde vacío en su interior.
Y el botón "Añadir" quedará mejor centrado si lo colocamos dentro de su propio panel...

Citar
public class PanelCrearSoldado extends JPanel {
   
   private JTextField jtNombre;
   private JComboBox<String> jcTipo;
   private JTextField jtVida;
   private JTextField jtArmadura;
   private JButton btAnadir;
   
   public PanelCrearSoldado(String titulo, String[] tipos) {
      //Inicializamos componentes
      jtNombre = new JTextField();
      jcTipo = new JComboBox<String>(tipos);
      jtVida = new JTextField();
      jtArmadura = new JTextField();
      btAnadir = new JButton("Añadir");
      
      //Layout de "cajas" verticales
      setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
      
      /*
       * Cada "caja" apilada verticalmente será un panel
       * de clase PanelConLabel
       */
      add(new PanelConLabel("Nombre:", jtNombre));
      add(new PanelConLabel("Tipo:", jcTipo));
      add(new PanelConLabel("Vida:", jtVida));
      add(new PanelConLabel("Armadura:", jtArmadura));
      
      //Boton añadir, le damos un panel propio para que quede mejor centrado
      JPanel pnAnadir = new JPanel();
      pnAnadir.add(btAnadir);
      add(pnAnadir);

      
      //Combinamos dos bordes, uno titulado y otro vacío para crear algo de relleno
      setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createEmptyBorder(15, 15, 15, 15),
            BorderFactory.createTitledBorder(titulo)));
      
   }
   
   private class PanelConLabel extends JPanel {
      
      public PanelConLabel(String texto, JComponent componente) {
         //Layout tipo grilla, será una fila con dos columnas
         setLayout(new GridLayout(1,2));
         //En la primera columna, la etiqueta
         add(new JLabel(texto));
         //En la segunda columna, el componente que acompaña la etiqueta
         add(componente);
         //"Inflamos" el panel con algo de borde vacío
         setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

      }
   }

}

A ver...

(https://i.ibb.co/s3h9DJt/imagen-2022-01-16-125144.png)

Bueno, ya tiene mejor aspecto. Por ahora puede servir para seguir avanzando.
Lo que haremos será poner a su lado un segundo panel, esta vez para crear Bestias.

Para ponerlos juntos, en el JFrame crearemos un "panel superior" donde insertaremos nuestros dos paneles de creación de soldados.

Citar
public class BatallaRPG extends JFrame {
   
   private PanelCrearSoldado crearHeroes;
   private PanelCrearSoldado crearBestias;
   
   public BatallaRPG() {
      
      crearHeroes = new PanelCrearSoldado("Heroes", new String[] {"Elfo", "Humano", "Hobbit"});
      crearBestias = new PanelCrearSoldado("Bestias", new String[] {"Trasgo", "Orco"});
      
      JPanel pnSuperior = new JPanel();
      pnSuperior.add(crearHeroes);
      pnSuperior.add(crearBestias);
      
      setContentPane(pnSuperior);

      
      setTitle("Batalla RPG");
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      pack();
      setLocationRelativeTo(null);
      setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         @Override
         public void run() {
            new BatallaRPG();   
         }
      });
   }
}

Y de esta manera, con una única clase JPanel, ya tenemos dos paneles idénticos para crear un tipo de soldado u otro.

(https://i.ibb.co/RpGNG41/imagen-2022-01-16-125919.png)


Bueno, y ahora viene el quid de la cuestión.
¿Dónde se guardan los soldados? ¿Cómo sabrán estos paneles que soldado crear y donde ubicarlos?

Bien, tendremos dos Ejercitos, y estos conviene que se encuentren en la clase principal JFrame, para que su "ámbito" sea lo más amplio posible y los distintos paneles que finalmente compongan la interfaz tengan posibilidad de acceder a ellos.

Así que los añadimos como atributos del JFrame, y los inicializamos en el constructor.
Citar
public class BatallaRPG extends JFrame {
   
   //Modelo
   private Ejercito bestias;
   private Ejercito heroes;

   
   //Vista
   private PanelCrearSoldado crearHeroes;
   private PanelCrearSoldado crearBestias;
   
   public BatallaRPG() {
      bestias = new Ejercito();
      heroes = new Ejercito();

      crearHeroes = new PanelCrearSoldado("Heroes", new String[] {"Elfo", "Humano", "Hobbit"});
      crearBestias = new PanelCrearSoldado("Bestias", new String[] {"Trasgo", "Orco"});
      
      JPanel pnSuperior = new JPanel();
      pnSuperior.add(crearHeroes);
      pnSuperior.add(crearBestias);
      
      setContentPane(pnSuperior);
      
      setTitle("Batalla RPG");
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      pack();
      setLocationRelativeTo(null);
      setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         @Override
         public void run() {
            new BatallaRPG();   
         }
      });
   }
}

Ahora necesitamos que los paneles de creación de soldados, tengan algún tipo de conexión con estos ejércitos para poder agregarles soldados.

Una forma, sería dotar a estos paneles de una referencia a estos ejércitos. Como cada panel va a interactuar con un único ejercito, le añadimos a su clase un ejercito como atributo, y recibirá por constructor la referencia de uno de los ejércitos declarados en la clase principal.

Ya de paso, desarrollamos un ActionListener para que el botón "Añadir" comience a funcionar.
Esta acción ha de recuperar los datos de los campos, evaluar que tipo de soldado se está creando y añadirlo al ejercito que tenga referenciado.
De momento, podemos informar por consola de que hay un nuevo recluta.

Citar
public class PanelCrearSoldado extends JPanel {
   
   private JTextField jtNombre;
   private JComboBox<String> jcTipo;
   private JTextField jtVida;
   private JTextField jtArmadura;
   private JButton btAnadir;
   
   //Atributo para referenciar alguno de los ejercitos de la clase main
   private Ejercito ejercito;

   
   public PanelCrearSoldado(String titulo, String[] tipos, Ejercito ejercito) {
      //Inicializamos componentes
      jtNombre = new JTextField();
      jcTipo = new JComboBox<String>(tipos);
      jtVida = new JTextField();
      jtArmadura = new JTextField();
      btAnadir = new JButton("Añadir");
      btAnadir.addActionListener(new AccionCrearSoldado());
      
      this.ejercito = ejercito; //Ejercito referenciado
      
      //Layout de "cajas" verticales
      setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
      
      /*
       * Cada "caja" apilada verticalmente será un panel
       * de clase PanelConLabel
       */
      add(new PanelConLabel("Nombre:", jtNombre));
      add(new PanelConLabel("Tipo:", jcTipo));
      add(new PanelConLabel("Vida:", jtVida));
      add(new PanelConLabel("Armadura:", jtArmadura));
      
      //Boton añadir, le damos un panel propio para que quede mejor centrado
      JPanel pnAnadir = new JPanel();
      pnAnadir.add(btAnadir);
      add(pnAnadir);
      
      //Combinamos dos bordes, uno titulado y otro vacío para crear algo de relleno
      setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createEmptyBorder(15, 15, 15, 15),
            BorderFactory.createTitledBorder(titulo)));
      
   }
   
   private class PanelConLabel extends JPanel {
      
      public PanelConLabel(String texto, JComponent componente) {
         //Layout tipo grilla, será una fila con dos columnas
         setLayout(new GridLayout(1,2));
         //En la primera columna, la etiqueta
         add(new JLabel(texto));
         //En la segunda columna, el componente que acompaña la etiqueta
         add(componente);
         //"Inflamos" el panel con algo de borde vacío
         setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
      }
   }
   
   private class AccionCrearSoldado implements ActionListener {

      @Override
      public void actionPerformed(ActionEvent e) {
         //Recogemos datos
         String nombre = jtNombre.getText();
         String tipo = (String) jcTipo.getSelectedItem();
         int vida = Integer.parseInt(jtVida.getText());
         int armadura = Integer.parseInt(jtArmadura.getText());
         
         //Creamos nuevo soldado según tipo
         switch(tipo) {
         case "Elfo":
            ejercito.reclutarSoldado(new Elfos(nombre, vida, armadura));
            break;
         case "Humano":
            ejercito.reclutarSoldado(new Humano(nombre, vida, armadura));
            break;
         case "Hobbit":
            ejercito.reclutarSoldado(new Hobbits(nombre, vida, armadura));
            break;
         case "Trasgo":
            ejercito.reclutarSoldado(new Trasgo(nombre, vida, armadura));
            break;
         case "Orco":
            ejercito.reclutarSoldado(new Orcos(nombre, vida, armadura));
            break;
         }
         //Limpiamos campos
         jtNombre.setText(null);
         jcTipo.setSelectedIndex(0);
         jtVida.setText(null);
         jtArmadura.setText(null);
         
         //Confirmamos en consola la creación
         System.out.println("Nuevo soldado reclutado");         
      }

   }

}

Ahora en la clase JFrame, ya podemos pasarles las referencias a estos paneles
Citar
public class BatallaRPG extends JFrame {
   
   //Modelo
   private Ejercito bestias;
   private Ejercito heroes;
   
   //Vista
   private PanelCrearSoldado crearHeroes;
   private PanelCrearSoldado crearBestias;
   
   public BatallaRPG() {
      bestias = new Ejercito();
      heroes = new Ejercito();
      //Este panel referenciará los Heroes
      crearHeroes = new PanelCrearSoldado("Heroes", new String[] {"Elfo", "Humano", "Hobbit"}, heroes);
      //Este refenciará a la Bestias
      crearBestias = new PanelCrearSoldado("Bestias", new String[] {"Trasgo", "Orco"}, bestias);


Y ahora ya podemos crear soldados en los ejércitos.
Parece que todo va por buen camino, pero luego va a venir una dificultad.... :o

Los soldados creados, han de mostrarse en un JList que aún no hemos creado. Este JList, se escribirá en otra clase JPanel.
La idea es que, al pulsar el botón "Añadir" en el panel de creación, se muestre el nuevo soldado en el JList.

Pero, y he aquí la dificultad, el botón "Añadir" y el JList pertenecen a clases diferentes. Desde el ActionListener que hemos escrito en el panel de crear personajes, no podemos actuar sobre el JList. Habrá que buscar, de nuevo, alguna solución para que haya comunicación entre esos paneles.
Pero eso ya lo veremos luego.
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: Kabuto en 16 de Enero 2022, 17:28
Vamos a crear los paneles inferiores.
Como antes, mediante una única clase, se pueden crear ambos paneles.

Código: [Seleccionar]
public class PanelLista extends JPanel {

private JList<Soldado> lista;
private DefaultListModel<Soldado> modeloLista;
private JButton btSubir;
private JButton btBajar;
private JButton btEliminar;

public PanelLista(String titulo) {
lista = new JList<Soldado>();
lista.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
modeloLista = new DefaultListModel<Soldado>();
lista.setModel(modeloLista);
btSubir = new JButton("Subir");
btBajar = new JButton("Bajar");
btEliminar = new JButton("Subir");

setLayout(new BorderLayout());
JScrollPane scrollLista = new JScrollPane();
scrollLista.setViewportView(lista);
scrollLista.setBorder(BorderFactory.createTitledBorder(null, titulo));
add(scrollLista, BorderLayout.CENTER);

JPanel pnBotones = new JPanel();
pnBotones.add(btSubir);
pnBotones.add(btBajar);
pnBotones.add(btEliminar);
add(pnBotones, BorderLayout.SOUTH);
}

}

Bien, ya tendríamos el panel con la lista. Ahora toca ver donde lo colocamos.

Pensando en el problema de cómo hacer que el panel con JList también tenga acceso a los ejercitos..., me han pasado por la cabeza varias formas de hacerlo, todas ellas correctas, pero a ver cuál es la que requiere menos código y/o resulta menos difícil de entender.

Y entonces se me ha ocurrido que este panel con la lista, aunque sea una clase escrita a parte, la podemos integrar directamente como un componente más del panel de creación de soldados.
Así de forma sencilla, podemos pasarle copia de la referencia de los ejercitos, y no habrá que añadir nuevo código en la clase JFrame.


Así que a PanelLista, la clase que acabamos de escribir, le incluimos atributo y referencia a un ejercito

Citar
public class PanelLista extends JPanel {
   
   private JList<Soldado> lista;
   private DefaultListModel<Soldado> modeloLista;
   private JButton btSubir;
   private JButton btBajar;
   private JButton btEliminar;
   
   private Ejercito ejercito;
   
   public PanelLista(String titulo, Ejercito ejercito) {
      this.ejercito = ejercito;
      lista = new JList<Soldado>();
      lista.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
      modeloLista = new DefaultListModel<Soldado>();
      lista.setModel(modeloLista);
      btSubir = new JButton("Subir");
      btBajar = new JButton("Bajar");
      btEliminar = new JButton("Eliminar");
      
      setLayout(new BorderLayout());
      JScrollPane scrollLista = new JScrollPane();
      scrollLista.setViewportView(lista);
      scrollLista.setBorder(BorderFactory.createTitledBorder(null, titulo));
      add(scrollLista, BorderLayout.CENTER);
      
      JPanel pnBotones = new JPanel();
      pnBotones.add(btSubir);
      pnBotones.add(btBajar);
      pnBotones.add(btEliminar);
      add(pnBotones, BorderLayout.SOUTH);
   }

}

Y en la clase PanelCrearSoldado, añadimos el panel de lista a los atributos como un componente más, le pasamos la referencia por constructor y lo colocamos debajo de las "cajas" apiladas en la interfaz.

Citar
public class PanelCrearSoldado extends JPanel {
   
   private JTextField jtNombre;
   private JComboBox<String> jcTipo;
   private JTextField jtVida;
   private JTextField jtArmadura;
   private JButton btAnadir;
   
   //Atributo para referenciar alguno de los ejercitos de la clase main
   private Ejercito ejercito;
   
   //Panel con la lista de soldados
   private PanelLista lista;

   
   public PanelCrearSoldado(String titulo, String[] tipos, Ejercito ejercito) {
      //Inicializamos componentes
      jtNombre = new JTextField();
      jcTipo = new JComboBox<String>(tipos);
      jtVida = new JTextField();
      jtArmadura = new JTextField();
      btAnadir = new JButton("Añadir");
      btAnadir.addActionListener(new AccionCrearSoldado());
      
      this.ejercito = ejercito; //Ejercito referenciado
      
      lista = new PanelLista(titulo, ejercito); //Pasamos titulo de borde y referencia a ejercito
      
      //Layout de "cajas" verticales
      setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
      
      /*
       * Cada "caja" apilada verticalmente será un panel
       * de clase PanelConLabel
       */
      add(new PanelConLabel("Nombre:", jtNombre));
      add(new PanelConLabel("Tipo:", jcTipo));
      add(new PanelConLabel("Vida:", jtVida));
      add(new PanelConLabel("Armadura:", jtArmadura));
      
      //Boton añadir, le damos un panel propio para que quede mejor centrado
      JPanel pnAnadir = new JPanel();
      pnAnadir.add(btAnadir);
      add(pnAnadir);
      
      //Colocamos el panel lista debajo de todo
      add(lista);

      
      //Combinamos dos bordes, uno titulado y otro vacío para crear algo de relleno
      setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createEmptyBorder(15, 15, 15, 15),
            BorderFactory.createTitledBorder(titulo)));
      
   }
   
   private class PanelConLabel extends JPanel {
      
      public PanelConLabel(String texto, JComponent componente) {
         //Layout tipo grilla, será una fila con dos columnas
         setLayout(new GridLayout(1,2));
         //En la primera columna, la etiqueta
         add(new JLabel(texto));
         //En la segunda columna, el componente que acompaña la etiqueta
         add(componente);
         //"Inflamos" el panel con algo de borde vacío
         setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
      }
   }
   
   private class AccionCrearSoldado implements ActionListener {

      @Override
      public void actionPerformed(ActionEvent e) {
         //Recogemos datos
         String nombre = jtNombre.getText();
         String tipo = (String) jcTipo.getSelectedItem();
         int vida = Integer.parseInt(jtVida.getText());
         int armadura = Integer.parseInt(jtArmadura.getText());
         
         //Creamos nuevo soldado según tipo
         switch(tipo) {
         case "Elfo":
            ejercito.reclutarSoldado(new Elfos(nombre, vida, armadura));
            break;
         case "Humano":
            ejercito.reclutarSoldado(new Humano(nombre, vida, armadura));
            break;
         case "Hobbit":
            ejercito.reclutarSoldado(new Hobbits(nombre, vida, armadura));
            break;
         case "Trasgo":
            ejercito.reclutarSoldado(new Trasgo(nombre, vida, armadura));
            break;
         case "Orco":
            ejercito.reclutarSoldado(new Orcos(nombre, vida, armadura));
            break;
         }
         //Limpiamos campos
         jtNombre.setText(null);
         jcTipo.setSelectedIndex(0);
         jtVida.setText(null);
         jtArmadura.setText(null);
         
         //Confirmamos en consola la creación
         System.out.println("Nuevo soldado reclutado");         
      }
   }

}


Y si ahora ejecutamos el programa, voilá, ya tenemos la interfaz prácticamente terminada. Falta el botón de "¡Lucha!", que aún no lo necesitamos (y el botón "Eliminar" tiene mal el texto, luego lo corrijo)
(https://i.ibb.co/rpcqXzb/imagen-2022-01-16-165117.png)

Tal vez no queda guay que el borde titulado que antes rodeaba el panel de creación, ahora también está rodeando el panel de la lista...., pero esto es un detalle estético del que yo me preocuparía al final, cuando el programa ya estuviera funcionando bien.

Porque ahora viene eso, lo difícil, hacer que todo funcione...

A ver, necesitamos que al agregar soldados, aparezcan en la lista.

Podemos hacer un método público en la clase PanelLista que renueve los elementos de la lista según lo que contenga el ArrayList del ejercito que tengamos referenciado.

Código: [Seleccionar]
public void actualizarLista() {
modeloLista.clear();
for (Soldado sold: ejercito.soldados) //Ejercito nos proporciona el ArrayList de Soldados
modeloLista.addElement(sold);

lista.setModel(modeloLista);
}

Y en la clase PanelCrearSoldado, en el ActionListener de su botón "Añadir", en lugar de lanzar un mensaje en consola, ahora lo que hacemos es invocar al método actualizarLista()
Citar
   private class AccionCrearSoldado implements ActionListener {

      @Override
      public void actionPerformed(ActionEvent e) {
         //Recogemos datos
         String nombre = jtNombre.getText();
         String tipo = (String) jcTipo.getSelectedItem();
         int vida = Integer.parseInt(jtVida.getText());
         int armadura = Integer.parseInt(jtArmadura.getText());
         
         //Creamos nuevo soldado según tipo
         switch(tipo) {
         case "Elfo":
            ejercito.reclutarSoldado(new Elfos(nombre, vida, armadura));
            break;
         case "Humano":
            ejercito.reclutarSoldado(new Humano(nombre, vida, armadura));
            break;
         case "Hobbit":
            ejercito.reclutarSoldado(new Hobbits(nombre, vida, armadura));
            break;
         case "Trasgo":
            ejercito.reclutarSoldado(new Trasgo(nombre, vida, armadura));
            break;
         case "Orco":
            ejercito.reclutarSoldado(new Orcos(nombre, vida, armadura));
            break;
         }
         //Limpiamos campos
         jtNombre.setText(null);
         jcTipo.setSelectedIndex(0);
         jtVida.setText(null);
         jtArmadura.setText(null);
         
         //Actualizamos lista
         lista.actualizarLista();   
      
      }
   }

Y ahora, cada vez que creamos un soldado, la lista los va mostrando.
(https://i.ibb.co/tqyhtDW/imagen-2022-01-16-171429.png)

En el método actualizarLista(), puede parecer un poco bruto que cada vez que se añada un elemento al ArrayList, me cargo el contenido del JList por completo y vuelvo a añadirlos todos, en lugar de simplemente añadir el último soldado creado, que sería más óptimo.

Lo he hecho así porque posiblemente, si luego queremos eliminar elementos de la lista, y cambiar el orden (que por lo tanto, habrá que cambiarlo también en el ArrayList de los ejercitos) en esos casos será más cómodo actualizar la lista por "las bravas", la borramos y la escribimos de nuevo, siempre después de haber hecho los cambios previamente en el ArrayList.

Así que ya tenemos ese método disponible para esas funcionalidades.

Curiosamente, para agregar nuevos soldados es precisamente cuándo no hace falta hacerlo por "las bravas" je je. Luego más adelante podemos crear otro método menos "agresivo" para ese caso, cuando el programa este terminado y queramos hacer algunas optimizaciones.

Pregunta si algo no te ha quedado claro de estos dos últimos mensajes. Si decides continuar con este código que he ido escribiendo, intenta tú ahora hacer los ActionListener para los botones "Subir", "Bajar" y "Eliminar"

Recuerda que los cambios que hagamos en el JList, han de reflejarse también en los ArrayList de los ejercitos.
Porque luego, cuando comience la "batalla", los Soldados que pelearan serán los que estén en los ArrayList, no en los JList, así que han de estar sincronizadas ambas listas: mismos soldados y en el mismo orden.
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: MirMiriam en 16 de Enero 2022, 21:00
Muchísimas gracias de verdad porque no veía la luz, con tus explicaciones, la verdad que son muy claras y sencillas, ahora me veo un poco más capaz de terminarlo  ;D Así que me atrevo con los botones que faltan junto con sus métodos y a cruzar los dedos  ;)

De nuevo muchísimas gracias.
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: MirMiriam en 18 de Enero 2022, 20:58
Hola de nuevo, estoy con el botón "Eliminar" y me gustaría, si es posible que me volvieras a ayudar ya que hay algo que no llego a entender de todo, a lo mejor es que lo estoy entendiendo mal tu explicación, con respecto a como indicar que el dato que tiene que eliminar es el del array, lo que he hecho es esto pero está claro que estoy equivocada porque no funciona (estoy en la clase PanelLista).

De nuevo gracias

Código: [Seleccionar]
    private void eliminar(){
        for (Soldado sold: ejercito.soldados)
            modeloLista.removeElement(sold);

    }

    private class ActionEliminarSoldado implements ActionListener{

        @Override
        public void actionPerformed(ActionEvent actionEvent) {
            if (btEliminar.isSelected()) {
                eliminar();
            }
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: Kabuto en 18 de Enero 2022, 21:35
Que casualidad...
Mientras escribías tu mensaje, estaba yo escribiendo la explicación de mis acciones.

Es genial que lo intentes, y te falle y no funcione... porque así es como se aprende...

Al eliminar, tienes primero que identificar cuál Soldado ha sido seleccionado en el JList.

El JList es capaz de darte el objeto Soldado elegido, este objeto se lo pasas al ArrayList para que lo borre de su lista, pero hay otro pequeño problema.

Y el problema es que el ArrayList, por sí solo, no sabe como comparar el objeto que tú le vas a pasar con los que tiene listados, para determinar cuál coincide.

Es decir, ¿Cuándo dos Soldados son iguales?
¿Si tienen la misma vida?¿El mismo nombre? ¿El mismo nombre, vida y armadura?....

En principio nos va a bastar cuando tenga el mismo nombre.
Si en la lista hemos elegido el soldado Gandalf, pues queremos que del ArrayList se borre también el objeto Soldado llamado Gandalf.

Pero claro, el ArrayList no sabe si lo que importa para decidir si dos objetos coinciden es el nombre, o la vida, o todos los atributos...

Para que el ArrayList pueda saberlo, necesitamos "enseñar" a los objetos de la clase Soldado a distinguirse unos de otros, o dicho de otro modo, a que sepan decir cuándo dos objetos Soldado son "iguales" y cuando no.

Esto lo hacemos sobreescribiendo el método equals() en la clase Soldado, y ahí vamos a establecer que dos objetos Soldado son iguales cuando coincidan sus nombres:
Código: [Seleccionar]
@Override
public boolean equals(Object obj) {
if (obj instanceof Soldado) {
Soldado otroSoldado = (Soldado) obj;
return nombre.equals(otroSoldado.getNombre());//Si los nombres coinciden, retorna true
}
else
return false;
}
Ahora, dos Soldados pueden compararse y dictaminar si son equivalentes o no.


Gracias a este paso previo, ahora veremos que la acción para el botón "Eliminar" va a requerir muy poco código.
Esta acción lo que hará será recoger el Soldado seleccionado en el JList.
Si no hay ninguno seleccionado, se lanzará un mensaje al usuario.
Y si hay uno seleccionado, simplemente se lo pasaremos al método remove() del ArrayList del Ejercito para que lo elimine y luego actualizamos el JList, donde ya no saldrá el soldado eliminado.

Código: [Seleccionar]
private class AccionEliminar implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
Soldado seleccionado = lista.getSelectedValue();
if (seleccionado == null)
JOptionPane.showMessageDialog(null, "Ha de seleccionar un Soldado");
else {
ejercito.soldados.remove(seleccionado);//Eliminamos del ArrayList
actualizarLista();
}
}
}

Ese método remove(), utilizará internamente el equals() de la clase Soldado para encontrar en su lista el Soldado que es igual al que le hemos pasado (que tiene el mismo nombre) y lo eliminará.
Si no hubieramos sobreescrito el equals(), no nos habría funcionado el remove(), ya que el ArrayList por si mismo, no sabe discernir cuándo dos Soldados son iguales, ya que esa "igualdad" en realidad será según las reglas que decida el programador.


Ahora quedan los botones "subir" y "bajar". Los explico también, pero de nuevo recomiendo que primero intentes tú resolverlos.
Mi primer impulso era escribir dos acciones, una para cada botón, pero luego pensé que ambas acciones iban a ser prácticamente idénticas, así que mejor buscar la forma de escribir una única acción que pueda adaptarse a ambos botones.

La acción de estos botones va a consistir en que el usuario escoge un soldado y lo mueve arriba o abajo.
Mover arriba supone restar -1 a la posición actual del soldado elegido. Si está en posición 5, pasa a la 4.
Bajar supone sumar +1, si está en la 5, pasa a la 6.

Ese -1 ó +1 es la única diferencia entre ambas acciones, así que podemos hacer que el ActionListener de estos botones tenga un atributo de tipo int, llamado "desplazamiento" y que por constructor le pasemos un -1 para el botón "subir" y un +1 para el botón "bajar".
Así la misma acción, se adapta a ambos botones.

Recordemos que el "desplazamiento" no es en el JList donde realmente importa, se ha de hacer en el ArrayList y ya luego el JList lo reflejará en pantalla.

Para hacer este cambio en el ArrayList, lo que hago es recoger el índice que ocupa en el JList el Soldado seleccionado (que es el mismo indice que ocupa en el ArrayList) y además recojo el objeto Soldado seleccionado.

Luego, aplicando el "desplazamiento" (-1 ó +1) recojo, del ArrayList, el soldado que ha de intercambiarse y su índice.
Y para hacer el intercambio, "seteo" en el ArrayList a ambos soldados de nuevo, pero a cada uno le pongo el indice del otro.
Y así quedan intercambiados.
Luego actualizo el JList, y listo.
Código: [Seleccionar]
private class AccionSubirBajar implements ActionListener {

private int desplazamiento; //-1 para subir, +1 para bajar

public AccionSubirBajar(int desplazamiento) {
this.desplazamiento = desplazamiento;
}

@Override
public void actionPerformed(ActionEvent e) {
//Soldado seleccionado en JList
Soldado seleccionado = lista.getSelectedValue();
if (seleccionado == null)
JOptionPane.showMessageDialog(null, "Ha de seleccionar un Soldado");
else {
//Indice de soldado seleccionado, es igual en ambas listas
int indiceSeleccionado = lista.getSelectedIndex();
//Indice del soldado para intercambiar, puede que sea uno anterior o uno posterior
int indiceIntercambiar = indiceSeleccionado + desplazamiento;
//Buscamos en el ArrayList, el soldado para intercambiar
Soldado intercambiar = ejercito.getSoldado(indiceIntercambiar);

//Si intercambiar fuese null, es que el soldado seleccionado no se puede intercambiar
//Por ejemplo, si intentamos subir el soldado que ya está arriba del todo.
if (intercambiar != null) {
//Seteamos los soldados, intercambiando los indices
ejercito.soldados.set(indiceSeleccionado, intercambiar);
ejercito.soldados.set(indiceIntercambiar, seleccionado);
actualizarLista();
}
}
}

}

Y ya tengo las acciones para los tres botones.
Solo tengo que añadírselas, teniendo cuidado de indicar -1 en el constructor para el botón "subir" y +1 para el botón "bajar"
Todo esto lo haremos en la clase PanelLista

Citar
   public PanelLista(String titulo, Ejercito ejercito) {
      this.ejercito = ejercito;
      lista = new JList<Soldado>();
      lista.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
      modeloLista = new DefaultListModel<Soldado>();
      lista.setModel(modeloLista);
      btSubir = new JButton("Subir");
      btSubir.addActionListener(new AccionSubirBajar(-1));
      btBajar = new JButton("Bajar");
      btBajar.addActionListener(new AccionSubirBajar(1));
      btEliminar = new JButton("Eliminar");
      btEliminar.addActionListener(new AccionEliminar());
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: MirMiriam en 21 de Enero 2022, 21:07
¡Muchas gracias de nuevo ;D ;D! los botones de subir y bajar los tenía planteados con dos métodos asociados a la posición detallándolo como comentabas, restando para subir y sumando para bajar, pero lo veo mucho más claro con un solo método.

Me surgen dudas con el botón de la batalla, este no lo veo nada claro, como ya  está el método batalla ¿se podría plantear instanciándolo en la clase Batalla RPG y asociando a cuando se pinche?

Gracias de nuevo.
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: Kabuto en 21 de Enero 2022, 22:11
Sí.
Debería bastar con crear un botón en BatallaRPG y en su ActionListener dar comienzo a la batalla invocando al método de la clase Batalla:
Código: [Seleccionar]
btLuchar = new JButton("¡Luchar!");
btLuchar.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Batalla.batallar(heroes, bestias);
}
});
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: MirMiriam en 22 de Enero 2022, 22:28
Una última pregunta, o eso espero que ya bastante me estás ayudando, para mostrar el resultado de la batalla por un campo de texto en vez de por consola, ¿declaro primero una variable JTextField y la asocio al botón de Luchar o mejor implementar getContentPane() en todos los mensajes.

Gracias de nuevo.
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: Kabuto en 23 de Enero 2022, 00:51
JTextField se te quedaría pequeño. Mejor un JTextArea.

Sin embargo, hacer esto va a requerir bastantes cambios en todo el proyecto.
La clase Batalla ahora tendrá que comunicarse de algún modo con ese JTextArea para lanzarle mensajes.
Pero recuerdo que algunos mensajes del combate, los lanzaban las distintas clases Soldado desde sus métodos atacar()..., ¿también habrá que buscarles conexión con el JTextArea o mejor pensar en otra solución?

También, usabamos un Scanner simplemente para pausar la consola de texto, poder leer lo que salía en pantalla, y pulsar la tecla enter para continuar con el siguiente turno de ataque.
Esto ahora no se podrá hacer y lo que vamos a tener van a ser decenas de mensajes a la velocidad de la luz, los combates van a durar apenas unos milisegundos.
Se puede intentar añadir un retardo entre un mensaje y otro..., pero para ello vamos a tener que jugar con hilos (threads)

 Lo ideal sería reescribir el proyecto desde casi cero, ya teniendo en mente que estará destinado a una GUI y no ha mostrarse por consola de texto.
Pero bueno, parcheando por un lado y por otro, supongo que se podrá apañar...


De momento, crearía una nueva clase JPanel, que tenga un botón y un JTextArea.
Le daremos un método para que reciba un String y lo añada como nueva línea al JTextArea. Así abrimos una vía para que otra clase (Batalla) pueda indicarle que ha de ir mostrando.

El botón de este panel, ha de poner en marcha la clase Batalla, la cuál ha de tener acceso a los Ejercitos (Heroes y Bestias) que se encuentran en la clase JFrame principal.
Esto significa que desde este nuevo panel, no podemos llamar a la clase Batalla, porque desde aquí no podemos "ver" a los ejércitos.

Se puede solucionar tal y como hemos hecho en los paneles anteriores, añadir atributos para poder referenciar a esos ejercitos mediante el constructor de este panel.

Pero otra opción, y que podríamos aplicarla aquí para variar un poco, sería escribir el ActionListener de este botón en la clase principal, y luego hacérselo llegar mediante un método.
Así que haremos eso, un método que reciba un ActionListener y lo aplique al botón de este panel.
Código: [Seleccionar]
public class PanelTexto extends JPanel {

private JTextArea areaTexto;
private JButton btLuchar;

public PanelTexto() {
areaTexto = new JTextArea();
areaTexto.setEditable(false);
btLuchar = new JButton("¡Luchar!");

JScrollPane scrollArea = new JScrollPane();
scrollArea.setViewportView(areaTexto);
scrollArea.setPreferredSize(new Dimension(50, 200));
scrollArea.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(10, 20, 20, 20),
BorderFactory.createLoweredSoftBevelBorder()));

JPanel pnBoton = new JPanel();
pnBoton.add(btLuchar);

setLayout(new BorderLayout());
add(pnBoton, BorderLayout.NORTH);
add(scrollArea, BorderLayout.CENTER);
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(0, 10, 10, 10),
BorderFactory.createRaisedSoftBevelBorder()));
}

//La accion de este botón se escribirá en otra clase
public void setAccionBotonLuchar(ActionListener accion) {
btLuchar.addActionListener(accion);
}

public void nuevaLinea(String linea) {
areaTexto.append(linea + "\n");
}

}

Luego veremos como ponemos este panel en el JFrame, pero antes vamos tratar el tema de la clase Batalla y hacer que los mensajes salgan con algo de retraso entre ellos.

Para esto, como dije antes, deberíamos transformar la clase Batalla en un Thread, un hilo.
No se si has trabajado con hilos antes, no son complicados la verdad. Si lo convertimos en un hilo, el proceso de batalla se llevará a cabo por separado del proceso que estará gestionando la interfaz gráfica: ventana, botones, etc..
Eso nos permite cosas como hacer "dormir" ese hilo durante unos milisegundos cada vez que queramos, y así podremos ver como los mensajes van apareciendo en el area de texto de uno en uno, y no todos de golpe como ocurrirá si no lo hacemos en un hilo paralelo.

Para convertir Batalla en un hilo, basta con hacer que herede de la clase Thread y poner el código que queremos que se ejecute en un método llamado run().
Si no quieres "cargarte" la clase que ya tenemos, puedes crear otra nueva.
Yo lo prefiero así y he creado una clase llamada BatallaHilo.
Le vamos a dar tres atributos: dos objetos Ejercito y un objeto PanelTexto, que será una referencia al nuevo panel que acabamos de crear. Así esta clase tendrá acceso a todo lo que necesita, los Ejércitos para que se líen a mamporrazos entre ellos y el área de texto donde ir publicando los detalles de la contienda.

En el método run(), vamos a poner el mismo código que teníamos en el método estático llamado batallar() de la anterior clase Batalla.
El código va a ser el mismo, pero los System.out.println() los vamos cambiar por llamadas al PanelTexto donde le pasamos los Strings para que los muestre en el JTextArea.

Es decir, donde antes hacíamos esto:
Código: [Seleccionar]
System.out.println(bestia.getNombre() + " ha muerto.");Ahora haremos:
Código: [Seleccionar]
pnTexto.nuevaLinea(bestia.getNombre() + " ha muerto.");
El método pausa() donde antes usábamos el Scanner para detener la consola, ahora lo vamos a cambiar por un código que hará dormitar el hilo durante los milisegundos que queramos indicarle.
Esta sería la clase BatallaHilo
Código: [Seleccionar]
public class BatallaHilo extends Thread {

private Ejercito heroes;
private Ejercito bestias;
private PanelTexto pnTexto;

public BatallaHilo(Ejercito heroes, Ejercito bestias, PanelTexto pnTexto) {
this.heroes = heroes;
this.bestias = bestias;
this.pnTexto = pnTexto;
}

@Override
public void run() {
//Mientras ningun ejército haya sido derrotado....
while(!heroes.esDerrotado() && !bestias.esDerrotado()) {

//Calculamos el total de turnos, según el ejército más grande
int turnosTotal;
if (heroes.soldados.size() >= bestias.soldados.size())
turnosTotal = heroes.soldados.size();
else
turnosTotal = bestias.soldados.size();

//Comienza una ronda de turnos
for (int turno = 0; turno < turnosTotal; turno++) {
//Seleccionamos combatientes
Heroe heroe = (Heroe) heroes.getSoldado(turno);
Bestia bestia = (Bestia) bestias.getSoldado(turno);
//Comprobamos que ninguno sea null
if (heroe == null && bestia == null)
//¿Ambos son null?Entonces esta ronda de turnos ha terminado
break;
else if (heroe == null) {
//No hay Heroe, Bestia queda en guardia
pnTexto.nuevaLinea(bestia.getNombre() + " queda en guardia");
pausa(500);
}
else if (bestia == null) {
//No hay Bestia, Heroe queda en guardia
pnTexto.nuevaLinea(heroe.getNombre() + " queda en guardia");
pausa(500);
}
else {
//Ninguno es null, comienza el combate
pnTexto.nuevaLinea("Lucha entre " + heroe + " y " + bestia);
pausa(250);
//Turno heroe
pnTexto.nuevaLinea("Turno de " + heroe.getNombre());
pausa(250);
pnTexto.nuevaLinea(heroe.atacar(bestia));
pnTexto.nuevaLinea("Datos Actualizados de " + bestia);
pausa(1000);
if (bestia.estaMuerto()) {
pnTexto.nuevaLinea(bestia.getNombre() + " ha muerto.");
pausa(1000);
}
else {
//Turno bestia
pnTexto.nuevaLinea("Turno de " + bestia.getNombre());
pausa(250);
pnTexto.nuevaLinea(bestia.atacar(heroe));
pnTexto.nuevaLinea("Datos Actualizados de " + heroe);
pausa(1000);
if (heroe.estaMuerto()) {
pnTexto.nuevaLinea(heroe.getNombre() + " ha muerto.");
pausa(1000);
}
}
}
//Turno combate finalizado, ejercitos actualizan sus filas
heroes.comprobarEjercito();
bestias.comprobarEjercito();
//Y se inicia el siguiente turno
}
}

//Las rondas de turnos han finalizado porque algún ejército ha sido derrotado. Comprobamos
if (heroes.esDerrotado())
pnTexto.nuevaLinea("Han ganado las Bestias. Soldados restantes: " + bestias.soldados.size());
else
pnTexto.nuevaLinea("Han ganado los Heroes. Soldados restantes: " + heroes.soldados.size());
}

private void pausa(long milis) {
try {
sleep(milis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

Hay otro cambio importante aquí. Recuerda que dije que algunos mensajes en pantalla, venían del método atacar de las clases Soldado, por ejemplo:
Código: [Seleccionar]
@Override
public void atacar(Soldado enemigo) {
if (enemigo instanceof Orcos) {
//Regla específica cuando un Elfo ataca un Orco
Random dado = new Random();
int tirada1 = dado.nextInt(101);
int tirada2 = dado.nextInt(101);
System.out.println("Primer dado: " + tirada1);
System.out.println("Segundo dado: " + tirada2);
int maximo =  Math.max(tirada1, tirada2);
System.out.println("*****¡¡El odio élfico hacia los Orcos incrementa el ataque en 1.5x!!*****");
maximo *= 1.5;
System.out.println("Valor de ataque resultante: " + maximo);
enemigo.recibirAtaque(maximo);

}
else //Si no es Orco, se aplica la regla general de los Heroes
super.atacar(enemigo);
}

Estos mensajes se lanzan a la consola durante la ejecución de la "batalla"_
Citar
               else {
                  //Turno bestia
                  System.out.println("Turno de " + bestia.getNombre());
                  bestia.atacar(heroe);
                  System.out.println("Datos Actualizados de " + heroe);

Nosotros hemos reconducido los System.out de la clase Batalla para que ahora salgan por el area de texto, pero estos no los tenemos controlados porque provienen de otras clases...
¿Cómo hacemos para tener control sobre ellos?

Pues vamos a tener que cambiar los métodos atacar() de las clases Soldado, para que en lugar de que hagan System.out por su cuenta, nos retornen esos mensajes como String, y así podamos reconducirlos para que vayan al JTextArea.

Esta "reconducción", ya la he puesto en el código de la clase BatallaHilo que he puesto antes:
Citar
               else {
                  //Turno bestia
                  pnTexto.nuevaLinea("Turno de " + bestia.getNombre());
                  pausa(250);
                  pnTexto.nuevaLinea(bestia.atacar(heroe));
                  pnTexto.nuevaLinea("Datos Actualizados de " + heroe);

Pero para que funcione, hay que modificar las clases Soldado. Comenzando por la clase madre. Aquí pusimos un método abstracto de tipo void para que las clases hijas lo sobreesribiesen a su manera cada una.
Ya no será void, ahora retornará un String
Clase Soldado
Citar
   //Este método lo han de sobreescribir Heroe y Bestia, porque será distinto para cada uno
   public abstract String atacar(Soldado enemigo);

Las siguientes clases hijas, ya no harán System.out. Ahora retornarán los mensajes en un String.

Clase Heroe
Código: [Seleccionar]
@Override
public String atacar(Soldado enemigo) {
// El ataque será el mejor lanzamiento entre dos dados de 0 a 100
Random dado = new Random();
int tirada1 = dado.nextInt(101);
int tirada2 = dado.nextInt(101);
//System.out.println("Primer dado: " + tirada1);
//System.out.println("Segundo dado: " + tirada2);
int maximo =  Math.max(tirada1, tirada2);
enemigo.recibirAtaque(maximo);

return "Primer dado: " + tirada1 + "\n" + "Segundo dado: " + tirada2;
}

Clase Bestia
Código: [Seleccionar]
@Override
public String atacar(Soldado enemigo) {
//Único lanzamiento de un dado entre 0 y 90
Random dado = new Random();
int tirada = dado.nextInt(91);
//System.out.println("Resultado del dado es: " + tirada);
enemigo.recibirAtaque(tirada);
return "Resultado del dado es: " + tirada;
}


La clase Elfos, es la única que tiene una versión propia de este método por aquello de que queríamos un ataque especial cuando luchaba con Orcos.
También ha de ser "reconducido":
Código: [Seleccionar]
@Override
public String atacar(Soldado enemigo) {
if (enemigo instanceof Orcos) {
//Regla específica cuando un Elfo ataca un Orco
Random dado = new Random();
int tirada1 = dado.nextInt(101);
int tirada2 = dado.nextInt(101);
//System.out.println("Primer dado: " + tirada1);
//System.out.println("Segundo dado: " + tirada2);
int maximo =  Math.max(tirada1, tirada2);
//System.out.println("*****¡¡El odio élfico hacia los Orcos incrementa el ataque en 1.5x!!*****");
maximo *= 1.5;
//System.out.println("Valor de ataque resultante: " + maximo);
enemigo.recibirAtaque(maximo);

return "Primer dado: " + tirada1 + "\n" + "Segundo dado: " + tirada2 + "\n"
+ "*****¡¡El odio élfico hacia los Orcos incrementa el ataque en 1.5x!!*****\n"
+ "Valor de ataque resultante: " + maximo;
}
else //Si no es Orco, se aplica la regla general de los Heroes
return super.atacar(enemigo);
}


Vale, tenemos un nuevo panel con area de texto y una renovada clase Batalla que ahora es un hilo que publicará mensajes en el area de texto.

Ahora nos vamos a la clase principal JFrame, porque tenemos que encajar este panel en la interfaz y escribir el ActionListener para el botón de "Luchar".
Este ActionListener lo que hará será poner en marcha el hilo de batalla.

Resalto en azul los cambios:
Citar
public class BatallaRPG extends JFrame {
   
   //Modelo
   private Ejercito bestias;
   private Ejercito heroes;
   
   //Vista
   private PanelCrearSoldado crearHeroes;
   private PanelCrearSoldado crearBestias;
   private PanelTexto pnTexto;
   
   public BatallaRPG() {
      bestias = new Ejercito();
      heroes = new Ejercito();
      reclutar();
      //Este panel referenciará los Heroes
      crearHeroes = new PanelCrearSoldado("Heroes", new String[] {"Elfo", "Humano", "Hobbit"}, heroes);
      //Este refenciará a la Bestias
      crearBestias = new PanelCrearSoldado("Bestias", new String[] {"Trasgo", "Orco"}, bestias);
      
      pnTexto = new PanelTexto();
      pnTexto.setAccionBotonLuchar(new AccionBotonLuchar());

      
      JPanel pnSuperior = new JPanel();
      pnSuperior.add(crearHeroes);
      pnSuperior.add(crearBestias);
      
      JPanel pnInferior = new JPanel();
      pnInferior.add(pnTexto);
      
      setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
      add(pnSuperior);
      add(pnTexto);

      
      setTitle("Batalla RPG");
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      pack();
      setLocationRelativeTo(null);
      setVisible(true);
   }
   
   private class AccionBotonLuchar implements ActionListener {
      @Override
      public void actionPerformed(ActionEvent e) {
         BatallaHilo batalla = new BatallaHilo(heroes, bestias, pnTexto);
         batalla.start();
      }
   }


   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         @Override
         public void run() {
            new BatallaRPG();   
         }
      });
   }
}

Y con todo esto, ya tenemos un JTextArea que muestra los vaivenes de la batalla.
(https://i.ibb.co/R4pKTCZ/imagen-2022-01-23-004615.png)


Hay una pega, el JTextArea no muestra directamente las últimas lineas que se añaden, tenemos que estar haciendo scroll todo el rato para ver los últimos mensajes.

No se si este comportamiento se puede cambiar, lo investigaré.

Pregunta si algo no ha quedado claro, o si sientes que te has perdido..., ya imagino que no esperabas tener que hacer taaaaantos cambios
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: Kabuto en 23 de Enero 2022, 01:01
Ah.., pues ya he descubierto como hacer que el scroll del area de texto vaya siguiendo las líneas publicadas.

Básicamente, es calcular el tamaño del area de texto tras cada línea publicada y setear el view position del scroll al final del todo.

Hay que cambiar la clase PanelTexto, poniendo el JScrollPane como atributo y añadiendo unas líneas de código al método de añadir nueva línea.

Citar
public class PanelTexto extends JPanel {
   
   private JTextArea areaTexto;
   private JButton btLuchar;
   private JScrollPane scrollArea;
   
   public PanelTexto() {
      areaTexto = new JTextArea();
      areaTexto.setEditable(false);
      btLuchar = new JButton("¡Luchar!");
      
      scrollArea = new JScrollPane();
      scrollArea.setViewportView(areaTexto);
      scrollArea.setPreferredSize(new Dimension(50, 200));
      scrollArea.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createEmptyBorder(10, 20, 20, 20),
            BorderFactory.createLoweredSoftBevelBorder()));
      
      JPanel pnBoton = new JPanel();
      pnBoton.add(btLuchar);
      
      setLayout(new BorderLayout());
      add(pnBoton, BorderLayout.NORTH);
      add(scrollArea, BorderLayout.CENTER);
      setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createEmptyBorder(0, 10, 10, 10),
            BorderFactory.createRaisedSoftBevelBorder()));
   }
   
   //La accion de este botón se escribirá en otra clase
   public void setAccionBotonLuchar(ActionListener accion) {
      btLuchar.addActionListener(accion);
   }
   
   public void nuevaLinea(String linea) {
      areaTexto.append(linea + "\n");
      Dimension dim = areaTexto.getSize();
      Point p = new Point(0, dim.height);
      scrollArea.getViewport().setViewPosition(p);

   }

}
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: MirMiriam en 24 de Enero 2022, 19:53
Muchas gracias por todo de verdad, creo que lo he entendido peeero voy a darle unas cuantas vueltas e intentar hacer pequeños proyectos para asentar lo que me has enseñado porque así visto lo entiendo pero cuando empiece a hacer un nuevo proyecto será cuando me de cuenta de si de verdad lo he comprendido.
De nuevo muchísimas gracias  :) :)
Título: Re: Problema Batalla RPG por turnos con java
Publicado por: Kabuto en 25 de Enero 2022, 00:00
Lo más "difícil" es encontrar la forma óptima de comunicar los objetos de unas clases con otras.
- "Donde pongo este ArrayList, que ha de recibir datos de los campos de texto de una clase JPanel, cuando se pulse el botón de otra clase JPanel y luego enviarlos a una tabla que está en otra clase..."

Pero ya hemos visto que se pueden poner referencias allá donde necesitemos.
Si hay tres o cuatro clases JPanel que necesitan interactuar con un objeto declarado en la clase principal, pues a esos JPanel les ponemos un atributo que haga referencia a ese objeto y listo.
Luego esa referencia se la hacemos llegar por constructor, o incluso por un método setter si hace falta.

Porque eso no va a implicar que hayan cuatro objetos distintos, solo habrá uno, pero con varias referencias apuntando hacia el mismo objeto, para que todo el que lo necesite pueda trabajar con él.

Un saludo.