Programación 3

Universidad de Alicante, 2020–2021

Práctica 5

Plazo de entrega: hasta el miércoles 23 de diciembre de 2020 a las 23:59.
Peso relativo de esta práctica en la nota de prácticas: 15%

Añadiendo reflexión y genericidad a la implementación del juego Hundir la flota

Introducción

El diseño propuesto para la práctica anterior tenía un problema que se resolverá en la actual: las factorías de naves y visualizadores no estaban completamente abiertas a la extensión, ya que añadir un nuevo tipo de nave o visualizador implicaría añadir una nueva instrucción condicional o una nueva cláusula case a una instrucción switch. En esta práctica, este problema se arreglará introduciendo convenientemente la reflexión. Además, el modelo se ampliará con un sistema de clasificación que será capaz de reflejar las puntuaciones obtenidas después de jugar cada partida y clasificarlas de acuerdo con dos criterios diferentes: la cantidad de posiciones alcanzadas y el valor de las diferentes naves destriudas.  

Diagrama de clases

Este es el diagrama de clases UML que representa las nuevas clases de nuestro modelo:

En las siguientes secciones se indican los métodos que hay que modificar o implementar desde cero para esta práctica. Los atributos o relaciones no se explicarán, ya que ya se muestran en el diagrama UML. Los elementos que no cambian con respecto a la práctica anterior no se explicarán de nuevo. Tampoco se comentarán los setters o getters que simplemente devuelven el valor actual de una propiedad, o establecen una propiedad a un nuevo valor. Tendrás que decidir cuándo usar copia defensiva en un setter o getter mirando el diagrama de clases.

Cuando se indique, tu código debe comprobar que los valores de los argumentos pasados a un método son correctos. Los argumentos que son referencias no necesitan ser comprobados, a menos que se indique lo contrario.

Estructura de paquetes y directorios

Crearemos dos nuevos paquetes:

  • model.score para las clases que implementan el sistema de puntuación y clasificación;
  • model.exceptions.score para una nueva clase de errores relacionados con la puntuación/clasificación.

Hoja de ruta

Se recomienda encarecidamente que implementes tu solución en el mismo orden que se sigue en este documento.

  1. Introducir la reflexión
  2. Implementar el sistema de puntuación
  3. Introducir los cambios que se explican a continuación en las clases existentes

Reflexión

Tienes que reemplazar las instrucciones condicionales o el switch existente en CraftFactory.createCraft(-) y VisualiserFactory.createVisualiser(-) con código que utilice la reflexión para crear una instancia de un objeto cuyo nombre de clase coincida con el parámetro; tendrás que calificar completamente el nombre de la clase a cargar añadiendo convenientemente el nombre del paquete. Los métodos devolverán null como antes si el parámetro no contiene un nombre válido de nave/visualizador.

Los métodos de factoría están ahora abiertos a la extensión pero cerrados a la modificación.

A tener en cuenta: Como las naves pueden estar en dos paquetes diferentes (model.ship y model.aircraft), a partir de ahora el método CraftFactory. createCraft(-) se llamará siempre con el nombre del subpaquete (ship o aircraft) seguido de un punto y el nombre de la clase a cargar (por ejemplo, ship.Carrier). De esta forma, se podrá calificar completamente el nombre de la clase a cargar, independientemente de si es un barco o un avión.

Sistema de clasificación y genericidad

Clase Score

La nueva clase abstracta Score encapsula el concepto de la puntuación de una partida. Como el objetivo es crear un sistema de clasificación, las puntuaciones deben ser comparables. Esto se logrará haciendo que Score implemente la interfaz de Java Comparable, en particular Comparable<Score<T>>, lo que obliga a la clase a implementar el método compareTo(-):

public abstract class Score<T> implements Comparable<Score<T>>{   
   ...   
}

El tipo T determinará el tipo de puntuación a definir.

Las subclases no abstractas de Score implementarán el método score(-) que, cuando sea apropiado, incrementa el atributo score; valores más altos de esta puntuación significarán un mejor rendimiento del jugador. El atributo score se utilizará en el método Score.compareTo(-) para comparar dos puntuaciones diferentes.

Método Score(IPlayer)

Constructor que almacena el jugador recibido como argumento e inicializa el atributo score a cero.

Excepciones: Si la referencia a IPlayer es null, la excepción NullPointerException debe ser lanzada.

Método toString()

Devuelve una representación en forma de cadena de caracteres del score (sin ‘’ al final). Para ello toma el nombre del jugador del atributo IPlayer y la puntuación del atributo score. Ejemplo de salida:

Julia (PlayerFile): 7

Método compareTo(Score<T>)

Este método compara dos puntuaciones por su valor. Como queremos implementar un orden descendente de las puntuaciones para la clasificación, dados dos objetos de tipo Score<T>, el que represente la mayor puntuación será considerado el más pequeño. Si ambos objetos tienen la misma puntuación el método devolverá lo que devuelva String.compareTo(·) al comparar los nombres de los jugadores. Como resultado obtendremos un orden de las puntuaciones: de la mayor a la menor. Echa un vistazo a la descripción del método compareTo(-) en la documentación de la API de Java 8 para saber lo que compareTo(-) debe devolver en cada caso.

