Plazo de entrega: Hasta el domingo 12 de diciembre de 2021 a las 23:59h. |
Peso relativo de está práctica en la nota de prácticas: 35% |
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). |
Como se puede ver en los ejemplos de tableros, en las coordenadas la x se corresponde con la columna, y la y con la fila
Hemos incluido un nuevo test, MethodsVisibilityTest.java, para comprobar que los métodos que deben ser públicos efectivamente lo son (en las clases principales)
En la jugada improve, el caza puede estar o no en el tablero. Si estuviera en el tablero, habría que quitarlo del tablero antes de mejorarlo, por lo que se puede llamar siempre a removeFighter e ignorar la FighterNotInBoardException que se lanzaría si no está en el tablero.
Hemos actualizado las pruebas unitarias porque los comentarios que indicaban el resultado esperado del test (p.ej. que debia ganar el jugador imperial, que se tenían que generar 960 cazas) no estaban bien. El cambio afecta solamente a algunos comentarios, no al código.
En el método PlayerRandom.nextPlay debe usarse el método GameShip.getFightersId para obtener las listas de cazas que pueden patrullar, se pueden lanzar o se pueden mejorar
Por claridad, hemos añadido un algoritmo en pseudo-código del método Game.play
Hemos añadido un par de pruebas de Game y sus salidas (en un fichero .java nuevo), las tenéis en el archivo prog3-ImperialCommander-p4-moretest.tgz
En esta práctica ampliaremos la práctica 3 con varios tipos de jugadores y unas clases para gestionar el juego. Con respecto a la práctica anterior, solamente hay cambios en las clases Ship y Board, en las que los atributos fleet y board pasan a ser protected porque vamos a derivar un par de clases de ellas que necesitan acceder a esos atributos.
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), y de Ship y Board solamente se muestra el cambio de la visibilidad de los atributos fleet y board. Ten en cuenta que algunas relaciones entre las clases se implementan con atributos (privados todos, en este caso).
En esta práctica se añade una nueva excepción, WrongFighterIdException, que es similar a las de la práctica anterior e irá en su propio paquete model.game.exceptions:
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:
A continuación se describen los métodos de cada una de las clases del modelo que cambian en esta práctica.
Se trata de una subclase de Board que usaremos para el juego. Sus métodos son:
El constructor simplemente llama al constructor de la clase padre con super(…)
Devuelve el número de cazas que hay en el tablero del bando indicado por el argumento side (se usará en la clase Game)
Devuelve una cadena con una representación del tablero. Un ejemplo de tablero de 10x10 con varios cazas sería:
0123456789
----------
0| A AX
1|
2| X
3| A
4|
5| A
6| i Y
7| Y b X
8|
9|
Evidentemente, si el tamaño del tablero es superior a 10 la representación no será tan bonita, pero en este momento no nos preocupa que sea visualmente agradable.
Es una clase para gestionar una nave en el juego. Es una subclase de Ship.
Constructor que lo único que debe hacer es llamar al constructor de la clase padre
Devuelve true si no quedan cazas sin destruir en la flota de la nave, es decir, si no hay cazas o todos los que hay están destruidos; si hay algún caza sin destruir, devolverá false.
Método privado que busca en la flota de la nave un caza cuyo identificador coincida con el argumento id. Si encuentra el caza y no ha sido destruido, lo devuelve; en caso contrario, lanza la WrongFighterIdException.
Devuelve una lista con los identificadores de los cazas (no destruidos) de la flota de la nave:
board
devolverá solamente los identificadores de los cazas que estén en el tablero (es decir, los que tengan posición asignada)ship
, devolverá los identificadores de los cazas que estén en la nave (los que no tengan posición asignada, es decir, los que la tengan a null).En ningún caso se incluirán los cazas destruidos.
Obtiene el caza indicado por el argumento id y lo lanza al tablero b en la coordenada c. Este método debe propagar las excepciones lanzadas por getFighter y Board.launch (para eso solamente hay que incluir las excepciones en la cláusula throws).
Obtiene el caza indicado por id y lo pone a patrullar en el tablero b. Como en el método anterior, propaga las excepciones que se lancen en su interior.
Obtiene el caza indicado por id, lo quita del tablero (si no estaba en el tablero se lanzará la FighterNotInBoardException, que se debe capturar para no hacer nada), y se mejorará el caza, sumándole la mitad de qty al ataque y la otra mitad al escudo; si por ejemplo qty es un valor impar como 67, se añadirá 33 al ataque y 34 al escudo. Propaga la WrongFighterIdException
Interfaz que modela un jugador del juego, que contiene los métodos necesarios para jugar. Como sabes, en el fichero IPlayer.java solamente se indica la signatura de los métodos (y su documentación en Javadoc), la implementación de los métodos debe hacerse en las clases que implementen el interfaz (en esta práctica serán PlayerFile y PlayerRandom), y se debe incluir la cláusula @Override
antes de cada método. Lo que debe hacer cada uno de los métodos es:
Asigna el tablero pasado como parámetro gb al atributo board del jugador
Devuelve la nave del jugador (sin hacer copia defensiva), se usará para los tests
Obtiene una cadena similar a 7/XWing:4/AWing
y llama al método addFighters de la nave del jugador. Más adelante se explica cómo se obtiene dicha cadena en las clases PlayerRandom y PlayerFile.
Llama al método del mismo nombre de la nave del jugador (y devuelve su valor en el caso de isFleetDestroyed)
Devuelve una cadena formada por la cadena que devuelve el método toString de la nave, un cambio de línea y la cadena devuelta por el método showFleet.
Realiza la siguiente jugada del jugador, que puede ser una de estas:
En la descripción de las clases PlayerRandom y PlayerFile se describirá con más detalle el funcionamiento de este método. Devuelve false si el jugador abandona, y true si sigue jugando
Es una clase para simular un jugador que juega al azar. Los métodos que son diferentes a los de otros tipos de jugadores son:
Construye la nave del jugador en función del argumento side (por ejemplo, si side es REBEL creará una nave rebelde con el nombre PlayerRamdom REBEL Ship), e inicializará el atributo numFighters con el valor del argumento numFighters
Construye la cadena para llamar a Ship.addFighters de la siguiente forma:
7/YWing
a la cadenaUna vez construida la cadena, se llama a Ship.addFighters (si la cadena no está vacía, claro)
Realiza un movimiento en el juego, para lo que lo primero que debe hacer es obtener un número aleatorio option entre 0 y 99 (ya sabes cómo).
ERROR:
) y no hacer nada más. La lista que hay que obtener depende de la opción, lógicamente, y el identificador se obtiene a partir de un número aleatorio que sea una posición válida de la lista.si el número option está entre 85 y 98 (ambos incluidos), será un movimiento de mejora, para lo que hay que obtener el identificador del caza y mejorarlo usando el valor option como cantidad (qty)
throw new RuntimeException(e); // 'e' es la excepción capturada
En caso de que el jugador abandone (exit) el método debe devolver false; si el jugador sigue jugando hay que devolver true, aunque se emita un error y realmente no se haga ningún movimiento.
Esta clase permite leer los movimientos de un jugador desde un fichero (o incluso desde la consola).
Constructor que inicializa la nave del jugador (con el nombre PlayerFile IMPERIAL Ship si es una nave imperial), e inicializa el atributo br con el argumento br
Leerá una línea con el método BufferedReader.readLine y la guardará en una cadena, y llamará a Ship.addFighters con esa cadena. La excepción que lanza readline se capturará y relanzará como RuntimeException (es complicado jugar si la primera lectura falla)
Se leerá una línea con readline (y se hará lo mismo que en initFighters si hay una excepción al leer), y se intentará ejecutar el movimiento contenido en la línea, que puede ser:
exit
: el jugador abandona la partida.improve id qty
: donde id y qty son números, para convertir de cadena a número debes usar Integer.parseInt (que lanzará una excepción si no es un número, pero no debes hacer nada al respecto, ni capturarla ni propagarla). Si después de improve no hay exactamente dos cadenas más hay que dar un mensaje de error (que debe comenzar por ERROR:
), y también debe emitirse un mensaje de error si la cantidad qty no es menor que 100; en caso de error no se puede mejorar el caza, por lo que no se hará ninguna mejora, se emitirá el mensaje de error y no se hará nada más. Finalmente, si al mejorar el caza se lanza la WrongFighterIdException se debe capturar y emitir un mensaje de error que incluya el mensaje de la excepción al finalpatrol id
: como en el caso anterior, id es un número. Si después de patrol no hay exactamente una cadena más (la del id), se debe emitir un mensaje de error. También como en el caso anterior, si la llamada al método patrol genera una excepción, se debe capturar y emitir un mensaje de error que incluya el mensaje de la excepción al finallaunch x y
, launch x y id
, launch x y type
: donde x e y son números. Si después de launch no hay 2 o 3 cadenas más, se emitirá un mensaje de error. Si solamente hay 2 cadenas (x e y), se lanzará el primer caza disponible (de cualquier tipo) a la coordenada formada por x e y del tablero. Si hay 3 cadenas, hay que intentar convertir la tercera cadena (la que va después de x e y) a número y capturar la NumberFormatException que lanza Integer.parseInt (sólo en este caso); si no se lanza la excepción, la tercera cadena es un número, y se asumirá que es el identificador de un caza de la nave, en cuyo caso se lanzará dicho caza a la coordenada indicada por x e y. Si no es un número, se asumirá que es un tipo de caza (p.ej. podría ser XWing
) y se lanzará el primer caza disponible de ese tipo a la coordenada indicada por x e y. En todos los casos, las posibles excepciones lanzadas por los métodos (de Ship o GameShip) se capturarán, generando como en los casos anteriores un mensaje de error que incluya el mensaje de la excepción. Algunos ejemplos de movimientos launch serían:launch 7 8 (lanzar el primer caza disponible a la [7,8])
launch 6 7 23 (lanzar el caza con id=23 a la [6,7])
launch 3 2 AWing (lanzar el primer AWing disponible a la [3,2])
(evidentemente, lo de (lanzar el ...
no hay que ponerlo, claro)
Si la línea leida no empieza por ninguno de los movimientos descritos, se mostrará un mensaje de error y no se hará ningún movimiento.
Como en la clase PlayerRandom, en caso de que el jugador abandone (exit) el método debe devolver false; si el jugador sigue jugando hay que devolver true, aunque se emita un error y realmente no se haga ningún movimiento.
Esta clase gestiona una partida entre dos jugadores, uno imperial y otro rebelde. Empieza jugando siempre el jugador imperial, y si hay algún error en el movimiento del jugador, simplemente pierde el turno (no se le vuelve a pedir).
Los métodos de esta clase son los siguientes (puede ser conveniente crear algunos métodos privados):
Constructor que guarda en sus atributos los jugadores que se le pasan como argumentos, crea un tablero de tamaño BOARD_SIZE
y se lo asigna a los jugadores. Si se lanza la InvalidSizeException en el constructor de Board debe relanzarse como una RuntimeException (sería un error de programación, porque el tamaño mínimo es 5 y la constante BOARD_SIZE es 10, que es mayor que 5, luego nunca debería lanzarse esa excepción).
Devuelve el tablero (sin hacer copia defensiva), se usará para los tests
Al principio, inicializa los cazas de los jugadores imperial y rebelde. A continuación comienza el bucle del juego: en cada paso, se mostrará el tablero y la información de las naves de los jugadores en tres momentos:
BEFORE IMPERIAL
),AFTER IMPERIAL, BEFORE REBEL
),AFTER REBEL
)Antes del movimiento de cada jugador se mostrará un prompt que incluirá el bando y el número de cazas de ese bando en el tablero. Por ejemplo, si le toca jugar al jugador rebelde y tiene 3 cazas en el tablero, se mostraría:
REBEL(3):
La partida termina cuando uno de los jugadores abandona (exit), o bien cuando la flota de alguno de los dos jugadores resulta destruida. Evidentemente, si después del movimiento del jugador imperial alguna de las dos flotas ha resultado destruida, al jugador rebelde no se le debe pedir el siguiente movimiento ni mostrarle el prompt (ni tampoco mostrar la información del tablero y de los jugadores).
Finalmente, después de mostrar la información del tablero y los jugadores después del movimiento del jugador rebelde, se eliminarán de las flotas los cazas destruidos. También se deben eliminar los cazas destruidos después de que la partida termine.
El método debe devolver el bando que ha ganado la partida (por abandono del contrario o porque ha destruido la flota enemiga).
BEFORE IMPERIAL
, el tablero y las navesIMPERIAL(n):
(donde n
es el número de cazas en el tablero)AFTER IMPERIAL, BEFORE REBEL
, el tablero y las navesREBEL(m):
AFTER REBEL
, el tablero y las navesA continuación tienes un ejemplo de salida de una partida (el fichero MainP4min.java genera esta salida). En esa partida, la nave imperial empieza con 2 TIEInterceptor, que el jugador coloca en la [1,1] y en la [2,2] (en el siguiente turno), y el jugador rebelde empieza con 2 YWing, que coloca en posiciones cercanas ([1,2] y [2,3]); el jugador imperial pone a patrullar a su TIEInterceptor con identificador 2, que derrota a los dos YWing y gana la partida.
BEFORE IMPERIAL
0123456789
----------
0|
1|
2|
3|
4|
5|
6|
7|
8|
9|
Ship [PlayerFile IMPERIAL Ship 0/0] 2/TIEInterceptor
(TIEInterceptor 1 IMPERIAL null {145,85,60})
(TIEInterceptor 2 IMPERIAL null {145,85,60})
Ship [PlayerFile REBEL Ship 0/0] 2/YWing
(YWing 3 REBEL null {80,70,110})
(YWing 4 REBEL null {80,70,110})
IMPERIAL(0): AFTER IMPERIAL, BEFORE REBEL
0123456789
----------
0|
1| i
2|
3|
4|
5|
6|
7|
8|
9|
Ship [PlayerFile IMPERIAL Ship 0/0] 2/TIEInterceptor
(TIEInterceptor 1 IMPERIAL [1,1] {145,85,60})
(TIEInterceptor 2 IMPERIAL null {145,85,60})
Ship [PlayerFile REBEL Ship 0/0] 2/YWing
(YWing 3 REBEL null {80,70,110})
(YWing 4 REBEL null {80,70,110})
REBEL(0): AFTER REBEL
0123456789
----------
0|
1| i
2| Y
3|
4|
5|
6|
7|
8|
9|
Ship [PlayerFile IMPERIAL Ship 0/0] 2/TIEInterceptor
(TIEInterceptor 1 IMPERIAL [1,1] {145,85,60})
(TIEInterceptor 2 IMPERIAL null {145,85,60})
Ship [PlayerFile REBEL Ship 0/0] 2/YWing
(YWing 3 REBEL [1,2] {80,70,110})
(YWing 4 REBEL null {80,70,110})
BEFORE IMPERIAL
0123456789
----------
0|
1| i
2| Y
3|
4|
5|
6|
7|
8|
9|
Ship [PlayerFile IMPERIAL Ship 0/0] 2/TIEInterceptor
(TIEInterceptor 1 IMPERIAL [1,1] {145,85,60})
(TIEInterceptor 2 IMPERIAL null {145,85,60})
Ship [PlayerFile REBEL Ship 0/0] 2/YWing
(YWing 3 REBEL [1,2] {80,70,110})
(YWing 4 REBEL null {80,70,110})
IMPERIAL(1): AFTER IMPERIAL, BEFORE REBEL
0123456789
----------
0|
1| i
2| Yi
3|
4|
5|
6|
7|
8|
9|
Ship [PlayerFile IMPERIAL Ship 0/0] 2/TIEInterceptor
(TIEInterceptor 1 IMPERIAL [1,1] {145,85,60})
(TIEInterceptor 2 IMPERIAL [2,2] {145,85,60})
Ship [PlayerFile REBEL Ship 0/0] 2/YWing
(YWing 3 REBEL [1,2] {80,70,110})
(YWing 4 REBEL null {80,70,110})
REBEL(1): AFTER REBEL
0123456789
----------
0|
1| i
2| Yi
3| Y
4|
5|
6|
7|
8|
9|
Ship [PlayerFile IMPERIAL Ship 0/0] 2/TIEInterceptor
(TIEInterceptor 1 IMPERIAL [1,1] {145,85,60})
(TIEInterceptor 2 IMPERIAL [2,2] {145,85,60})
Ship [PlayerFile REBEL Ship 0/0] 2/YWing
(YWing 3 REBEL [1,2] {80,70,110})
(YWing 4 REBEL [2,3] {80,70,110})
BEFORE IMPERIAL
0123456789
----------
0|
1| i
2| Yi
3| Y
4|
5|
6|
7|
8|
9|
Ship [PlayerFile IMPERIAL Ship 0/0] 2/TIEInterceptor
(TIEInterceptor 1 IMPERIAL [1,1] {145,85,60})
(TIEInterceptor 2 IMPERIAL [2,2] {145,85,60})
Ship [PlayerFile REBEL Ship 0/0] 2/YWing
(YWing 3 REBEL [1,2] {80,70,110})
(YWing 4 REBEL [2,3] {80,70,110})
IMPERIAL(2): AFTER IMPERIAL, BEFORE REBEL
0123456789
----------
0|
1| i
2| i
3|
4|
5|
6|
7|
8|
9|
Ship [PlayerFile IMPERIAL Ship 2/0] 2/TIEInterceptor
(TIEInterceptor 1 IMPERIAL [1,1] {145,85,60})
(TIEInterceptor 2 IMPERIAL [2,2] {145,85,9})
Ship [PlayerFile REBEL Ship 0/2]
(YWing 3 REBEL null {80,70,-30}) (X)
(YWing 4 REBEL null {80,70,-20}) (X)
And the winner is IMPERIAL
En la práctica 2 se indicaba que, en los métodos que reciben como parámetro algún objeto, se debería comprobar que el objeto no es null (excepto en algún caso en el que tenga sentido, como en Fighter.setPosition) llamando al método Objects.requireNonNull
. A continuación tienes una tabla con todos los métodos de la práctica que deberían incluir esa comprobación:
Clase | método |
---|---|
Fighter | constructor |
getDamage | |
fight | |
FactoryFighter | createFighter |
Board | launch |
patrol | |
removeFighter | |
getFighter | |
getNeighborhood | |
Ship | constructor |
addFighters | |
getFirstAvailableFighter | |
PlayerFile | constructor |
setBoard | |
PlayerRandom | constructor |
setBoard | |
GameShip | launch |
patrol | |
improveFighter | |
Game | constructor |
Ten en cuenta que:
Descarga este fichero MainP4.java y copialo en la carpeta ‘src/mains’ de tu proyecto.
Puedes usar MainP4.java como un ejemplo inicial muy sencillo de partida parcial que usa las clases implementadas en esta práctica. Ten presente, en cualquier caso, que este código explora un subconjunto muy reducido de todas las posibles situaciones que se pueden dar en el juego. El archivo salida-MainP4.txt contiene la salida de este programa.
El archivo prog3-ImperialCommander-p4-pretest.tgz contiene una carpeta test
con un paquete model
con pruebas unitarias, algunas de ellas usan ficheros que están en la carpeta files
. Para que las pruebas funcionen debes copiar la carpeta files
al mismo nivel que src
y test
.
Hay un par de pruebas más en el archivo prog3-ImperialCommander-p4-moretest.tgz
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 dos nuevos paquetes:
model.game
para las clases relacionadas con el juegomodel.game.exceptions
para las excepciones del juegoLa 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-p4.tgz model
Sube este fichero prog3-imperialCommander-p4.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". |