Plazo de entrega: Hasta el domingo 8 de noviembre de 2020 a las 23.59. |
Peso relativo de está práctica en la nota de prácticas: 25% |
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). |
---|
En caso de duda, consulta primero la sección ‘Aclaraciones’ al final de este enunciado. La iremos actualizando de vez en cuando. |
Esta práctica ampliará la anterior:
Para poder tener tableros 3D y 2D, así como barcos y aviones de diferentes formas, y al mismo tiempo evitar la duplicación de nuestro código, implementaremos una jerarquía de clases (ver el diagrama de clases más abajo), algunas de las cuales serán abstractas. La mayor parte del código que maneja la lógica del juego (añadir barcos o aviones, dispararles, etc.) estará en esas clases abstractas. Estas clases utilizarán los métodos de sus subclases para implementar la lógica del juego sin tener que preocuparse por el tipo de tablero (2D o 3D) y el tipo de nave (barco o avión) con el que trabajan.
Estos son los diagramas de clases UML que representan las clases en nuestro modelo; los métodos en cursiva son métodos abstractos:
En las siguientes secciones se describen todos los métodos que tienes que modificar o implementar desde cero para esta práctica. No se describirán los atributos o relaciones como 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 un atributo, o asignan un nuevo valor a un atributo. Tendrás que decidir cuándo usar copia defensiva en un setter o getter mirando el diagrama de clase.
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.
Crearemos tres nuevos paquetes:
model.ship
para el tablero 2D y los barcos;model.aircraft
para el tablero 3D y los aviones;model.exceptions
para las clases que tratan de las excepciones.Te recomendamos que implementes tu solución en el mismo orden que se sigue en este documento.
A partir de este trabajo práctico manejaremos las situaciones de error por medio de excepciones. Cuando se produce una excepción, se altera el flujo normal de ejecución del programa: el método que se está ejecutando aborta su ejecución y devuelve el control al método que lo invocó. Este método puede capturar y manejar la situación de error o abortar su ejecución y devolver el control al método que lo invocó. Si la excepción no es manejada por ningún método en la secuencia de invocaciones de métodos, el programa se aborta.
En Java, las excepciones se heredan de la clase java.lang.Exception. Crearemos una superclase abstracta con el nombre BattleshipException de la que heredarán el resto de excepciones. Todas las clases relacionadas con las excepciones pertenecerán al paquete model.exceptions.
Cada nueva clase de excepción tendrá un método getMessage(-) que devolverá una cadena que describirá el motivo por el que se lanza la excepción. El texto particular utilizado para el mensaje depende de ti: las pruebas de evaluación no asumirán ninguna cadena concreta para el mismo; sin embargo, se recomienda incluir la mayor cantidad de información posible para facilitar la identificación del error. Como todas las excepciones tienen que ver con las coordenadas, BattleshipException tendrá un atributo para almacenar una referencia a la coordenada que causa la situación de error. Esta coordenada será recibida por el constructor y almacenada en un atributo para que getMessage(-) pueda utilizarla.
El nombre y el propósito de las excepciones que deben ser creadas para esta práctica son:
Si un método pudiera lanzar, según estas instrucciones, dos o más excepciones de la lista anterior, la excepción que aparezca primero en la lista será la que se lance; en otras palabras, tu código tiene que tratar las situaciones de error en el mismo orden que el definido por la lista.
Eclipse probablemente mostrará una advertencia sobre un campo undeclared field serialVersionUID al definir las clases de excepción (por ejemplo, “The serializable class MyException does not declare a static final serialVersionUID field of type long”). Puedes seguir la recomendación de Eclipse y añadir este campo a tu clase:
private static final long serialVersionUID = 1L;
Alternativamente, puedes añadir la siguiente anotación justo antes de la línea donde se declara la clase:
@SuppressWarnings("serial")
public class MyException extends Exception {
...
}
En esta práctica, sólo el método main
o las pruebas unitarias capturan las excepciones lanzadas por los métodos descritos en las secciones siguientes. Sin embargo, muchas de estas excepciones serán capturadas por métodos de algunas de las nuevas clases en prácticas posteriores.
Tendremos una superclase abstracta con el nombre Coordinate que mantendrá la mayor parte del código que escribiste para esta clase en las prácticas anteriores. Esta clase tendrá dos subclases diferentes: Coordinate2D y Coordinate3D.
public abstract class Coordinate {
...
}
components
.this
). Por ejemplo: (1, 4, 2) + (2, 3) = (3, 7, 2)
.protected
.null
, en ese caso se debe lanzar la excepción NullPointerException. La comprobación y lanzamiento de la excepción puede hacerse fácilmente de la siguiente manera:Objects.requireNonNull(c);
Coordinate2D pertenecerá al paquete model.ship. Heredará de Coordinate y tiene que implementar los métodos que aparecen en el diagrama UML.
Coordinate3D pertenecerá al paquete model.aircraft. Como Coordinate2D, hereda de Coordinate y tiene que implementar los métodos que aparecen en el diagrama UML. La implementación de sus métodos es similar a su implementación en Coordinate2D pero extendiéndolos a una tercera dimensión:
(1, 1, 4)
A menudo es muy útil tener un método que encapsula la creación de objetos. Esto se lleva a cabo generalmente implementando un método factoría. Para ello, añade la nueva clase CoordinateFactory a tu implementación. Esta clase sólo tiene un método estático, createCoordinate(-), que recibe un número variable de argumentos de tipo int y devuelve una coordenada 2D o 3D, dependiendo de cuántos argumentos reciba. La signatura del método es la siguiente:
public static Coordinate createCoordinate(int... coords) {
...
}
Dentro del método, coords
será un vector. Si la cantidad de elementos en este vector está por debajo de 2 o por encima de 3, el método lanzará la excepción IllegalArgumentException. Recuerda que cada array tiene un atributo length
que almacena la cantidad de elementos del array.
Tendremos una superclase abstracta con el nombre Craft de la que heredarán dos clases abstractas, Ship y Aircraft. Los barcos y aviones específicos que se utilicen heredarán de una de estas dos clases abstractas.
Todo el código que tenías en la clase Ship en la práctica anterior estará en la clase Craft, que se encuentra en la parte superior de la jerarquía. Lo único que se mueve hacia abajo de la jerarquía es la forma (shape
) de las diferentes naves y aviones.
Vamos a extraer una superclase con el nombre Craft usando la opción Refactor proporcionada por Eclipse. Para extraer esta superclase haz lo siguiente:
Como resultado tendremos dos clases, Craft y Ship, con Ship heredando de Craft. Si se mira la línea donde se declara la clase Ship, se verá que se utiliza la palabra reservada extends
:
public class Ship extends Craft {
...
}
abstract
.shape
a protected
y elimina su inicialización. A partir de ahora se inicializará en el constructor de las subclases.super()
.Ship tendrá un total de cuatro subclases, todas pertenecientes al paquete model.ship: Battleship, Carrier, Cruiser y Destroyer. Haz lo siguiente:
super()
. Después, tienen que asignar memoria e inicializar el atributo shape
(ver abajo).Estos son los símbolos para representar los diferentes tipos de barcos, copialos de esta descripción a tu código:
Battleship
: O
Carrier
: ®
Cruiser
: Ø
Destroyer
: Ω
El atributo shape
debe ser inicializado en el constructor de cada subclase. Lo que cambia entre las subclases son las posiciones que tienen uno y cero. Puedes descargar la inicialización del atributo shape
para cada barco (y avión) desde aquí.
Aircraft tendrá un total de tres subclases, todas pertenecientes al paquete modelo.aircraft: Bomber, Fighter y Transport. Como las subclases de Ship estas subclases tienen que inicializar el atributo shape
en su constructor (puede descargar la inicialización del atributo shape
desde aquí). Sus constructores también pasarán la orientación, el símbolo utilizado para representarlos (ver más adelante) y su nombre (el de la clase) a su superclase usando super()
.
Estos son los símbolos para representar los diferentes tipos de aviones, copialos de esta descripción a tu código:
Bomber
: ⇶
Fighter
: ⇄
Transport
: ⇋
Tendremos una superclase abstracta con el nombre Board que mantendrá la mayor parte del código de la clase Board de la práctica 2. Esta clase tendrá dos subclases diferentes: Board2D y Board3D. Los métodos que se implementarán en estas subclases son checkCoordinate(-) y show(-): su implementación depende del tipo de tablero (2D o 3D).
Lo que sigue es una explicación de cómo extraer la superclase Board
abstract public boolean checkCoordinate(Coordinate c);
public abstract String show(boolean unveil);
Estos dos métodos se implementarán en Board2D y Board3D.
Board_SEPARATOR
con valor '|'
.board
para que utilice naves (Craft
) en lugar de barcos (Ship
):private Map<Coordinate,Craft> board;
@Override
para decirle al compilador que implementan los métodos heredados de Board:@Override
public boolean checkCoordinate(Coordinate c) {
...
}
La nueva clase Boarda3D es análoga a la Board2D pero trabajando con coordenadas del tipo Coordinate3D. Esta clase hereda de Board y tiene que implementar su constructor y los métodos checkCoordinate(-) y show(-):
Este método es similar al de Board2D pero teniendo en cuenta que el tablero tiene tres dimensiones. Podemos pensar en un tablero 3D como una pila de tableros 2D; al representar el tablero 3D como una cadena, cada tablero 2D de esa pila se representará uno tras otro. Por ejemplo, un tablero 3D con tamaño 8 puede ser como una pila de 8 tableros 2D y será representada como (con unveil=false
):
????????|????????|????????|????????|????????|????????|????????|????????
????????|????????|??? ????|????????|????????|????????|????????|????????
????????|????????|???•????|????????|????????|????????|????????|????????
????????|????????|??•••???|????????|????????|????????|????????|????????
????????|????????|???•????|????????|????????|????????|????????|????????
????????|????????|????????|????????|????????|????????|????????|????????
????????|????????|????????|????????|????????|????????|????????|????????
????????|????????|????????|????????|????????|????????|????????|????????
donde las coordenadas (3, 4, 2), (2, 3, 2), (3, 3, 2), (3, 2, 2), (4, 3, 2)
han sido alcanzadas y la coordenada (3, 1, 2)
fue disparada y el resultado fue AGUA.
Descarga este archivo con una clase que contiene un programa principal y copialo en la carpeta ‘src/mains’ de tu proyecto.
Puedes usar MainP3.java como un ejemplo inicial muy simple de una partida parcial usando las clases implementadas en esta práctica. Ten en cuenta que este código está lejos de ser exhaustivo y sólo explora un subconjunto muy pequeño de todas las situaciones posibles que pueden ocurrir en una partida. El archivo output-p3.txt contiene un ejemplo de la salida de este programa.
Aquí tienes los test de la práctica 2 adaptados a esta práctica. Copia la carpeta model
que se crea al descomprimirla en la carpeta test
de tu proyecto.
Aquí puedes descargar los pre-tests. Cópialos en la carpeta model
en el directorio test
de tu proyecto.
Hay algunas pruebas que deben ser completadas en cada archivo (busca el comentario //TODO
). También se usarán para calificar tu práctica, así que vale la pena intentar completarlas para que puedas obtener una buena nota. Lo más probable es que se te pida que escribas algunas pruebas unitarias en el examen práctico, así que asegúrate de que sabes cómo hacerlo.
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.
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.
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.
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-p3.tgz model
Sube este fichero prog3-battleship-p3.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 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". |