Quiero explicar de forma pormenorizada los métodos de la clase
Tablero.
Aunque ya incorporan comentarios, a ver si puedo explicarlos mejor.
El primero es el método
insertarRectangulo()Recibe un Rectángulo he intentará insertarlo en el Tablero.
Lo primero que hace es comprobar a que jugador pertenece este Rectángulo.
Si es del Jugador 1, buscará donde insertarlo comenzando desde la esquina superior izquierda del tablero, es decir, las coordenadas 0,0 de la matriz.
Si es del Jugador 2, comenzará a buscar hueco por el lado opuesto, por la esquina inferior derecha.
Los bucles que recorren la matriz ya descartan las últimas posiciones donde es evidente que no va a caber, por eso en las condiciones se tiene en cuenta el alto y ancho del Rectángulo.
Por cada posible coordenada válida, se graba dentro del objeto Rectángulo y este se transfiere a un segundo método de apoyo llamado
insertar() a quien además le indicamos si el Jugador tiene ya Rectángulos en el tablero o no.
Indicar esto es importante porque si el Jugador no tiene Rectángulos (porque este es el primero o porque el rival se los ha eliminado todos) entonces no hay que comprobar si el Rectángulo queda anexionado a un "hermano".
De lo contrario, si habrá que hacer esta comprobación.
Si la inserción ha tenido éxito, se guarda este Rectángulo en el ArrayList del jugador ys e retorna true.
Si no hay éxito, se vuelve a repetir el proceso con las siguientes coordenadas disponibles.
Si se agotan todas las coordenadas sin éxito, se retornara false para que el programa principal sepa que no hay forma de insertar este Rectángulo.
public boolean insertarRectangulo(Rectangulo rect) {
if (rect.getJugador() == 1) {
//Intentarmos inserciones comenzando por la esquina superior izquierda del tablero
for (int a = 0; a <= (tablero.length - rect.getAltura()); a++)
for (int l = 0; l <= (tablero[0].length - rect.getAncho()); l++) {
//Establecemos las posiciones que ha de ocupar el rectangulo en el tablero
int[] posiciones = new int[] {a, l};
rect.setPosiciones(posiciones);
//Intentamos insercion
if (insertar(rect, rectangulosJug1.isEmpty())) {
rectangulosJug1.add(rect);
return true;
}
}
//Bucles finalizados sin insercion exitosa. No se puede insertar este Rectángulo
return false;
}
else { //Se trata del jugador 2
//Intentaremos inserciones comenzando por la esquina inferior derecha del tablero
for (int a = (tablero.length - rect.getAltura()); a >= 0; a--)
for (int l = (tablero[0].length - rect.getAncho()); l >= 0; l--) {
int[] posiciones = new int[] {a, l};
rect.setPosiciones(posiciones);
if (insertar(rect, rectangulosJug2.isEmpty())) {
rectangulosJug2.add(rect);
return true;
}
}
return false;
}
}
Vamos a ver ahora ese método de apoyo llamado
insertar()Lo primero que hace es crear una copia del Tablero. Como dije antes, los intentos de inserción se hacen en una copia del Tablero original.
Luego preguntamos al Rectángulo que posiciones (coordenadas) se le han asignado y ahí es donde intentaremos la inserción.
Este proceso consiste en recorrer el area que va a ocupar el Rectángulo según sus coordenadas y dimensiones.
Si encontramos valores 0 en dicha area, es que de momento no se está solapando y podemos ocuparla.
En cuanto encontremos un valor distinto de 0, esto implica que el area está ocupada por otro Rectángulo, así que retornamos false para indicar que aquí estamos solapandonos y se tendrá que intentar en otro sitio.
Si conseguimos colocar el Rectángulo sin solaparnos, aún queda otra comprobación que hacer.
Si este Rectángulo es el "único" que el Jugador tiene en el Tablero, no hay que comprobar nada, de lo contrario, hay que comprobar si Rectángulo ha quedado anexionado a un "hermano" para validar la inserción.
Esta comprobación la hacen otros métodos que veremos después.
Si la inserción queda validada, la copia del Tablero donde hemos hecho los cambios pasa a ser el original. Si no, dicha copia simplemente quedará descartada.
Por último destacar la importancia de usar try catch en este proceso, ya que hay riesgo de que intentemos escrituras fuera de los limites de la matriz y esto provocaría una excepción que pondría fin a nuestro programa.
private boolean insertar(Rectangulo rect, boolean unicoEnTablero) {
hacerCopia(); //Hacemos copia de seguridad tablero actual
int[] pos = rect.getPosiciones();
try {
for (int a = pos[0]; a < pos[0] + rect.getAltura(); a++)
for (int l = pos[1]; l < pos[1] + rect.getAncho(); l++) {
if (copiaTablero[a][l] == 0) //Espacio disponible
copiaTablero[a][l] = rect.getJugador();
else //Espacio ocupado, imposible insertar
return false;
}
/*
* Ha sido insertado sin solapamientos. Pero si este NO es el único
* Rectángulo del jugador, hay que comprobar además si ha quedado anexo a otro
* Rectángulo del mismo jugador
*/
if (!unicoEnTablero)
if (!comprobarAnexion(rect)) { //No ha quedado anexo
return false;
}
} catch(Exception e) { //Hay riesgo de salirnos de las dimensiones del tablero
return false;
}
//Si se han completado los bucles, es que la insercion ha sido correcta
restaurarCopia(); //Pasamos la copia sobre la que hemos trabajado al tablero principal
return true;
}
El método que comprueba la anexión, lo que hace es ver qué informan otros cuatro submétodos. Cada uno de estos submétodos comprueba si hay un Rectángulo "hermano" alrededor del Rectángulo que estamos intentado insertar.
Si alguno de ellos devuelve true, la inserción quedará validada:
private boolean comprobarAnexion(Rectangulo rect) {
return (compruebaDerecha(rect) || compruebaIzquierda(rect) ||
compruebaArriba(rect) || compruebaAbajo(rect));
}
Vamos a ver ahora uno de esos 4 métodos, que servirá para entenderlos todos.
Lo que hacen es pedir al Rectángulo sus posiciones asignadas (ya dije que este atributo era muy útil) y a partir de estas coordenadas comprobarán que valores hay en los costados adyacentes.
En el caso de la Izquierda, lo que hace es recorrer la Altura del Rectángulo, pero desviado una posicion a la izquierda.
Si un Rectángulo en por ejemplo las coordenadas 4,7 y un Altura de valor 5...
Lo que hará será recorrer las coordenadas:
Y comprobar si alguna de esas coordenadas tiene un valor coincidente con el número de Jugador "dueño" de este Rectángulo.
Si encuentra al menos uno, se devolverá true confirmando la correcta anexión a un Rectángulo "hermano".
Si no encuentra ninguno, retornará false para informar que, al menos por este costazo izquierdo, no hay ningún "hermano" al que anexionarse.
De nuevo es importante usar try cactch. Y esto me ha traido de cabeza, porque la posible Excepción en realidad sería capturada desde el método
insertar() evitando que el programa se detenga.
Pero si no se pone aquí también, en caso de excepción este método termina abruptamente sin terminar de comprobar todas las posibles posiciones.
Esto hace que durante el juego el Tablero no colocase Rectángulos en algunas zonas cercanas a los limites de la matriz, a pesar de que en realidad si era posible una inserción exitosa.
Esto como digo me traía de cabeza hasta que me he dado cuenta de que podía ser importante capturar la excepción dentro de este método para hacer que continue con el reto de posiciones posibles.
private boolean compruebaIzquierda(Rectangulo rect) {
int[] posi = rect.getPosiciones();
for (int a = posi[0]; a < posi[0] + rect.getAltura(); a++)
try {
if (copiaTablero[a][posi[1]-1] == rect.getJugador())
return true; //Hay al menos una celda contigua a un Rectángulo "hermano"
}catch(Exception e) {
//Nada que hacer aquí, seguiremos probando en otras posiciones
}
return false; //No se encontraron celdas "hermanas"
}
El método
eliminarRectángulo()Parece que tiene mucho código, pero es fácil de entender. Si el rival tiene Rectángulos insertados (por lo tanto, guardados en su ArrayList) pues escogemos uno al azar, usamos sus coordenadas para llenar su area de valor 0 y lo eliminamos del ArrayList.
Listo, Rectángulo eliminado.
public boolean eliminarRectangulo(int jugador) {
if (jugador == 1) { //Jugador1 elimina rectangulo de Jugador2
if (rectangulosJug2.isEmpty())
return false; //No hay rectangulos para eliminar
else {
int azar = (int)(Math.random() * rectangulosJug2.size());
int[] posi = rectangulosJug2.get(azar).getPosiciones();
for (int a = posi[0]; a < posi[0] + rectangulosJug2.get(azar).getAltura() ; a++)
for (int l = posi[1]; l < posi[1] + rectangulosJug2.get(azar).getAncho(); l++)
tablero[a][l] = 0;
rectangulosJug2.remove(azar);
System.out.printf("Jugador %d ha eliminado un Rectangulo en %d,%d de su oponente\n",
jugador, posi[0], posi[1]);
return true;
}
}
else {
if (rectangulosJug1.isEmpty())
return false; //No hay rectangulos para eliminar
else {
int azar = (int)(Math.random() * rectangulosJug1.size());
int[] posi = rectangulosJug1.get(azar).getPosiciones();
for (int a = posi[0]; a < posi[0] + rectangulosJug1.get(azar).getAltura(); a++)
for (int l = posi[1]; l < posi[1] + rectangulosJug1.get(azar).getAncho(); l++)
tablero[a][l] = 0;
rectangulosJug1.remove(azar);
System.out.printf("Jugador %d ha eliminado un Rectangulo en %d,%d de su oponente\n",
jugador, posi[0], posi[1]);
return true;
}
}
}
Por último vemos el método
indicarGanador()No tiene mucho misterio. Sumamos las areas de los Rectángulos de cada Jugador y el que sume más, es el ganador.
Para ser más informativos de cara al usuario, hacemos un cálculo del porcentaje de Tablero que ha logrado ocupar cada Jugador.
public void indicarGanador() {
double areaTablero = tablero.length * tablero[0].length;
double areaJug1 = 0;
double areaJug2 = 0;
for (Rectangulo rect: rectangulosJug1)
areaJug1 += rect.getArea();
for (Rectangulo rect: rectangulosJug2)
areaJug2 += rect.getArea();
areaJug1 = areaJug1 * 100 / areaTablero;
areaJug2 = areaJug2 * 100 / areaTablero;
System.out.printf("\nArea ocupada por Jugador1: %.2f\n", areaJug1);
System.out.printf("Area ocupada por Jugador2: %.2f\n", areaJug2);
if (areaJug1 == areaJug2)
System.out.println("Los JUGADORES han empatado.");
else if (areaJug1 > areaJug2)
System.out.println("Ha ganado el Jugador 1");
else
System.out.println("Ha ganado el Jugador 2");
}
Falta por ver la última clase, la que tiene el main.
La vemos en el siguiente post