Clase HitScore

Esta clase representa un score basado en CellStatus y simplemente lleva la cuenta de las posiciones alcanzadas por el jugador. Debe ser declarado como sigue:

public class HitScore extends Score<CellStatus> {
   ...
}

Método HitScore(IPlayer)

Constructor que simplemente pasa el argumento recibido al constructor de su superclase.

Método score(CellStatus)

Este método incrementa el atributo score en 1 si el CellStatus recibido como argumento es igual a HIT o DESTROYED.

Clase CraftScore

Esta clase representa un anotador basado en Craft y suma el valor de las diferentes naves destruidas por el jugador. Se declara de forma análoga a la de HitScore pero usando Craft en lugar de CellStatus.

Método CraftScore(IPlayer)

Constructor que simplemente pasa el argumento recibido al constructor de su superclase.

Método score(Craft)

Este método incrementa el atributo score con el valor de la nave recibida como argumento. Consulta los cambios a introducir en la clase Craft y sus subclases más abajo.

Clase Ranking

Para tener un sistema de clasificación basado en las puntuaciones obtenidas por los diferentes jugadores, se añadirá una nueva clase RankingRanking es una clase genérica que tiene un parámetro de tipo restringido para evitar que puntuaciones de diferentes tipos coexistan en una clasificación particular:

class Ranking<ScoreType extends Score<?>> {
   ...
}

Por lo tanto, un objeto de tipo Ranking tendrá que ser creado de una de dos maneras, ya sea como

Ranking<HitScore> rh = new Ranking<>();

o como

Ranking<CraftScore> rc = new Ranking<>();

Por consiguiente, un error del compilador impedirá que el código del cliente añada puntuaciones de diferentes criterios al mismo objeto de clasificación (pruébalo cuando ya tengas estas clases implementadas). Nótese que a partir de Java 7 los argumentos de tipo requeridos para invocar al constructor de una clase genérica pueden ser reemplazados por un conjunto vacío (el par de corchetes angulares se llama el diamante) siempre que el compilador pueda inferir los tipos a partir del contexto.

Método Ranking()

Este método sólo inicializa el atributo scoreSet (de clase TreeSet); scoreSet debe ser declarado de la siguiente manera:

private SortedSet<ScoreType> scoreSet;

y se inicializa así en el constructor:

scoreSet = new TreeSet<>();

Asegúrate de que entiendes las líneas anteriores.

Método addScore(ScoreType)

Este método añade el objeto pasado como parámetro al conjunto scoreSet. Obsérvese que scoreSet es un conjunto ordenado y, en consecuencia, se invocará el método compareTo(-) de Score para tener ordenados los elementos del conjunto.

Método getWinner()

Devuelve el primer elemento del conjunto scoreSet.

Excepciones: Si el conjunto está vacío, la excepción EmptyRankingException será lanzada. Esta nueva excepción pertenece al paquete model.exceptions.score y hereda de BattleshipException. Impleméntala como el resto de excepciones en las prácticas anteriores.

Método Ranking.getSortedRanking()

Un simple getter.

Cambios a introducir en las clases ya existentes

Esta sección describe los cambios menores que debe hacer en el código de la práctica anterior.

Clase Craft y sus subclases

Las diferentes naves y aviones tendrán un valor que será utilizado por los sistemas de puntuación. Para esto tienes que:

  • Añadir a Craft un método abstracto getValue(), retornando y int y sin parámetros.
  • Implementar el método getValue() en las subclases de Ship y Aircraft. El valor que deben devolver se muestra en la siguiente tabla:
Nombre de la clase Valor
model.ship.Destroyer 3
model.ship.Cruiser 5
model.ship.Battleship 6
model.ship.Carrier 8
model.aircraft.Fighter 10
model.aircraft.Bomber 15
model.aircraft.Transport 18

Interfaz IPlayer

Añade el nuevo método getLastShotStatus().

Clase PlayerRandom

Estos son los cambios que se debe introducir:

  • El método PlayerRandom.putCrafts() necesita ser modificado para incluir el nombre del subpaquete (ship o aircraft) en las diferentes llamadas a CraftFactory.createCraft(-).
  • En PlayerRandom.nextShoot(), el estado devuelto por Board.hit(-) debe ser almacenado en el atributo lastShotStatus.
  • Implementar el getter PlayerRandom.getLastShotStatus().

Clase PlayerFile

Cambios que se debe introducir:

  • En PlayerFile.nextShoot(), el estado devuelto por Board.hit(-) debe ser almacenado en el atributo lastShotStatus. Si no se realiza ningún disparo, por ejemplo porque se ha leido el comando exit desde el fichero de entrada o porque no hay más líneas que leer, lastShotStatus debe ponerse a null.
  • Implementar el getter PlayerFile.getLastShotStatus().

