Autor Tema: juego de batalla naval en netbeans con swing  (Leído 219 veces)

jose130

  • Sin experiencia
  • *
  • APR2.COM
  • Mensajes: 1
    • Ver Perfil
juego de batalla naval en netbeans con swing
« : 19 de Junio 2020, 06:44 »
hola buenas noches queria saber si alguien me podia ayudar a realizar un juego de batalla naval que tengo como proyecto  y es que la verdad no se como hacerlo el proyecto pide lo siguiente:

Battleship es un juego muy popular de 2 jugadores. El juego consiste en una matriz (arreglo
bidimensional) de 8 x 8 por jugador.
Cada jugador con su matriz debe colocar la cantidad de barcos que tiene disponible según la
dificultad, los cuales según tu tamaño ocuparan de 2 a 5 bombas para destruirlos. Luego que se
coloquen los barcos cada jugador se irá turnando en bombardear una celda de la matriz del jugador
contrario, en el tablero se debe indicar si fallo el tiro o si dio con algún barco. Un jugador logra
hundir un barco contrario cuando logra bombardearlo la cantidad de veces que ocupaba dicho
barco para destruirle. El juego termina cuando un jugador logra hundir todos los barcos del jugador
contrario o si su oponente se retira del juego.
LO DINAMICO entra en el que luego de que un barco es bombardeado, el tablero se REGENERA
con todos los barcos en distintas posiciones dentro del tablero. Las nuevas posiciones de los barcos
dentro del tablero se consiguen aleatoriamente, evitando claro está que dos barcos queden en una
misma casilla.
El programa inicia mostrando el siguiente menú:
Menú de Inicio
1- Login. Se pide del teclado el username y el password, si este se encuentra dentro de la
colección de players, se carga el MENÚ PRINCIPAL, si no, se muestra en pantalla el error
y se vuelve a desplegar este MENÚ DE lNICIO.
2- Crear Player. Se permite con esta opción crear un nuevo player. Se pide que se ingrese los
datos de Username (Se valida que sea UNICO) y password. El player tendra tambien un
atributo de puntos que inicialmente se le asignara 0. Si todo se hace bien se crea el player
para guardarlo en la colección. Al momento de crearlo bien se va al MENÚ PRINCIPAL,
si no, se informa el problema y se carga el MENÚ DE lNICIO.
3- Salir. La aplicación se cierra
Ing. Erick Amaya
Menú Principal
El programa debe contener un menú que contiene lo siguiente:
1- Jugar Battleship
2- Configuración.
a. Dificultad
b. Modo de Juego
c. Regresar al Menú Principal
3- Reportes
a. Descripción de mis últimos 10 juegos.
b. Ranking de Jugadores
c. Regresar al Menú principal
4- Mi Perfil
a. Ver mis Datos
b. Modificar mis datos
c. Eliminar mi cuenta
d. Regresar al Menú Principal
5- Salir
El jugador debe seleccionar una opción para proceder.
1- JUGAR BATTLESHIP
Tomando en cuenta el usuario LOGGED IN que será el PLAYER 1, lo primero que se debe pedir
del teclado antes de iniciar a jugar es el username del PLAYER 2 (debe ser un jugador registrado,
si no lo es se pide que se ingrese de nuevo, si se ingresa EXIT se cancela todo y se regresa al
MENÚ PRINCIPAL). Una vez hecho esto se turnará cada uno colocando la cantidad de barcos que
según la dificultad se lo permite (Véase Dificultad 2.a). Los tipos de barcos que pueden colocar
son los siguientes:
Ing. Erick Amaya
Barco
Cantidad
Bombas
necesarias
Código
Portaaviones 5 PA
Acorazado 4 AZ
Submarino 3 SM
Destructor 2 DT
Para colocar los barcos se debe pedir primero el código de este (PA, AZ, SM o DT), seguidamente
se indicará una celda inicial para el barco dentro de la matriz (indicando el índice de la fila y la
columna). NO se permite poner 2 barcos del mismo tipo, A MENOS que estén jugando de modo
EASY (que son 5 barcos permitidos y solo hay 4 tipos) que se le permite repetir, pero 1 Destructor.
NOTA: Se DEBE validar que un barco no se coloque encima de otros o que las coordenadas
ingresadas se salgan de los límites de la matriz.
El proceso de colocar los barcos se repetirá según la cantidad posible de barcos disponible según
la dificultad, una vez terminado el PLAYER 1, el PLAYER 2 debe realizar la misma acción de
colocación en su matriz.
Una vez que ambos han terminado de colocar sus barcos el juego se da por iniciado. El jugador 1
comienza indicando el no. de la fila y el no. de la columna (celda de la matriz) en la cual desea
mandar una bomba al jugador contrario. EL JUGADOR 1 DEBE VER LA MATRIZ DEL
JUGADOR CONTRARIO. Por ejemplo: El jugador 1 manda una bomba en la celda [3,3]:
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
Coordenada de bomba (Jugador 1):
-Jugador 2 tiene 5 barcos aun ---
Fila: 3
Columna 3
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ F ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
Coordenada de bomba (Jugador 2):
-Jugador 1 tiene 5 barcos aun –
Fila: 2
Columna 2
Ing. Erick Amaya
Si el jugador 1 falla el tiro en el tablero se dibujará una letra F indicando que para esa coordenada
la bomba cayó en agua y no en algún barco. Si en cambio le dio algún barco se debe indicar con
una X. Seguidamente será el turno del jugador 2 en hacer exactamente lo mismo. Cada jugador
tendrá su turno para mandar bombas al jugador contrario. Pero en el siguiente turno la F tiene que
desaparecer en el tablero.
Si un jugador logra finalmente bombardear un barco este se debe mostrar en pantalla indicándolo
con su código de este, Y si el barco recibió su último bombazo se muestra también en pantalla que
el barco de tal tipo se ha hundido:
HAY QUE RECORDAR que una vez que un barco es bombardeado todo el tablero de ese jugador
afectado se regenera moviendo sus barcos cada uno a posiciones distintas del tablero, de manera
ALEATORIA para no entorpecer el flujo del juego.
El juego terminara cuando alguien logre hundir todos los barcos del jugador contario. Al finalizar
el mismo se muestra un mensaje de que el jugador X fue el triunfador y automáticamente se
mostrara de nuevo el MENÚ PRINCIPAL. El triunfador recibe 3 ptos.
RETIRO
Un jugador se puede retirar en cualquier momento si ingresa -1 tanto en la parte de filas como de
columnas, no sin antes preguntarle se de verdad se desea salir, si el jugador confirma que si se
desea salir el juego termina y el otro jugador triunfo por retiro del contario.
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
Coordenada de bomba (Jugador 1):
-Jugador 2 tiene 5 barcos aun ---
Fila: 2
Columna 1
~ ~ ~ ~ ~ ~ ~ ~
~ AZ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~
SE HA BOMBARDEADO UN ACORAZADO!
SE HUNDIO EL ACORAZADO! Del Jugador 2
Coordenada de bomba (Jugador 2):
-Jugador 1 tiene 5 barcos aun ---
Fila:
Ing. Erick Amaya
2. CONFIGURACION
a. DIFICULTAD
En la parte de dificultad el usuario podrá determinar cuántos barcos por juego un jugador puede
colocar. Las opciones son (EASY – 5 barcos, NORMAL – 4 barcos, EXPERT – 2 barcos y
GENIUS – 1 barco). Luego de modificar la dificultad, el programa vuelve a cargar el SUB MENU
DE CONFIGURACION. Nota: Por default el juego DEBE estar en modo NORMAL..
b. MODO DE JUEGO
En esta opción se selecciona como se va jugar el juego. Hay 2 opciones, ARCADE o TUTORIAL.
La diferencia es que el modo ARCADE esconde todos los barcos a la vista de los jugadores y el
TUTORIAL muestra todos los barcos. Luego de modificar el modo de juego, el programa vuelve
a cargar el SUB MENU DE CONFIGURACION.
Nota: Por default el juego DEBE estar en modo TUTORIAL.
3- REPORTES
a. Descripción De mis Últimos 10 Juegos
Al seleccionar esta opción se debe mostrar que fue lo que paso en los últimos 10 juegos del jugador
que esta LOGGED IN. Luego de imprimir el listado el programa vuelve a mostrar el SUB MENU
DE REPORTES. Un ejemplo podría ser lo siguiente:
1- Carlos hundió todos los barcos de Javier en modo EASY.
2- Cindy hundió todos los barcos de Marcos en modo NORMAL.
3- Catracho hundió todos los barcos de Chapin en modo PRO.
4- El Mero mero se retiro del juego dejando como ganador a Tom.
5-
6-
7-
Ing. Erick Amaya
8-
9-
10-
NOTA: De primero siempre DEBE estar el registro del último juego realizado.
b. Ranking de Jugadores
Al seleccionar esta opción muestra el listado total de jugadores inscritos en el juego. Se muestran
TODOS sus datos. El listado imprime jugadores ordenados del jugador que tiene MAS PUNTOS
al que MENOS tiene.
Luego el programa vuelve a mostrar el SUB MENU DE REPORTES
4- MI PERFIL
a. Ver Mis Datos.
Muestra TODOS los datos del jugador que esta LOGGED IN. Luego el programa vuelve a mostrar
el SUB MENU DE MI PERFIL.
b. Modificar Mis Datos
Me permite modificar tanto el Username como el password del jugador que esta LOGGED IN.
Luego el programa vuelve a mostrar el SUB MENU DE MI PERFIL.
c. Eliminar Cuenta
Elimina la cuenta del jugador LOGGED IN de la colección de jugadores. El jugador YA NO
EXISTIRA MAS. Luego el programa muestra el MENU DE INICIO.
5- CERRAR SESION
Cierra la sesión del jugador LOGGED IN y se vuelve a mostrar el MENU DE INICIO.
Ing. Erick Amaya
BONO 2 oro EXTRA: Si se hace el proyecto con la librería Swing (VISUAL)
Los siguientes avances completados, por ahora TODO en el Main pero dejen bien marcado y
comentado todo para que sea fácil su estructuración una vez sepan clases:
• Manejar la colección de usuarios como un arreglo de Strings, donde se guarda solo el
username y el password digamos que es “honduras” para todos. Esto para probar la lógica
del Login, Logout, Crear Player, Ver Mis Datos y Editar mis Datos.
• Que, en Jugar, ya tomé automáticamente el usuario logged in (que solo es una string, POR
AHORA) y pide del teclado el usuario 2 y que validé que ese username este dentro del
arreglo de strings de usuarios. Que se puedan colocar los barcos dentro del tablero de cada
jugador. Que se puedan turnar mandando bombas y sepa reconocer cuando fallo o cuando
le dio a un barco. SI LE PEGA NO ES REQUERIDO QUE SE ORDENE
ALEATORIAMENTE de nuevo el tablero. NO es requerido evaluar el ganador, PERO SI
que se pueda retirar. Que usa por ahora la CONFIGURACION DEFAULT (NORMAL y
TUTORIAL)
• Que funcione TODA la logica del Menú de Inicio y el Menú Principal como se debe. En
las opciones que no se tienen que hacer aun como: Ranking y Ver mis partidas anteriores,
que desplieguen un listado INVENTADO hardcoded, solo para comprobar que se esta
llamando la opción correcta. En la configuración solo se toma la dificultad y el modo de
juego default.
ESPECIFICACIONES DE SUBIDA FINAL
REQUISITOS FINALES:
1- Se deben crear MINIMO las siguientes clases:
a. Clase Player que maneje la información del jugador. Aquí se debe tener un arreglo
de String para manejar los logs finales de la partida (Para mostrar luego las ultimas
partidas realizadas).
b. Clase Battleship con TODA la lógica del juego incluyendo la colección de Players,
los 2 tableros, el desarrollo de una partida, el usuario logged in (current user)….
Ing. Erick Amaya
c. Clase donde estará el Main de la aplicación que contendrá los menús de la
aplicación y llamará las opciones que ofrece la clase Battleship. ¡EL MAIN NO
TIENE NINGUNA LOGICA DEL JUEGO!
2- Se debe utilizar Arreglos Unidimensionales y bidimensionales.
3- Los proyectos con sus clases deben de estar debidamente estructurado, es decir sin
duplicación de códigos, sin funciones MUY cargadas.
4- Se debe usar TODOS los temas vistos en clase incluyendo: Foreach, Switch, Operador
Ternario, Static (¡que no sea la función MAIN!)

De antemano quedo muy agradecido por su atención y su ayuda!

Saludos!

Kabuto

  • Moderador Global
  • Avanzado
  • *******
  • Mensajes: 364
    • Ver Perfil
Re:juego de batalla naval en netbeans con swing
« Respuesta #1 : 20 de Junio 2020, 20:42 »
Hola.
Me parece interesante desarrollar este ejercicio. Y ya puestos, intentar hacerlo con Swing.
Aunque no se hasta donde podría llegar je je.
Además por desgracia mi tiempo libre es muy limitado.

Si te parece, puedo ir mostrando como lo resolvería yo, y tu en base a eso lo vas adaptando a tu manera o bien a lo que creas que se ajusta mejor a lo que pide el enunciado.

Supongo que debemos empezar por lo básico, la clase Player.
En principio tendría 4 atributos: nombre, password, puntos acumulados el "histórico".

Para el histórico se pide guardar el resultado de sus últimas 10 partidas. El enunciado habla de utilizar un arreglo de Strings, pero para no tener que añadir código extra con el que comprobar en cuál posición del arreglo hay que insertar cada nueva entrada, me parece más cómodo usar un ArrayList para guardar Strings. Tu usa lo que creas conveniente.

Habrá un método para insertar una nueva entrada en el histórico. Puesto que solo son 10, habrá que controlar el tamaño del ArrayList.
Si es menor que 10, no pasa nada, se agrega la nueva entrada y ya está.
Pero si ya tiene 10, hay que hacer sitio a la nueva entrada y eliminar la más antigua. Esto podemos hacerlo moviendo todas las entradas una posición.
La que está en posición [1] pasa a
  • , y así ya queda eliminada la más antigua.

La [2] pasa a la [1], la [3] a la [2],....la [9] a la [8] y la nueva entrada la colocamos en la [9]

Otro método sería para mostrar el Histórico, para mostrarlo podemos construir un String con todas las entradas. Así este String podemos mostrarlo tanto por consola como en algún componente Swing, según queramos.

Otro método importante será sobreescribir el método equals() para que Java sepa como comparar objetos Player y decidir si son iguales o no.
El enunciado nos pide evitar que en la lista de Players existan usuarios con el mismo nombre, por tanto, ese será el atributo decisivo para decidir si dos objetos Player son "equivalentes" o no.

Este podría ser el código de esta clase. Según avancemos en el programa podría cambiar.

Código: [Seleccionar]
public class Player {

private String nombre;
private String password;
private int puntos;
private ArrayList<String> historico;

public Player(String nombre, String password) {
this.nombre = nombre;
this.password = password;
puntos = 0;
historico = new ArrayList<String>();
}

public String getNombre() {
return nombre;
}

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

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public int getPuntos() {
return puntos;
}

/**
* Aumenta los puntos del player.<br>
* La puntuacion aumenta en 3 cuando se gana una partida.
*/
public void aumentarPuntos() {
puntos += 3;
}

/**
* Añade una nueva anotación al histórico del Player.<br>
* Existe un límite de 10 anotaciones, por lo que al alcanzar
* este límite se eliminarán las anotaciones más antiguas para
* dar cabida a las más recientes.
* @param anotacion String con la anotación que queremos anotar.
*/
public void anotarHistorico(String anotacion) {
if (historico.size() == 10) { //Alcanzado límite de 10 anotaciones
/*
* En este caso se han de desplazar todas las anotaciones
* una posición, eliminando así la más antigua (posición 0).
* La nueva anotación queda como la más reciente (posición 9).
*/
for (int i = 1; i < 9; i++)
historico.set((i-1), historico.get(i));

historico.set(9, anotacion);
}
else //Aun no tenemos 10 anotaciones, añadimos sin más.
historico.add(anotacion);
}

/**
* Muestra el histórico de resultados de las partidas
* jugadas por el Player.
* @return String con todas las anotaciones del histórico.
*/
public String mostrarHistorico() {
StringBuilder sb = new StringBuilder("\t\tHISTÓRICO");
sb.append("\t\t---------\n");
for (String anotacion: historico)
sb.append(anotacion + "\n");

return sb.toString();
}


@Override
public boolean equals(Object objeto) {
if (objeto instanceof Player) { //Es un objeto Player
Player otroPlayer = (Player)objeto;
//Dos Player son iguales si tienen mismo nombre
return nombre.equals(otroPlayer.nombre);
}
else
return false; //Ni siquiera es un objeto Player
}

}
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

Kabuto

  • Moderador Global
  • Avanzado
  • *******
  • Mensajes: 364
    • Ver Perfil
Re:juego de batalla naval en netbeans con swing
« Respuesta #2 : 20 de Junio 2020, 21:21 »
Vamos a pensar un poco en la clase BattleShip.
Será sin duda la más importante porque albergará casi toda la lógica del programa, será el "Modelo".

