Programación 3

Universidad de Alicante, 2020–2021

Práctica 4

Plazo de entrega: Hasta el domingo 6 de diciembre de 2020 a las 23.59.
Peso relativo de está práctica en la nota de prácticas: 30%

Battleship: Interfaces, juego y entrada/salida

Introducción

Esta práctica amplia la anterior añadiendo dos interfaces:

  • una llamada IPlayer para simular un jugador disparando al tablero de un segundo jugador;
  • otra llamada IVisualiser para visualizar los dos tableros usados en el juego del Hundir la flota, un tablero para cada jugador.

La interfaz IPlayer será implementada en esta práctica por dos clases, cada una representa un tipo diferente de jugador. La interfaz IVisualiser será implementada por dos clases que producirán una representación diferente de los tableros. Ten en cuenta, sin embargo, que dado que la mayor parte de tu código utilizará los tipos de interfaz y no una subclase en particular, se podrían añadir a la aplicación nuevos jugadores o visualizadores sin problemas, con pequeños cambios en el código existente.

Como complemento a estas interfaces, y con el objetivo de instanciar fácilmente los objetos de las clases que las implementan, se crearán nuevos métodos factoría. Además, pueden surgir nuevas situaciones de error y, en consecuencia, se añadirán nuevas excepciones al modelo.   Finalmente, implementaremos una clase Game para manejar el juego.

Tendrás que consultar la documentación de la API de Java 8 para saber cómo trabajar con algunas de las clases que usaremos en esta práctica.

Diagrama de clase

Estos son los diagramas de clase UML que representan las clases en 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 tres nuevos paquetes:

  • model.io para las interfaces y las clases que las implementan;
  • model.exceptions.io para una nueva clase para los errores relacionados con la entrada/salida;
  • model.io.gif para dos clases, que no tendrás que implementar, y que usarás para generar GIF animados.

Hoja de ruta

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

  1. Implementa las clases para las nuevas excepciones.
  2. Implementa el método de factoría en CraftFactory.
  3. Implementa la interfaz IPlayer, las clases que la implementan y los métodos de factoría correspondientes.
  4. Implementa la interfaz IVisualiser, las clases que lo implementan y los correspondientes métodos de factoría.
  5. Implementa la clase Game.

Excepciones

Hay dos nuevas excepciones:

  • model.exceptions.io.BattleshipIOExcepción se usará para los errores de entrada/salida. Su constructor almacena el mensaje que se pasa como parámetro en el atributo de instancia message, que luego será usado por getMessage(). Todas las situaciones que manejen esta excepción serán comentadas cuando sea apropiado.

  • model.exceptions.CoordinateException es una nueva clase abstracta. Esta clase es idéntica a la clase BattleshipException de la práctica anterior. En esta práctica BattleshipException es simplemente una clase abstracta sin métodos y sin atributos.

Como en la práctica anterior, a menos que se indique explícitamente, los métodos que reciben referencias de objetos como parámetros no necesitan comprobar si son nulos.

Clase CraftFactory

Esta clase pertenece al paquete model y simplemente proporciona un método de factoría para crear objetos de las diferentes clases que heredan de Ship y Aircraft.

Método createCraft(String, Orientation)

El primer parámetro es el tipo de nave (nombre de la clase que se utilizará para crearla); el segundo es la orientación que se utilizará para crear la nave.

Este método crea la nave correspondiente y la devuelve; si no se conoce el tipo de nave que se va a crear, se devuelve null.

Interfaz IPlayer y clases relacionadas

Las clases que implementan la interfaz IPlayer deben contener un método putCrafts(·) para agregar naves a un tablero y un método nextShoot(·) para disparar a un tablero. Ambos métodos pueden lanzar la excepción BattleshipIOException.  

Clase PlayerFile

Esta clase implementa la interfaz IPlayer. Permite jugar al juego leyendo comandos de un archivo de texto que contendrá una línea para cada comando diferente. Cada comando recibe un número diferente de parámetros separados por uno o más caracteres de espacio en blanco. La siguiente lista ilustra los comandos válidos en un tablero 2D (su significado es obvio y no se explica; obsérvese de nuevo que puede aparecer cualquier número de caracteres de espacio en blanco entre un comando y sus parámetros):

