Mostrar Mensajes

Esta sección te permite ver todos los posts escritos por este usuario. Ten en cuenta que sólo puedes ver los posts escritos en zonas a las que tienes acceso en este momento.


Mensajes - Kabuto

Páginas: 1 2 3 4 [5] 6 7 8 9 10 ... 50
81
Aprender a programar desde cero / Re: Herencia Java
« en: 11 de Marzo 2023, 02:42 »
A ver, el enunciado ya nos da bastantes pistas.
Todas las clases heredan de la superclase Personal, que es la que ha de tener los atributos y métodos comunes a todas las demás clases. Así que empecemos por ella:
Citar
Todo el personal se identifica con su nombre y DNI y recibe un salario mensual que debemos de calcular, de forma simplificada, como el número de horas de trabajo a la semana multiplicado por el importe/hora de su categoría y por el número de semanas al mes (4).

Así que de momento esta podría ser la clase Personal. Puede que luego se deba modificar, o no..

Código: [Seleccionar]
public class Personal {

//Atributos
protected String DNI;
protected String nombre;
protected int salario;

//Constructor
public Personal(String DNI, String nombre) {
this.DNI = DNI;
this.nombre = nombre;
}

//Métodos
public String getDNI() {
return DNI;
}

public void setDNI(String DNI) {
this.DNI = DNI;
}

public String getNombre() {
return nombre;
}

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

public void calcularSalario() {
//Este cálculo dependerá de las clases hijas
}
}
Yo habría declarado esta clase como "abstracta" pero por no alterar las plantillas que han dado hechas, no lo haremos.
El salario es un cálculo que se hace en un momento dado (a final de mes para pagar la nómina) y además depende las clases hijas.

Sigamos...
Detrás de Personal, tenemos las categorias PAS y PDI

Veamos que nos dicen de los PAS
Citar
PAS: En un mes determinado los PAS (tanto administrativos como informáticos) pueden acumular horas extras. Cada hora extra se paga a 6 euros/hora. Las horas extra, una vez pagadas, se inicializan a cero de nuevo (por ejemplo, a la hora de calcular el sueldo de personal).

Esto quizás se podría modelar de esta manera:
Código: [Seleccionar]
public class PAS extends Personal{

//Atributos
private final int VALOR_HORA_EXTRA = 600; //6€
protected int horasExtra;

//Constructor
public PAS(String DNI, String nombre) {
super(DNI, nombre);
horasExtra = 0;
}

//Métodos
public void acumularHorasExtra(int horas) {
horasExtra += horas;
}

public int calcularHorasExtra() {
int extra = horasExtra * VALOR_HORA_EXTRA;
horasExtra = 0; //Reinicio a 0
return extra;
}

}

OK, ¿y de los PDI?
Pues de los PDI no nos dicen nada, así que esta clase parece que por ahora se va a quedar así:

Código: [Seleccionar]
public class PDI extends Personal{

public PDI(String DNI, String nombre) {
super(DNI, nombre);
}

}

Es una clase que no va a aportar nada, más allá de ser una subclase intermedia entre la superclase y las clases hijas Profesor e Investigador.

Por cierto, de Profesor si nos dicen que tienen un ingreso adicional:
Citar
Profesores: A los profesores se les añade un complemento salarial en función de lo que se conoce como sexenios (periodos de 6 años evaluados positivamente), y que se pueden conceder hasta un máximo de 6. En concreto, se les añade 100 euros al mes por sexenio reconocido. Una vez concedido el sexenio el aumento de sueldo se mantiene para siempre.

Lo cuál es un complemento de su cálculo salarial base:
Citar
Profesores: 37 horas/semana con un importe de 8 euros/hora.


Así que la clase Profesor podría ser esta.
Fíjate que para el calculo de salario, lo que hacemos es "sobre escribir"(Override) el método que viene heredado de la clase Personal
Código: [Seleccionar]
public class Profesor extends PDI{

private final int VALOR_SEXENIO = 100000; //100€
private final int HORAS_SEMANA = 37;
private final int VALOR_HORAS = 800; //8€
private int sexenios;

public Profesor(String DNI, String nombre, int sexenios) {
super(DNI, nombre);
this.sexenios = 0;
aumentarSexenios(sexenios);
}

public void aumentarSexenios(int sexenios) {
this.sexenios += sexenios;
if (sexenios > 6) //Máximo 6 sexenios
sexenios = 6;
}

@Override
public void calcularSalario() {
//Sueldo base
salario = HORAS_SEMANA * VALOR_HORAS;
salario *= 4;//4 semanas de salario
//Complemento sexenios
salario += (sexenios * VALOR_SEXENIO);
}
}


El otro PDI, la clase Investigador, no tiene complementos, así que segun su salario base...

Citar
Investigadores: 35 horas/semana con un importe de 7 euros/hora.

...queda así:
Código: [Seleccionar]
public class Investigador extends PDI{

private final int HORAS_SEMANA = 35;
private final int VALOR_HORAS = 700; //7€

public Investigador(String DNI, String nombre) {
super(DNI, nombre);
}

@Override
public void calcularSalario() {
salario =  HORAS_SEMANA * VALOR_HORAS;
salario *= 4;//4 semanas de salario
}
}

Nos faltan los dos PAS
Tenemos Administrativo
Citar
Administrativos: 37 horas/semana y un importe de 7,5 euros/hora.
a lo que hay que sumarle el valor de sus horas extra por ser un PAS
Código: [Seleccionar]
public class Administrativo extends PAS{

private final int HORAS_SEMANA = 37;
private final int VALOR_HORAS = 750; //7,5€
   
    public Administrativo(String nombre, String dni, int horasExtra) {
    super(nombre, dni);
    this.horasExtra = horasExtra;
    }
   
    @Override
    public void calcularSalario() {
    salario = HORAS_SEMANA * VALOR_HORAS;
    salario *= 4;//4 semanas de salario
    salario += calcularHorasExtra(); //Método heredado de PAS
    }
}

Y lo mismo con los Informatico
Citar
Informáticos: 40 horas/semanas y un importe de 6 euros/hora.

Código: [Seleccionar]
public class Informatico extends PAS{

private final int HORAS_SEMANA = 40;
private final int VALOR_HORAS = 600; //6€
   
    public Informatico(String nombre, String dni, int horasExtra) {
    super(nombre, dni);
    this.horasExtra = horasExtra;
    }
   
