Programación 1 - Trabajo Práctico 1
1 Introducción
2 Recorriendo el laberinto
3 Alcanzando el objetivo
4 El tiempo pasa
5 Color de fondo y mensajes
6 Opcional:   Fantasmas.
7 Opcional:   Vidas.
8 Diversos laberintos
9 Opcional:   Un fantasma más inteligente
6.3

Programación 1 - Trabajo Práctico 1

Este es el primer trabajo práctico de la asignatura. Recordá que, tal como se aclaró en clase, una condición para regularizar la materia es tener aprobado los trabajos prácticos.

Datos importantes:
  • Forma de entrega: A través de la plataforma.

  • Fecha límite: 30/05. No se considerará aprobado un trabajo que haya sido entregado luego de esta fecha.

  • El trabajo puede hacerse de forma individual o en grupos de 2 personas. Desde la cátedra recomendamos esta última opción.

Antes de pasar a los ejercicios, algunas consideraciones generales:
  • Por cualquier duda te recomendamos acercarte a consulta. Desde la cátedra podemos ayudarlos a resolver cualquiera de las consignas, e incluso a organizar la solución. También podés hacer preguntas a través de la plataforma, pero mediante mensajes privados a los docentes. No uses el foro para consultas sobre este práctico, pues podrías no cumplir con la siguiente condición.

  • No está permitido compartir código con otro grupo. Esto incluye signaturas, declaraciones de propósito, casos de test y, obviamente, definiciones de funciones. Desde la cátedra se revisará (ayudados por un software desarrollado a tal fin) que todos los grupos se hayan ajustado a esta condición. No cumplir con esta restricción tiene como consecuencia inmediata la no aprobación del trabajo.

  • Los ejercicios opcionales no se tienen en cuenta para la nota final. Es decir, un grupo puede obtener un 10 como calificación sin necesidad de haber resuelto ninguno de los ítems opcionales.

  • Quizás no puedas resolver todos los ítems. Sin embargo, si seguís la receta de diseño, podés obtener una solución parcial que sólo incluye parte de la funcionalidad requerida. A modo de ejemplo, puede suceder que no puedas resolver la parte que controla el tiempo restante. No tendrás un sobresaliente, pero si el resto de las cosas funciona, seguramente aprobarás el trabajo. Recordá que siempre es mejor esta situación a entregarlo fuera del plazo establecido.

  • Lo repetimos porque es muy importante. Vení a consulta ante la primer dificultad que encuentres.

1 Introducción

A lo largo de este trabajo práctico implementaremos un juego simple, en el cual un personaje recorre un laberinto buscando alcanzar un objetivo. Iniciaremos con las funcionalidades básicas, y luego iremos incorporando diferentes extensiones.

En el siguiente video se puede ver la versión final: (muestra.mp4)

A lo largo del diseño del programa, deberá seguir fielmente la "receta de diseño", acompañando al código con la adecuada documentación. A la hora de la evaluación, no solo se analizarán los requerimientos funcionales, sino también la correcta organización del código, los criterios a la hora de las definiciones, etc.

A modo de guía en la organización del trabajo, se presenta una plantilla ("template.rkt") con la estructura principal del programa y sus principales funciones. Claramente se puede modificar y agregar definiciones de constantes y funciones auxiliares en caso de necesitarlas, con un criterio adecuado.

Para implementar el juego, contaremos con la función interseca?, importada del módulo "extras.rkt". Como nos aclara su definición de propósito, esta función determina si dos imágenes se superponen en la escena:

; interseca? : Posn Image Posn Image -> Boolean
; (interseca? p1 img1 p2 img2) Determina si img1 en la posición p1 se interseca
; con img2 en la posición p2. Al decir "interseca" nos referimos a si cambia el
; resultado de acuerdo al orden en que son dibujadas las imágenes.

Esta función será útil en varias partes del código. Asegúrese de entender qué hace (recuerde que para esto no debería ser necesario mirar el código).

2 Recorriendo el laberinto

Complete el código para que el personaje se mueva a lo largo del laberinto, de acuerdo a las flechas del teclado, sin pasar por encima de las paredes ni salir fuera de escena.

3 Alcanzando el objetivo

Modifique el código para que se dibuje la posición objetivo, y se detecte y termine el programa cuando el jugador alcanza dicha posición (utilizar la función interseca?).

(define POS-OBJETIVO (make-posn 930 460))