put  Cruiser NORTH -1 2
put Destroyer  EAST 3 3
put Carrier WEST 6   4
put Battleship SOUTH 1 0
endput
shoot 0 3
shoot 1 3
shoot 0  1
exit

Algunos comandos no válidos son “PUT Cruiser NORTH 0 1” (los comandos válidos distinguen entre mayúsculas y minúsculas), “put Cruiser NORTH 0” (falta un parámetro), “shooooot 0 3”, o “shoot a b c”.

En un tablero 3D los comandos válidos se verían de la siguiente manera:

put Cruiser NORTH -1 2 0
endput
shoot 0 1 1
exit

Importante:

  • Después de “endput” no se pueden colocar nuevos comandos “put”.
  • Antes de “endput” no se permiten comandos “shoot”.
  • Cualquier comando después de “exit” es ignorado.
  • Todos los comandos terminan con ‘\n’.

Método PlayerFile(String, BufferedReader)

Este constructor recibe el nombre del jugador y un objeto ya inicializado de tipo BufferedReader y lo almacena en el atributo de instancia br.

Excepciones: Si la referencia al BufferedReader es null, se debe lanzar la excepción NullPointerException.

Método getName()

Este método devuelve una cadena con el nombre del jugador (el recibido en el constructor) y el tipo de jugador. Por ejemplo:

Martin (PlayerFile)

Método putCrafts(Board)

Este método lee los comandos put uno a uno (una línea a la vez) del atributo br (BufferedReader) y ejecuta las acciones correspondientes (Board.addCraft(·)) en el tablero recibido como argumento. Recuerda usar CraftFactory.createCraft(·) para crear barcos y aviones, y CoordinateFactory.createCoordinate(·) para crear nuevas coordenadas.

El método deja de leer los comandos de br cuando se leen los comandos endput o exit o no hay más líneas que leer.

Utiliza String.split(·) para dividir cada línea en tokens como se indica aquí.
Usa BufferedReader.readline(·) como se explica aquí para leer el archivo de entrada línea por línea.
Usa Integer.parseInt(·) para convertir una cadena en un entero. Ten en cuenta que si la cadena pasada como argumento no puede ser analizada como un entero, se lanza la excepción NumberFormatException.

Excepciones: Cualquier excepción lanzada por Board.addCraft(·) debe ser lanzada de nuevo; es decir, las excepciones lanzadas por Board.addCraft(·) no tienen que ser capturadas.

El método lanzará la excepción NullPointerException si el tablero recibido como argumento es null, y la excepción BattleshipIOException en cualquiera de las siguientes situaciones:

  • Ocurre una excepción IOException cuando se lee una línea de br.
  • Se lee un comando diferente de put, endput o exit.
  • La cantidad de parámetros a put no es correcta. Cabe señalar que la orientación debe ser seguida por dos o tres números y que ambas situaciones son correctas. No hay que preocuparse aquí por el tipo de tablero y las coordenadas (2D o 3D) que se utilizan.
  • La orientación no es ninguna de las declaradas en el enum Orientation.
  • Alguno de los parámetros que deben ir después de la orientación no son números.

Método nextShoot(Board)

Este método lee el siguiente comando shoot del atributo br (BufferedReader) y ejecuta la acción correspondiente (Board.hit(·)) en el tablero recibido como argumento.

El método devuelve la coordenada utilizada para golpear el tablero o null si se lee el comando exit o no hay más líneas que leer.

Excepciones: Cualquier excepción lanzada por Board.hit(·) debe ser relanzada; es decir, las excepciones lanzadas por Board.hit(·) no tienen que ser capturadas.

El método lanza la excepción BattleshipIOException en cualquiera de las siguientes situaciones:

  • Ocurre una excepción IOException cuando se lee una línea de br.
  • Se lee un comando diferente de shoot o exit.
  • La cantidad de parámetros a shoot no es correcta. Cabe señalar que el comando shoot debe ir seguido de dos o tres números y que ambas situaciones son correctas. No tienes que preocuparte aquí por el tipo de coordenadas (2D o 3D) que se utilizan.
  • Alguno de los parámetros después de shoot no son números.

Clase PlayerRandom