Tendrá como atributo un ArrayList de tipo Player, donde se irán guardando los jugadores que se den de alta en el sistema.
Por lo tanto tendrá métodos para añadir nuevo Player, eliminarlo, comprobar el password para el login....

EL login, para dar comienzo a todo se ha de loguear un jugador. Y luego para jugar se puede loguear un segundo Player.
Hay que controlar que jugador/es están logueados y para ellos podemos usar dos atributos de tipo Player.
Cuando un jugador u otro se loguea, los pasamos a estos atributos para tener constancia de que estan logueados.
Cuando hagan logout, les pasamos valor null para saber que no hay nadie logueado.

Nos puede interesar otro atributo para indicar que nivel de dificultad se ha activado, hay cuatro niveles, podemos representarlos con un simple int que vaya de 1 a 4:
1-EASY, 2-NORMAL, 3-EXPERT, 4-GENIUS

Lo mismo con el modo de juego, otro int para los dos posibles modos:
1-TUTORIAL, 2-ARCADE


Otro objeto importante que ha de tener esta clase es el Tablero, quien merece ser modelado con una clase propia.
De hecho, BattleShip tendrá dos Tableros, uno para cada Player.


Vamos a dejar aparcado de momento a BattleShip y vamos a pensar en Tablero.

Un objeto Tablero tendrá indudablemente una matriz de 8x8.
¿De qué tipo?
Pues de momento me parece que puede ser de tipo int y usar números para representar el posible valor de cada casilla del tablero, como:
0 -> Casilla sin desvelar
1 -> Barco
2 -> Barco Tocado
3 -> Agua/Disparo fallido


Insisto en que todo esto puede variar según vaya avanzando en el programa.
Y que tú, si lo crees necesario, lo adaptes como mejor te parezca.
Todo esto es como se me está ocurriendo a mi hacerlo sobre la marcha..., sin saber si llegará "a buen puerto este barco"  ;D

Creo, que puede facilitarnos las cosas que el Tablero NO se limite a tener una matriz de enteros y ya.
Quizás nos interese modelar una clase Barco, para que cada objeto Barco guarde las coordenadas (posiciones que ocupa en la matriz) por sí mismo.
Y por sí mismo, nos diga si está sano, si está tocado, si está hundido, cuantas casillas/coordenadas mide (ya que hay barcos de distinto tamaño).

Así por ejemplo, la clase Tablero puede tener un ArrayList de objetos Barco.
Y si le pasamos las coordenadas donde se ha disparado una bomba, el Tablero puede recorrer sus Barcos y que cada Barco le diga si las coordenadas de ese disparo le afecta o no.

Otra cosa, las coordenadas están representados por dos valores int. Un Barco que mida 5 casillas, significa que va a tener 5 pares de int representando esas coordenadas.
Puede que nos interese modelar otra clase para representar cada coordenada, esto nos facilitaría más compararlas para saber si dos coordenadas son iguales.
Si son iguales podemos saber cuando ha habido un impacto o cuando un Barco se está intentando colocar encima de otro.

En Java existe una clase llamada Point de la librería AWT con dos atributos X e Y para representar un "punto" en la pantalla gráfica.
Se podría usar esa clase, pero no me parece correcto coger una clase destinada para tareas con la GUI (interfaz gráfica) y usarla en otra cosa distinta.
Además, es muy sencilla, podemos modelar la nuestra propia y adaptarla a lo que necesitemos.

Podemos llamarla Punto, o PuntoXY, como prefieras.
Dos atributos serán dos int para X e Y, que vienen a indicar en que fila y columna (coordeandas) de la matriz representa este punto.

Como los Barcos van a estar "compuestos" de estos PuntosXY, nos puede interesar un tercer atributo de tipo boolean para indicar si este punto está "tocado" por un disparo o no.
Así un Barco podrá "recordar" en cuáles de sus coordenadas ha recibido disparos o no.

También le podemos poner un método equals(), así fácilmente sabremos cuando dos coordenadas son iguales.

Los atributos X e Y, debido a su sencillez, los podemos dejar como public, y así nos ahorramos los getter y setters. Pero vamos, tú hazlo como prefieras.

Esta podría ser la clase PuntoXY

Código: [Seleccionar]
public class PuntoXY {

public int x;
public int y;
private boolean tocado;

public PuntoXY(int x, int y) {
this.x = x;
this.y = y;
tocado = false;
}

public boolean esTocado() {
return tocado;
}

public void setTocado(boolean tocado) {
this.tocado = tocado;
}

@Override
public boolean equals(Object objeto) {
if (objeto instanceof PuntoXY) {
PuntoXY otroPunto = (PuntoXY)objeto;
//Son iguales si coincide la X y la Y
return (x == otroPunto.x && y == otroPunto.y);
}
else
return false;
}

}
« última modificación: 24 de Junio 2020, 18:01 de Kabuto »
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

Kabuto

  • Moderador Global
  • Avanzado
  • *******
  • Mensajes: 364
    • Ver Perfil
Re:juego de batalla naval en netbeans con swing
« Respuesta #3 : 20 de Junio 2020, 21:32 »
La clase Barco entonces, tendría un atributo String para guardar su código como propone el enunciado.
Y tendría un ArrayList de objetos PuntoXY para guardar las coordenadas que le componen.

Se me ocurren por ahora un par de métodos, uno que reciba las coordenadas de un disparo y decida si "toca" o no al Barco.
Y otro para indicar si el Barco está hundido o no.

Código: [Seleccionar]
public class Barco {

private String codigo;
/*
* Portaaviones 5 PA
* Acorazado 4 AZ
* Submarino 3 SM
* Destructor 2 DT
*/
public ArrayList<PuntoXY> coordenadas;

public Barco(String codigo) {
this.codigo = codigo;
coordenadas = new ArrayList<PuntoXY>();
}

public String getCodigo() {
return codigo;
}

public void addCoordenada(PuntoXY coord) {
coordenadas.add(coord);
}

/**
* Evalúa si un disparo ha tocado a este Barco.
* @param disparo Objeto PuntoXY con las coordenadas del disparo
* @return <i>True</i> si ha sido tocado, <i>False</i> en caso contrario.
*/
public boolean evaluaDisparo(PuntoXY disparo) {
for (PuntoXY coordenada: coordenadas)
if (coordenada.equals(disparo)) {
coordenada.setTocado(true);
return true;
}
//Ninguna coordenada ha coincidido con el disparo
return false;
}

/**
* Indica si este Barco ha sido hundido.<br>
* Un Barco está hundido cuando todos sus PuntosXY
* han sido "tocados".
* @return <i>True</i> si está hundido, <i>False</i> en caso contrario.
*/
public boolean esHundido() {
for (PuntoXY coordenada: coordenadas)
if (!coordenada.esTocado()) //Al menos una coordenada no ha sido tocada
return false; //Barco no está hundido :-)
//Si durante el bucle for no se ha devuelto false, es que todas las coord. han sido tocadas
return true; //Barco hundido :-(
}

}


Con esto, con Barcos compuestos de PuntosXY, ya podemos comenzar una clase Tablero, que seguramente su código irá creciendo según avance el programa y nos surjan necesidades:

Código: [Seleccionar]
public class Tablero {

/*
* El tablero será una matriz 8x8 de tipo int.
* 0 -> Casilla sin desvelar
* 1 -> Barco
* 2 -> Barco Tocado
* 3 -> Agua/Disparo fallido
*/
private int[][] tablero;
private ArrayList<Barco> barcos;

public Tablero() {
tablero = new int[8][8];
barcos = new ArrayList<Barco>();
}

/**
* Agrega un Barco a la lista de Barcos.<br>
* Además actualiza los valores del tablero para
* representar la posición del Barco.
* @param barco Objeto Barco que añadimos al tablero.
*/
public void addBarco(Barco barco) {
barcos.add(barco);
//Modificamos tablero según coordenadas nuevo Barco
for (PuntoXY coord: barco.coordenadas)
tablero[coord.x][coord.y] = 1;
}

/**
* Recibe un disparo (un objeto PuntoXY) y comprueba
* si coincide con las coordenadas de algún barco de este
* tablero.<br>Si coincide, es que un Barco ha sido tocado.
* De lo contrario, el disparo ha fallado.<br>
* Los valores del tablero se actualizan según lo que haya ocurrido.
* @param disparo Objeto PuntoXY con las coordenadas donde se ha disparado.
* @return <i>True</i> si se ha tocado un Barco, <i>False</i> en caso contrario.
*/
public boolean evaluarDisparo(PuntoXY disparo) {
//De entrada consideramos el disparo como fallido
tablero[disparo.x][disparo.y] = 3;
/*
* Ahora recorremos los barcos y comprobamos si
* alguno tiene coordenada coincidente con
* el disparo.
* Si coincide, corregimos el valor del tablero
* para la coordenada del disparo
*/

for (Barco barco: barcos) {
if (barco.evaluaDisparo(disparo)) {
tablero[disparo.x][disparo.y] = 2; //El disparo ha tocado barco
return true;
}
}
//Si el bucle no ha retornado true, el que el disparo se ha confirmado como fallido
return false;
}

}

Ya tenemos Players y Tablero con Barcos.

También podemos comenzar la clase BattleShip, que también irá aumentando según avance el programa.

Código: [Seleccionar]
public class BattleShip {

private ArrayList<Player> jugadores;
private Player jugador1;
private Player jugador2;
private int dificultad;//1-EASY, 2-NORMAL, 3-EXPERT, 4-GENIUS
private int modo; //1-TUTORIAL, 2-ARCADE
private Tablero tablero1;
private Tablero tablero2;

public BattleShip() {
jugadores = new ArrayList<Player>();
jugador1 = null;
jugador2 = null;
dificultad = 2; //Dificultad por defecto
modo = 1; //Modo de juego por defecto
tablero1 = new Tablero();
tablero2 = new Tablero();
}


/**
* Recibe un Player para añadir al listado.<br>
* El Player será rechazado si ya existe otro con el mismo nombre.
* @param jugador Objeto Player que queremos registrar.
* @return <i>True</i> si se admitó el registro,
* <i>False</i> si ha sido rechazado.
*/
public boolean addPlayer(Player jugador) {
if (jugadores.contains(jugador)) {
JOptionPane.showMessageDialog(null, "Este Jugador ya está registrado", "Nuevo Jugador",
JOptionPane.WARNING_MESSAGE);
return false; //Ya existe este Player, lo rechazamos
}
else
return jugadores.add(jugador);
}



}

De momento solo tiene un método para añadir jugadores.

Lo siguiente sería darle métodos como por ejemplo comprobar si un login es aceptado o no.

Pero no tengo más tiempo por ahora, a ver cuando puedo retomar.
Comprueba el código que he escrito, cambia lo que quieras y pregunta lo que no te quede claro.
« última modificación: 24 de Junio 2020, 18:01 de Kabuto »
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

Kabuto

  • Moderador Global
  • Avanzado
  • *******
  • Mensajes: 364
    • Ver Perfil
Re:juego de batalla naval en netbeans con swing
« Respuesta #4 : 21 de Junio 2020, 01:01 »
A ver, que he rascado algo más de tiempo.
Podemos comenzar a meterle mano ya a la GUI, así vamos concretando algunas ideas de la "Vista" para saber mejor que necesitamos del "Controlador" y del "Modelo" (la clase BattleShip)

La primera pantalla que ha de mostrar el programa es un formulario de login.
O sea, dos campos para nombre y password y por ejemplo tres botones:
Login, Nuevo Usuario y Cerrar Programa.

Podemos crear una clase que herede de JPanel y modelar este formulario.
Lo haremos sencillo, sin demasiadas florituras. Solo conseguir que los elementos queden más o menos alineados.

Le daremos dos métodos básicos en todo formulario. Uno para limpiar los campos y otro para retornar los valores introducidos por el usuario.
Este último comprobará que los campos tienen datos.
Si los tiene, los retornará juntos en un array de dos String: nombre y password.
Si falta alguno, mostrará un aviso al usuario y retornará valor null.

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

private JTextField campoNombre;
private JTextField campoPass;
public JButton botonLogin;
public JButton botonNuevo;
private JButton botonSalir;

public PanelLogin() {
campoNombre = new JTextField(10);
campoPass = new JTextField(10);
botonLogin = new JButton("Login");
botonNuevo = new JButton("Nuevo Usuario");
botonSalir = new JButton("Cerrar Programa");
botonSalir.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0); //Esto cierra el programa
}
});

setLayout(new BorderLayout());
add(new PanelCentro(), BorderLayout.CENTER);
add(new PanelSur(), BorderLayout.SOUTH);
setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
}
//Paneles que componen el panel principal
private class PanelCentro extends JPanel {

public PanelCentro() {
JPanel datos = new JPanel();
datos.setLayout(new GridLayout(2,2,5,5));

JPanel nombreLabel = new JPanel();
nombreLabel.setLayout(new FlowLayout(FlowLayout.RIGHT));
nombreLabel.add(new JLabel("Nombre: "));
JPanel nombreCampo = new JPanel();
nombreCampo.setLayout(new FlowLayout(FlowLayout.LEFT));
nombreCampo.add(campoNombre);

JPanel passLabel = new JPanel();
passLabel.setLayout(new FlowLayout(FlowLayout.RIGHT));
passLabel.add(new JLabel("Password: "));
JPanel passCampo = new JPanel();
passCampo.setLayout(new FlowLayout(FlowLayout.LEFT));
passCampo.add(campoPass);

datos.add(nombreLabel);
datos.add(nombreCampo);
datos.add(passLabel);
datos.add(passCampo);
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
add(datos);
JPanel boton = new JPanel();
boton.add(botonLogin);
add(boton);

setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10),
BorderFactory.createTitledBorder("Datos Login")));
}
}

private class PanelSur extends JPanel {
public PanelSur() {
JPanel nuevo = new JPanel();
nuevo.add(botonNuevo);
JPanel salir = new JPanel();
salir.add(botonSalir);

setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
add(nuevo); add(salir);
}
}

//Métodos de la clase
public void resetFormulario() {
campoNombre.setText(null);
campoPass.setText(null);
}

/**
* Recupera los datos del formulario y los retorna juntos en un array
* de dos elementos: nombre y password.
* @return Array String[] con los dos datos del formulario.
*/
public String[] getDatosLogin() {
String nombre = campoNombre.getText();
String password = campoPass.getText();

if (nombre.isEmpty()) {
JOptionPane.showMessageDialog(null, "El campo Nombre no puede estar vacío",
"Login Player", JOptionPane.ERROR_MESSAGE);
return null;
}

if (password.isEmpty()) {
JOptionPane.showMessageDialog(null, "El campo Password no puede estar vacío",
"Login Player", JOptionPane.ERROR_MESSAGE);
return null;
}
//Tenemos datos, los devolvemos en un array
return new String[] {nombre, password};
}
}

Este, y otros paneles que iremos haciendo, los mostraremos en un JFrame.
Este JFrame lo crearemos en la clase main, la principal. Haremos que herede de JFrame y de atributos tendrá la clase BattleShip, el "Modelo" y los distintos paneles que iremos creando, la "Vista".
Por lo tanto, esta clase equivaldrá al "Controlador", es decir, será quien ponga en comunicación la "Vista" con el "Modelo".
Estas tareas de Controlador las realizará mediante distintas clases internas de ActionListener que se asignarán a los botones de cada panel.

Por ejemplo, ya podemos incluir en esta clase main el ActionListener correspondiente al botón de validar los datos del login.
Recogerá los datos que retorna la clase PanelLogin y se los pasará a la clase BattleShip para que busque un Player que coincida con esos datos.
De momento, solo mostrará avisos indicando si el login es válido o no.

Esta sería la clase principal Main, donde ponemos como atributo un objeto de PanelLogin, lo inicializamos en el constructor, le agregamos al boton Login el ActionListener escrito al final del código.
Establecemos como contenedor del JFrame el objeto PanelLogin para que sea visible en pantalla.
Código: [Seleccionar]
public class Main extends JFrame{

private BattleShip batalla;
private PanelLogin panelLogin;

public Main() {
batalla = new BattleShip();
panelLogin = new PanelLogin();
panelLogin.botonLogin.addActionListener(new AccionLogin());

setContentPane(panelLogin);

setTitle("BattleShip");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setResizable(false);
setVisible(true);
}

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

//Clases ActionListener para PanelLogin
private class AccionLogin implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {

String[] datos = panelLogin.getDatosLogin();
if (datos != null) {
if (batalla.validarLogin(datos))
JOptionPane.showMessageDialog(null, "Login Aceptado");
else
JOptionPane.showMessageDialog(null, "Login Rechazado");
}

}
}
}

En esa clase ActionListener, pasamos los datos de login a un método boolean de la clase BattleShip, que aún no está escrito.
Pues vamos a escribírselo:

