Plazo de entrega: Hasta el domingo 26 de diciembre de 2021 a las 23:59h. |
Peso relativo de está práctica en la nota de prácticas: 10% |
IMPORTANTE: Todos tus ficheros de código fuente deben utilizar la codificación de caracteres UTF-8. No entregues código que use otra codificación (como Latin1 o iso-8859-1). |
El diseño propuesto para la práctica anterior tenía un problema que se resolverá en la actual: la factoría de cazas no estaba completamente abierta a la extensión, ya que añadir un nuevo tipo de caza 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 durante la cada partida y clasificarlas de acuerdo con dos criterios diferentes: la cantidad de victorias y el valor de los diferentes cazas destruidos.
El siguiente diagrama de clases UML representa las clases de nuestro modelo. Solamente se muestran las clases nuevas (que pertenecen todas al paquete model.game.score), y los cambios en las clases de la práctica anterior.
El orden aconsejado para ir construyendo tu práctica es:
Recuerda, ve diseñando pruebas unitarias según vayas terminando cada clase, y utilízalas para depurar tu código antes de pasar a la siguiente clase.
Los cambios que debes hacer en las clases de la práctica anterior son:
En la clase Ship, hay que añadir un argumento Fighter al método updateResults; tendrás que actualizar las llamadas a ese método en Board para que pasen como parámetro el caza destruido
En la clase Fighter debes añadir un método getValue, que debe devolver el valor del caza. Por simplificar, vamos a asumir que el valor del caza es la suma de su velocidad y su ataque
En GameShip tendrás que añadir los atributos de las puntuaciones (WinsScore y DestroyedFightersScore), los getters para esos atributos, y tendrás que añadir un método updateResults que sobreescriba el de la clase padre, que debe hacer lo mismo (llamando al método de la clase padre) y además, cuando el resultado sea 1, debe actualizar las puntuaciones llamando al método score para cada una de ellas
En IPlayer, PlayerFile y PlayerRandom sólo tienes que añadir los getters nuevos, que simplemente llamarán al getter correspondiente de GameShip
En Game.play hay que modificar el bucle de la partida para que muestre al principio de cada jugada los rankings. Para ello es recomendable usar un método privado que cree los rankings (como variables locales), les añada las puntuaciones de cada jugador y los muestre por pantalla (en las salidas de los programas principales puedes ver el formato de salida). Además, cuando acabe el bucle, antes de devolver el ganador, debe mostrar otra vez los rankings, para que aparezcan justo al final.
Tienes que reemplazar las instrucciones condicionales o el switch existente en FighterFactory.createFighter(-) con código que utilice la reflexión para crear una instancia de un objeto cuyo nombre de clase coincida con el parámetro type; tendrás que calificar completamente el nombre de la clase a cargar añadiendo convenientemente el nombre del paquete, es decir, si el nuevo tipo es FWing
, el nombre de la clase será model.fighters.FWing
El método createFighter devolverá null como en la práctica anterior si el parámetro no contiene un nombre válido de caza; esta circunstancia se detectará cuando se lance alguna de las excepciones de los métodos de la API de reflexión, que deben capturarse e ignorarse para al final devolver null
El método createFighter está ahora abierto a la extensión (podemos añadir tipos de cazas sin modificar su código) pero cerrados a la modificación. Los nuevos tipos de cazas podrán utilizarse en la clase PlayerFile, pero no en PlayerRandom, que debe usar solamente los tipos de cazas de la práctica 3.
NOTA: si no lo has hecho ya, te recomendamos que visualices los vídeos sobre reflexión que hay en el Moodle de teoría de la asignatura
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, que será Integer para WinsScore, o Fighter para DestroyedFightersScore.
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.
Constructor que almacena el bando recibido como argumento e inicializa el atributo score a cero.
Devuelve una representación en forma de cadena de caracteres del score (sin ‘\n’ al final). Para ello toma el bando del atributo side y la puntuación del atributo score. Ejemplo de salida para un jugador imperial con puntuación 7:
Player IMPERIAL: 7
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 compareTo(·) al comparar los side de las puntuaciones (el bando IMPERIAL aparecerá antes que el bando REBEL). 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 (aunque igual lo recuerdas de la práctica 2).
Esta clase representa un score basado en Integer y simplemente lleva la cuenta de las victorias obtenidas por la nave del jugador. Debe ser declarado como sigue:
public class WinsScore extends Score<Integer> {
...
}
Constructor que simplemente pasa el argumento recibido al constructor de su superclase.
Este método incrementa el atributo score en 1 si el entero recibido como argumento es 1.
Esta clase representa una puntuación basada en Fighter y suma el valor de los diferentes cazas destruidos por la nave del jugador. Se declara de forma análoga a la de WinsScore pero usando Fighter en lugar de Integer.
Constructor que simplemente pasa el argumento recibido al constructor de su superclase.
Este método incrementa el atributo score con el valor del caza recibido como argumento (si no es null), usando el método Fighter.getValue() descrito anteriormente.
Para tener un sistema de clasificación basado en las puntuaciones obtenidas por los diferentes jugadores, se añadirá una nueva clase Ranking. Ranking 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:
public 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<WinsScore> rh = new Ranking<>();
o como
Ranking<DestroyedFightersScore> 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.
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.
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.
Devuelve el primer elemento del conjunto scoreSet.
Excepciones: Si el conjunto está vacío, la excepción RuntimeException será lanzada.
Un simple getter.
Devuelve una cadena formada por las puntuaciones del ranking, rodeada con |
. Por ejemplo, para un ranking de WinsScore en el que el jugador rebelde lleva 7 victorias y el imperial 6, debería devolver:
| Player REBEL: 7 | Player IMPERIAL: 6 |
Ten en cuenta que la cadena Player REBEL: 7
se genera en el método toString de la clase Score
Los programas principales de la práctica 4 MainP4.java y MainP4min.java son perfectamente válidos para esta práctica, lo que cambia es la salida de ambos por la introducción de los rankings: salida-MainP4-p5.txt y salida-MainP4min-p5.txt
El archivo prog3-ImperialCommander-p5-pretest.tgz contiene una carpeta pretestp5
con un paquete model
con pruebas unitarias, algunas de ellas usan ficheros que están en la carpeta files
, como en la práctica anterior.
Debéis incluir en los ficheros fuente todos los comentarios necesarios en formato Javadoc. Estos comentarios deben definirse para:
No se debe entregar los ficheros HTML que genera Javadoc, y tampoco debes generar la documentación automáticamente con plugins, es una buena práctica generar la documentación a mano, justo en el momento en que se empieza a escribir el método.
En esta práctica crearemos un nuevo paquete:
model.game.score
para las clases relacionadas con las puntuacionesLa práctica se entrega en el servidor de prácticas del DLSI.
Debes subir allí un archivo comprimido que incluya el directorio model y toda la estructura de directorios y ficheros fuente tal y como se especifica en la práctica. En un terminal, sitúate en el directorio ‘src’ de tu proyecto Eclipse e introduce la orden
tar czvf prog3-imperialCommander-p5.tgz model
Sube este fichero prog3-imperialCommander-p5.tgz
al servidor de prácticas. Sigue las instrucciones de la página para entrar como usuario y subir tu trabajo.
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 exactamente dos argumentos de tipo int.
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". |