Esta clase implementa la interfaz IPlayer. Se comporta como un jugador ciego aleatorio, decidiendo aleatoriamente las coordenadas donde se ponen las naves y aviones y las coordenadas a disparar.

Los números aleatorios no son estrictamente posibles con un ordenador si no tiene sensores conectados al mundo exterior. Para asegurar cierta aleatoriedad, se utiliza un semilla para establecer el punto de partida de la fórmula que genera una secuencia de números pseudo-aleatorios (a esta semilla se le suele dar un valor basado en la fecha y hora actuales). Si no se cambia el generador de la secuencia, siempre se recuperará la misma secuencia si se utiliza la misma semilla. Como esta práctica se evaluará automáticamente, la semilla se pasará al constructor para que cada ejecución pueda ser fácilmente reproducida una y otra vez, siempre que la semilla no se cambie.

El JDK proporciona la clase java.util.Random para la generación de números aleatorios. El siguiente código de ejemplo establece la semilla en 12345 y genera un número entero aleatorio entre 0 y 99 (ambos números incluidos):

Random random = new Random(12345);
int r = random.nextInt(100);

Método PlayerRandom(String, long)

Este constructor recibe el nombre del jugador y la semilla que se utilizará para inicializar el atributo de instancia random cuando se llame al constructor de Random.

Método getName()

Este método devuelve una cadena con el nombre del jugador (el recibido en el constructor) y el tipo de jugador. Por ejemplo:

Martin (PlayerRandom)

Método genRandomInt(int, int)

Este método genera un entero aleatorio en el intervalo [min, max) recibido como argumento. Utiliza la siguiente implementación (simplemente copia y pega de este enunciado en tu código):

private int genRandomInt(int min, int max) { 
    return random.nextInt(max-min)+min;
}

Método genRandomCoordinate(Board b, int offset)

Este método genera y devuelve una coordenada aleatoria 2D o 3D, dependiendo del tipo de tablero. Llama al método genRandomInt(·) para generar el valor de los componentes de las coordenadas de la siguiente manera:

genRandomInt(0-offset, b.getSize());

El offset se utiliza para generar coordenadas con componentes negativos para que se puedan utilizar para añadir naves en los márgenes del tablero.

Importante: Nunca generes un nuevo número aleatorio a menos que estés completamente seguro de que se utilizará para crear una coordenada; de lo contrario, las partidas jugadas por tu jugador aleatorio serán diferentes a las de la implementación de referencia y las pruebas utilizadas para calificar tu práctica fallarán. En otras palabras, sólo genera tres componentes cuando el tablero sea del tipo Board3D.

Método putCrafts(Board)

Este método añade los barcos/aviones al tablero con una orientación aleatoria y en una coordenada aleatoria. Los barcos deben ser añadidos en el siguiente orden: Battleship, Carrier, Cruiser, Destroyer. Los aviones deben ser añadidos (sólo si el tablero es del tipo Board3D) en el siguiente orden: Bomber, Fighter, Transport. Cabe señalar que sólo debe añadirse un barco/avión de cada clase.

Para cada barco/avión que se añada el método debe comportarse de la siguiente manera:

  1. Crear el barco/avión con una orientación aleatoria.
  2. Añadir el barco/avión al tablero en una coordenada aleatoria generada con genRandomCoordinate(·) con un offset de Craft.BOUNDING_SQUARE_SIZE; para acceder a Craft.BOUNDING_SQUARE_SIZE tendrás que cambiar su visibilidad de protegida a pública. Puede suceder que un barco/avión no pueda ser añadido al tablero utilizando la coordenada aleatoria generada; en ese caso el método seguirá generando coordenadas aleatorias hasta que el barco/avión a ser añadido pueda realmente ser añadido o la cantidad de coordenadas aleatorias generadas sea igual a 100.

Excepciones: Este método no lanza excepciones.

Método nextShoot(Board)

Este método genera una coordenada aleatoria con genRandomCoordinate(·) y un offset de cero, y la utiliza para disparar al tablero recibido como argumento. El método devuelve la coordenada utilizada.

Excepciones: Cualquier excepción lanzada por Board.hit(·) debe ser relanzada; es decir, las excepciones lanzadas por Board.hit(·) no tienen que ser capturadas.

Clase PlayerFactory

Esta clase simplemente proporciona un método factoría para crear objetos de las diferentes clases que implementan IPlayer.