Código: [Seleccionar]
/**
* Recibe nombre y password para comprobar si existe un Player registrado
* con estos datos.<br>Si existe, este Player queda logueado.
* @param datos Array String[] con nombre y password.
* @return <i>True</i> si los datos son válidos, <i>False</i> en caso contrario.
*/
public boolean validarLogin(String[] datos) {
//Recorremos jugadores y buscamos mismo nombre, y mismo password
for (Player player: jugadores)
if (player.getNombre().equals(datos[0]) && player.getPassword().equals(datos[1])) {
//Login valido
jugador1 = player;
return true;
}
//Finalizado bucle for sin retornar true, es que el login no es válido
return false;
}

Es sencillo, recibe el array con los dos String, nombre y password.
Recorre los Players registrados y si encuentra coincidencia, marca a ese Player como logueado y retorna true.
De lo contrario, retorna falso.

Con todo esto, podemos probar a ver como va la interfaz.


Si probamos a loguearnos:


Login rechazado.
Claro, nuestra lista de Players esta vacía. Aún no hemos registrado Jugadores.
Tenemos que dotar de funcionalidad para poder hacer registros mediante el botón "Nuevo Usuario".
¿Que hacemos cuando el usuario pulse este botón?

Mostrar un nuevo formulario para pedirle nombre y password es redundante, podemos usar el mismo PanelLogin. Así, con los mismos datos, el usuario podrá loguearse con el botón "Login" o bien registrarse con el botón "Nuevo Usuario".

Para ello, volvemos a la clase Main y creamos un nuevo ActionListener para esta funcionalidad.
Haremos que BattleShip reciba un nuevo Player con los datos obtenidos del PanelLogin y si todo va bien, quedará registrado.
A Main le añadimos esta clase interna:

Código: [Seleccionar]
private class AccionNuevoUsuario implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String[] datos = panelLogin.getDatosLogin();
if (datos != null) {
//Con los datos obtenidos, creamos un nuevo Player
if (batalla.addPlayer(new Player(datos[0], datos[1])))
JOptionPane.showMessageDialog(null, "Registro aceptado",
"Nuevo Usuario", JOptionPane.INFORMATION_MESSAGE);
}

}
}
Al botón correspondiente, le añadimos esta nueva acción:
Código: [Seleccionar]
public Main() {
batalla = new BattleShip();
panelLogin = new PanelLogin();
panelLogin.botonLogin.addActionListener(new AccionLogin());
panelLogin.botonNuevo.addActionListener(new AccionNuevoUsuario());

Por último, el método de la clase BattleShip que recibe nuevos jugadores, lo vamos a retocar para que cuando el registro sea aceptado, este nuevo usuario quede ya logueado.
Código: [Seleccionar]
/**
* Recibe un Player para añadir al listado. El nuevo Player quedará logueado<br>
* El Player será rechazado si ya existe otro con el mismo nombre.
* @param jugador Objeto Player que queremos registrar.
* @return <i>True</i> si se admitó el registro,
* <i>False</i> si ha sido rechazado.
*/
public boolean addPlayer(Player jugador) {
if (jugadores.contains(jugador)) {
JOptionPane.showMessageDialog(null, "Este Jugador ya está registrado", "Nuevo Jugador",
JOptionPane.WARNING_MESSAGE);
return false; //Ya existe este Player, lo rechazamos
}
else {
jugadores.add(jugador);
jugador1 = jugador; //El nuevo usuario queda logueado
return true;
}
}

Y ahora ya podemos registrar nuevos jugadores.
« última modificación: 21 de Junio 2020, 11:57 de Kabuto »
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

Kabuto

  • Moderador Global
  • Avanzado
  • *******
  • Mensajes: 364
    • Ver Perfil
Re:juego de batalla naval en netbeans con swing
« Respuesta #5 : 21 de Junio 2020, 01:30 »
Aún tengo tiempo de añadir un detalle.
Podemos registrar jugadores, sí, pero se borran cada vez que cerramos el programa.
Aunque no lo pide el enunciado, lo ideal sería guardar en disco los Players registrados para poder recuperarlos cada vez que se inicia el programa.
Así no hay que crear nuevos login cada vez, y el histórico de partidas se guarda realmente.

¿Cómo hacemos esto?
Podemos serializar el ArrayList de Players y guardarlo en disco cada vez que se haga un cambio.
Luego, al arrancar el programa, se recuperarán los datos guardados.

Para ello, lo primer es añadirle a nuestra clase Player la interface Serializable

Código: [Seleccionar]
public class Player implements Serializable{

private String nombre;
private String password;
private int puntos;
private ArrayList<String> historico;

Si tuviera algún atributo "raro", habría que hacerlo serializables también.
Pero esos atributos ya son serializables tal cuál.

Bien, ahora vamos a la clase BattleShip y añadiremos dos métodos.
Este para guardar en disco los datos del ArrayList:
Código: [Seleccionar]
/**
* Guarda en disco los Players registrados.
*/
private void guardarPlayers() {
File fichero = new File("datos/players.dat");

try {
if (!fichero.exists())
fichero.createNewFile();
ObjectOutputStream obs = new ObjectOutputStream(new FileOutputStream(fichero));
obs.writeObject(jugadores); //Grabamos objeto ArrayList<Player> en disco
obs.close();
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "No se puede acceder a " + fichero.getAbsolutePath(),
"Guardar Players", JOptionPane.WARNING_MESSAGE);
}
}

Este método lo llamaremos en determinadas situaciones, por ejemplo, cada vez que añadimos un nuevo Player
Código: [Seleccionar]
public boolean addPlayer(Player jugador) {
if (jugadores.contains(jugador)) {
JOptionPane.showMessageDialog(null, "Este Jugador ya está registrado", "Nuevo Jugador",
JOptionPane.WARNING_MESSAGE);
return false; //Ya existe este Player, lo rechazamos
}
else {
jugadores.add(jugador);
jugador1 = jugador; //El nuevo usuario queda logueado
guardarPlayers(); //Guardamos en disco
return true;
}
}

Luego, el otro método para cargar los datos guardados en disco:
Código: [Seleccionar]
/**
* Recupera los datos guardados en disco de Players registrados,
* si los hubiera.
*/
private void cargarPlayers() {
jugadores = new ArrayList<Player>(); //Por si acaso no hay datos
//Ahora, buscamos datos guardados
File fichero = new File("datos/players.dat");
if (fichero.exists()) { //Hay un fichero en disco
ObjectInputStream ois;
try {
ois = new ObjectInputStream(new FileInputStream(fichero));
Object aux = ois.readObject();
jugadores = (ArrayList<Player>)aux;
ois.close();
} catch (FileNotFoundException e) {
JOptionPane.showMessageDialog(null, "No se encuentra " + fichero.getAbsolutePath(),
"Cargar Players", JOptionPane.WARNING_MESSAGE);
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "No se puede acceder a " + fichero.getAbsolutePath(),
"Cargar Players", JOptionPane.WARNING_MESSAGE);
} catch (ClassNotFoundException e) {
JOptionPane.showMessageDialog(null, "Los datos recuperados no son válidos",
"Cargar Players", JOptionPane.WARNING_MESSAGE);
}
}
}

Este método, lo llamaremos en el constructor de la clase BattleShip, para que sea lo primero que se ejecute:
Código: [Seleccionar]
public BattleShip() {
cargarPlayers();

jugador1 = null;
jugador2 = null;
dificultad = 2; //Dificultad por defecto
modo = 1; //Modo de juego por defecto
tablero1 = new Tablero();
tablero2 = new Tablero();
}

Si todo va bien, ahora los jugadores registrados se guardan en disco y cada vez que iniciemos el programa, podrán loguearse directamente.

Lo siguiente, sería que una vez logueado el usuario, mostrarle el menú de opciones.
Eso tendrá que ser en otro momento.
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

Kabuto

  • Moderador Global
  • Avanzado
  • *******
  • Mensajes: 364
    • Ver Perfil
Re:juego de batalla naval en netbeans con swing
« Respuesta #6 : 22 de Junio 2020, 00:08 »
Menú de opciones.

Son 4 opciones: Jugar, Configuración, Reportes y Perfil.

Podemos mostrar estos 4 botones en otro panel que modelaremos en una clase aparte.
Además un quinto botón para "Cerrar Sesión", es decir, el usuario hace un logout que nos llevaría de nuevo a la pantalla inicial de login/registro.

Podemos crear una clase llamada PanelMenu con estos 5 botones:
Código: [Seleccionar]
public class PanelMenu extends JPanel{

public JButton botonJugar;
public JButton botonConfig;
public JButton botonReporte;
public JButton botonPerfil;
public JButton botonSalir;

public PanelMenu() {
botonJugar = new JButton("Jugar BattleShip");
botonConfig = new JButton("Configuración");
botonReporte = new JButton("Reportes");
botonPerfil = new JButton("Mi Perfil");
botonSalir = new JButton("Cerrar Sesión");

setLayout(new BorderLayout());
add(new PanelCentro(), BorderLayout.CENTER);
JPanel sur = new JPanel();
sur.add(botonSalir);
add(sur, BorderLayout.SOUTH);
}

private class PanelCentro extends JPanel {
public PanelCentro() {
setLayout(new GridLayout(2, 2, 5, 5));
add(botonJugar); add(botonConfig);
add(botonReporte); add(botonPerfil);
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(20, 20, 20, 20),
BorderFactory.createCompoundBorder(
BorderFactory.createBevelBorder(BevelBorder.RAISED),
BorderFactory.createEmptyBorder(10, 10, 10, 10))));
}
}

}

En la clase Main añadiremos un objeto de esta clase como atributo y modificaremos el ActionListener para el botón "Login" de manera que si nos logamos con éxito, muestre el nuevo panel de opciones.

Aquí ahora hay que pensar como queremos organizar el JFrame para que vaya mostrando un panel u otro según las acciones del usuario.
Podemos organizarlo usando un CardLayout. Este layout puede manejar varios paneles de forma similar a un "mazo de cartas" (de ahí su nombre) de manera que los paneles están uno sobre otro y solo es visible el "que está encima".
Cada panel que agregamos a este layout, le damos un nombre identificativo. Así podemos dar orden de que panel queremos que sea visible en cada momento.

Como estas ordenes las daremos desde distintas clases ActionListener, y para dar estas ordenes necesitamos acceso al contenedor al que aplicamos el CardLayout, en lugar de aplicárselo directamente al JFrame, se lo aplicaremos a otro JPanel que declararemos como atributo global, para tener acceso a él desde cualquier ámbito.

Lo llamaremos panelPrincipal, le aplicaremos un CardLayout, y le agregaremos los distintos paneles que vayamos modelando para la aplicación, cada uno con un nombre identificativo, para poder dar orden de cuál queremos hacer visible en cada momento.

Así que a la clase Main le añadimos nuevos atributos:
Código: [Seleccionar]
public class Main extends JFrame{

private BattleShip batalla;
private CardLayout paneles; //Albergará varios paneles como si fuera un "mazo" de cartas
private JPanel panelPrincipal; //Este panel recibirá el CardLayout que gestiona el resto de paneles
private PanelLogin panelLogin;
private PanelMenu panelMenu;

En el constructor, inicializamos el nuevo atributo PanelMenu y además configuramos el CardLayout y el panelPrincipal que gestionará el resto de paneles.
Este panelPrincipal, una vez configurado, se lo añadimos al JFrame.
Código: [Seleccionar]
public Main() {
batalla = new BattleShip();

panelLogin = new PanelLogin();
panelLogin.botonLogin.addActionListener(new AccionLogin());
panelLogin.botonNuevo.addActionListener(new AccionNuevoUsuario());
panelMenu = new PanelMenu();

//Configuramos el "mazo" de paneles
paneles = new CardLayout();
panelPrincipal = new JPanel();
panelPrincipal.setLayout(paneles);
panelPrincipal.add(panelLogin, "login");
panelPrincipal.add(panelMenu, "menu");
add(panelPrincipal);

setTitle("BattleShip");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setResizable(true);
setVisible(true);
}

Ahora modificamos la clase AccionLogin para que cuando el usuario se haya logueado, demos orden al contenedor del CardLayout para que muestre el nuevo PanelMenu.
Esto lo hacemos con el método show()
Código: [Seleccionar]
private class AccionLogin implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {

String[] datos = panelLogin.getDatosLogin();
if (datos != null) {
if (batalla.validarLogin(datos)) {
paneles.show(panelPrincipal, "menu");
}
else
JOptionPane.showMessageDialog(null, "Login Rechazado");
}
}
}
Y listo, cuando nos hemos logueado, tenemos a la vista el menú de opciones:


Lo siguiente es dar funcionalidad a esos botones escribiendo nuevas clases ActionListener para cada uno de ellos.
La acción de Jugar va a ser la más complicada, así que yo lo dejaría para lo último.

Comencemos por lo más fácil, que es la funcionalidad "Cerrar Sesión", es decir, el usuario hace logout y volvemos al panel inicial.
Podemos pedir confirmación mediante un JOptionPane.

Este sería el ActionListener para "Cerrar Sesion":
Código: [Seleccionar]
private class AccionLogout implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
int respuesta = JOptionPane.showConfirmDialog(null, "¿Seguro que desea Cerrar Sesión?",
"Logout", JOptionPane.YES_NO_OPTION);
if (respuesta == JOptionPane.YES_OPTION) {
batalla.logoutPlayer1();
paneles.show(panelPrincipal, "login");
panelLogin.resetFormulario();
}

}
}

Ahí llamamos a un método llamado logoutPlayer1() de la clase BattleShip, que aún no hemos escrito. Luego lo vemos, antes hay que agregar este ActionListener al botón correspondiente desde el constructor de la clase Main

Código: [Seleccionar]
public Main() {
batalla = new BattleShip();

panelLogin = new PanelLogin();
panelLogin.botonLogin.addActionListener(new AccionLogin());
panelLogin.botonNuevo.addActionListener(new AccionNuevoUsuario());
panelMenu = new PanelMenu();
panelMenu.botonSalir.addActionListener(new AccionLogout());

Y ahora sí nos vamos a la clase BattleShip para escribir el método que "desloguea" al usuario Jugador1.
Es muy sencillo, tan solo hay que establecer a null el atributo correspondiente, para indicar que ya no hay usuario logueado.
Código: [Seleccionar]
/**
* Hace que el Player que actualmente consta
* logueado como jugador1, deje de estarlo.
*/
public void logoutPlayer1() {
jugador1 = null;
}

Y listo, ahora ya podemos loguearnos, acceder al menu de opciones y cerrar sesión volviendo de nuevo a la pantalla de login.
Luego veremos como añadir funcionalidad al resto de botones.
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

Kabuto

  • Moderador Global
  • Avanzado
  • *******
  • Mensajes: 364
    • Ver Perfil
Re:juego de batalla naval en netbeans con swing
« Respuesta #7 : 22 de Junio 2020, 01:22 »
Sigamos por lo fácil.
La opción de  visualizar nuestro perfil.
Se ha de mostrar nombre y password.
Se ha de dar la opción de modificar los datos y de eliminar esa cuenta de jugador.
Y también la posibilidad de volver al menú de opciones.

Así que necesitaremos 2 campos para los datos, y 3 botones.
De nuevo crearemos una nueva clase JPanel llamada PanelPerfil.
Como tenemos que trabajar con los datos del usuario logueado, esta clase tendrá un método para recibir una referencia a este Player, referencia que pediremos a la clase Battleship

Esta podría ser de momento la clase PanelPerfil, luego seguramente añadiremos más métodos:
Código: [Seleccionar]
public class PanelPerfil extends JPanel{

private JTextField campoNombre;
private JTextField campoPass;
public JButton botonModificar;
public JButton botonEliminar;
public JButton botonVolver;
private Player jugador;

public PanelPerfil() {

setLayout(new BorderLayout());
add(new PanelCentro(), BorderLayout.CENTER);
JPanel sur = new JPanel();
botonVolver = new JButton("Volver");
sur.add(botonVolver);
add(sur, BorderLayout.SOUTH);
setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
}

private class PanelCentro extends JPanel {

public PanelCentro() {
campoNombre = new JTextField(15);
campoPass = new JTextField(15);
botonModificar = new JButton("Modificar");
botonEliminar = new JButton("Eliminar");
JPanel nombre = new JPanel();
nombre.add(campoNombre);
nombre.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder("Nombre usuario"),
BorderFactory.createEmptyBorder(10, 10, 10, 10)));
JPanel pass = new JPanel();
pass.add(campoPass);
pass.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder("Password"),
BorderFactory.createEmptyBorder(10, 10, 10, 10)));
JPanel botones = new JPanel();
botones.setLayout(new FlowLayout(FlowLayout.CENTER, 50, 10));
botones.add(botonModificar);
botones.add(botonEliminar);

setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
add(nombre); add(pass); add(botones);
}
}

/**
* Recibe el Player logueado en la clase BatteShip,
* lo setea como atributo de esta clase para poder trabajar
* con él y actualiza los campos con los datos de este jugador.
* @param jugador Objeto Player logueado como jugador1 en clase BattleShip
*/
public void setPlayer(Player jugador) {
this.jugador = jugador;
campoNombre.setText(jugador.getNombre());
campoPass.setText(jugador.getPassword());
}

}

Para poder verla en pantalla antes de seguir trabajando con ella, la añadimos como atributo en la clase Main y desde el constructor, la añadimos al CardLayout tal y como hicimos con las otras.
Además le añadimos ya a su boton "Volver" una accion que nos permite regresar de nuevo al panel de menú.