Además, los archivos de entrada usados con PlayerFile deben ser actualizados. Puedes descargar una versión actualizada de los archivos playerfile-john.txt y playerfile-mary.txt que incluyen el prefijo no común cuando se especifica el barco/avión en un comando put.

Clase Game

Estos son los cambios que se introducirán:

  • En el constructor de la clase se deben inicializar los atributos hitScore1, hitScore2, craftScore1 y craftScore2.
  • En Game.playNext(), después de que el jugador dispare, el correspondiente hitScore se actualiza llamando a HitScore.score(-) con el estado del último disparo realizado por el jugador (utilice el método IPlayer.getLastShotStatus() para obtener este estado). Además, si este estado es DESTROYED, actualizar el correspondiente craftScore llamando a CraftScore.score(-) con la nave que fue destruida.
  • Implementar el método Game.getScoreInfo(). Debería devolver una cadena con información sobre las puntuaciones de los dos jugadores. Aquí hay un ejemplo de la salida:
Player 1
HitScore: John (PlayerFile): 9
CraftScore: John (PlayerFile): 11
--------------
Player 2
HitScore: Mary (PlayerFile): 10
CraftScore: Mary (PlayerFile): 6

Programa principal

MainP5.java puede ser usado como un ejemplo de una partida usando las clases implementadas en esta asignación. Ten en cuenta, sin embargo, que este código explora un pequeño subconjunto de todas las posibles situaciones en una partida; por tanto, es muy recomendable que escribas tus propios tests para probar tu código. Recuerda actualizar los dos archivos utilizados por MainP5.java (files/playerfile-john.txt y files/playerfile-mary.txt); consulta la sección Class PlayerFile. Puedes descargar la salida esperada de MainP5.java de aquí.

Pruebas unitarias

Las pruebas de la práctica anterior deben ejecutarse correctamente para esta práctica.

Documentación

Tu código fuente ha de incluir todos los comentarios de Javadoc que ya se indicaban en el enunciado de la primera práctica. No incluyas el HTML generado por Javadoc en el fichero comprimido que entregues.

Requisitos mínimos para evaluar la práctica

  • La práctica debe poder ejecutarse sin errores de compilación. 
  • Ninguna operación debe emitir ningún tipo de comentario o mensaje por salida estándar, a menos que se indique lo contrario. Evitad también los mensajes por la salida de error.

  • Se debe respetar de manera estricta el formato del nombre de todas las propiedades (públicas, protegidas y privadas) de las clases, tanto en cuanto a ámbito de visibilidad como en cuanto a tipo y forma de escritura. En particular se debe respetar escrupulosamente la distinción entre atributos de clase y de instancia, así como las mayúsculas y minúsculas en los identificadores.
  • La práctica debe estar suficientemente documentada, de manera que el contenido de la documentación que se genere mediante la herramienta javadoc sea significativo.  

Entrega de la práctica

La práctica se entrega en el servidor de prácticas del DLSI.

Debes subir allí un archivo comprimido con tu código fuente (sólo archivos .java). En un terminal, sitúate en el directorio ‘src’ de tu proyecto Eclipse e introduce la orden

tar czvf prog3-battleship-p5.tgz model

Sube este fichero prog3-battleship-p5.tgz al servidor de prácticas. Sigue las instrucciones de la página para entrar como usuario y subir tu trabajo.

Evaluación

La corrección de la práctica es automática. Esto significa que se deben respetar estrictamente los formatos de entrada y salida especificados en los enunciados, así como la interfaz pública de las clases, tanto en la signatura de los métodos (nombre del método, número, tipo y orden de los argumentos de entrada y el tipo devuelto) como en el funcionamiento de éstos. Así, por ejemplo, el método model.Coordinate(int, int) debe tener dos enteros como argumento y guardarlos en los atributos correspondientes.

Tienes más información sobre el sistema de evaluación de prácticas en la ficha de la asignatura.

Además de la corrección automática, se va a utilizar una aplicación detectora de plagios. Se indica a continuación la normativa aplicable de la Escuela Politécnica Superior de la Universidad de Alicante en caso de plagio:

“Los trabajos teórico/prácticos realizados han de ser originales. La detección de copia o plagio supondrá la calificación de”0" en la prueba correspondiente. Se informará la dirección de Departamento y de la EPS sobre esta incidencia. La reiteración en la conducta en esta u otra asignatura conllevará la notificación al vicerrectorado correspondiente de las faltas cometidas para que estudien el caso y sancionen según la legislación vigente".

Aclaraciones

  • Aunque no se recomienda, se pueden añadir los atributos y métodos privados que se considere oportuno a las clases. No obstante, eso no exime de implementar TODOS los métodos presentes en el enunciado, ni de asegurarse de que funcionan tal y como se espera, incluso si no se utilizan nunca en la implementación de la práctica. 

  • Cualquier aclaración adicional aparecerá publicada en el foro de Moodle para las prácticas; por favor, suscríbete a él.