Método createPlayer(String, String)

El primer parámetro es el nombre del jugador; el segundo se utiliza para crear un jugador del tipo apropiado:

  • Si el segundo parámetro contiene uno de los caracteres ‘.’ (punto), ‘\’ o ‘/’, se supone que contiene el nombre de un archivo que contiene comandos y, en consecuencia, se crea y devuelve un objeto de la clase PlayerFile.
  • Si el segundo parámetro contiene un valor entero largo, se considera como la semilla para el generador de números aleatorios y un objeto de la clase PlayerRandom se crea con él y luego se devuelve. Para comprobar si una cadena contiene un número largo válido, se puede utilizar el método Long.parseLong(·). Puedes crear un método auxiliar (privado) isLong(·) que devuelva un booleano que indique si una cadena contiene un número largo válido o no. 
  • Si no se cumple ninguna de las dos condiciones anteriores, no se crea ningún jugador y se devuelve null.

Excepciones: Este método lanza BattleshipIOException (con un mensaje apropiado) si el objeto de clase BufferedReader a pasar al constructor de PlayerFile no puede ser creado (por ejemplo, porque el fichero de comandos que se especifica no existe).

Interfaz IVisualiser y clases relacionadas

Las clases que implementen esta interfaz deben definir un método show() que será llamado para visualizar el estado actual de la partida y un método close() que será llamado cuando el visualizador ya no se utilice.

Clase VisualiserConsole

Esta clase implementa la interfaz IVisualiser. Nos permite producir una representación de la partida en forma de cadena a través de la salida estándar.

Método VisualiserConsole(Game)

Este constructor sólo almacena el argumento game en el atributo correspondiente.

Excepciones: Lanza la excepción NullPointerException si el game recibido como argumento es null.

Método show()

Llama a game.toString() para obtener una representación en forma de cadena de la partida actual, que se envía a la salida estándar usando System.out.println(·).

Método close()

En esta clase este método no hace nada.

Clase VisualiserGIF

Esta clase implementa la interfaz IVisualiser. Nos permite producir un GIF animado que representa la partida. Para poder generar estas imágenes tienes que:

  1. Descargar la librería de terceros GIF4J, es un archivo JAR. Cópiarlo en una carpeta con el nombre lib dentro de tu proyecto Eclipse y refresca la vista del proyecto. Luego, ve a Project -> Properties, opción Java Build Path, pestaña Libraries y presiona Add JARs…, luego selecciona el archivo gif4j_light_trial_1.0.jar.
  2. Crear un paquete con el nombre model.io.gif, descargar los archivos AnimatedGIF.java y FrameGIF.java y añadirlos al paquete recién creado.

Método VisualiserGIF(Game)

Este constructor almacena el argumento game en el atributo correspondiente e inicializa el atributo AnimatedGIF.

Excepciones: Lanza la excepción NullPointerException si el game recibido como argumento es null.

Método show()

Accede a los dos tableros de la partida por medio del método apropiado de Game y obtiene sus representaciones de cadena usando Board.show(false). Luego procesa estas dos representaciones de cadenas y genera un nuevo frame para ser añadido al GIF animado. Cada frame contendrá los dos tableros separados por una línea gris oscura.

Para generar un nuevo frame haz lo siguiente:

FrameGIF frame = new FrameGIF(w, h*2+1);

donde w es el ancho del frame (la longitud de cada línea en la representación de la cadena de un tablero) y h*2+1 es la altura del frame (dos veces el número de líneas en la representación de la cadena de un tablero más una para la línea que separa los dos tableros). Ten en cuenta que se asume que los dos tableros de la partida son del mismo tamaño.

Usaremos la clase java.awt.Color para indicar los colores de las posiciones del tablero en la imagen que se genera.

El método procesa entonces la representación de la cadena del primer tablero e imprime un cuadrado en gris claro (Color.LIGHT_GRAY) para las posiciones no visibles (Board.NOTSEEN_SYMBOL), un cuadrado en azul (Color.BLUE) para las posiciones de agua (Board.WATER_SYMBOL), un cuadrado en rojo (Color.RED) para las posiciones alcanzadas (Board.HIT_SYMBOL) o destruidas, y un cuadrado en naranja (Color.ORANGE) para el símbolo utilizado para separar los diferentes tableros 2D que tenemos en un tablero 3D.