Código: [Seleccionar]
public class Main extends JFrame{

private BattleShip batalla;
private CardLayout paneles; //Albergará varios paneles como si fuera un "mazo" de cartas
private JPanel panelPrincipal; //Este panel recibirá el CardLayout que gestiona el resto de paneles
private PanelLogin panelLogin;
private PanelMenu panelMenu;
private PanelPerfil panelPerfil;

public Main() {
batalla = new BattleShip();
//PanelLogin
panelLogin = new PanelLogin();
panelLogin.botonLogin.addActionListener(new AccionLogin());
panelLogin.botonNuevo.addActionListener(new AccionNuevoUsuario());
//PanelMenu
panelMenu = new PanelMenu();
panelMenu.botonSalir.addActionListener(new AccionLogout());
panelMenu.botonPerfil.addActionListener(new AccionPerfil());
//PanelPerfil
panelPerfil = new PanelPerfil();
panelPerfil.botonVolver.addActionListener(new AccionSalirPerfil());

//Configuramos el "mazo" de paneles
paneles = new CardLayout();
panelPrincipal = new JPanel();
panelPrincipal.setLayout(paneles);
panelPrincipal.add(panelLogin, "login");
panelPrincipal.add(panelMenu, "menu");
panelPrincipal.add(panelPerfil, "perfil");
add(panelPrincipal);

Esta sería la acción que escribimos dentro del Main, para añadirsela a ese botón.
Código: [Seleccionar]
//Clases ActionListener para PanelPerfil
private class AccionSalirPerfil implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
paneles.show(panelPrincipal, "menu");
}
}

Listo, ahora si nos logueamos y en el menu pulsamos el boton de Perfil, podemos ver nuestros datos. Y con el botón "Volver" regresamos al menú.


Sigamos con el perfil, vamos a darle la funcionalidad de modificar el perfil.
Cuando se pulse este botón, se cogerá lo que haya escrito en los campos, se le setearán al Player que consta como jugador1, se guardarán los cambios en disco y se volverá al menú.

Para ello, lo primero es añadir este método en la clase PanelPerfil
Código: [Seleccionar]
/**
* Modifica el perfil del Player logueado con los datos
* del fomulario.
* @return <i>True</i> si se hizo la modificación, <i>False</i> en caso contrario.
*/
public boolean modificarPerfil() {
String nombre = campoNombre.getText();
String pass = campoPass.getText();
if (nombre.isEmpty()) {
JOptionPane.showMessageDialog(null, "El campo Nombre no puede estar vacío",
"Modificar Perfil", JOptionPane.ERROR_MESSAGE);
return false;
}
if (pass.isEmpty()) {
JOptionPane.showMessageDialog(null, "El campo Password no puede estar vacío",
"Modificar Perfil", JOptionPane.ERROR_MESSAGE);
return false;
}
//Sí, tenemos todos los datos
jugador.setNombre(nombre);
jugador.setPassword(pass);
return true;
}

Esto modifica el Player y retorna true para confirmarlo o false si no se ha cambaido nada.
Wste método lo llamaremos desde un nuevo ActionListener que vamos a escribir en el Main

Código: [Seleccionar]
private class AccionModificarPerfil implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (panelPerfil.modificarPerfil()) { //Si se han modificado los datos...
batalla.guardarPlayers(); //Guardamos los cambios
paneles.show(panelPrincipal, "menu"); //Regresamos a menu
}
}
}
Si el método devuelve true, pedimos a la clase BattleShip que guarde cambios en disco y volvemos a menú.
El método para guardar cambios en disco, lo habíamos declarado como private, ya que en principio iba a ser de uso interno de la clase BattleShip.
Pero aquí ha surgido la necesidad de poder invocarlo desde otras clases, así que habrá que cambiar su declaración para que sea public.

No olvidemos añadir este nuevo ActionListener al botón "Modificar" de la clase PanelPerfil. Lo hacemos en el constructor de Main
Código: [Seleccionar]
//PanelPerfil
panelPerfil = new PanelPerfil();
panelPerfil.botonVolver.addActionListener(new AccionSalirPerfil());
panelPerfil.botonModificar.addActionListener(new AccionModificarPerfil());

Y con esto ya podemos modificar nuestro nombre y contraseña desde la opción ver Perfil.
Ya solo faltaría la función "eliminar cuenta de jugador".
Para esto, primero nos vamos a la clase BattleShip, que es quien gestiona a los Players, y le añadimos un método para eliminar el jugador logueado actualmente.
Código: [Seleccionar]
/**
* Elimina el Player que está logueado actualmente.<br>
* El borrado es definitivo.
*/
public void eliminarPlayerActual() {
jugadores.remove(jugador1);
guardarPlayers();
jugador1 = null;
}

Este método lo invocaremos en el correspondiente ActionListener, volvemos a la clase Main y le añadimos esta clase:
Código: [Seleccionar]
private class AccionEliminarPlayer implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
int respuesta = JOptionPane.showConfirmDialog(null, "¿Seguro que quiere eliminar su cuenta?",
"Eliminar Player", JOptionPane.YES_NO_OPTION);
if (respuesta == JOptionPane.YES_OPTION) {
batalla.eliminarPlayerActual();
paneles.show(panelPrincipal, "login");
panelLogin.resetFormulario();
}
}
}
Pide confirmación y si el usuario confirma, invocamos el método que elimina a este Player y volvemos a la pantalla de login.
No se si sigue siendo necesario recordarlo, pero para que funcione hay que añadir esta acción al botón correspondiente, en el constructor del Main
Código: [Seleccionar]
//PanelPerfil
panelPerfil = new PanelPerfil();
panelPerfil.botonVolver.addActionListener(new AccionSalirPerfil());
panelPerfil.botonModificar.addActionListener(new AccionModificarPerfil());
panelPerfil.botonEliminar.addActionListener(new AccionEliminarPlayer());


Y con esto queda completada la función relativa a la gestión del perfil del jugador, pudiendo visualizar sus datos, modificarlos y eliminar su cuenta del sistema.
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

Kabuto

  • Moderador Global
  • Avanzado
  • *******
  • Mensajes: 364
    • Ver Perfil
Re:juego de batalla naval en netbeans con swing
« Respuesta #8 : 22 de Junio 2020, 12:50 »
Vamos a meternos ahora con la opción de Configuración.
Esta opción ha de permitirnos escoger el modo de juego entre Tutorial y Arcade.
También escoger el nivel de dificultad entre los 4 posibles que hay.

Todo esto lo haremos dentro de un mismo panel.
Para el modo de juego podemos usar dos JRadioButton para que el usuario seleccione uno u otro.

Para el nivel de dificultad, podemos usar un JSlider, una barra deslizadora que contemple los 4 valores posibles: EASY, NORMAL, EXPERT y GENIUS

Incluiremos un botón para "Volver" al menú anterior. Este botón no solo volverá hacia atrás, si no que también dará orden de transmitir lo que haya seleccionado el usuario a la clase BattleShip para que modifique la dificultad y modo de juego según la selección.

Esta podría ser la clase PanelConfig. Incluye dos métodos, uno para retornar la selección del usuario y otro para setear los valores que constan actualmente en la clase BattleShip.
Estos valores, modo y dificultad, los estamos representando con valores int. Así que podemos enviarlos de una clase a otra juntándolos en un array de dos int.

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

private JRadioButton radioTutorial;
private JRadioButton radioArcade;
private JSlider dificultad;
public JButton botonVolver;

public PanelConfig() {

setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
add(new PanelModo());
add(new PanelDifi());
JPanel pnBoton = new JPanel();
botonVolver = new JButton("Volver");
pnBoton.add(botonVolver);
add(pnBoton);
setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
}

private class PanelModo extends JPanel {

public PanelModo() {
radioTutorial = new JRadioButton("Tutorial");
radioArcade = new JRadioButton("Arcade");
ButtonGroup grupo = new ButtonGroup();
grupo.add(radioTutorial);
grupo.add(radioArcade);

JPanel tuto = new JPanel();
tuto.add(radioTutorial);
JPanel arc = new JPanel();
arc.add(radioArcade);

setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
add(tuto); add(arc);
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder("Modo de Juego"),
BorderFactory.createEmptyBorder(10, 10, 10, 10)));
}
}

private class PanelDifi extends JPanel {

public PanelDifi() {
dificultad = new JSlider(1,4);
Hashtable<Integer, JLabel> valores = new Hashtable<Integer, JLabel>();
valores.put(1, new JLabel("EASY"));
valores.put(2, new JLabel("NORMAL"));
valores.put(3, new JLabel("EXPERT"));
valores.put(4, new JLabel("GENIUS"));
dificultad.setLabelTable(valores);
dificultad.setPaintLabels(true);
dificultad.setSnapToTicks(true);

add(dificultad);
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder("Nivel Dificultad"),
BorderFactory.createEmptyBorder(10, 10, 10, 10)));
}
}

/**
* Recupera los valores seleccionados y los retorna
* en un array.
* @return Array de int[] con los valores de configuración.
*/
public int[] getConfig() {
int modoJuego = radioTutorial.isSelected()?1:2;
int nivel = dificultad.getValue();

return new int[] {modoJuego, nivel};
}

/**
* Recibe los valores de configuración que constan en la
* clase BattleShip y los setea en este panel.
* @param config Array de int[] con los valores de configuración
*/
public void setConfig(int[] config) {
if (config[0] == 1)
radioTutorial.doClick(); //Selecciona modo tutorial
else
radioArcade.doClick();

dificultad.setValue(config[1]); //Seteamos dificultad

}

}

Hemos dicho que estos dos métodos, envían y reciben datos a la clase BattleShip
Pero esta clase ahora mismo no tiene métodos para responder a esta comunicación, va a necesitar dos métodos que también envíen y reciban estos valores int de la misma forma.
Así que nos vamos a BattleShip y le añadimos estos dos métodos:

Código: [Seleccionar]
/**
* Recibe nuevos valores de configuración para setear.
* @param config Array de int[] con los valores
*/
public void setConfiguracion(int[] config) {
modo = config[0];
dificultad = config[1];
}

/**
* Retorna los valores de configuración actuales
* @return Array de int[] con los valores
*/
public int[] getConfiguracion() {
return new int[] {modo, dificultad};
}

Bien, ahora PanelConfig y BattleShip tienen métodos con los que comunicarse.
Ahora nos vamos al Main, quien hace la función de "Controlador" para incluir la lógica que establezca estas comunicaciones.

Lo primero es declarar como atributo el nuevo PanelConfig, inicializarlo y agregarlo al CardLayout igual que con los anteriores:
Código: [Seleccionar]
public class Main extends JFrame{

private BattleShip batalla;
private CardLayout paneles; //Albergará varios paneles como si fuera un "mazo" de cartas
private JPanel panelPrincipal; //Este panel recibirá el CardLayout que gestiona el resto de paneles
private PanelLogin panelLogin;
private PanelMenu panelMenu;
private PanelPerfil panelPerfil;
private PanelConfig panelConfig;

Además de inicializarlo y agregarlo al CardLayout, le añadimos ya un ActionListener a su botón "Volver" que luego escribiremos:
Código: [Seleccionar]
//PanelConfig
panelConfig = new PanelConfig();
panelConfig.botonVolver.addActionListener(new AccionSalirConfig());

//Configuramos el "mazo" de paneles
paneles = new CardLayout();
panelPrincipal = new JPanel();
panelPrincipal.setLayout(paneles);
panelPrincipal.add(panelLogin, "login");
panelPrincipal.add(panelMenu, "menu");
panelPrincipal.add(panelPerfil, "perfil");
panelPrincipal.add(panelConfig, "config");
add(panelPrincipal);

Ahora tenemos que escribir dos ActionListener.
Uno es para el botón "Volver" de PanelConfig. Esta acción transmite los valores de configuración al objeto BattleShip (que yo lo llamo "batalla") y vuelve a mostrar en pantalla el menú principal.
Código: [Seleccionar]
//Clases ActionListener para PanelConfig
private class AccionSalirConfig implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
batalla.setConfiguracion(panelConfig.getConfig());
paneles.show(panelPrincipal, "menu");
}
}

El otro ActionListener, es para el botón "Configuración" del PanelMenu. Esta acción lo que hace es recibir de BattleShip los valores de configuración para setearlos en el PanelConfig y luego lo muestra en pantalla.
Código: [Seleccionar]
private class AccionConfig implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
panelConfig.setConfig(batalla.getConfiguracion());
paneles.show(panelPrincipal, "config");
}
}

No olvidemos añadir también esta acción al botón "Configuracion" de PanelMenu
Código: [Seleccionar]
//PanelMenu
panelMenu = new PanelMenu();
panelMenu.botonSalir.addActionListener(new AccionLogout());
panelMenu.botonPerfil.addActionListener(new AccionPerfil());
panelMenu.botonConfig.addActionListener(new AccionConfig());

Y ya podemos acceder al menú de configuración y setear los valores que queramos:
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

Kabuto

  • Moderador Global
  • Avanzado
  • *******
  • Mensajes: 364
    • Ver Perfil
Re:juego de batalla naval en netbeans con swing
« Respuesta #9 : 26 de Junio 2020, 13:02 »
No se si alguien me lee, pero yo sigo con lo mío...

De lo "fácil" aún queda la opción de mostrar reportes y ranking.
Pero como todavía no se puede jugar, no hay nada que reportar, así que por ahora esta parte la dejaremos de lado y vamos a empezar con el hueso duro: la parte jugable.

Esta parte va a implicar muchas cosas, así que vamos a tener que ir pasito a pasito para no volvernos locos.
Para jugar vamos a necesitar un Tablero. Tenemos ya una clase con ese nombre, fue de lo primero que hicimos. Pero ese Tablero sirve para representar el "modelo", lo que es la lógica interna.
Necesitamos otro tablero visual para la "vista", que muestre lo que está ocurriendo en el "modelo".

Este tablero para la vista, lo podemos usar en dos situaciones distintas: una para que los Players coloquen sus Barcos y otro para el desarrollo del juego

Así que para este tablero haremos también un panel como hemos hecho hasta ahora, pero formará parte de otros paneles que cumplirán otras funciones.

¿Cómo dibujamos el tablero?
Pues ayuda mucho hacer previamente un dibujo con lápiz o con un programa tipo Paint, para tener algo visual con lo que hacernos una idea.
Incluso algo tan cutre como esto, puede ayudar:


Ahí nos damos cuenta que aunque el Tablero del modelo es 8x8, si queremos representar las valores de coordenadas, el tablero de la vista tendrá que ser de 9x9.
Vemos que algunas celdas tendrán números, otras no pero usarán color para indicar que es agua, o que ha habido un disparo fallido, o un barco...
Vemos que la primera celda de todas, no mostrará nada, será una celda vacía...

Con esto clarificamos ideas y parece que vamos a tener una clase PanelTablero compuesta de celdas con distintos comportamientos, así que nos va a interesar crear una clase Celda, aunque sea interna dentro de PanelTablero

Esta clase Celda podemos hacer que tenga un atributo JLabel para mostrar el número y quizás nos ayude también un atributo PuntoXY para facilitar el identificar que coordenada del modelo está representando cada Celda de la vista.
Unas celdas tendrán número, otras no pero si coordenadas... en lugar de crear dos clases distintas, podemos hacer solo una pero con dos constructores distintos para contemplar ambas posibilidades.

Las celdas "jugables" comenzarán con color de fondo blanco y todas tendrán un borde redondeado.

Luego, el PanelTablero lo maquetaremos con un GridLayout que precisamente nos da una disposición en forma de grilla muy apropiada para este caso.
De atributo usaremos una matriz de Celdas, donde solo tendremos las celdas "jugables", es decir, las que tendrán agua y barcos.
Las otras Celdas no jugables, las que solo mostrarán números, no las necesitamos tener a mano en un atributo.
En el constructor crearemos unos arrays con estas celdas no jugables, pero únicamente para poder automatizar la inserción de celdas en el GridLayout con bucles.

Ojo a esta parte del código, puede ser un poco confuso si no se conoce bien el GridLayout.

Esta es PanelTablero, con la clase Celda interna:
Código: [Seleccionar]
public class PanelTablero extends JPanel{

public Celda[][] celdas; //Celdas jugables donde habra agua, barcos, etc..

public PanelTablero() {
//Construimos celdas para las "aguas"
celdas = new Celda[8][8];
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++)
celdas[i][j] = new Celda(new PuntoXY(i,j));

//Celdas con número
Celda[] celdasX = new Celda[8];
Celda[] celdasY = new Celda[8];
for (int i = 0; i < 8; i++) {
celdasX[i] = new Celda(new JLabel(Integer.toString(i)));
celdasY[i] = new Celda(new JLabel(Integer.toString(i)));
}
//Maquetamos tablero
setLayout(new GridLayout(9,9,4,4)); //Esta grilla tendrá ambos tipos de celdas
//La grilla se rellena fila a fila.
//1ª fila
add(new JPanel());//Primera celda de la grilla no será nada, solo un panel vacío.
//A continuación, las Celdas con número para las Columnas, o sea, el eje Y de coordenadas
for (Celda valorEjeY: celdasY)
add(valorEjeY);

//Las siguientes filas las automatizamos con bucles anidado
for (int i = 0; i < 8 ; i++) {
add(celdasX[i]); //Comienza con número de Fila, o sea, eje X
//A continuación, una tanda de Celdas "agua" de las que tenemos en la matriz
for (Celda agua: celdas[i])
add(agua);
}

}