4 El tiempo pasa

Modifique el código para que se lleve un contador del tiempo, y si no se alcanza el objetivo en el tiempo dado, el programa termine. El tiempo restante se debe mostrar en la esquina inferior derecha.
  • En este punto, ya no alcanzará con llevar registro de la posición del jugador en el estado, además, debemos registrar el tiempo disponible. Debemos recurrir a una estructura con diferentes campos.

    Cuando trabajamos con una estructura compleja, es decir, con muchos campos, para no tener que enumerar todos los campos cada vez que se modifica uno solo, podemos definirnos algunas funciones auxiliares que nos permitan actualizar un campo en particular, dejando el resto intacto. Por ejemplo, supongamos definimos el estado del juego como:

    (define-struct estado [campo1 campo2 campo3 ...])

    Y en varias oportunidades vamos a modificar únicamente el campo1, entonces definimos:

    (define (actualizar-campo1 val st) (make-estado val (estado-campo2 st) (estado-campo3 st) ...))

    Y luego, utilizamos la función actualizar-campo1 en el resto del código.

    Siguiendo este enfoque, diseñe las funciones: actualizar-timer y actualizar-jugador.
    ; Estado global del programa:
    ; ==========================
    ; (define-struct estado ....)
     
    ; actualizar-timer : Number Estado -> Estado
    ; ..................................
     
    ; actualizar-jugador : Posn Estado -> Estado
    ; ..................................

  • Diseñe la función dibujar-timer que dibuje el tiempo restante en la esquina inferior derecha.
    ; Imprimir en la pantalla:
    ; =======================
    ; ....
    ; dibujar-timer : Estado Imagen -> Imagen
    ; ..................................

  • Además, debemos ir actualizando el paso del tiempo con cada tick del reloj. Para esto, defina la función manejador-tick que se suscriba al evento on-tick.
    ; Otros handlers:
    ; ==============
    ; manejador-tick : Estado -> Estado
    ; ..................................

  • Nuestro programa tendrá dos posibles condiciones para terminar, cuando se alcanza el objetivo, o cuando se acaba el tiempo. Defina la función fin? que analice ambas posibles condiciones (se asociará a la cláusula stop-when).
    ; Condiciones de Terminación:
    ; ==========================
    ; ...
    ; fin? : Estado -> Boolean
    ; ..................................

5 Color de fondo y mensajes

Modifique el código para que el color de fondo cambie, durante el juego:
  1. Al inicio debe ser blanco.

  2. Cuando quedan menos de 500 ticks debe ser rojo.

  3. Cuando se pierde, debe ser púrpura.

  4. Cuando se gana, debe ser dorado.

Además, al lograr el objetivo, se debe imprimir el mensaje "GANASTE!", y al perder: "GAME OVER!".

6 Opcional: Fantasmas.

Modifique el código para incluir un nuevo elemento a la escena, un fantasma que cae verticalmente a un cierto paso por tick, iniciando siempre en una posición aleatoria del tope de la escena. Además de mover al fantasma, se debe detectar cuando interseca con el jugador (utilizando interseca?) y en dicho caso terminar el programa (con el mensaje "GAME OVER!").

(define FANTASMA (bitmap "fantasma.png"))

7 Opcional: Vidas.

Modifique el código para que se lleve el registro de vidas disponibles (por ejemplo 5) Y cada vez que el jugador interseca con el fantasma, se reinicie de la posición inicial, con una vida menos pero sin reiniciar el timer. Si las vidas se acaban el programa debe terminar.

Las vidas restantes se debe mostrar en la esquina inferior izquierda.

; Estado global del programa:
; ==========================
; ...
; actualizar-vidas : Number Estado -> Estado
; ..................................
 
; Imprimir en la pantalla:
; =======================
; ...
; dibujar-vidas : Estado Imagen -> Imagen
; ..................................

8 Diversos laberintos

Una vez terminado el diseño del programa, puede probarlos con diferentes laberintos, por ejemplo las imágenes "laberinto2.png" y "laberinto3.png". Si el programa está adecuadamente implementado, no debería modificar ningún campo, excepto POS-INICIAL y POS-OBJETIVO, que determinan la posición inicial y el objetivo a alcanzar.

9 Opcional: Un fantasma más inteligente

Programar el comportamiento del fantasma para que recorra aleatoriamente el laberinto sin pasar por encima de las paredes.