Aquí tienes un ejemplo que ilustra cómo imprimir un cuadrado en gris claro:

frame.printSquare(column, row, Color.LIGHT_GRAY);

Después de procesar la representación de la cadena del primer tablero, imprime una fila de cuadrados en gris oscuro (Color.DARK_GRAY), y procesa la representación de la cadena del segundo tablero de manera análoga a la del primer tablero.

Por último, añade el frame al GIF animado de la siguiente manera:

agif.addFrame(frame);

A continuación hay un ejemplo de un GIF animado obtenido de una partida con tableros 3D de tamaño 6: animatedgif

Excepciones: Los métodos AnimatedGIF.addFrame(·) y FramGIF.printSquare(·) pueden lanzar la excepción BattleshipIOException; en ese caso debe ser capturada y relanzada como una RuntimeException. Esto es algo que hacemos porque esos métodos no deberían lanzar la excepción BattleshipIOException, a menos que tengamos un error en nuestro código.

Método close()

Este método guarda el GIF generado en un archivo con el nombre output.gif en la carpeta files:

agif.saveFile(new File("files/output.gif"));

Asegúrate de que la carpeta files existe dentro de tu proyecto Eclipse.

Excepciones: Cualquier excepción lanzada por AnimatedGIF.saveFile(·) debe ser relanzada como RuntimeException.

Clase VisualiserFactory

Esta clase simplemente proporciona un método factoría para crear objetos de las diferentes clases que implementan IVisualiser.

Método createVisualiser(String,Game)

Si el valor del primer parámetro es “Console”, crea y devuelve un objeto de la clase VisualiserConsole. Si el valor del primer parámetro es “GIF”, crea y devuelve un objeto de la clase VisualiserGIF. Si el valor del primer parámetro no es ni Console, ni GIF devuelve null.

Clase Game

Esta clase tiene la responsabilidad de jugar una partida del juego de Hundir la flota. La partida la juegan dos jugadores, cada uno poniendo barcos/aviones en sus respectivos tableros y disparando al tablero del oponente. Primero los dos jugadores ponen sus barcos/aviones en sus respectivos tableros; después el juego comienza y los dos jugadores alternativamente disparan al tablero del oponente. El juego acaba cuando uno de los jugadores deja de disparar, o si tras un disparo se han hundido todas las naves de uno de los jugadores.

Usaremos el atributo nextToShoot para saber qué jugador debe disparar a continuación (inicialmente player1); el atributo gameStarted se usa para indicar si el juego ha comenzado o no; el atributo shootCounter se usará para llevar la cuenta de la cantidad total de disparos realizados por los dos jugadores.

Método Game(Tablero, Tablero, IPlayer, IPlayer)

Este constructor almacena los dos tableros y jugadores recibidos como argumentos en los atributos correspondientes de la clase. También establece el flag gameStarted a false.

Exceptions: Lanza la excepción NullPointerException si alguno de sus parámetros es nulo.

Método start()

Este método comienza la partida haciendo que los jugadores pongan sus barcos/aviones en sus tableros. El jugador player1 pone sus barcos/aviones en el tablero board1; el jugador player2 pone sus barcos/aviones en el tablero board2. También inicializa los atributos gameStarted, shootCounter y nextToShoot.

Excepciones: Cualquier excepción lanzada por las llamadas a IPlayer.putCrafts(·) debe ser relanzada como RuntimeException.

Método gameEnded()

Devuelve true si la partida ha terminado. Una partida se considera terminada si se inició y todas las naves de uno de los tableros han sido destruidas.

Método playNext()

Este método hace que el jugador que debe jugar dispare al tablero del oponente. El jugador player1 dispara al tablero board2, y el jugador player2 dispara al tablero board1. Este método devuelve true si el jugador realmente disparó al tablero del oponente (independientemente del resultado, incluso si disparó a una coordenada ya alcanzada), false en caso contrario.

Si el método va a devolver true, actualiza el contador de disparos y el atributo nextToShoot antes de acabar.