/**
* Habrán dos tipos de Celdas.
* Una tendrá una etiqueta para mostrar el número de fila
* o columna que representa, con valores de 0 a 7. Serán las
* Celdas que identifican las coordenadas.
*
* Las otras no tendrán etiqueta y representarán las "aguas" donde
* se lleva a cabo la batalla.
*
* Tendremos dos constructores, cada uno para configurar la Celda
* de un modo u otro.
*/
private class Celda extends JPanel {

PuntoXY coord; //Solo algunas cosillas tendrán coordenadas

public Celda(PuntoXY coord) {
this.coord = coord;
setPreferredSize(new Dimension(50, 50));
setBorder(BorderFactory.createLineBorder(Color.BLACK, 4, true));
setBackground(Color.WHITE);
}

public Celda(JLabel numCelda) {
setPreferredSize(new Dimension(25, 25));
setBorder(BorderFactory.createLineBorder(Color.BLACK, 4, true));
numCelda.setFont(new Font("Verdana", Font.BOLD, 22));
numCelda.setForeground(Color.WHITE);
add(numCelda);
setBackground(new Color(120, 118, 118));
}
}

}

No es una clase terminada, luego habrá que añadirle muchas funcionalidades, pero ya nos da una visual del aspecto del tablero.

Bien, vamos a ver si hemos conseguido modelar un tablero en condiciones.
Vamos a la clase Main y, solo por probar, vamos a instanciar este panel donde deberíamos poner el panelLogin.
Es solo para poder verlo en pantalla y darle el visto bueno antes de seguir con otras cosas.
Hacemos este cambio en el constructor. Luego hay que deshacerlo y dejarlo como estaba:
Código: [Seleccionar]
//Configuramos el "mazo" de paneles
paneles = new CardLayout();
panelPrincipal = new JPanel();
panelPrincipal.setLayout(paneles);
panelPrincipal.add(new PanelTablero(), "login"); //Probamos PanelTablero temporalmente
panelPrincipal.add(panelMenu, "menu");
panelPrincipal.add(panelPerfil, "perfil");
panelPrincipal.add(panelConfig, "config");
add(panelPrincipal);

Y al ejecutar, se nos mostrará el tablero en pantalla:


Se parece a lo que teníamos en mente. Podemos cambiar tamaño de fuente, colores, grosor del borde... si queremos.
Por mi  parte se queda así, y ya tenemos un PanelTablero para usar donde lo necesitemos.
Deshacemos el cambio y volvemos a poner PanelLogin como primer panel del CardLayout.

Ahora vamos a pensar que ha de ocurrir cuando el usuario pulse la opción de "Jugar BattleShip"

1º Ha de solicitar que haga login un segundo jugador.
2º Ha de ofrecer al primer  jugador que coloque sus barcos y luego lo mismo para el segundo.
3º Comienza la batalla naval, donde los Players se van turnando para dispararse.
Aquí habrá que pensar varias cosas, como por ejemplo hacer que cada vez que se hunde un barco enemigo, los barcos se recoloquen solos al azar en el Tablero (cosa que ahora mismo ni idea cómo hacerlo  :P )
4º Al terminar la batalla, construir reportes para cada Player y actualizar un ranking. Que por cierto, no hemos pensado todavía como hacer lo del ranking. Supongo que con un array simple, ya lo pensaremos en su momento.

Vayamos al primer paso: Facilitar login a un segundo Player

Para esto podemos usar un JDialog. Es muy similar a un JFrame y lo que hará será mostrar un nuevo marco sobre el marco principal.
Se podría usar otro JFrame, pero por convención un programa solo debería tener un JFrame, el principal. Para el resto se debería usar JDialog

En este JDialog mostraremos campos de texto para que el jugador 2 pueda loguearse.
Podemos reaprovechar la clase PanelLogin y usarla para este JDialog.
Vamos a ver que pasa.
Esta puede ser por ahora la clase JDialog para login del segundo jugador.
Código: [Seleccionar]
public class DialogoLoginP2 extends JDialog{

public PanelLogin panelLogin; //Reaprovechamos clase PanelLogin

public DialogoLoginP2(Frame parent, boolean modal) {
super(parent, modal);
panelLogin = new PanelLogin();
setContentPane(panelLogin);

setTitle("Login Player 2");
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setResizable(false);
}

}

Básicamente lo que hacemos es construir un marco para utilizar de nuevo a PanelLogin.
Vamos primero a comprobar si conseguimos que salga en pantalla.

En BattleShip, añadimos este JDialogo como un atributo de clase.
Código: [Seleccionar]
public class BattleShip {

private ArrayList<Player> jugadores;
private Player jugador1;
private Player jugador2;
private int dificultad;//1-EASY, 2-NORMAL, 3-EXPERT, 4-GENIUS
private int modo; //1-TUTORIAL, 2-ARCADE
private Tablero tablero1;
private Tablero tablero2;

//JDialog
private DialogoLoginP2 loginp2;

Y creamos un método loginP2() y por ahora lo único que va a hacer va a ser instanciar nuestra clase JDialog y hacerla visible en pantalla.
Código: [Seleccionar]


/**
* Abre un diálogo para que pueda loguearse un segundo jugador.
*/
private void loginP2() {
loginp2 = new DialogoLoginP2(null, true);
loginp2.setVisible(true);
}


Volvemos a la clase Main y creamos un ActionListener para el botón "Jugar Batalla" de PanelMenu.
En principio, esta ActionListener tan solo invocará al método loginP2() de la clase BattleShip.
Código: [Seleccionar]
//Clases ActionListener para PanelMenu

private class AccionJugarBatalla implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
batalla.loginP2();
}
}
Y añadimos la acción al botón correspondiente desde el constructor del Main
Código: [Seleccionar]
public Main() {
batalla = new BattleShip();
//PanelLogin
panelLogin = new PanelLogin();
panelLogin.botonLogin.addActionListener(new AccionLogin());
panelLogin.botonNuevo.addActionListener(new AccionNuevoUsuario());
//PanelMenu
panelMenu = new PanelMenu();
panelMenu.botonJugar.addActionListener(new AccionJugarBatalla());
panelMenu.botonSalir.addActionListener(new AccionLogout());
panelMenu.botonPerfil.addActionListener(new AccionPerfil());
panelMenu.botonConfig.addActionListener(new AccionConfig());
//PanelPerfil

Si ahora abrimos programa y comenzamos una partida, veremos el dialogo ofreciendo login al player 2 que se situa sobre el JFrame principal


Ahora mismo los botones no funcionan, excepto el de "Cerrar Programa" porque su ActionListener está escrito dentro de su propia clase.
Este botón cierra TODO el programa. Esto tiene sentido cuando lo usamos en el marco principal al iniciar el programa, pero quizás no tanto cuando estamos pidiendo loguear a un segundo jugador.

Si queremos reutilizar la clase PanelLogin, vamos a tener que modificarla para que se adapte según en que sitio la estamos usando.
Vamos a esta clase.
Lo primero establecemos el atributo "botonSalir" como public. Al principio lo pusimos private porque su acción (cerrar todo el programa) se podía escribir desde su propia clase y no necesitábamos acceso a él desde otras clases.
Pero esto ha cambiado, necesitamos acceder a este botón desde la clase BattleShip, así que necesitamos que sea public.

A su constructor añadiremos un boolean para recibir como argumento.
Si su valor es true, significará que este panel se está instanciando para usarse en login principal, así que el botón sí mantendrá la acción de cerrar todo.
Si el valor es false, entenderemos que es para usarse en el dialogo para login del segundo jugador.
En este caso el botón cambia su texto y no tendrá ninguna acción asociada. Su acción se la pondremos desde el JDialog donde lo instanciamos.
El resto del código no necesita cambios:
Código: [Seleccionar]
public class PanelLogin extends JPanel{

private JTextField campoNombre;
private JTextField campoPass;
public JButton botonLogin;
public JButton botonNuevo;
public JButton botonSalir;

public PanelLogin(boolean loginPrincipal) {
campoNombre = new JTextField(10);
campoPass = new JTextField(10);
botonLogin = new JButton("Login");
botonNuevo = new JButton("Nuevo Usuario");

/*
* botonSalir será distinto segun si esta clase se
* está usando para el login principal o si es para
* loguear a un segundo Player al comenzar el jugo
*/
if (loginPrincipal) {
botonSalir = new JButton("Cerrar Programa");
botonSalir.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0); //Esto cierra el programa
}
});
}
else { //Es para loguear a un segundo jugador
botonSalir = new JButton("Volver a Menu");
//Su acción se escribirá en la clase DialogoLoginP2
}

Bien, si guardamos estos cambios, estamos obligados a pasarle un boolean al construir este panel.
En la clase Main, le pasaremos valor true, porque es para el login principal.
Código: [Seleccionar]
public Main() {
batalla = new BattleShip();
//PanelLogin
panelLogin = new PanelLogin(true);
panelLogin.botonLogin.addActionListener(new AccionLogin());
panelLogin.botonNuevo.addActionListener(new AccionNuevoUsuario());
//PanelMenu

Y en la clase DialogoLoginP2 le pasaremos valor false.
Además, le añadimos al botón "salir" una acción para cerrar el Jdialog y no todo el programa como ocurría antes
Código: [Seleccionar]
public class DialogoLoginP2 extends JDialog{

public PanelLogin panelLogin; //Reaprovechamos clase PanelLogin

public DialogoLoginP2(Frame parent, boolean modal) {
super(parent, modal);
panelLogin = new PanelLogin(false);
panelLogin.botonSalir.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dispose(); //Cierra dialogo
}
});
setContentPane(panelLogin);

setTitle("Login Player 2");
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setResizable(false);
}

}

Lo siguiente a realizar, será dar funcionalidad al resto de botones.
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

Kabuto

  • Moderador Global
  • Avanzado
  • *******
  • Mensajes: 364
    • Ver Perfil
Re:juego de batalla naval en netbeans con swing
« Respuesta #10 : 26 de Junio 2020, 13:07 »
Vamos a hacer funcionar el boton para loguear un segundo Player.

El proceso es básicamente el mismo que usamos para loguear a player 1 y de hecho podríamos reutilizar los mismo método de BattleShip
El problema es que los escribimos pensando solo en el jugador1, por ejemplo el método para validar el login, automáticamente logueaba a player 1 si los datos eran correctos.
Ahora nos interesa poder decidir si queremos loguear a player 1 o a 2.

Vamos a modificar el método validarLogin() de BattleShip para incluir un int como argumento.
Este int le podemos dar valor 1 para loguear a jugador1, o 2 para el otro.
Código: [Seleccionar]
/**
* Recibe nombre y password para comprobar si existe un Player registrado.<br>
* Si existe, será logueado como jugador1 o jugador2, según el valor del parámetro
* <i>numPlayer</i>
* @param datos Array String[] con nombre y password.
* @param numPlayer Valor int entre 1 y 2 para decidir que jugador vamos a loguear
* @return <i>True</i> si los datos son válidos, <i>False</i> en caso contrario.
*/
public boolean validarLogin(String[] datos, int numPlayer) {
//Recorremos jugadores y buscamos mismo nombre, y mismo password
for (Player player: jugadores)
if (player.getNombre().equals(datos[0]) && player.getPassword().equals(datos[1])) {
//Login valido
if (numPlayer == 1)
jugador1 = player;
else
jugador2 = player;
return true;
}
//Finalizado bucle for sin retornar true, es que el login no es válido
return false;
}

Al alterar este método, nos toca corregir allá donde ya lo habíamos invocado.
En la clase Main, en el ActionListener de AccionLogin, añadimos valor 1 como parámetro al invocar validarLogin()

Código: [Seleccionar]
//Clases ActionListener para PanelLogin
private class AccionLogin implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {

String[] datos = panelLogin.getDatosLogin();
if (datos != null) {
if (batalla.validarLogin(datos, 1)) { //Logueamos a player 1
paneles.show(panelPrincipal, "menu");
}
else
JOptionPane.showMessageDialog(null, "Login Rechazado");
}
}
}

Ahora ya podemos reutilizar este método para loguear a un segundo Player.
Volvemos a BattleShip y comenzamos a escribir este ActionListener
Código: [Seleccionar]
//Acciones para Dialogo login player 2
private class AccionLogin2 implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
//Obtenemos datos del JDialog
String[] datos = loginp2.panelLogin.getDatosLogin();
if (validarLogin(datos, 2)) {
loginp2.dispose();
System.out.println("Jugador 2 es " + jugador2.getNombre());
}
else
JOptionPane.showMessageDialog(null, "Login Rechazado");
}
}

Ese system.out es solo para comprobar si funciona el logueo de un segundo jugador.
Para ponerlo a prueba, volvemos al método loginP2(), donde instanciamos el JDialog y le añadimos esta accion al botón correspondiente:
Código: [Seleccionar]
/**
* Abre un diálogo para que pueda loguearse un segundo jugador.
*/
public void loginP2() {
loginp2 = new DialogoLoginP2(null, true);
loginp2.panelLogin.botonLogin.addActionListener(new AccionLogin2());
loginp2.setVisible(true);
}

Si probamos el programa, veremos que ya podemos loguear un segundo player y se mostrará su nombre por consola.
Por cierto, no estamos evitando que la misma persona pueda loguearse como player 1 y como player 2 al mismo tiempo.
Solo lo comento, no me parece interesante perder tiempo ahora en este detalle.

Lo siguiente es hacer que funcione el botón de registrar nuevo Player cuando se loguea un segundo jugador.
Esta posibilidad no la contemplaba el enunciado, pero bueno, ya que tenemos el botón, hagamos que funcione.

Como antes, nos tocará modificar el método addPlayer(), quien por defecto deja logueado al nuevo Player como jugador1, para poder escoger si queremos que sea jugador2. También lo hacemos con un int.

En clase Main corregimos la invocación de este método para indicar que será jugador 1 en ese caso:
Código: [Seleccionar]
private class AccionNuevoUsuario implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String[] datos = panelLogin.getDatosLogin();
if (datos != null) {
//Con los datos obtenidos, creamos un nuevo Player
if (batalla.addPlayer(new Player(datos[0], datos[1]), 1)) //Será jugador1
JOptionPane.showMessageDialog(null, "Registro aceptado",
"Nuevo Usuario", JOptionPane.INFORMATION_MESSAGE);
}

}
}