    @Override
    public void calcularSalario() {
    salario = HORAS_SEMANA * VALOR_HORAS;
    salario *= 4; //4 semanas de salario
    salario += calcularHorasExtra(); //Método heredado de PAS
    }
}

Pasemos  a la clase Universidad
Citar
•   Crear una clase Universidad con una función public static void imprimirNominas(Personal[] listaPersonal) que imprima por pantalla, para cada Personal incluido en listaPersonal, el nombre del personal, su categoría y su sueldo.
•   Crear en la misma clase Universidad, una función obtenerPresupuestoTotal que devuelva el total de dinero que se gasta la universidad en personal.
Para evitar usar decimales en cantidades monetarias podemos trabajar con el dinero en céntimos de Euro y solo convertirlo en euros a la hora de mostrarlo por pantalla.

Aquí lo tenemos:
Código: [Seleccionar]
public class Universidad {

public static void imprimirNominas(Personal[] listaPersonal) {
      System.out.println("\n\t\tNOMINAS DEL PERSONAL");
     
      for (Personal pers: listaPersonal) {
     
      pers.calcularSalario();
     
      String categoria;
      if (pers instanceof Administrativo || pers instanceof Informatico)
      categoria = "PAS";
      else
      categoria = "PDI";
     
      System.out.printf("-> Nombre: %-25s Categoria: %-15s Sueldo: %.2f\n",
      pers.getNombre(), categoria, pers.salario / 100f);
      }
}
   
public static long obtenerPresupuestoTotal(Personal[] listaPersonal) {
      int total = 0;
      for (Personal pers: listaPersonal)
      total += pers.salario;
     
      return total;
}

}

Y por último el main para ponerlo a prueba.
Yo hago un ejemplo con solo 4 personas, tú añade más si quieres.
Código: [Seleccionar]
public class Main {

public static void main(String[] args) {

//Crear personal
Personal[] listaPersonal = new Personal[4];
listaPersonal[0] = new Administrativo("45678901X", "Luis Segura", 15);
listaPersonal[1] = new Informatico("34890012M", "Ana Montes", 23);
listaPersonal[2] = new Investigador("23987634K", "Susana Rego");
listaPersonal[3] = new Profesor("43789011G", "Antonio Paz", 4);


//Imprimir las nóminas
Universidad.imprimirNominas(listaPersonal);

//Ver el presupuesto total
System.out.printf("\nPresupuesto total: %.2f",
Universidad.obtenerPresupuestoTotal(listaPersonal) / 100f);
}
}

Pregunta si algo no se entiende.
Un saludo.

82
Hola.
Falta un detalle.
Los ActionListener de "procesar" y "borrar" están escritos, pero no están agregados a sus correspondientes botones.
Hay que añadir esta par de líneas:

