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
PanelTableroEsta 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:
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:
//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
)
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 PlayerPara 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.
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.
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.
/**
* 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.
//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 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:
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.
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
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.