Y en BattleShip, creamos nuevo ActionListener para jugador 2, también con system.out de comprobación:
Código: [Seleccionar]
private class AccionNuevoPlayer2 implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String[] datos = loginp2.panelLogin.getDatosLogin();
if (datos != null) {
if (addPlayer(new Player(datos[0], datos[1]), 2)) { //Jugador 2
JOptionPane.showMessageDialog(null, "Registro aceptado",
"Nuevo Usuario", JOptionPane.INFORMATION_MESSAGE);
loginp2.dispose();

System.out.println("Jugador 2 es " + jugador2.getNombre());
}
}

}
}
Y añadimos acción al botón correspondiente:
Código: [Seleccionar]
public void loginP2() {
loginp2 = new DialogoLoginP2(null, true);
loginp2.panelLogin.botonLogin.addActionListener(new AccionLogin2());
loginp2.panelLogin.botonNuevo.addActionListener(new AccionNuevoPlayer2());
loginp2.setVisible(true);
}
Bien, ahora ya podemos loguear/registrar un segundo jugador.
Lo que tendría que ocurrir ahora, es comenzar la opción de colocar Barcos.
Esto va a ser lo "divertido"  :(
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

Kabuto

  • Moderador Global
  • Avanzado
  • *******
  • Mensajes: 364
    • Ver Perfil
Re:juego de batalla naval en netbeans con swing
« Respuesta #11 : 26 de Junio 2020, 13:12 »
Para colocar los Barcos usaremos otro JDialog.
Este proceso requerirá bastante código, va a ser casi un programa independiente por sí mismo.
Necesitamos que el usuario pueda escoger el Barco a colocar.
Luego, podemos ofrecerle que pinche con el ratón sobre la casilla donde lo quiere colocar.
Preguntamos orientación del Barco (horizontal o vertical) e intentamos colocar.
Este "intento de colocar" tiene que contemplar el tamaño del Barco seleccionado, que no se salga de los límites, que no se solape con otro barco...

Bien, vamos a ir maquetando el Dialogo y ya luego pensaremos en la lógica.
Se me ocurre poner un JComboBox con la lista de barcos disponible para que escoja.
Puesto que no se permite colocar dos barcos del mismo tipo, cada vez que se coloque un Barco podemos eliminarlo de la lista de este JComboBox y así evitar que el usuario repita Barco.
Solo en nivel EASY, se permite repetir dos destructores, ya pondremos código para contemplar este detalle.

Debajo mostramos la grilla de PanelTablero y a cada Celda le añadiremos un MouseListener para detectar cuando hacemos click en alguna de ellas.

Por último un par de botones para confirmar o bien cancelar el proceso.

En este JDialog, la clase PanelTablero(Vista) y la clase Tablero(Modelo) van a trabajar mano a mano.
Así que vamos a hacer unos primeros cambios en la clase PanelTablero.
Tanto el array de Celdas, como la clase interna Celda, las declaramos como public para facilitar su acceso desde otras clases, tal y como necesitaremos en este JDialog.

Además le añadimos un método que recibe el modelo del Tablero y cambiar el color de las Celdas según sus valores.
Esto nos puede servir tanto para colocar barcos, como para luego durante el proceso de juego.
Así queda actualmente la clase PanelTablero:
Código: [Seleccionar]
public class PanelTablero extends JPanel{

public Celda[][] celdas; //Celdas jugables donde habra agua, barcos, etc..
private Tablero tablero;

public PanelTablero(Tablero tablero) {
this.tablero = tablero;
//Construimos celdas para las "aguas"
celdas = new Celda[8][8];
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++)
celdas[i][j] = new Celda(new PuntoXY(i,j));

//Celdas con número
Celda[] celdasX = new Celda[8];
Celda[] celdasY = new Celda[8];
for (int i = 0; i < 8; i++) {
celdasX[i] = new Celda(new JLabel(Integer.toString(i)));
celdasY[i] = new Celda(new JLabel(Integer.toString(i)));
}
//Maquetamos tablero
setLayout(new GridLayout(9,9,4,4)); //Esta grilla tendrá ambos tipos de celdas
//La grilla se rellena fila a fila.
//1ª fila
add(new JPanel());//Primera celda de la grilla no será nada, solo un panel vacío.
//A continuación, las Celdas con número para las Columnas, o sea, el eje Y de coordenadas
for (Celda valorEjeY: celdasY)
add(valorEjeY);

//Las siguientes filas las automatizamos con bucles anidado
for (int i = 0; i < 8 ; i++) {
add(celdasX[i]); //Comienza con número de Fila, o sea, eje X
//A continuación, una tanda de Celdas "agua" de las que tenemos en la matriz
for (Celda agua: celdas[i])
add(agua);
}

}

/**
* Cambia el color de las Celdas según la información del Tablero modelo
*/
public void setTableroModelo() {
//Recorremos celdas y modificamos colores según valores del modelo
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++) {
switch(tablero.tablero[i][j]) { //Consultamos matriz del Tablero
case 0: //Casilla sin desvelar
celdas[i][j].setBackground(Color.WHITE);
break;
case 1: //Barco
celdas[i][j].setBackground(Color.DARK_GRAY);
break;
case 2: //Barco Tocado
celdas[i][j].setBackground(Color.RED);
break;
case 3: //Agua/Disparo fallido
celdas[i][j].setBackground(Color.CYAN);
break;
}
}

}

/**
* Habrán dos tipos de Celdas.
* Una tendrá una etiqueta para mostrar el número de fila
* o columna que representa, con valores de 0 a 7. Serán las
* Celdas que identifican las coordenadas.
*
* Las otras no tendrán etiqueta y representarán las "aguas" donde
* se lleva a cabo la batalla.
*
* Tendremos dos constructores, cada uno para configurar la Celda
* de un modo u otro.
*/
public class Celda extends JPanel {

public PuntoXY coord; //Solo algunas casillas tendrán coordenadas
private JLabel numCelda;//Solo algunas casillas mostrarán número

public Celda(PuntoXY coord) {
this.coord = coord;
setPreferredSize(new Dimension(50, 50));
setBorder(BorderFactory.createLineBorder(Color.BLACK, 4, true));
setBackground(Color.WHITE);
}

public Celda(JLabel numCelda) {
this.numCelda= numCelda;
setPreferredSize(new Dimension(25, 25));
setBorder(BorderFactory.createLineBorder(Color.BLACK, 4, true));
numCelda.setFont(new Font("Verdana", Font.BOLD, 22));
numCelda.setForeground(Color.WHITE);
add(numCelda);
setBackground(new Color(120, 118, 118));
}

}

}

Ahora sí creamos una nueva clase que podemos llamar DialogoCrearBarcos

Tendremos unos atributos referentes al Modelo y otros para la Vista.
Para el Modelo, de momento tendremos una referencia al objeto Tablero con el que se asociará este dialogo (que será tablero1 o tablero2 de la clase BattleShip), el nivel de dificultad y un límite de barcos.
La dificultad la necesitamos para saber el límite de barcos y para permitir o no un segundo Barco destructor cuando la dificultad es EASY.

Para la Vista tendremos el JComboBox con una lista de Barcos para elegir.
Esta lista irá cambiando según los barcos que se vayan colocando.
Un PanelTablero para mostrar las Celdas y los dos botones.

Hay por ahora un método para cerrar este JDialog, con una confirmación previa y una clase MouseListener para añadir a cada una de las Celdas.
Este listener lo añadimos a las Celdas al construir el panel central del dialogo, por eso necesitábamos que el array de Celdas fuera public, para hacer más sencillo este proceso.

La lógica de este Listener aún no está escrita, de momento solo captura el código del barco seleccionado (obteniendo las dos primeras letras de lo seleccionado en el JComboBox), referencia a la Celda que ha sido clickada y muestra en pantalla sus coordenadas para comprobar que funciona.

Código: [Seleccionar]
public class DialogoCrearBarcos extends JDialog{

//Modelo
private Tablero tablero;
private int dificultad; //Determina cuantos barcos se colocan
private int maxBarcos;
//Vista
public PanelTablero panelTablero;
private JComboBox<String> listaBarcos;
private JButton botonTerminar;
private JButton botonCancelar;

public DialogoCrearBarcos(Frame parent, boolean modal, int dificultad, Tablero tablero) {
super(parent, modal);
this.tablero = tablero;
this.dificultad = dificultad;
switch (dificultad) {
case 1: //5 barcos, se repite un destructor
maxBarcos = 5;
break;
case 2: //4 barcos
maxBarcos = 4;
break;
case 3: //2 barcos
maxBarcos = 2;
break;
case 4: //1 barco
maxBarcos = 1;
break;
}

setLayout(new BorderLayout());
add(new PanelNorte(), BorderLayout.NORTH);
add(new PanelCentro(), BorderLayout.CENTER);
add(new PanelSur(), BorderLayout.SOUTH);

setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setResizable(false);
}

private class PanelNorte extends JPanel {

public PanelNorte() {

String[] lista = new String[] {"PA-PortaAviones", "AZ-Acorazado",
"SM-Submarino", "DT-Destructor"};
listaBarcos = new JComboBox<String>(lista);
add(listaBarcos);
setBorder(BorderFactory.createTitledBorder("Seleccione tipo de Barco y pulse una casilla para colocarlo"));

}
}

private class PanelCentro extends JPanel {

public PanelCentro() {
panelTablero = new PanelTablero(tablero);
panelTablero.setTableroModelo();
//Añadimos listener a las Celdas
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++)
panelTablero.celdas[i][j].addMouseListener(new ClickCelda());
add(panelTablero);

setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(20, 20, 20, 20),
BorderFactory.createBevelBorder(BevelBorder.RAISED)));
}

}

private class PanelSur extends JPanel {

public PanelSur() {
botonTerminar = new JButton("Terminar");
botonTerminar.setEnabled(false);
botonCancelar = new JButton("Cancelar");
botonCancelar.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
cerrarDialogo();
}
});

JPanel izq = new JPanel();
izq.add(botonTerminar);
JPanel der = new JPanel();
der.add(botonCancelar);

setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
add(izq); add(der);
setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
}
}

/**
* Cierra este JDialog, previa confirmación del usuario.
*/
private void cerrarDialogo() {
int respuesta = JOptionPane.showConfirmDialog(null, "¿Seguro que desea Cancelar?",
"Colocar Barcos", JOptionPane.YES_NO_OPTION);
if (respuesta == JOptionPane.YES_OPTION)
dispose();
}

//Clase MouseListener para las Celdas
class ClickCelda implements MouseListener {

@Override
public void mouseClicked(MouseEvent e) {
//Consultamos código barco seleccionado
String codigo = ((String) listaBarcos.getSelectedItem()).substring(0, 2);
//Celda en la que se ha hecho click
Celda cel = (Celda) e.getSource();
System.out.println("X: " + cel.coord.x + " Y: " + cel.coord.y);
}

@Override
public void mousePressed(MouseEvent e) {}

@Override
public void mouseReleased(MouseEvent e) {}

@Override
public void mouseEntered(MouseEvent e) {}

@Override
public void mouseExited(MouseEvent e) {}
}

}

Para poder visualizar este JDialog, nos vamos a la clase BattleShip y añadimos el siguiente método, que muestra el JDialog asociándolo a tablero1 o a tabler2, según el parámetro numPlayer que recibe como argumento.

Código: [Seleccionar]
/**
* Abre el diálogo para colocar barcos.<br>
* El tablero mostrado dependerá del jugador que va a colocar barcos.
* @param numPlayer Valor int entre 1 y 2 para indicar que Player coloca barcos.
*/
public void colocarBarcos(int numPlayer) {
if (numPlayer == 1)
crearBarcos = new DialogoCrearBarcos(null, true, dificultad, tablero1);
else
crearBarcos = new DialogoCrearBarcos(null, true, dificultad, tablero2);

crearBarcos.setVisible(true);
}

Este método lo invocamos desde las acciones para cuando se ha logueado el Jugador2.
Cuando este se ha logueado, ya sea con cuenta existente o una recién registrada, se abre el DialogoCrearBarcos para que el jugador1 coloque sus Barcos, por eso le pasamos valor 1 como parámetro.

Código: [Seleccionar]
//Acciones para Dialogo login player 2
private class AccionLogin2 implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
//Obtenemos datos del JDialog
String[] datos = loginp2.panelLogin.getDatosLogin();
if (validarLogin(datos, 2)) {
loginp2.dispose();
colocarBarcos(1); //Dialogo para colocar Barcos
}
else
JOptionPane.showMessageDialog(null, "Login Rechazado");
}
}

private class AccionNuevoPlayer2 implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String[] datos = loginp2.panelLogin.getDatosLogin();
if (datos != null) {
if (addPlayer(new Player(datos[0], datos[1]), 2)) { //Jugador 2
JOptionPane.showMessageDialog(null, "Registro aceptado",
"Nuevo Usuario", JOptionPane.INFORMATION_MESSAGE);
loginp2.dispose();

colocarBarcos(1); //Dialogo para colocar Barcos
}
}

}
}

Así, tras loguear a un segundo jugador, podemos ver este dialogo:


El botón "Terminar" estará desactivado hasta que se hayan colocado todos los barcos.

Bien, ya tenemos lo básico. Ahora hay que pensar en la lógica que nos permite colocar el barco seleccionado.
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

Kabuto

  • Moderador Global
  • Avanzado
  • *******
  • Mensajes: 364
    • Ver Perfil
Re:juego de batalla naval en netbeans con swing
« Respuesta #12 : 29 de Junio 2020, 20:15 »
Vamos a la clase ClickCelda, que es un MouseListener.

Cuando el usuario pincha en una Celda para colocar Barco, lo primero que vamos a querer saber es si la celda está libre u ocupada.
Así que capturaremos las coordenadas de dicha Celda, cuyos valores x e y que nos da su atributo PuntoXY, van a coincidir con los indices de la matriz Tablero del modelo.

Así con esos dos datos podemos preguntar que tiene la matriz en esa posición. Si tiene valor 1, significa que ya hay un barco colocado y se lo haremos saber al usuario.
En caso contrario, es que está libre, así que tomaremos esa Celda como origen para colocar el barco seleccionado, pero tenemos que preguntarle al usuario si quiere ponerlo en horizontal o en vertical.
Esto lo preguntaremos con un JOptionPane con un botón para cada posibilidad y capturaremos la respuesta.
Este JOptionPane retornará valor 0 cuando se pulse el primer botón "Vertical" o valor 1 para el segundo botón "Horizontal".

Llegados a este punto, tenemos tres datos primordiales:
- Código del Barco que se va a colocar.
- Orientación del Barco
- Celda origen donde colocar el Barco.

Estos tres datos, se los vamos a pasar a un método llamado colocarBarco() que se encargará de colocarlo, si es posible.

Así queda nuestro MouseListener, que estamos aplicando a cada Celda
Código: [Seleccionar]
class ClickCelda implements MouseListener {

@Override
public void mouseClicked(MouseEvent e) {
//Consultamos código barco seleccionado
String codigo = ((String) listaBarcos.getSelectedItem()).substring(0, 2);
//Celda en la que se ha hecho click
Celda cel = (Celda) e.getSource();
///Sus coordenadas
int x = cel.coord.x;
int y = cel.coord.y;
//Consultamos a la matriz del tablero modelo si está libre o no esta Celda
if (tablero.tablero[x][y] == 1)
JOptionPane.showMessageDialog(null, "Ya hay un Barco en esta casilla",
"Colocar Barcos", JOptionPane.WARNING_MESSAGE);
else {
//Pedimos orientacion
String[] opciones = new String[] {"Vertical", "Horizontal"};
int orientacion = JOptionPane.showOptionDialog(null, "Elija orientación",
"Colocar Barcos", 0, JOptionPane.QUESTION_MESSAGE, null, opciones, 0);

colocarBarco(codigo, orientacion, cel);
}
}

@Override
public void mousePressed(MouseEvent e) {}

@Override
public void mouseReleased(MouseEvent e) {}

@Override
public void mouseEntered(MouseEvent e) {}

@Override
public void mouseExited(MouseEvent e) {}
}

}

Veamos el método colocarBarco(). Hemos dicho que recibe código de barco, un int indicando la orientación (vertical u horizontal) y la Celda desde la que se ha de intentar colocar el barco.

Lo que hará será apoyarse en dos submétodos, que se invocará uno u otro según la orientación.
Si estos submétodo tienen éxito colocando el barco, decrementaremos la varaible limiteBarcos para contar que ya hemos colocado uno de los Barcos que se nos permiten colocar.

A continuación preguntaremos si limiteBarcos ya es igual a 0. Esto significa que ya no se pueden colocar más barcos, así que desactivaremos el JcomboBox y los listener de las Celdas para que el usuario ya no pueda interactuar con ellas.
Y activaremos el botón "Terminar" de este Dialogo, para que el usuario de confirmación de los barcos colocados.

Si no es igual a 0, es que aún se pueden poner más barcos.
En este caso, lo que hacemos es quitar de la lista de Barcos el barco que se acaba de poner, porque no se pueden repetir.
Solo en el caso de que la dificultad sea EASY, se permite repetir un Destructor.
En este caso, preguntaremos al tablero si ya hay dos destructores colocados, en caso afirmativo lo retiraremos de la lista, en caso negativo, no tocaremos la lista permitiendo que se pueda colocar otro destructor más.

Este es el método colocarBarco() de la clase DialogoCrearBarcos

Código: [Seleccionar]
/**
* Intentará colocar el Barco correspondiente al código recibido,
* en la orientación indicada, a partir de la Celda especificada.<br>
* Se apoya en los métodos colocarVertical() y colocarHorizontal().
* @param codigo String que identifica el tipo de Barco
* @param orientacion Si el valor es 0 será vertical y 1 será horizontal.
* @param celda Celda a partir de la cuál se intentará colocar el Barco
*/
private void colocarBarco(String codigo, int orientacion, Celda celda) {

boolean colocado;

if (orientacion == 0) //Vertical
colocado = colocarVertical(codigo, celda);
else
colocado = colocarHorizontal(codigo, celda);

//Si se ha colocado el Barco, comprobamos si se permite poner más

if (colocado) {
limiteBarcos--;

if (limiteBarcos == 0) {
listaBarcos.setEnabled(false);
panelTablero.desactivarListener();
botonTerminar.setEnabled(true);
}
else {
/*
* Se permiten más barcos, pero no el mismo que acabamos de poner.
* Solo se puede repetir el Destructor, si estamos en nivel EASY
*/
if (codigo.equals("DT")) {
if (dificultad != 1) //No es EASY
listaBarcos.removeItem("DT-Destructor");
else if (tablero.hayDosDestructores()) //EASY, solo si ya hay dos eliminamos DT de la lista
listaBarcos.removeItem("DT-Destructor");
}
else //No es destructor, eliminamos el Barco correspondiente al código
listaBarcos.removeItem(listaBarcos.getSelectedItem());
}
}
}

Este método invoca varios métodos nuevos que no hemos visto todavía, y los vamos a ver a continuación.

El método hayDosDestructores(). Se lo vamos a escribir a la clase Tablero y básicamente lo que hace es contar los destructores que hay en en ArrayList de Barcos.
Si ya hay dos en la lista, devuelve true y así sabemos si al colocar barcos, ya se ha cumplido el límite de dos destructores para el nivel EASY.