Excepciones: si las excepciones BattleshipIOException o InvalidCoordinateException son lanzadas por cualquiera de los métodos utilizados por este método, la excepción es lanzada de nuevo como RuntimeException (y no se realiza ninguna actualización del contador de disparos ni del atributo nextToShoot). Si se lanza la excepción CoordinateAlreadyHitException, se imprime un mensaje a través de la salida estándar; este mensaje debe incluir la cadena Action by, el nombre del jugador que disparó y el mensaje de error de la excepción:

Action by Mary (PlayerRandom)...

Método getPlayerLastShoot()

Este método devuelve el jugador que disparó en último lugar o null si ninguno de los jugadores ha disparado todavía.

Método playGame(IVisualiser)

Este método inicia la partida y juega hasta que la partida termina o el jugador que debe disparar a continuación no tiene más tiros que realizar (Game.playNext() devuelve false).

El método utiliza el visualizador recibido como argumento para mostrar la partida a medida que progresa; para ello llama al método IVisualiser.show(). Nótese que IVisualiser.show() debe ser llamado después de comenzar la partida (y antes de que el primer jugador empiece a disparar) y después de cada llamada a Game.playNext() si devuelve true.

Una vez que la partida ha terminado, o el jugador que debe disparar a continuación no tiene más tiros que realizar, el método debe cerrar el visualizador (IVisualiser.close()).

Método toString()

Este método devuelve una cadena que representa el estado de la partida. Lo que sigue en un ejemplo de la cadena que representa una partida con tableros 2D de tamaño 10:

=== ONGOING GAME ===
==================================
John (PlayerRandom)
==================================
 ??   ?  ?
 ? ???    
 ?   •  ? 
   ? • ?••
    ?•? ? 
   ? ? •  
 ? ?   ? ?
      ?• ?
?      •  
 ?  ΩΩ •? 
==================================
Mary (PlayerRandom)
==================================
  •   ? ? 
  ?      ?
? •   ?   
? ?    ?  
  ?       
 •??     ?
   ?      
   ? ?    
 •?•••? Ω 
    ??  Ω 
==================================
Number of shots: 281

Esta cadena comienza con === GAME NOT STARTED ===, === GAME ENDED === o === ONGOING GAME ===, dependiendo de si la partida no se ha iniciado, ya ha terminado o está en juego. A continuación tiene los tableros de los dos jugadores (junto con el nombre de los jugadores). Después de eso, la cadena incluye el número de tiros realizados hasta ahora.

Si la partida terminó y todas las naves en uno de los tableros fueron destruidas, se debe añadir al final una línea con el nombre del ganador. Esta línea debe ser de la siguiente manera:

Mary (PlayerRandom) wins

Importante: La cadena que representa el juego no debe terminar con ‘\n’.

Programa principal

MainP4.java puede ser usado como ejemplo de partida que usa algunas de las clases implementadas en esta práctica. Ten en cuenta que este código explora un subconjunto muy pequeño de todas las situaciones de juego posibles; por lo tanto, se recomienda que escribas tus propias pruebas para comprobar tu código. El código de MainP4.java utiliza dos archivos (files/playerfile-john.txt y files/playerfile-mary.txt) que puedes descargarte de aquí y de aquí (crea una carpeta normal con el nombre files en la carpeta raíz de tu proyecto Eclipse y copia estos archivos de texto allí). Puedes descargar la salida esperada al ejecutar este programa aquí y la imagen GIF animada que genera aquí.

Pruebas unitarias

Los test de la práctica anterior deben seguir funcionando con el código de esta práctica.

Pruebas previas

Aquí tienes los pre-tests. Copia las carpetas creadas al descomprimirlo en la carpeta 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 merece 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.

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-p4.tgz model

Sube este fichero prog3-battleship-p4.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. 
  • Cómo generar la orientación y las coordenadas en RandomPlayer:
    • La orientación la debéis generar mediante una única llamada al método Random.nextInt(·).
    • Las coordenadas la debéis generar mediante llamada a PlayerRandom.genRandomCoordinate()
    • Generad primero la orientación y después la coordenada
    • En PlayerRandom.genRandomCoordinate(·) generad primero la componente ‘x’, después la componente ‘y’ y finalmente la componente ‘z’; esta última solo si es necesario.
  • Cualquier aclaración adicional aparecerá publicada en el foro de Moodle para las prácticas; por favor, suscríbete a él.