Citar
    }

    private class PanelDerecho extends JPanel {

        public PanelDerecho() {

            etMensajes = new JLabel("Escriba y use botones", SwingConstants.CENTER);
            etMensajes.setFont(new Font("Verdana", Font.BOLD, dim.width * 3 / 100));

            //JPanel pnMensajes = new JPanel();
            //pnMensajes.add(etMensajes);

            btProcesar = new JButton("Procesar");
            btProcesar.setFont(new Font("Verdana", Font.PLAIN, dim.width * (3/2) / 100));
            btProcesar.addActionListener(new AccionProcesar());
            btBorrar = new JButton("Borrar");
            btBorrar.setFont(new Font("Verdana", Font.PLAIN, dim.width * (3/2) / 100));
            btBorrar.addActionListener(new AccionBorrar());

83
Comunidad / Re: DISEÑAR UN JUEGO CON ECLIPSE JAVA DESDE CERO
« en: 23 de Febrero 2023, 11:00 »
Hola y bienvenido.

Comparte aquí el enunciado de ese ejercicio para que sepamos en que consiste y decirte por donde deberías empezar.

Un saludo.

84
Ok.
Vayamos por partes:
Citar
Desarrollar un programa que modele una cuenta bancaria que tiene los siguientes atributos, que deben ser de acceso protegido:
• Saldo, de tipo float.
• Número de ingresos con valor inicial cero, de tipo int.
• Número de retiros con valor inicial cero, de tipo int.
• Tasa anual (porcentaje), de tipo float.
• Comisión mensual con valor inicial cero, de tipo float.
La clase Cuenta tiene un constructor que inicializa los atributos saldo y tasa anual con valores pasados como parámetros. La clase
Cuenta tiene los siguientes métodos:
• Ingresar una cantidad de dinero en la cuenta actualizando su saldo.
• Retirar una cantidad de dinero en la cuenta actualizando su saldo. El valor a retirar no debe superar el saldo.
• Calcular el interés mensual de la cuenta y actualiza el saldo correspondiente.
• Extracto mensual: actualiza el saldo restándole la comisión mensual y calculando el interés mensual correspondiente (invoca el
método anterior).
• Imprimir: muestra en pantalla los valores de los atributos.

Esta sería la clase Cuenta, los métodos para calcular el interes mensual y el extracto mensual los he dejado vacíos porque la verdad no me queda claro como se supone que ha de calcularse con un atributo que se llama tasa "anual". Por otro lado, es algo irrelevante, ya que aquí lo que importa es practicar herencias de clases.

Código: [Seleccionar]
public class Cuenta {

protected float saldo;
protected int numIngresos;
protected int numRetiros;
protected float tasaAnual;
protected float comiMensual;

public Cuenta(float saldo, float tasaAnual) {
this.saldo = saldo;
this.tasaAnual = tasaAnual;
numIngresos = 0;
numRetiros = 0;
comiMensual = 0;
}

public void ingresar(float cantidad) {
saldo += cantidad;
numIngresos++;
System.out.println("Ingreso realizado");
}

public void retirar(float cantidad) {
if (cantidad > saldo)
System.out.println("No hay fondos para cubrir la retirada");
else {
saldo -= cantidad;
numRetiros++;
System.out.println("Retiro realizado");
}
}

public void calcularInteresMensual() {

}

public void extractoMensual() {

}

public void imprimir() {
System.out.printf("\nSaldo: %.2f\n", saldo);
System.out.println("Numero Ingresos: " + numIngresos);
System.out.println("Numero Retiros: " + numRetiros);
System.out.printf("Tasa Anual: %.2f%%\n", tasaAnual);
System.out.printf("Comision Mensual: %.2f\n\n", comiMensual);
}

}

Siguiente:
Citar
Cuenta de ahorros: posee un atributo para determinar si la cuenta de ahorros está activa (tipo boolean). Si el saldo es menor a
100€, la cuenta está inactiva, en caso contrario se considera activa. Los siguientes métodos se redefinen:
1. Ingresar: se puede ingresar dinero si la cuenta está activa. Debe invocar al método heredado.
2. Retirar: es posible retirar dinero si la cuenta está activa. Debe invocar al método heredado.
3. Extracto mensual: si el número de retiros es mayor que 4, por cada retiro adicional, se cobra 1.5€ como comisión
mensual. Al generar el extracto, se determina si la cuenta está activa o no con el saldo.
4. Un nuevo método imprimir que muestra en pantalla el saldo de la cuenta, la comisión mensual y el número de
transacciones realizadas (suma de cantidad de consignaciones y retiros).

Pues esta es la clase CuentaAhorros, añadiendo nuevo atributo y sobre escribiendo los métodos según se pide:
Código: [Seleccionar]
public class CuentaAhorros extends Cuenta {

private boolean esActiva;

public CuentaAhorros(float saldo, float tasaAnual) {
super(saldo, tasaAnual);
esActiva = saldo >= 100;
}

@Override
public void ingresar(float cantidad) {
if (esActiva)
super.ingresar(cantidad);
else
System.out.println("Ingreso no es posible. Cuenta inactiva");
}

@Override
public void retirar(float cantidad) {
if (esActiva) {
super.retirar(cantidad);
esActiva = saldo >= 100; //Tras retirar puede quedar inactiva
}
else
System.out.println("Retiro no es posible. Cuenta inactiva");
}

@Override
public void extractoMensual() {

}

@Override
public void imprimir() {
System.out.printf("\nSaldo: %.2f\n", saldo);
System.out.printf("Comision Mensual: %.2f\n", comiMensual);
System.out.println("Total Transacciones: " + (numIngresos + numRetiros) + "\n");
}

}

Siguiente:
Citar
Cuenta corriente: posee un atributo de sobregiro, el cual se inicializa en cero. Se redefinen los siguientes métodos:
• Retirar: se retira dinero de la cuenta actualizando su saldo. Se puede retirar dinero superior al saldo. El dinero que se debe
queda como sobregiro.
• Ingresar: invoca al método heredado. Si hay sobregiro, la cantidad consignada reduce el sobregiro.
• Extracto mensual: invoca al método heredado.
• Un nuevo método imprimir que muestra en pantalla el saldo de la cuenta, la comisión mensual, el número de
transacciones realizadas (suma de cantidad de consignaciones y retiros) y el valor de sobregiro.

Pues clase CuentaCorriente:
Código: [Seleccionar]
public class CuentaCorriente extends Cuenta {

private float sobreGiro;

public CuentaCorriente(float saldo, float tasaAnual) {
super(saldo, tasaAnual);
sobreGiro = 0;
}

@Override
public void retirar(float cantidad) {
saldo -= cantidad;
if (saldo < 0) {
sobreGiro = saldo;
saldo = 0;
}
System.out.println("Retiro realizado");
}

@Override
public void ingresar(float cantidad) {
//¿Hay sobregiro?
if (sobreGiro < 0) { //Sí lo hay
sobreGiro += cantidad;
/*
* Si ahora sobreGiro tiene ahora valor positivo
* es que ha quedado cancelado (vuelve a 0)
* y la cantidad positiva es la que se ingresa al saldo
*/
if (sobreGiro > 0) {
super.ingresar(sobreGiro);
sobreGiro = 0; //Vuelve a 0
}
else
System.out.println("Ingreso realizado");
}
else //No hay sobregiro
super.ingresar(cantidad);
}

@Override
public void imprimir() {
System.out.printf("\nSaldo: %.2f\n", saldo);
System.out.printf("Comision Mensual: %.2f\n", comiMensual);
System.out.println("Total Transacciones: " + (numIngresos + numRetiros));
System.out.printf("Sobregiro: %.2f\n\n", sobreGiro);
}

}

Por último:
Citar
Realizar un método main que implemente un objeto Cuenta de ahorros y llame a los métodos correspondientes

Aquí un main, donde llamo a los métodos básicos para comprobar que se puede retirar y operar con la cuenta, hasta que se queda inactiva por la cantidad de saldo.
No llamo a los métodos que no he sabido escribir.
Código: [Seleccionar]
public class Main {

public static void main(String[] args) {
CuentaAhorros ahorros = new CuentaAhorros(2300, 2.6f);

ahorros.imprimir();
ahorros.retirar(2200);
ahorros.imprimir();
ahorros.retirar(100);
ahorros.imprimir();
ahorros.ingresar(1500);

}

}

Bueno, a parte de esos cálculos que no he entendido como hacer, en lo que respecta a la herencia no ha habido ningún problema.

Para el "main" nos piden crear un objeto de clase CuentaAhorros, que hereda de Cuenta.

No necesitas nada de la otra clase CuentaCorriente para cumplir con el enunciado.

Ya que la has escrito, puedes crear también un objeto de esa clase y comprobar que funciona. Pero el enunciado no lo exige.
Te han pedido que la escribas, pero no que la pongas a prueba.

Quizás esto es lo que te estaba confundiendo, que te hayan hecho crear algo para luego no probarlo...  ??? ...y quizás pensaste que en realidad sí debías probarla, pero solo a través de una de la clases hijas.
Lo cuál es imposible, como ya comenté antes.

Según el enunciado, no es necesario comprobar la clase CuentaCorriente.
Puedes hacerlo si quieres, pero tendrás que crear un segundo objeto que pertenezca a dicha clase.

Espero que se hayan aclarados tus dudas.
Un saludo.

85
Creo que ya respondí a esta duda, o una muy similar, por mensaje privado, pero no encuentro la respuesta que te envié.
He ahí otro motivo por el que no me gusta responder consultas por privado.

Pero vamos, según entiendo tu planteamiento, pues no puede hacerse.


Supongamos que tenemos la superclase A
De esta heredan dos hijas B y C

Pues si en el main implementamos un objeto de la clase B, tendremos disponibles los atributos y métodos de la clase B y de la clase A(por herencia)

Pero es imposible que tengamos además los atributos y métodos de un objeto de la clase C
¿Cómo vamos a tenerlos si no instanciamos un objeto de dicha clase?

A no ser que los declarasemos como estáticos..., pero entonces esos atributos/métodos ya no pertenecerían a los objetos, si no a la propia clase.


Quizás sería conveniente que publicases el enunciado completo de ese ejercicio, a ver si es que no estás interpretando bien lo que están solicitando.

86
Hola.
Si vives en España, puedes intentar acceder a un ciclo superior de DAM (Desarrollo Aplicaciones Multiplataforma), ya sea en un centro privado o público (más barato)

Son 2 años, pero es bastante completo e incluye varias disciplinas a parte de la programación: lenguaje de marcas(XML y HTML), bases de datos, despliegue de sistemas, programas ERP, CRM,....

Es formación reglada e incluye prácticas en empresas, lo cuál abre muchas puertas laborales.

87
Comunidad / Re: Presentación
« en: 31 de Enero 2023, 11:18 »
Bienvenido, aunque en realidad ya llevas un tiempo por aquí.
Espero que compartas con nosotros tanto dudas como conocimientos.

Un saludo.

88
Hola.
C# no es lo mío, pero aún así veo un par de cosas raras.

Primero, este constructor recibe como argumento un string llamado numero, es decir, el numero de cuenta del usuario.
Pero no parece que hagas nada con ese valor.
En el constructor llamas dos veces al método dcCorrecto() sin que reciba ese dato.
Citar
public NumeroCuenta(string numero)
    {
        dcCorrecto(dcEntSuc, entidad + sucursal, new int[] { 4, 8, 5, 10, 9, 7, 3, 6 });
        dcCorrecto(dcNumero, cuenta, new int[] { 1, 2, 4, 8, 5, 10, 9, 7, 3, 6 });
    }

En cambio, la primera vez que llamas dcCorrecto(), le pasas como segundo argumento la concatenación de los string: entidad + sucursal

Pero esos string, que son atributos de la clase, en ese momento creo que están vacíos, ¿no?

En cualquier caso, estén vacíos o no, dentro del método haces algo muy "peligroso", que es la causa principal del error.
En el bucle, estás usando el mismo índice i para recorrer el string digitos (que es la suma de entidad + sucursal) y también para recorrer el array ponderaciones.

Citar
    private bool dcCorrecto(string dc, string digitos, int[] ponderaciones)
    {
        int sumaPonderacion = 0;
        int sumaTotal = 0;
        int operacion = 0; System.Console.WriteLine(ponderaciones.Length);
        for (int i = 0; i < ponderaciones.Length; i++)
        {
            sumaPonderacion += (digitos[i ] - 48) * ponderaciones[i ];
        }

Ese índice i va a aumentar su valor según ponderaciones.Length, así que para recorrer el array de ponderaciones no hay problema.
Pero recorrer también el string digitos es arriesgado, porque no tiene porque medir lo mismo que el array de ponderaciones.

Por tanto, ese índice puede quedarse corto si el string digitos es más largo que el array ponderaciones.
O peor aún, y es probablemente lo que está pasando, si digitos es más corto el índice i va a ser demasiado alto para recorrer el string digitos y te lanzará el error de que el índice está fuera de los límites del array (un string también es un array)

Tienes que repensar la lógica que estás siguiendo.
Los atributos están vacíos cuando llamas a esos métodos, por eso los string miden 0 y el bucle no puede recorrerlos, porque no hay nada que recorrer.

Así que:

- Mira a ver que se supone que deberías hacer con el argumento "numero" que recibe el constructor, de momento, no estás haciendo nada con él.

- Comprueba qué debe suceder primero para que los atributos reciban valores antes de operar con ellos con los métodos.

- Estás llamando dos veces al método dcCorrecto(). ¿Quizás primero deberías llamar a FormatoCorrecto()? Parece que es este método quien sí podría hacer algo con el argumento "numero" y parece que además da valores a los atributos.
Aunque en este método también veo algo raro así a primera vista. Hay un else donde se crea un nuevo array de ponderaciones, pero no se usa ni se guarda en ningún atributo.
Este array, "muere" y desaparece en cuanto se cierra la llave de ese if else

Citar
        if (isMatch == false) //Si es falso
        {

            throw new NumeroCuentaIncorrectoException("Mensaje");

        }
        else
        {
            int[] ponderacion = new int[8] { 4, 8, 5, 10, 9, 7, 3, 6 };


        }

- Comprueba que string deberías recorrer en el bucle donde también recorres el array de ponderaciones.
¿Qué pasa si el string no tiene el mismo tamaño que ponderaciones?
¿Quizás haya que recorrerlo con otro bucle?


Lamento no poder darte indicaciones más precisas, no tengo mucho tiempo ahora para mirar más a fondo.

Pero esos puntos que he mencionado, son los que por ahora deberías revisar.

Un saludo.

89
He probado las medidas relativas que has puesto con mi pantalla, que imagino será distinta que la tuya, y funciona perfectamente.

Así que sí parece buena estrategia, al menos para que desde un primer momento no aparezcan texto cortados en pantallas pequeñas ni fuentes demasiado pequeñas en las grandes.

Como dije en otro mensaje, con tanta variedad de pantallas, dispositivos y grados de dioptría, al final lo ideal es crear un menú donde el usuario personalice las fuentes.
Pero bueno, eso es muy trabajoso, los que estamos aprendiendo a programar no vamos a meternos en esos "embolaos" para cada programita que escribamos.
Con usar dimensiones relativas, ya es más que suficiente.

Un saludo.

90

Ahora en el panel derecho se establece un layout con 2 filas, arriba el panel que muestra "Escriba y use botones" y abajo el panel con los propios botones.

            setLayout(new GridLayout(2,1));
            add(etMensajes);
            add(pnBotones);

Pero aquí en lugar de añadir los dos subpaneles (pnMensajes y pnBotones) lo que veo es que se añade el JLabel etMensajes y el panel pnBotones.

¿Por qué no se añaden los dos paneles? Aquí parece que pnMensajes quedara sin uso. ¿Por qué?

Pues ni más ni menos..., que porque me equivoqué  :o

O me equivoqué, o tal vez olvidé eliminar las líneas donde creamos el panel pnMensajes.

Quizás en su momento estuve probando distintas maquetaciones hasta elegir la que más me gustaba.
Si te fijas, si en lugar de añadir directamente la etiqueta al GridLayout, añadimos el panel que contiene la etiqueta..., el texto de la etiqueta aparece en una posición distinta. Sale más elevada respecto a los botones.

Seguramente probé ambas posibilidades y me gustó más como quedaba al añadir directamente la etiqueta, sin envolverla con un panel.
Pero luego olvidé borrar las líneas donde la envolvía con el panel.

Así que tienes razón, el panel pnMensajes no se está usando. Así que puedes elegir eliminarlo, o bien darle uso y agregarlo al GridLayout en lugar de la etiqueta.
Prueba ambas posibilidades y elige la que más te guste.

Bien observado. ;)

Un saludo.

91
Hola.
No controlo C# así que no se decirte el código concreto que necesitas.

Pero la lógica a seguir sería que con el indice i del primer bucle
Código: [Seleccionar]
for (int i = 0; i < Baraja.GetLength(0); i++)Lo uses para recorrer los 4 valores del enum Tipo

Y el índice j del segundo bucle,
Código: [Seleccionar]
for (int j = 0; j < Baraja.GetLength(1); j++) para asignar los 12 valores posibles a las cartas.
Este índice j va a ir de 0 a 11, pero las cartas van a tener valores de 1 a 12, así que al asignar valores tendrás que sumar +1 al índice.

En esto que pongo abajo, la línea azul ya estaría correcta.
Es la roja la que hay que averiguar como, mediante el índice i que va a recorrer los valores de 0 a 3, obtener cada uno de los 4 enum.
No se como se hace eso en C# ni dispongo de tiempo para investigarlo  :-\
Pero quizás tú tengas apuntes o algún ejercicio de ejemplo donde se explique

Citar
        for (int i = 0; i < Baraja.GetLength(0); i++)
        {
            for (int j = 0; j < Baraja.GetLength(1); j++)
            {
                Baraja[i, j].tipo = Naipe.Tipo.Oros;
                Baraja[i, j].valor= j + 1;
            }
        }


92
me gustaria saber si se podria realizar simplemente en un archivo con multiples funciones

¿Te refieres a hacerlo todo en un único archivo .java?

Sí, se puede hacer. Pero no es nada recomendable. Sería totalmente opuesto a las "buenas costumbres" de programación.

Un programa con tantas clases, aunque algunas clases sean de pocas líneas, siempre mejor "modularlo" en distintos archivos .java, e incluso lo correcto sería separarlos en distintos packages

A lo sumo, se podrían agrupar en un mismo archivo .java la clase Personaje y el resto de sus clases "hijas".

Pero juntarlo TODO en un único archivo .java..., no interesa tener un archivo gigantesco de cientos de líneas.
Si necesitamos hacer un cambio concreto en la clase Mago, es más fácil localizar la línea que queremos modificar si la clase está escrita en su propio archivo que si está entre todo el barullo de clases en un único archivo.

Además, puede que luego queramos escribir otro programa similar y reutilizar únicamente la clase Personaje
Si está en su propio archivo, podremos hacer un import solo de esta clase. Pero si está todo junto en un único archivo, el import tendrá que ser de TODAS las clases, lo cuál no es óptimo y además nos puede generar duplicidades si en nuestro "nuevo" programa queremos escribir clases con el mismo nombre que las clases del programa "antiguo" que en realidad no queríamos utilizar, pero que hemos importado innecesariamente.

"Divide y vencerás" suele decirse en programación. Y se refiere a esto, a modular los programas en distintos segmentos y archivos

93
Con la clase BufferedWriter se puede ir añadiendo líneas de texto a un fichero.

Hay que tener en cuenta que en este programa, estamos publicando líneas desde distintas clases, no solo desde la clase main.
Cuando un personaje ataca, lo que sale en pantalla se está publicando desde la clase Personaje

O si un mago usa el hechizo de sanación, esto se publica desde la clase Mago

Así que habría que añadir un objeto BufferedWriter a todas las clases.

Aunque, si somos un poco listos  ;D, en lugar de eso, lo que haremos será crear una nueva clase que incluya ese objeto BufferedWriter y un método estático que se encargue de escribir en fichero el mensaje String que le indiquemos.
Así podemos llamar a esta clase desde las otras, sin tener que ir una por una declarando y configurando un BufferedWriter

Eso si solo somos un poco listos, pero si resulta que somos muy listos  8), lo que además vamos hacer es que esta nueva clase, también se encargue de mostrar el mensaje en pantalla.
Porque si no, vamos a tener que estar repitiendo dos veces cada mensaje que queramos publicar, una para el fichero y otra para la pantalla.
Pues en lugar de eso, que esta nueva clase se encargue de publicar el mensaje en ambos sitios.
Así que en el resto de clases, ya no usaremos la instrucción System.out.println(), a no ser que se trate de algún mensaje destinado únicamente para pantalla.
Para los que queramos publicar en ambos medios, se los pasaremos a la nueva clase, que podemos llamar Publicador
Código: [Seleccionar]
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class Publicador {

public static void publicar(String mensaje) {

//Publicamos en pantalla
System.out.println(mensaje);
//Publicamos en fichero
try {
BufferedWriter escritor = new BufferedWriter(new FileWriter("d:/log/combate.txt", true));
escritor.write(mensaje);
escritor.newLine();
escritor.close();
} catch (IOException e) {
System.err.println("No se pudo escribir en fichero");
}
}

}
Importante escoger una ruta de archivo donde sepamos que no vamos a tener problemas de permisos de escritura y tal.

Ahora, en el resto de clases, vamos sustituyendo los system.out por llamadas al Publicador

Por ejemplo, en el método atacar() de Personaje, lo que antes publicábamos en pantalla:
Citar
   public int atacar() {
      System.out.println(nombre + " ataca.");
      Random azar = new Random();
      //Ataque mínimo: 1, Maximo: según nivel de ataque
      int puntosAtaque = azar.nextInt(ataque) + 1;
      
      //Damos un 10% de posibilidad de obtener un "Ataque Crítico"
      int valor = azar.nextInt(100);

      if (valor%10 == 0) { //Comprobamos si es multiplo de 10
         System.out.println("¡" + nombre + " consigue un Ataque Crítico!");
         //Un "Ataque Crítico" duplica el daño del ataque
         return puntosAtaque * 2;
      }
      else
         return puntosAtaque;
   }

Ahora lo hacemos con el Publicador en ambos soportes:
Citar
   public int atacar() {
      Publicador.publicar(nombre + " ataca.");
      Random azar = new Random();
      //Ataque mínimo: 1, Maximo: según nivel de ataque
      int puntosAtaque = azar.nextInt(ataque) + 1;
      
      //Damos un 10% de posibilidad de obtener un "Ataque Crítico"
      int valor = azar.nextInt(100);

      if (valor%10 == 0) { //Comprobamos si es multiplo de 10
         Publicador.publicar("¡" + nombre + " consigue un Ataque Crítico!");
         //Un "Ataque Crítico" duplica el daño del ataque
         return puntosAtaque * 2;
      }
      else
         return puntosAtaque;
   }


En las clases Personaje, Caballero y Mago, cambiaremos todos los system.out por el "publicador".

En la clase Combate, puede que no queramos sustituirlos todos.
Por ejemplo, los del método para crearPersonaje() donde nos sale un menú para elegir personaje, nombre, nivel de ataque, etc... pues eso no hace falta que salga reflejado en el fichero de texto.
Aunque eso ya va a gusto de cada uno.

Una cosa necesaria que sí vamos a tener que hacer en Combate es añadir un método para "preparar el fichero".
Más que prepararlo, en realidad lo que hará será comprobar si ya existe. Si existe, lo elimina y creará uno nuevo.
Esto es para que cada vez que ejecutemos el juego, el fichero se renueve y solo salgan los datos de la última partida.

Código: [Seleccionar]
private static void prepararFichero() {
File fichero = new File("d:/log/combate.txt");
if (fichero.exists()) {
fichero.delete();
try {
fichero.createNewFile();
} catch (IOException e) {
System.err.println("No se pudo crear fichero: " + "d:/log/combate.txt");
}
}
}

Y es al primer método que llamaremos en el main()
Código: [Seleccionar]
public static void main(String[] args) {

prepararFichero();

crearPersonaje();

elegirDificultad();

//Comienza el combate...
while(jugador.getVida() > 0 && CPU.getVida() > 0) {


A no ser que quieras que se guarden TODAS las partidas.
En ese caso, se podría cambiar y hacer que por ejemplo se añada una línea con la fecha y hora actuales, para separar las líneas de cada partida.


En fin, si se ha hecho todo esto bien, podemos ver que ahora los mensajes de la terminal, quedan reflejados también en un fichero de texto




Y eso es todo, cualquier duda, solo hay que preguntar.
Un saludo

94
Se puede hacer, claro.
Pero habría que añadir bastante código extra.

Así a bote pronto, lo que se me ocurre sería usar una matriz de 2xN, donde N comenzaría por ejemplo con valor 4 y a lo largo del programa iría menguando según se van eliminando contendientes.

Es decir, si empezamos con 2x4, ya tenemos a 8 contendientes en la matriz, ya sean humanos o CPU.
Citar
[HUM][CPU][CPU][HUM]
[CPU][HUM][CPU][CPU]
Y esto funcionaría como una eliminatoria, los cuatro contendientes de la fila 0 se enfrentan con los de la fila 1.

Al final de esto tendremos cuatro eliminados y cuatro ganadores. Pues con esos 4 ganadores habría que crear una nueva matriz de 2x2
Citar
[CPU][HUM]
[HUM][CPU]

De nuevo hacemos enfrentarse los de la fila 0 con la fila 1, quedando dos contendientes con los que crear una nueva matriz de 2x1
Citar
[HUM]
[HUM]

Y de aquí ya saldría el ganador definitivo.

En fin, hacer esto, no es cuestión de añadir 4 líneas de código.
Habría que añadir bastante más y modificar parte de lo que ya hay escrito.
El código actual está pensado para que se enfrente un humano vs CPU. Pero ahora puede ocurrir que se enfrenten humano vs humano, o CPU vs CPU.

Habría que rediseñar los métodos para las acciones de combate para que recibieran como argumentos los personajes que se van a enfrentar, analice quién es humano, quién es CPU y aplique distintas reglas de combate según cada caso.

Hará falta código para poder crear varios personajes, para ir reconstruyendo la matriz con distintos tamaños según se van eliminando personajes, para que haga un "sorteo" aleatorio para elegir quien se enfrenta a quién...
Y bueno, esto es lo que se me ocurre en un primer pensamiento, luego irían surgiendo imprevistos y tal...

Sería interesante hacerlo, pero en estas fechas, el tiempo libre escasea... :o

95
Hola.

En ese caso, lo ideal sería trasladar el código donde los personajes realizan sus acciones de combate, a distintos métodos, uno para el JUGADOR y otro para la CPU.

De esa forma, según los resultados de los dados, podemos decidir quién realizará primero sus acciones.

Para que todo funcione bien, al menos en el código que escribimos en este hilo, habría que trasladar también algunas variables que íbamos declarando a lo largo del main, para declararlas como atributos de clase.
Así serán visibles para los métodos que vamos a tener que crear.
Citar
public class Combate {
   
   private static Scanner teclado = new Scanner(System.in);
   private static Personaje jugador; //Podrá ser un Mago o un Caballero
   private static Caballero CPU; //La CPU siempre es un Caballero
   private static boolean modoDificil = false; //Nivel de dificultad
   private static int accionJugador = 0; //Recoge la acción que escoge el jugador en su menu
   private static int accionCPU = 0; //Recoge la acción que escoge CPU
   //Stats de jugador y CPU
   private static int ataqueJugador = 0, ataqueCPU = 0;
   private static int defensaJugador = 0, defensaCPU = 0;



Luego, creamos los métodos donde jugador y CPU elegirán sus acciones.
Es simplemente copiar y pegar lo que antes estaba en el main, dentro de estos métodos.

Método para JUGADOR:
Código: [Seleccionar]
private static void atacaJugador() {
//Recogemos acción del jugador
accionJugador = menuCombateJugador();
System.out.println(); //Salto de línea
//Evaluamos la acción escogida
switch(accionJugador) {
case 1: //Ataque normal
ataqueJugador = jugador.atacar();
System.out.println("Consigues un ATAQUE de " + ataqueJugador + " pts.");
break;
case 2: //Ataque alternativo, según clase
if (jugador instanceof Caballero) {
ataqueJugador = ((Caballero)jugador).ataqueAlternativo();
System.out.println("Consigues un ATAQUE de " + ataqueJugador + " pts.");
}
else {
//Ataque Mago alternativo aún no definido
}
break;
case 3: //Defender
defensaJugador = jugador.defender();
System.out.println("Consigues una DEFENSA de " + defensaJugador + " pts.");
break;
case 4: //Ataque relámpago o hechizo ataque, según clase
if (jugador instanceof Caballero) {
ataqueJugador = ((Caballero)jugador).ataqueRelampago();
System.out.println("Consigues un ATAQUE de " + ataqueJugador + " pts.");
}
else {
ataqueJugador = ((Mago)jugador).hechizoAtaque();
System.out.println("Consigues un ATAQUE de " + ataqueJugador + " pts.");
}
break;
case 5: //Hechizo sanación 50 puntos, solo sirve para Mago
if (jugador instanceof Caballero)
System.out.println("La acción no es válida. Pierdes tu turno");
else
((Mago)jugador).curar50puntos();
break;
default:
System.out.println("La acción no es válida. Pierdes tu turno");
}
pausa();
}

Método para CPU:
Código: [Seleccionar]
private static void atacaCPU() {
//A continuación elige acción la CPU
if (modoDificil)
accionCPU = CPUmodoDificil(ataqueJugador, defensaJugador);
else
accionCPU = CPUmodoNormal();
//Analizamos accion de CPU
switch(accionCPU) {
case 1:
ataqueCPU = CPU.atacar();
break;
case 2:
defensaCPU = CPU.defender();
break;
case 3:
ataqueCPU = CPU.ataqueRelampago();
}
pausa();
}


Ahora, un tercer método para la tirada de dados.
Partiendo del código que has publicado, yo lo modificaría para hacer que retorne un int.
Si la tirada la ha ganado JUGADOR, que retorne valor 1, por ejemplo.
Si la ha ganado CPU, pues que retorne valor 2.

Además le añadiría código para que la tirada se repita en caso de empate, y para que muestre el nombre de los jugadores:
Código: [Seleccionar]
private static int tirarDado() {
int dado1 = 0;
        int dado2 = 0;

        while (dado1 == dado2) {
        dado1 = (int)(Math.random()*6)+1;
        System.out.println("Dado de " + jugador.getNombre() + ": " + dado1);
        dado2 = (int)(Math.random()*6)+1;
        System.out.println("Dado de " + CPU.getNombre() + ": " + dado2);
        if (dado1 == dado2) {
        System.out.println("Empate, se repetirá lanzamiento de dados");
        pausa();
        }
        }
       
        if (dado1 > dado2) {
        System.out.println("Primer turno es para " + jugador.getNombre());
        return 1;
        }
        else {
        System.out.println("Primer turno es para " + CPU.getNombre());
        return 2;
        }
       
}

Con esto, ahora en el main, en la parte donde hemos extraído el código donde JUGADOR y CPU elegían sus acciones, ahora colocaremos un if para analizar que valor nos retorna el método que tira los dados.
Si retorna 1, pues el primer turno ser para JUGADOR. De lo contrario, será para CPU:

Citar
      
      ............................
      ............................
      ............................
      //Comienza el combate...
      while(jugador.getVida() > 0 && CPU.getVida() > 0) {
         /*
          * Las siguientes variables recogerán los valores de ataques o defensas
          * de los combatientes, según la acción que escojan, en cada turno.
          */
         
         
         System.out.println("\n\n\t\tCOMBATIENDO...");
         System.out.println("> Jugador: " + jugador);
         System.out.println("> CPU: " + CPU);
         
         System.out.println("Se decidirá primer turno con una tirada de dados.");   
         pausa();

         
         if (tirarDado() == 1) {
            atacaJugador();
            atacaCPU();
         }
         else {
            atacaCPU();
            atacaJugador();
         }

            
         
         //Ambos Jugadores han actuado, evaluamos daños conseguidos
         int danoRecibeJugador = ataqueCPU - defensaJugador;
         int danoRecibeCPU = ataqueJugador - defensaCPU;
         //Qué ha pasaso con Jugador
         if (danoRecibeJugador > 0) {
      ............................
      ............................
      ............................

Así queda implementado este sistema para elegir turno al azar.

Es cierto que antes JUGADOR tenía ventaja por ser siempre el primero, pero era para compensar que CPU siempre es el doble de potente que JUGADOR.

Ahora JUGADOR, las va a pasar canutas... ;D


Un saludo.

96

Hasta ahora durante el combate el usuario tiene 4 opciones:
1- Ataque normal
2- Defender
3- Ataque "Especial", que es distinto según Caballero o Mago
4- Hechizo Sanación, solo válido para el Mago. Si el Caballero elige esta opción, pierde turno

Entonces, aquí pueden haber dos planteamientos.
Añadir una opción más a ese menú:
1- Ataque normal
2- Ataque alternativo
3- Defender
4- Ataque "Especial".
5- Hechizo Sanación.

El otro planteamiento sería hacer que al elegir 1-Ataque Normal, se abra un submenú para escoger entre las dos armas distintas.
Esto podría ser un poco más laborioso, porque además el submenú sería distinto según si es caballero o mago ya que habría que indicar la descripción de las distintas armas.

Yo escogería el primer planteamiento.

Eso en cuanto al menú de opciones.


Sobre donde especificar ese "ataque alternativo" o como queramos llamarlo.
Pues también depende de cuáles son las "reglas" de ese ataque.

Si van a ser distintas según el personaje, entonces sí será mejor definirlo en las clases Caballero y Mago.

Por ejemplo, queremos que el ataque alternativo del Caballero sea un poco más potente que el ataque normal, pero menos que el "Relámpago", basta con añadir un nuevo método a Caballero
Citar
public class Caballero extends Personaje{

   public Caballero() {
      super();
   }

   public Caballero(String nombre, int ataque, int defensa, int vida) {
      super(nombre, ataque, defensa, vida);
   }
   
   public int ataqueRelampago() {
      /*
       * Nivel de ataque máximo, incrementado un 50%
       */
      System.out.println(nombre + " usa su ataque RELÁMPAGO");
      return (int)(ataque * 1.5);
   }
   
   public int ataqueAlternativo() {
      /*
       * Nivel de ataque máximo, incrementado un 25%
       */
      System.out.println(nombre + " usa la Espada Ancestral");
      return (int)(ataque * 1.25);
   }

   
   @Override
   public String toString() {
      return String.format("Caballero: %s / Nivel de Vida: %d", nombre, vida);
   }
   
}

Y luego en la clase Combate, donde habremos añadido una nueva opción al menu y un nuevo case al switch, pues bastará con llamar a ese método:
Citar
         //Evaluamos la acción escogida
         switch(accionJugador) {
         case 1: //Ataque normal
            ataqueJugador = jugador.atacar();
            System.out.println("Consigues un ATAQUE de " + ataqueJugador + " pts.");
            break;
         case 2: //Ataque alternativo, según clase
            if (jugador instanceof Caballero) {
               ataqueJugador = ((Caballero)jugador).ataqueAlternativo();
               System.out.println("Consigues un ATAQUE de " + ataqueJugador + " pts.");
            }
            else {
               //Ataque Mago alternativo aún no definido
            }
            break;

97
Claro, yo puse las entidades básicas.
Luego posiblemente queramos añadir más clases que se encarguen de gestionar esas entidades.

Por ejemplo una clase GestionEspacios con un ArrayList de Espacios y los métodos necesarios para crear espacios, para añadirles/quitar Equipamientos (quizás apoyándose en otra clase GestorEquipamientos)

También es posible que queramos añadir nuevos atributos, por ejemplo un boolean para indicar si un espacio está disponible o no.

Sobre el histórico de precios por año..., quizás el ArrayList<Double> no sea la mejor opción ya que solo permite guardar valor de costes, pero sin asociarlos a ningún año en concreto.

Puede que un HashMap<Integer, Double>, que permite asociar claves y valores, sea mejor opción al poder usar el año(Integer) como clave y el coste de dicho año(Double) como valor.

O si no, crear una nueva clase llamada CosteAnual con dos atributos, el año y el coste.

La verdad es que es un ejercicio interesante y completo. Lástima que ahora en Navidades soy esclavo a tiempo completo del trabajo los siete días de la semana  :'( y no tengo tiempo ni fuerzas para intentar solucionarlo por mi parte.

Mantennos informados de tus avances y expón tus dudas, por si podemos ayudarte en algo.

Un saludo.

98
Sobre los patrones poco puedo ayudar, porque no me los se...  :-\
Vamos, seguro que tú podrías explicarme más sobre ellos que yo a ti.


Sobre las clases, tampoco soy un experto en UML, pero creo que estas podrían ser las clases.

Equipamiento y Producto las he puesto como abstractas.
Sin embargo, ya que entre sus hijas no hay atributos que pudieran diferenciarlas unas de otras, en realidad se podría haber resuelto con sendos enumeradores (Enum).
Pero intuyo que la intención del ejercicio es representar relaciones de herencias con estas entidades.

Un Espacio puede tener, o no, un Equipamiento, por eso remarco que este atributo podría tener valor null.

Reserva se relaciona con Espacio a través de su código/ID. Es decir, que Reserva no tiene un atributo de tipo Espacio, si no un String con el código identificador de un Espacio

Una Reserva si puede tener agregadas varias Consumiciones, por eso su atributo es un ArrayList.
Y cada Consumición consta de un Producto


99
Comunidad / Re: Feliz Navidad y Próspero Año Nuevo para todos
« en: 15 de Diciembre 2022, 11:07 »
Encantado de ir sumando Navidades en este foro.

Felices fiestas a todos  ;)

100
A ver, las variables "nombre" y "traduccion" JAMÁS van a ser iguales(equals) porque "traduccion" es el producto de alterar la variable "nombre".
Así que SIEMPRE serán distintas.

Parece ser que lo que necesitas es almacenar los nombres (sin traducir) que el usuario vaya introduciendo y evitar que vuelvan a ser traducidos.
Para esto sí tiene sentido usar el ArrayList del que hablábamos al principio.

En ese ArrayList podemos ir guardando los nombres introducidos y, antes de hacer ninguna traducción, comprobar si el nombre que nos da el usuario ya existe en el ArrayList.

Si ya existe, pues no traducimos.

Si no existe, hacemos la traducción y guardamos el nombre original en el ArrayList para no volver a traducirlo.

Señalo con colores los cambios en el código.
La línea en rojo y tachada, puedes borrarla del código, no tiene ninguna utilidad.

Citar
   public static void main(String args[]) {

      ArrayList<String> nombres = new ArrayList<String>();
      String tecla = null;
      Scanner scan = new Scanner(System.in);
      Scanner scanner = new Scanner(System.in);
      do {
         System.out.println("---Menu de opciones---");
         System.out.println("1. Traducir nombre");
         System.out.println("Ingresar opcion:");

         int opcion = scanner.nextInt();
         System.out.println("Ingrese el nombre del terricola: ");
         String nombre = scan.nextLine();
         boolean opc = (opcion <= 1);

         switch (opcion) {
         case 1:
            //Antes de traducir, comprobamos si este nombre ya ha sido traducido anteriormente
            if (nombres.contains(nombre)) //Sí ha sido traducido
               System.out.println("El nombre ya existe");
            else { //Nunca ha sido traducido
               String traduccion = nombre.replace('a', '!').replace('b', '"').replace('c', '#')
                     .replace('d', '$').replace('e', '%').replace('f', '&').replace('g', '´')
                     .replace('h', '(').replace('i', ')').replace('j', '*').replace('k', '+')
                     .replace('l', ',').replace('m', '-').replace('n', '.').replace('o', '/')
                     .replace('p', '0').replace('q', '1').replace('r', '2').replace('s', '3')
                     .replace('t', '4').replace('u', '5').replace('v', '6').replace('w', '7')
                     .replace('x', '8').replace('y', '9').replace('z', ':');
               
               System.out.printf("Nombre traducido a marciano: %s\n", traduccion);
               
               nombres.add(nombre); //Añadimos a la lista de nombres traducidos   
            }
            break;
         }

         System.out.print("\n¿Quiere seguir?\n");
         tecla = new Scanner(System.in).nextLine();

      } while (tecla.equals("si") || tecla.equals("s"));
   }

Páginas: 1 2 3 4 [5] 6 7 8 9 10 ... 50

Sobre la educación, sólo puedo decir que es el tema más importante en el que nosotros, como pueblo, debemos involucrarnos.

Abraham Lincoln (1808-1865) Presidente estadounidense.

aprenderaprogramar.com: Desde 2006 comprometidos con la didáctica y divulgación de la programación

Preguntas y respuestas

¿Cómo establecer o cambiar la imagen asociada (avatar) de usuario?
  1. Inicia sesión con tu nombre de usuario y contraseña.
  2. Pulsa en perfil --> perfil del foro
  3. Elige la imagen personalizada que quieras usar. Puedes escogerla de una galería de imágenes o subirla desde tu ordenador.
  4. En la parte final de la página pulsa el botón "cambiar perfil".