Código: [Seleccionar]
/**
* Comprueba si ya hay dos Destructores en el Tablero.<br>
* El Destructor es el único Barco que se puede repetir y solo
* cuando el nivel de dificultad es EASY. Con este método comprobamos
* si ya se ha se ha alcanzado este límite permitido.
* @return <i>True</i> si ya hay dos destructores, <i>False</i> en caso contrario.
*/
public boolean hayDosDestructores() {
int dt = 0;
for (Barco b: barcos)
if (b.getCodigo().equals("DT"))
dt++;
return dt == 2;
}

El método desactivarListener(). Este lo usamos para que la clase PanelTablero elimine los MouseListener de las Celdas. Así el usuario ya no puedo activarlas una vez que ya se han colocado todos los Barcos.
Desactivar Listeners es sencillo, con getMouseListener() nos devuelven un array con todos los MouseListener que tenga el objeto en cuestión (las Celdas en este caso).
Como sabemos que solo hay un único MouseListener, le pedimos que elimine el elemento
  • de ese array, es decir, el único Listener que tiene cada Celda.

    Código: [Seleccionar]
    /**
    * Elimina los MouseListener de cada Celda.<br>
    * Esto sirve para cuando queremos que el usuario
    * no pueda clickar en las Celdas, por ejemplo
    * cuando ya ha colocado todos los barcos
    */
    public void desactivarListener() {
    for (int i = 0; i < 8; i++)
    for (int j = 0; j < 8; j++)
    celdas[i][j].removeMouseListener(celdas[i][j].getMouseListeners()[0]);
    }

    Ahora vienen los más complicados, colocarVertical() y colocarHorizontal().

    EL método colocarVertical() recibirá como parámetro el código el barco y la celda origen.
    El código servirá para determinar cuántas celdas/casillas se necesitan para dicho barco, además de la casilla que ocupa la celda origen.
    Por ejemplo, un portaaviones "PA" requiere 5 casillas en total, así que además de la celda origen, necesitaremos 4 casillas más.
    Una vez sabemos las casillas que necesitamos, se llamará a otro submétodo llamado comprobarVerticalAbajo() que determinará si las casillas necesarias por debajo de la celda origen están libres y/o son suficientes.
    Si están libres, retornará true y se procederá a crear un Barco con las coordenadas PuntoXY correspondientes a estas casillas.
    Se añadirá este Barco al Tablero (modelo) quien lo guardará en su ArrayList y además actualiza los valores correspondientes en la matriz.
    Y luego pedimos al PanelTablero (vista) que actualice lo que vemos en pantalla según los nuevos datos que contiene ahora el modelo.
    Así se cambiarán los colores de las Celdas para representar el nuevo Barco que hemos colocado.

    Si no hubiera espacio libre desde la celda origen "hacia abajo", entonces se intentará el mismo proceso pero "hacia arriba".

    Si tampoco hubiera espacio libre, se informará al usuario que en esa Celda es imposible colocar un Barco en vertical y tendrá que elegir otra casilla o probar otra orientación.

    Es el método colocarVertical(), para la clase DialogoCrearBarco
    Código: [Seleccionar]
    /**
    * Intentará colocar el barco indicado en la columna vertical de la Celda
    * donde el usuario ha clickado.<br>Dicha Celda será el origen y se intentará
    * colocar primero desde ese origen hacia abajo. Si no fuera posible se
    * intentará hacia arriba.<br>Si consigue colocar el Barco, quedará registrado
    * en la Lista de Barcos del Tablero(modelo).
    * Este método se apoya en los métodos <i>comprobarVerticalAbajo()</i> y
    * <i>comprobarVerticalArriba()</i>
    * @param codigo String para indicar tipo de Barco
    * @param origen Celda a partir de la cuál se intentará colocar el Barco.
    * @return <i>True</i> si se colocó con éxito, <i>False</i> en caso contrario
    */
    private boolean colocarVertical(String codigo, Celda origen) {
    //Calculamos casillas restantes necesarias según tipo de barco
    int casillas = 0;
    switch(codigo) {
    case "PA": //PortaAviones 5 casillas, necesitamos 4 más la Celda origen
    casillas = 4;
    break;
    case "AZ": //Acorazado 4 casillas, 3 más queremos
    casillas = 3;
    break;
    case "SM": //Submarino 3 casillas, 2 más
    casillas = 2;
    break;
    case "DT": //Destructor 2 casillas, 1 más
    casillas = 1;
    break;
    }
    //Intentamos colocar hacia abajo primero, y si no, hacia arriba
    if (comprobarVerticalAbajo(origen, casillas)) {
    //Colocamos Barco desde la casilla indicada, hacia abajo
    Barco barco = new Barco(codigo);
    int y = origen.coord.y;
    int x = origen.coord.x;
    for (int i = x; i <= (x + casillas); i++)
    barco.addCoordenada(new PuntoXY(i, y)); //Añadimos coordenada al Barco
    /*
    * Barco construido, lo añadimos a la lista de Barcos del Tablero.
    * Este método para añadir, se encarga también de actualizar
    * la matriz del Tablero poniendo valor 1 ahí donde ocupa el Barco.
    */
    tablero.addBarco(barco);
    panelTablero.setTableroModelo(); //Actualizamos la Vista para mostrar nuevo Barco
    return true;
    }
    else if (comprobarVerticalArriba(origen, casillas)) {
    Barco barco = new Barco(codigo);
    int y = origen.coord.y;
    int x = origen.coord.x;
    for (int i = x; i >= (x - casillas); i--)
    barco.addCoordenada(new PuntoXY(i, y));

    tablero.addBarco(barco);
    panelTablero.setTableroModelo(); //Actualizamos la Vista para mostrar nuevo Barco
    return true;
    }
    else {
    JOptionPane.showMessageDialog(null, "No hay espacio disponible en esta vertical"
    + "\nPruebe otra Celda como origen","Colocar Barco", JOptionPane.WARNING_MESSAGE);
    return false;
    }
    }
    Y estos son los submétodos en los que se apoya.
    comprobarVerticalAbajo()
    Código: [Seleccionar]
    /**
    * Comprueba si hay espacio en la vertical inferior a partir
    * de la Celda considerada como origen.<br>
    * @param origen Celda en la que se ha clickado
    * @param casillas Cantidad de casillas necesarias además de la
    * ocupada por la Celda origen.
    * @return <i>True</i> si hay espacio libre, <i>False</i> si no hay espacio.
    */
    private boolean comprobarVerticalAbajo(Celda origen, int casillas) {
    int y = origen.coord.y;
    int x = origen.coord.x;
    if (7 - x >= casillas) {
    //Hay espacio hacia abajo, pero hay que comprobar si está libre.
    for (int i = x; i <= (x + casillas); i++)
    if (tablero.tablero[i][y] != 0)
    return false; //Hay otro barco, no se puede colocar
    //Si bucle for no retorna false, es que hay hueco libre
    return true;
    }
    else
    return false; //No hay tablero suficiente
    }

    Y comprobar comprobarVerticalArriba():
    Código: [Seleccionar]
    /**
    * Comprueba si hay espacio en la vertical superior a partir
    * de la Celda considerada como origen.<br>
    * @param origen Celda en la que se ha clickado
    * @param casillas Cantidad de casillas necesarias además de la
    * ocupada por la Celda origen.
    * @return <i>True</i> si hay espacio libre, <i>False</i> si no hay espacio.
    */
    private boolean comprobarVerticalArriba(Celda origen, int casillas) {
    int y = origen.coord.y;
    int x = origen.coord.x;
    if (x - 0 >= casillas) {
    for (int i = x; i >= (x - casillas); i--)
    if (tablero.tablero[i][y] != 0)
    return false;

    return true;
    }
    else
    return false;
    }

    Sí, hay mucho código, pero es necesario. Y puede resultar confuso, pero básicamente lo que hacemos es movernos por la matriz desde la posición que nos indican y comprobar si hay espacio suficiente y si además está libre (posiciones con valor 0).

    Esto era para colocar un barco en vertical.

    Si lo queremos colocar en horizontal, vamos a seguir prácticamente el mismo proceso.
    Solo que en lugar de comprobar "hacia abajo" y "hacia arriba", comprobaremos "hacia derecha" y "hacia izquierda".

    Método colocarHorizontal():
    Código: [Seleccionar]
    /**
    * Intentará colocar el barco indicado en la columna horizontal de la Celda
    * donde el usuario ha clickado.<br>Dicha Celda será el origen y se intentará
    * colocar primero desde ese origen hacia derecha. Si no fuera posible se
    * intentará hacia izquierda.<br>Si consigue colocar el Barco, quedará registrado
    * en la Lista de Barcos del Tablero(modelo).
    * Este método se apoya en los métodos <i>comprobarHorizontalDerecha()</i> y
    * <i>comprobarHorizontalIzquierda()</i>
    * @param codigo String para indicar tipo de Barco
    * @param origen Celda a partir de la cuál se intentará colocar el Barco.
    * @return <i>True</i> si se colocó con éxito, <i>False</i> en caso contrario
    */
    private boolean colocarHorizontal(String codigo, Celda origen) {
    //Calculamos casillas restantes necesarias según tipo de barco
    int casillas = 0;
    switch(codigo) {
    case "PA": //PortaAviones 5 casillas, necesitamos 4 más la Celda origen
    casillas = 4;
    break;
    case "AZ": //Acorazado 4 casillas, 3 más queremos
    casillas = 3;
    break;
    case "SM": //Submarino 3 casillas, 2 más
    casillas = 2;
    break;
    case "DT": //Destructor 2 casillas, 1 más
    casillas = 1;
    break;
    }

    if (comprobarHorizontalDerecha(origen, casillas)) {
    //Colocamos Barco desde la casilla indicada, hacia derecha
    Barco barco = new Barco(codigo);
    int y = origen.coord.y;
    int x = origen.coord.x;
    for (int i = y; i <= (y + casillas); i++)
    barco.addCoordenada(new PuntoXY(x, i)); //Añadimos coordenada al Barco
    /*
    * Barco construido, lo añadimos a la lista de Barcos del Tablero.
    * Este método para añadir, se encarga también de actualizar
    * la matriz del Tablero poniendo valor 1 ahí donde ocupa el Barco.
    */
    tablero.addBarco(barco);
    panelTablero.setTableroModelo(); //Actualizamos la Vista para mostrar nuevo Barco
    return true;
    }
    else if (comprobarHorizontalIzquierda(origen, casillas)) {
    Barco barco = new Barco(codigo);
    int y = origen.coord.y;
    int x = origen.coord.x;
    for (int i = y; i >= (y - casillas); i--)
    barco.addCoordenada(new PuntoXY(x, i));

    tablero.addBarco(barco);
    panelTablero.setTableroModelo(); //Actualizamos la Vista para mostrar nuevo Barco
    return true;
    }
    else {
    JOptionPane.showMessageDialog(null, "No hay espacio disponible en esta horizontal"
    + "\nPruebe otra Celda como origen","Colocar Barco", JOptionPane.WARNING_MESSAGE);
    return false;
    }
    }

    Y los submétodos de apoyo.
    comprobarHorizontalDerecha():
    Código: [Seleccionar]
    /**
    * Comprueba si hay espacio en la horizontal derecha a partir
    * de la Celda considerada como origen.<br>
    * @param origen Celda en la que se ha clickado
    * @param casillas Cantidad de casillas necesarias además de la
    * ocupada por la Celda origen.
    * @return <i>True</i> si hay espacio libre, <i>False</i> si no hay espacio.
    */
    private boolean comprobarHorizontalDerecha(Celda origen, int casillas) {
    int y = origen.coord.y;
    int x = origen.coord.x;
    if ((7 - y) >= casillas) {
    for (int i = y; i <= (y + casillas); i++)
    if (tablero.tablero[x][i] != 0)
    return false;

    return true;
    }
    else
    return false;
    }

    Y comprobarHorizontalIzquierda():
    Código: [Seleccionar]
    /**
    * Comprueba si hay espacio en la horizontal izquierda a partir
    * de la Celda considerada como origen.<br>
    * @param origen Celda en la que se ha clickado
    * @param casillas Cantidad de casillas necesarias además de la
    * ocupada por la Celda origen.
    * @return <i>True</i> si hay espacio libre, <i>False</i> si no hay espacio.
    */
    private boolean comprobarHorizontalIzquierda(Celda origen, int casillas) {
    int y = origen.coord.y;
    int x = origen.coord.x;
    if ((y - 0) >= casillas) {
    for (int i = y; i >= (y - casillas); i--)
    if (tablero.tablero[x][i] != 0)
    return false;

    return true;
    }
    else
    return false;
    }

    Bien, con todos estos métodos, el usuario player1 ya puede ir colocando sus barcos donde quiera y con la orientación que escoja.

    ¿Qué ocurre cuando ya ha colocado todos los barcos que se le permiten según el nivel de dificultad? Lo vemos a continuación.
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

Kabuto

  • Moderador Global
  • Avanzado
  • *******
  • Mensajes: 364
    • Ver Perfil
Re:juego de batalla naval en netbeans con swing
« Respuesta #13 : 29 de Junio 2020, 20:16 »
¿Qué ocurre cuando ya ha colocado todos los barcos que se le permiten según el nivel de dificultad?

Recordemos este trozo de código del método colocarBarco():
Código: [Seleccionar]
if (limiteBarcos == 0) {
listaBarcos.setEnabled(false);
panelTablero.desactivarListener();
botonTerminar.setEnabled(true);
}
La lista de barcos seleccionables queda desactivada.
Los listeners de las celdas también.
Y se activa el botón "Terminar" para que el player1 ponga fin al proceso y dar paso al player2 para que ponga sus propios barcos.

Este botón, es el mismo para ambos Player, pero lo que será distinto será el ActionListener que tendrá para cada uno.

Volvamos a la clase BattleShip para hacer memoria.

Cuando el player2 se logueaba, se llamaba a un método llamado colocarBarcos() a quien le pasábamos un int, con el 1 indicábamos que era player1 quien coloca barcos y con valor 2 para el player2.

En ambos casos se abre el JDialog llamado DialogoCrearBarcos, lo que cambia principalmente es el tablero (modelo) asociado a este dialogo (cada Player tiene su propio Tablero) y también la acción que asociamos al botón "Terminar" de este dialogo.
Código: [Seleccionar]
public void colocarBarcos(int numPlayer) {
if (numPlayer == 1) {
crearBarcos = new DialogoCrearBarcos(null, true, dificultad, tablero1);
crearBarcos.botonTerminar.addActionListener(new AccionColocarBarcoPlayer2());
}
else {
crearBarcos = new DialogoCrearBarcos(null, true, dificultad, tablero2);
crearBarcos.botonTerminar.addActionListener(new AccionComenzarJuego());
}

crearBarcos.setVisible(true);
}

Cuando el player1 termina de colocar su barcos, ya puede pulsar el botón "Terminar" y ejecutar la acción llamada AccionColocarBarcoPlayer2
¿Y que hace esta acción?
Pues poca cosa en realidad:
- Cierra el dialogo de colocar barcos del player1, quién ya ha terminado.
- Muestra un mensaje informando de que ahora es el player 2 quien colocará barcos.
- Llama de nuevo al método colocarBarcos(), pero esta vez le pasamos valor 2 para indicar que ahora es el jugador2 quien colocará barcos en su Tablero.

Código: [Seleccionar]
private class AccionColocarBarcoPlayer2 implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {

//Cerramos el Dialogo que ha usado Player1
crearBarcos.dispose();
//Abrimos uno nuevo para Player2
JOptionPane.showMessageDialog(null, "A continuación colocará Barcos el jugador: "
+ jugador2.getNombre(), "Colocar Barcos", JOptionPane.INFORMATION_MESSAGE);
colocarBarcos(2);

}
}

Cuando el jugador2 termine de colocar barcos y pulse el botón "Terminar", entonces se ejecutará una acción distinta llamada AccionComenzarJuego.
Porque es ahora, en este momento, cuando ha de comenzar la batalla naval.

¿Y que hace esta acción? Pues ahora mismo cierra el dialogo de crear barcos para jugador2 y muestra un simple mensaje.
Código: [Seleccionar]
private class AccionComenzarJuego implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
crearBarcos.dispose();
JOptionPane.showMessageDialog(null, "Comienza Battleship");
}
}

Ahora tenemos pendiente pensar en como será la parte del programa donde los jugadores podrán dispararse a sus barcos.
Pero antes, tenemos que pensar en otra cosa.

Se supone que durante la partida los barcos se han de recolocar solos en posiciones al azar cada vez que un Barco resulta hundido.
Habrá que escribir un código para realizar esto. Incluso, podríamos ofrecer la posibilidad al usuario de colocar barcos al azar durante los diálogos de colocar barcos, por si no quiere perder tiempo decidiendo él donde colocarlos manualmente.

En fin, esto será el paso siguiente a realizar.
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

Kabuto

  • Moderador Global
  • Avanzado
  • *******
  • Mensajes: 364
    • Ver Perfil
