Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Codificación
Información relevante:
• Fecha límite de entrega: 29 de enero.
• Peso en la nota final de Prácticas: 70%.
Contenido
Información docente 4
Prerrequisitos 4
Objetivos 4
Resultados de aprendizaje 4
Introducción 5
Metodología en cascada o waterfall 5
Patrón Modelo-Vista-Controlador (MVC) 7
Enunciado 8
Entorno 8
Estructura de la práctica 8
Aspectos a tener en cuenta 10
Antes de empezar 11
Modelo 12
Sugerencia de orden a seguir a la hora de codificar 13
Orientación acerca de la dificultad de los elementos a codificar 13
Indicaciones 14
Blinky, Pinky, Inky y Clyde (clases) 14
ChaseAggressive (clase) 15
ChaseAmbush (clase) 15
ChaseBehaviour (interfaz) 15
ChaseCoward (clase) 15
ChasePatrol (clase) 15
Direction (enumeración) 16
Dot (clase) 16
Energizer (clase) 16
Ghost (clase) 16
Level (clase) 16
Life (clase) 16
MapItem (clase) 17
Pacman (clase) 17
Path (clase) 17
Position (clase) 17
Sprite (enumeración) 17
Corolario 19
Evaluación 20
Prerrequisitos
Para hacer esta Práctica necesitas:
● Tener asimilados los conceptos de los apuntes teóricos (i.e. los 4 módulos que se
han tratado durante las PEC).
● Haber adquirido las competencias prácticas de las PEC. Para ello te recomendamos
que mires las soluciones que se publicaron en el aula y las compares con las tuyas.
Objetivos
Con esta Práctica el Equipo Docente de la asignatura busca que:
Resultados de aprendizaje
Con esta Práctica debes demostrar que eres capaz de:
● Usar con cierta soltura un entorno de desarrollo integrado (IDE) como IntelliJ.
Por su parte, la etapa de diseño describe cómo es la estructura interna del producto (p.ej.
qué clases usar, cómo se relacionan, etc.), patrones a utilizar, la tecnología a emplear, etc.
El resultado de esta etapa suele ser un conjunto de diagramas (p.ej. diagramas de clases
UML, casos de uso, diagramas de secuencia, etc.) acompañado de información textual. Si
nos decantamos en esta etapa por hacer un programa basado en programación orientada a
objetos, será también en esta fase cuando usemos el paradigma bottom-up para la
identificación de los objetos, de las clases y de la relación entre ellas.
Si nos fijamos en la figura, siempre se puede volver atrás desde una etapa. Por ejemplo, si
nos falta información en la etapa de diseño, siempre podemos volver por un instante a la
etapa de análisis para recoger más información. Lo ideal es no tener que volver atrás.
En esta asignatura hemos pasado, de alguna manera, por las cuatro primeras fases. Así
pues, la etapa de análisis la hemos hecho desde el Equipo Docente. Nosotros nos “hemos
reunido” con el cliente y hemos analizado/documentado todas sus necesidades (Práctica 1).
A partir de estas necesidades, hemos ido tomando decisiones de diseño. Por ejemplo,
decidimos que el software se basaría en el paradigma de la programación orientada a
objetos y que usaríamos el lenguaje de programación Java. Una vez decididos estos
aspectos clave, hemos ido definiendo, a partir de la identificación de objetos, las diferentes
clases (con sus atributos y métodos, i.e. datos y comportamientos) y las relaciones entre
ellas a partir de las necesidades del cliente confirmadas en la etapa de análisis y de
entidades reales que aparecen en el problema (i.e. objetos). Para ello hemos usado un
paradigma bottom-up. Como puedes imaginar, la etapa de diseño es una de las más
importantes y determinantes de la calidad del software, ya que en ella se realiza una
descripción detallada del sistema a implementar. Es importante que esta descripción permita
mostrar la estructura del programa de forma que su comprensión resulte sencilla. Por ese
motivo es frecuente que la documentación de la fase de diseño vaya acompañada de
diagramas UML, entre ellos los diagramas de clases (Práctica 1). Estos diagramas además
de ser útiles para la implementación, también lo son en la fase de mantenimiento.
Finalmente, la etapa de test/pruebas la hemos tratado durante el semestre con los ficheros
de test JUnit que se proporcionaban con los enunciados de las PEC y con alguna prueba
extra que hayas hecho por tu cuenta. Estos ficheros nos permitían saber si las clases
codificadas se comportaban como esperábamos. En esta segunda práctica, también
ahondaremos en esta fase de testeo.
Evidentemente, la metodología en cascada no es la única que existe, hay muchas más: por
ejemplo, prototipado y las actualmente conocidas como metodologías ágiles: eXtreme
Programming, etc. En este punto podemos decir que en las PEC hemos seguido, en parte,
una metodología TDD (Test-Driven Development), en cuya fase de diseño se definen los
requisitos que definen los test (te los hemos proporcionado con los enunciados) y son estos
Entorno
Para esta práctica utiliza el siguiente entorno:
● IntelliJ Community.
Estructura de la práctica
Si abres el .zip que se te proporciona con este enunciado, encontrarás el proyecto PACman.
Para crear este proyecto se ha utilizado el framework de desarrollo de videojuegos
multiplataforma libgdx, el cual se basa en Java y OpenGL. En este vídeo se explica los
pasos que hemos seguido para crear el proyecto PACman con libgdx.
Si lo abres en IntelliJ, verás que la estructura difiere un poco de la que hemos usado
habitualmente. Si te fijas, no aparece de primeras el directorio src. En cambio, aparecen
dos directorios llamados core y desktop. Esto es así porque el wizard (configurador) de
libgdx genera tantos directorios como subproyectos (salidas) hemos seleccionado. En
nuestro caso sólo hemos seleccionado la opción desktop, obviando Android, iOS y
HTML. La manera de trabajar con libgdx es que en el directorio core se programa la
mayoría (el núcleo, core) del videojuego, y en cada una de las subcarpetas (desktop,
Android, iOS y HTML), se programan las especificidades de cada entorno de ejecución. Así
pues, si, por ejemplo, la clase Person se debe comportar igual para Android, iOS y
HTML, pero diferente para desktop, entonces programamos dicha clase en el directorio
core (la cual servirá para Android, iOS y HTML), mientras que en el directorio desktop
creamos una clase Person especial para este entorno. Así pues, libgdx sobreescribe las
clases de core con las indicadas en el entorno para la que vamos a generar el output. Fíjate
que si abres core y desktop, ambos directorios se estructuran de igual manera. Es en
ellas es donde aparece el directorio src que hemos usado a lo largo de la asignatura.
A continuación explicamos los subdirectorios ubicados dentro del directorio core que son
más importantes para realizar esta Práctica:
Importante: Consultar el Javadoc será vital para hacer esta Práctica 2, puesto
que complementa las explicaciones dadas en este enunciado.
A diferencia de los proyectos que hemos ido haciendo a lo largo de las PECs, los recursos
del programa no los encontraremos en src/main/resources, sino en un directorio
ubicado en la raíz del proyecto llamado assets. Será en assets donde encontrarás las
imágenes y pantallas que se utilizan en la vista gráfica del juego así como los ficheros de
configuración de los niveles.
En el menú izquierdo elige Editor → File Encodings. Asegúrate que todos los valores
sean UTF-8 y que el valor de la opción Create UTF-8 files es "with NO BOM".
1 LevelException 8 ChaseBehaviour
7 Character 14 Pacman
15 Level
Importante: Para poder ejecutar los test así como para codificar elementos
más sencillos que dependen de otros más complejos (p.ej. Character tiene
un atributo de tipo Level), hay que codificar primero el esqueleto de todos los
elementos para que el programa compile, de lo contrario no se podrán ejecutar
los test. Ten presente el modelo de evaluación de esta Práctica 2, donde es
necesario superar satisfactoriamente todos los test de tipo "sanity".
Por “esqueleto” nos referimos a crear todos los elementos del programa con
todos sus métodos codificados con un cuerpo/código mínimo que permita
compilar el programa. Por ejemplo, si un método devuelve un int, podemos
poner como cuerpo del programa simplemente:
return 1;
Más adelante se cambiará el cuerpo para que el método se comporte como es
esperado.
La siguiente tabla muestra una orientación sobre la dificultad de los diferentes elementos
que componen el modelo del programa. Dentro de cada nivel de dificultad, los distintos
elementos están ordenados, aproximadamente, de menor a mayor dificultad. Así pues, la
interfaz Scorable sería más sencilla que la clase Life.
Alta
Pacman (entities.characters.pacman)
Ghost (entities.characters.ghosts)
Para codificar todas las clases, interfaces y enumeraciones sigue las indicaciones
proporcionadas en el Javadoc. No obstante, ten presente los detalles que se indican en el
siguiente apartado (están enumerados por orden alfabético) que pueden ser de ayuda.
Indicaciones
Ten en cuenta que cada uno de ellas instancia un objeto diferente para el atributo
chaseBehaviour.
Codifica esta clase siguiendo las indicaciones del Javadoc. Este comportamiento es el
asociado a los fantasmas de tipo Blinky. Su posición objetivo es la posición en la que está
Pacman. Por este motivo es el tipo de persecución más agresivo.
ChaseAmbush (clase)
Codifica esta clase siguiendo las indicaciones del Javadoc. Este comportamiento es el
asociado a los fantasmas de tipo Pinky. Intenta adelantarse a los movimientos de Pacman
para tenderle una emboscada. Para ello su posición objetivo es 4 posiciones hacia adelante
respecto a la posición actual de Pacman y en la dirección en la que está mirando Pacman.
Recuerda que el enum Direction almacena para cada valor su offset. Por ejemplo, para
UP, éste indica que el ítem debe moverse -1 en el eje Y, pero 0 en el eje X.
ChaseBehaviour (interfaz)
Codifica esta interfaz siguiendo las indicaciones del Javadoc. Gracias a esta interfaz y las
clases que la implementan (i.e. ChaseXXX), estamos aplicando el patrón de diseño llamado
Strategy. Dicho patrón se sustenta en el principio de Polimorfismo.
ChaseCoward (clase)
Codifica esta clase siguiendo las indicaciones del Javadoc. Este comportamiento es el
asociado a los fantasmas de tipo Clyde. Primero calcula la distancia euclídea que hay entre
su posición y la de Pacman. Si es menor a 8, su posición objetivo es la misma posición que
cuando está en estado SCATTER. En caso contrario, su posición objetivo es la posición de
Pacman. Con este comportamiento a la hora de perseguir, da la sensación que este tipo de
fantasma se comporta de forma cobarde.
ChasePatrol (clase)
Codifica esta clase siguiendo las indicaciones del Javadoc. Este comportamiento es el
asociado a los fantasmas de tipo Inky. Para determinar su posición objetivo, los fantasmas
de tipo Inky usan la posición y orientación de Pacman así como la posición del primer
fantasma Blinky añadido al nivel. Primero calcula, teniendo en cuenta la dirección en la
que está orientado Pacman, la posición que está dos posiciones delante de la posición
actual de Pacman. Una vez determinada esta nueva posición (llamémosla targetBlinky),
calcula un vector que va desde la posición del primer fantasma de tipo Blinky añadido al
nivel hasta la posición targetBlinky. Con el vector calculado, lo dobla (i.e. lo multiplica
por 2). El resultado es la posición objetivo a la que debe ir Inky. Con esta manera de
moverse, los fantasmas Inky parecen que estén patrullando. En caso de que no haya
ningún fantasma de tipo Blinky en el nivel, entonces la posición objetivo será
targetBlinky.
Codifica esta enumeración siguiendo las indicaciones del Javadoc. Asimismo ten en cuenta
que el objetivo de esta enumeración es recopilar las direcciones/orientaciones en las que el
jugador podrá desplazarse, i.e. derecha, abajo, izquierda y arriba.
Como verás en el Javadoc, cada dirección es “construida” a partir de tres parámetros: (1) x,
(2) y, y (3) keyCode. Los valores x e y indican el offset en los que un elemento debe
moverse si va en esa dirección. Así pues:
RIGHT 1 0 22
DOWN 0 1 20
LEFT -1 0 21
UP 0 -1 19
Dot (clase)
Energizer (clase)
Ghost (clase)
No debemos poder instanciar objetos cuyo tipo dinámico sea Ghost. Además ten en cuenta
que cuando se resetea un fantasma, éste vuelve a estar vivo en su posición inicial (i.e. la
indicada en el fichero de configuración del nivel), con un comportamiento igual a INACTIVE
y con dirección/orientación UP.
Level (clase)
Para esta clase te proporcionamos el esqueleto de esta clase y algún método ya codificado
que no debes modificar. El resto de métodos tienen el comentario //TODO y los debes
codificar siguiendo las especificaciones indicadas en el Javadoc.
Life (clase)
El valor por defecto para el atributo pathable es true y para sprite es Sprite.LIFE.
Esta clase hereda de Entity y tiene un constructor. No debemos poder instanciar objetos
cuyo tipo dinámico sea MapItem.
Pacman (clase)
Ten en cuenta que cuando se reseteaPacman, éste vuelve a estar vivo en su posición inicial
(i.e. la indicada en el fichero de configuración del nivel), con un estado igual a INVINCIBLE
y con dirección/orientación UP.
Path (clase)
El valor por defecto para el atributo pathable es true y para sprite es Sprite.PATH.
Position (clase)
Codifica esta clase según las indicaciones del Javadoc. También ten en cuenta:
● equals: la sobrescritura de este método debe devolver true cuando las dos
posiciones sean la misma, es decir, cuando sus filas coincidan y sus columnas
también. En caso contrario (i.e. no coincidencia de las filas y/o columnas, una
coordenada es null o se pasa como argumento un objeto que no sea del tipo
Position), debe devolver false.
● add: devuelve un nuevo objeto Position cuyos valores son la suma de los ejes X e
Y de los objetos Position pasados como parámetro. Si uno de los 2 parámetros
es null, entonces debe lanzar una NullPointerException sin mensaje.
Sprite (enumeración)
Codifica esta enumeración siguiendo las indicaciones del Javadoc y los valores expuestos
en el enunciado de la Práctica 1 y los test.
Wall (clase)
El valor por defecto para el atributo pathable es false y para sprite es Sprite.WALL.
En el paquete controller del proyecto verás una clase llamada Game. Ésta es la clase
controladora del juego (un programa puede tener varias clases controladoras). Ya te la
damos hecha. Básicamente tiene un objeto de tipo Level e invoca sus métodos.
Vistas
Las vistas son las “pantallas” con las que interactúa el usuario. En este caso, tenemos una
manera de interactuar, es decir, el juego en modo gráfico. Con el proyecto ya te damos las
vistas/pantallas del programa hechas, puesto que están realizadas con el framework libgdx
que no es objeto de estudio de esta asignatura.
Para ejecutar esta vista, verás que la ventana de Gradle de IntelliJ muestra dentro de
other una tarea llamada run. Si ejecutas esta tarea, se ejecutará el juego en modo
gráfico. La manera de jugar es usando las flechas del teclado.
Para crear un fichero .jar para una aplicación realizada con libgdx puedes ir a la tarea de
Gradle llamada other → dist, la cuál creará un directorio build en cada “proyecto”, en
nuestro caso core y desktop. Si miras dentro, verás que hay un subdirectorio llamado
libs → dist, en él hay el correspondiente .jar. Este es un fat jar, es decir, un fichero
.jar que, además de las clases de nuestro programa, contiene también todas las clases de
todas las librerías de las que depende. Así pues, es un fichero más grande (de ahí el uso del
adjetivo fat) de lo que sería un .jar generado de manera normal, puesto que contiene
también los ficheros e imágenes. Puedes ejecutarlo haciendo doble click o usando el
comando java -jar desktop-1.0.jar en un terminal (puedes cambiarle el nombre al
fichero). Este fichero .jar funcionará en aquellos ordenadores que tengan tanto JDK como
JRE. Si tienes curiosidad, puedes descomprimirlo con WinZip (o similar) para ver su interior.
Quizás estés pensando: ¿qué sucede si en el ordenador en que se quiere ejecutar el .jar
no hay ni JDK ni JRE? Pues, o bien lo instalas, o bien usas jlink. Lo que hace jlink es
empaquetar el .jar junto con una versión ad hoc de JRE. Para ello necesita que el
proyecto Java esté modularizado, puesto que, según los módulos que se indiquen en el
fichero module-info.java, el JRE ad hoc que cree será mayor o menor. Para usar
jlink hay que configurar la tarea jar de build.gradle. Esto queda fuera del alcance
de la asignatura.
Cabe destacar que jlink es un comando propio de JDK y, por lo tanto, se puede ejecutar
desde línea de comandos sin necesidad de usar Gradle (y el plugin correspondiente):
https://www.devdungeon.com/content/how-create-java-runtime-images-jlink.
¿Y si queremos un instalador? Pues a partir de JDK 16 está disponible jpackage. Lee más
sobre jar, jlink y jpackage en: https://dev.to/cherrychain/javafx-jlink-and-jpackage-h9.
De todas maneras, hoy en día se usan aplicaciones como Docker para distribuir programas.
Estos test aseguran que la parte crítica del esqueleto del programa es
respetada. Para probarlos haz:
Gradle → verification → testSanity
El último día para entregar esta Pràctica es el 29 de enero de 2023 antes de las 23:59.
Cualquier Pràctica entregada más tarde será considerada como no presentada.