Re:juego de batalla naval en netbeans con swing
« Respuesta #14 : 01 de Julio 2020, 21:24 »
Veamos como "AutoColocar" barcos, de manera que sea el sistema quien coloque Barcos al azar.
Esto debe ocurrir durante la partida, cada vez que un Player logure hundir un barcor del rival.
Pero también vamos a ofrecerlo como opción extra a la hora de colocar barcos en el tablero.

Puesto queremos disfrutar de esta opción desde dos ámbitos distintos, vamos a crear el código dentro de la clase PanelTablero y no desde la clase DialogoCrearBarcos ni tampoco desde la previsible (aún no escrita) clase DialogoJugar

Se me ocurre que, para darle un poquito de "vistosidad", podemos mostrar una barra de progreso que avance por cada barco colocado al azar, además de ir actualizando el PanelTablero (Vista) cada vez que un barco se coloca.

Para poder mostrar esto en tiempo real vamos a necesitar un "thread", crear un hilo paralelo a la ejecución del programa que se encargue de colocar barcos y actualizar la interfaz.

Así que, dentro de PanelTablero, vamos a crear una clase interna llamada BarcosRandom que va a heredar de la clase Thread, para así poder crear con ella un hilo de ejecución.

Sus atributos serán un JProgressBar, un ArrayList con la lista de barcos que hay que colocar, un objeto de la clase Random para cuando necesitemos valores numéricos al azar

Va a tener dos constructores.
Uno de ellos recibirá por parámetro el ArrayList con los barcos a colocar. Este será el constructor que invocaremos mientras la partida está en curso y un barco ha sido hundido.
El programa ha de recolocar los Barcos restantes de los jugadores, por eso recibirá un lista de dichos Barcos.

El otro constructor, recibirá el valor de la dificultad del juego. Este constructor es el que se invocará cuando el jugador esté colocando Barcos.
Como no vamos a tener una lista de Barcos concreta, se creará una lista al azar, según el nivel de dificultad actual.

Esta clase tendrá dos métodos.
Uno se llamará crearLista() y será quien confeccione la lista al azar según la dificultad.
Para los niveles EASY y NORMAL no se requiere una selección al azar, ya que usan todos los barcos (EASY repite un destructor)
Para los otros niveles, se selecciona al azar un código de una lista de códigos de barcos y se crea un nuevo Barco con él. Si estuviera repetido, se descarta y se pide otro código al azar.

El otro método, será sobreescribir el método run().
Este método lo heredamos de la clase Thread y es donde se escribe el código que ha de ocurrir mientras dure el hilo paralelo.
Este hilo lo primero que hará será construir un JDialog muy sencillo y sobrio, no tendrá ni botones ni barra superior. Tan solo mostrará el JProgressBar durante el tiempo que dure el proceso de colocar barcos al azar, apenas unos segundos.
Una vez construido y mostrado el dialogo con la barra de progreso, comienza un bucle que se repetirá mientras queden barcos en la lista pendientes de colocar.
Dentro del bucle seleccionaremos una Celda al azar para que sea la celda "origen" y una orientación al azar.
Intentaremos colocar el barco en el origen, si no se puede, se repite el proceso con un otra celda y orientación.
Si se consigue colocar, borramos este Barco de la lista, aumentamos el progreso de la barra, hacemos una pequeña pausa de 1 segundo para que el usuario vea dicho progreso (si no todo ocurre demasiado rápido) y repetimos proceso con el siguiente Barco de la lista.

Una vez la lista ha quedado vacía, el Dialogo con la barra progreso desaparece y este hilo llega a su fin, por lo tanto, "muere".

Esta es la clase BarcosRandom
Código: [Seleccionar]
private class BarcosRandom extends Thread{

private JProgressBar barraProgreso;
private ArrayList<String> listaBarcos;
private Random azar;

public BarcosRandom(ArrayList<String> listaBarcos) {
barraProgreso = new JProgressBar();
azar = new Random();
this.listaBarcos = listaBarcos;
//Tamaño de la lista barcos decide el máximo progreso de la barra
barraProgreso.setMaximum(this.listaBarcos.size());
}

public BarcosRandom(int dificultad) {
barraProgreso = new JProgressBar();
azar = new Random();
listaBarcos = new ArrayList<String>();
crearLista(dificultad);
barraProgreso.setMaximum(this.listaBarcos.size());
}

@Override
public void run() {
JPanel panel = new JPanel();
panel.add(barraProgreso);
TitledBorder bordeTitulo = new TitledBorder("Colocando Barcos al azar");
bordeTitulo.setTitleFont(new Font("Verdana", Font.ITALIC, 18));
bordeTitulo.setTitlePosition(TitledBorder.CENTER);
panel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(10, 10, 10, 10),
BorderFactory.createCompoundBorder(bordeTitulo, BorderFactory.createEmptyBorder(20, 20, 20, 20))));

Frame parent = null; //Necesario para lograr que la barra progreso sea visible
JDialog mostrarBarra = new JDialog(parent, false); //Modal ha de ser false, de lo contrario el hilo no actua
mostrarBarra.add(panel);
mostrarBarra.setUndecorated(true);
mostrarBarra.pack();
mostrarBarra.setLocationRelativeTo(null);
mostrarBarra.setVisible(true);

while(!listaBarcos.isEmpty()) {
//Celda al azar
Celda origen = celdas[azar.nextInt(8)][azar.nextInt(8)];
//Orientacion al azar
int orientacion = azar.nextInt(2);
if (colocarBarco(listaBarcos.get(0), orientacion, origen)) {
barraProgreso.setValue(barraProgreso.getValue() + 1);
listaBarcos.remove(0);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

//Barcos colocados
mostrarBarra.dispose();

}

Y con esto los barcos quedan colocados al azar.
¿Y cómo hacemos lo de colocarlos? Para colocarlos manualmente escribimos unos métodos en la clase DialogoCrearBarcos, ya sabéis, colocarVertical(), colocarHorizontal(), etc...

¿Podemos acceder a esos métodos desde la clase donde nos encontramos ahora?
La respuesta es no, además, tal cuál están escritos no nos sirven para el contexto actual. Requieren unas ligeras modificaciones, pero por lo demás, nos habrían servido.
¿Qué hacemos entonces? Pues, aunque no es lo ideal, los vamos a copiar y pegar dentro de la clase PanelTablero
Esto implica duplicar código que es casi idéntico, no es buena práctica de programación y llegados a este punto parece que el error ha sido escribir esos métodos dentro de la clase DialogoCrearBarcos.
Deberían haber sido escritos y pensados para que formasen parte de PanelTablero desde el principio y haberlos hecho para sirvieran tanto para colocar barcos manualmente como en automático.
Pero ahora no vamos a acometer este cambio, vamos a ir por lo fácil, y vamos a copiar y pegar todos esos métodos, y haremos unos cambios muy ligeros (como no mostrar ningún JOptionPane cuando no puedan colocar barcos) para que el proceso quede automatizado.
Así que ahora la clase PanelTablero va a tener estos métodos, que como hemos dicho, son una copia ligeramente modificada de los mismos que tenemos en DialogoCrearBarcos

Código: [Seleccionar]
/*
* Los métodos que vienen a continuación son los mismos
* escritos para la clase DialogoCrearBarcos, con algunas
* variaciones de cara a la automatización del proceso.
*/
private boolean colocarBarco(String codigo, int orientacion, Celda celda) {

if (orientacion == 0) //Vertical
return colocarVertical(codigo, celda);
else
return colocarHorizontal(codigo, celda);
}

private boolean colocarVertical(String codigo, Celda origen) {
//Calculamos casillas restantes necesarias según tipo de barco
int casillas = 0;
switch(codigo) {
case "PA": //PortaAviones 5 casillas, necesitamos 4 más la Celda origen
casillas = 4;
break;
case "AZ": //Acorazado 4 casillas, 3 más queremos
casillas = 3;
break;
case "SM": //Submarino 3 casillas, 2 más
casillas = 2;
break;
case "DT": //Destructor 2 casillas, 1 más
casillas = 1;
break;
}
//Intentamos colocar hacia abajo primero, y si no, hacia arriba
if (comprobarVerticalAbajo(origen, casillas)) {
//Colocamos Barco desde la casilla indicada, hacia abajo
Barco barco = new Barco(codigo);
int y = origen.coord.y;
int x = origen.coord.x;
for (int i = x; i <= (x + casillas); i++)
barco.addCoordenada(new PuntoXY(i, y)); //Añadimos coordenada al Barco
/*
* Barco construido, lo añadimos a la lista de Barcos del Tablero.
* Este método para añadir, se encarga también de actualizar
* la matriz del Tablero poniendo valor 1 ahí donde ocupa el Barco.
*/
tablero.addBarco(barco);
setTableroModelo(); //Actualizamos la Vista para mostrar nuevo Barco
return true;
}
else if (comprobarVerticalArriba(origen, casillas)) {
Barco barco = new Barco(codigo);
int y = origen.coord.y;
int x = origen.coord.x;
for (int i = x; i >= (x - casillas); i--)
barco.addCoordenada(new PuntoXY(i, y));

tablero.addBarco(barco);
setTableroModelo(); //Actualizamos la Vista para mostrar nuevo Barco
return true;
}
else
return false;
}

private boolean comprobarVerticalAbajo(Celda origen, int casillas) {
int y = origen.coord.y;
int x = origen.coord.x;
if (7 - x >= casillas) {
//Hay espacio hacia abajo, pero hay que comprobar si está libre.
for (int i = x; i <= (x + casillas); i++)
if (tablero.tablero[i][y] != 0)
return false; //Hay otro barco, no se puede colocar
//Si bucle for no retorna false, es que hay hueco libre
return true;
}
else
return false; //No hay tablero suficiente
}

private boolean comprobarVerticalArriba(Celda origen, int casillas) {
int y = origen.coord.y;
int x = origen.coord.x;
if (x - 0 >= casillas) {
for (int i = x; i >= (x - casillas); i--)
if (tablero.tablero[i][y] != 0)
return false;

return true;
}
else
return false;
}

private boolean colocarHorizontal(String codigo, Celda origen) {
//Calculamos casillas restantes necesarias según tipo de barco
int casillas = 0;
switch(codigo) {
case "PA": //PortaAviones 5 casillas, necesitamos 4 más la Celda origen
casillas = 4;
break;
case "AZ": //Acorazado 4 casillas, 3 más queremos
casillas = 3;
break;
case "SM": //Submarino 3 casillas, 2 más
casillas = 2;
break;
case "DT": //Destructor 2 casillas, 1 más
casillas = 1;
break;
}

if (comprobarHorizontalDerecha(origen, casillas)) {
//Colocamos Barco desde la casilla indicada, hacia derecha
Barco barco = new Barco(codigo);
int y = origen.coord.y;
int x = origen.coord.x;
for (int i = y; i <= (y + casillas); i++)
barco.addCoordenada(new PuntoXY(x, i)); //Añadimos coordenada al Barco
/*
* Barco construido, lo añadimos a la lista de Barcos del Tablero.
* Este método para añadir, se encarga también de actualizar
* la matriz del Tablero poniendo valor 1 ahí donde ocupa el Barco.
*/
tablero.addBarco(barco);
setTableroModelo(); //Actualizamos la Vista para mostrar nuevo Barco
return true;
}
else if (comprobarHorizontalIzquierda(origen, casillas)) {
Barco barco = new Barco(codigo);
int y = origen.coord.y;
int x = origen.coord.x;
for (int i = y; i >= (y - casillas); i--)
barco.addCoordenada(new PuntoXY(x, i));

tablero.addBarco(barco);
setTableroModelo(); //Actualizamos la Vista para mostrar nuevo Barco
return true;
}
else
return false;
}

private boolean comprobarHorizontalIzquierda(Celda origen, int casillas) {
int y = origen.coord.y;
int x = origen.coord.x;
if ((y - 0) >= casillas) {
for (int i = y; i >= (y - casillas); i--)
if (tablero.tablero[x][i] != 0)
return false;

return true;
}
else
return false;
}

private boolean comprobarHorizontalDerecha(Celda origen, int casillas) {
int y = origen.coord.y;
int x = origen.coord.x;
if ((7 - y) >= casillas) {
for (int i = y; i <= (y + casillas); i++)
if (tablero.tablero[x][i] != 0)
return false;

return true;
}
else
return false;
}

Bien, ya tenemos un hilo que coloca barcos al azar. ¿Y desde donde lanzamos este hilo?
Vamos a añadir un botón al dialogo de crear barcos, vámonos a su clase y añadimos un nuevo botón en los atributos llamado "botonAuto"

Código: [Seleccionar]
public class DialogoCrearBarcos extends JDialog{

//Modelo
private Tablero tablero;
private int dificultad; //Determina cuantos barcos se colocan
private int limiteBarcos;
//Vista
public PanelTablero panelTablero;
private JComboBox<String> listaBarcos;
public JButton botonTerminar;
private JButton botonAuto; //Para colocar barcos automáticamente
private JButton botonCancelar;

La interfaz gráfica de este dialogo está dividida en tres paneles: norte, centro y sur.
En "sur" es donde tenemos los botones, pues ahí es donde inicializamos y colocamos este botón.
Y además, le añadimos un ActionListener directamente.
Este ActionListener se activa cuando el usuario pulsa el botón de colocar barcos automáticamente y lo que hace es:
- Eliminar cualquier barco que haya colocado el jugador previamente.
- Invoca a un método de PanelTablero llamado autoColocar() recibiendo el nivel de dificultad por parámetro. Este método, luego lo veremos, es quien pone en marcha el Thread que coloca Barcos
- Mientras los barcos se colocan, se desactivan las celdas, el botón "autocolocar" (una vez activado ya no es necesario) y también el JComboBox de la lista de barcos para que el usuario ya no interactue con ellos.
-Activamos el botón "Terminar", para que el usuario pueda aceptar los barcos colocados automáticamente.

Así queda la subclase PanelSur que compone la interfaz del DialogoCrearBarcos.

Código: [Seleccionar]
private class PanelSur extends JPanel {

public PanelSur() {
botonTerminar = new JButton("Terminar");
botonTerminar.setEnabled(false);
botonAuto = new JButton("AutoColocar");
botonAuto.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
tablero.resetTablero(); //Eliminamos lo que haya colocado el jugador
panelTablero.setTableroModelo();
//Comienza el sistema automático
panelTablero.autoColocar(dificultad);
panelTablero.desactivarListener();
botonAuto.setEnabled(false);
listaBarcos.setEnabled(false);
botonTerminar.setEnabled(true);
}
});
botonCancelar = new JButton("Cancelar");
botonCancelar.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
cerrarDialogo();
}
});

JPanel izq = new JPanel();
izq.add(botonTerminar);
JPanel cent = new JPanel();
cent.add(botonAuto);
JPanel der = new JPanel();
der.add(botonCancelar);

setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
add(izq); add(cent); add(der);
setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
}
}

Llegados a este punto quiero comentar el detalle de un problemita con el tema de que la barra de progreso quede por encima del dialogo para crear barcos.
Para que quede por encima, tuve que cambiar el parámetro "modal" a false cuando invocamos este dialogo desde la clase BattleShip
Código: [Seleccionar]
public void colocarBarcos(int numPlayer) {
if (numPlayer == 1) {
crearBarcos = new DialogoCrearBarcos(null, false, dificultad, tablero1);
crearBarcos.botonTerminar.addActionListener(new AccionColocarBarcoPlayer2());
}
else {
crearBarcos = new DialogoCrearBarcos(null, false, dificultad, tablero2);
crearBarcos.botonTerminar.addActionListener(new AccionComenzarJuego());
}

crearBarcos.setVisible(true);
}

No es la solución que me gusta, pero no vale la pena invertir tiempo en encontrar otra.

Dicho esto, hemos visto que en el ActionListener del botón "AutoColocar" llamamos a un método para que active el hilo paralelo.
Este método, está en la clase PanelTablero y tiene dos versiones.
Una versión recibe la dificultad, para que a partir de ella se confeccione una lista de barcos. Esta es la que usamos mientras colocamos barcos.

Código: [Seleccionar]
/**
* Pone en marcha el hilo paralelo que se encarga de configurar el tablero
* con barcos al azar.<br>
* La lista de barcos a colocar se confecciona en función del valor de la
* dificultad.<br>
* Este método será invocado cuando el jugador está colocando barcos.
* @param dificultad Valor int con la dificultad actual.
*/
public void autoColocar(int dificultad) {
new BarcosRandom(dificultad).start();
}

La otra versión, es la que usaremos cuando se inicia la partida y recibe la lista de barcos del jugador.
Código: [Seleccionar]
/**
* Pone en marcha el hilo paralelo que se encarga de configurar el tablero
* con barcos al azar.<br>
* Este método será invocado cuando la batalla está en marcha y se ha hundido
* el barco de algún jugador.
* @param listaBarcos Un ArrayList con los códigos de los Barcos a colocar.<br>
*/
public void autoColocar(ArrayList<String> listaBarcos) {
new BarcosRandom(listaBarcos).start();
}

Y con todo esto, ya podemos colocar barcos al azar automáticamente como podemos apreciar en este vídeo:
https://youtu.be/7GMRRxkYKx8
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

 

Esto es un laboratorio de ideas...
Aprender a programar

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".