Universidad Galileo
Programación III

Laboratorio 2: Mundo de Pelotas! (pero no se hagan bolas!)

Vistazo en General

Este laboratorio le permitira practicar la escritura de clases (definirlas), y tambien le ayudara a entender las diferencias entre interfaces, clases y objetos.

Antes de que empieze este lab, asegurese de haber leido los capitulos asignados durante el semestre (especialmente interfaces, clases y diseño orientado a objetos). Van a escribir bastante codigo en este lab y deben estar muy preparados para hacerlo. Deben escribir por adelantado el codigo asignado en el pre-laboratorio. Si escribe mas de lo que se le pide, solamente le sera de utilidad, y podra experimentar mas en el lab.

Vamos a cambiar las reglas del juego, ahora si puede colaborar con sus amigos y amigas en cuanto detalle deseen. Pueden discutir como solucionar el problema y compartir ideas, pero cada quien debe escribir su propio programa (es decir, si se lo pasan y me doy cuenta, sacan cero). Ademas debe anotar el nombre de las personas que ayudo (o le ayudaron) y cual fue la colaboracion.

Se hara entrega de el codigo escrito en este lab. Este debe ser su propio codigo. Pero los exhorto a que se pidan ayuda para "debuguear" el sus clases. El ayudar a un compañero a debuguear su codigo es una magnifica idea de mejorar sus habilidades de programacion.

Esta asignacion se enfoca en los siguientes puntos:

Debe leer toda esta especificacion (asignacion) y completar la seccion de Pre-laboratorio antes de llegar al laboratorio.

La entrega formal de este laboratorio se hara el dia Lunes, 19 de Febrero 2001 -- Antes de las 11:59 p.m.

Pre-Laboratorio

A. Ejercicios en Papel y Lapiz

Responda todos las preguntas en la seccion Preparacion de Laboratorio.

B. Lo que debe esperar

Cuando corra el programa para este laboratorio, aparecera una ventana como la ventana a la derecha. La ventana contiene un fondo verde (gramoso) y un juego de Botones en la parte inferior, cada uno representa una clase de Java que usted va a escribir. Recuerde que cada clase en Java se puede ver como una fabrica de objetos. Cada vez que haga click sobre un boton que representa la clase, la fabrica indicada va a crear una nueva instancia de la clase y aparecera una nueva pelota en el area grasmosa. Como va a comportarse la pelota, depende de la clase que se uso para crearla. Definiendo varias clases, sera capas de crear pelotas de varios tipos. Cada clase representa un diferente tipo de pelota. Diferentes tipos de pelotas se pueden mover de distinta manera. Una pudiera saltar por la pantalla, mientras otra pudiera crecer y achiquitar su tamaño. Otra pudiera simplemente quedarse quieta hasta que la agarre con el mouse, y la coloque en otro lugar. Cada uno de estos tipos corresponde a una clase diferente. Por supuesto que puede haber varias instancias (vairas pelotas) de cada clase.

Todas las pelotas, sin importar la clase a cual pertenecen, comparten algunas caracteristicas. Una pelota debe tener coordenadas horizontal y vertical (x, y). Debe tener in tamaño (radio). Puede hasta responder a los eventos del mouse y el teclado. De hecho todas las pelotas deberan responder, pero algunas responderan haciendo nada. Vamos a capturar estos conceptos de "pelotes" en una interface.

La distincion entre interfaces, clases, y objetos puede ser confusa, asi que revisaremos estos conceptos ahora:

Lo que en realidad esta haciendo cuando presiona un boton en la ventana BallWorld es pedirle a una de las clases que escribio que genere un nuevo objeto, o sea una nueva instancia de la clase representada por el boton. Si hace click en el mismo objeto otra ves, obtendra otro objeto (otra pelota) con el mismo comportamiento, pero estado independiente.

Siempre que sus clases implementen la interface Ball, el codigo de graficas que ya esta escrito, sabrá como preguntarle a su objeto, cual es su posicion y radio.
Interface 
interface Ball
double getX(); 
double getY(); 
...
 
Class 
class MyBall implements Ball
double x; 
double y; 

double getX() { 
  ... 
} 
double getY() { 
  ... 
} 
...

 
Object 
 
 

double getX() { 
  ... 
} 
double getY() { 
  ... 
} 
...

 

C. Preparacion de Laboratorio

En el laboratorio de la semana pasada, escribieron metodos para fijar contenido en areas de texto, leer de archivos, etc, pero les escondí bastante de las mecanicas, y los aislé de escribir una clase.

Esta semana lo tienen que hacer ustedes mismos.

Van a crear una clase entrea desde cero. La clase va a ser una especificacion de toda la informacion que desee acerca de una pelota: su posicion horizontal y vertical, su radio, alguna interaccion con el usuario, y el comportamiento que muestra la pelota.

Lo cool de este lab es que podra escribir diferentes clases para diferentes comportamientos: una clase podria describir reglas para una pelota que rebota por los lados de la ventana, otra reglas para una pelota que solo se queda en un lugar y se hace mas grande y mas pequeña, y otra podra describir reglas para una pelota que se hace grande y se parte en dos pelotas.

Ahora, como funciona? El codigo que va a escribir es bastante simple. Todo lo que necesita son metodos que indiquen donde esta la pelota y que tan grande es, y que hacer cuando el usuario le hace click o le escribe algo. Otras clases que ya estan escritas se encargaran de desplegar su pelota en la pantalla.

Interfaces que ya estan implementadas

Ahora aqui esta la cosa engañosa: Nosotros ya les damos escrita una clase enorme que puede animar circulos en la pantalla. Ustedes van a escribir sus propias clases que implementan varios comportamientos de una pelota. Nuestra clase necesita saber como pedirle a su clase (la de ustedes) la posicion de la pelota en la pantalla. Como vamos a saber eso si todavia no la ha escrito?

Muy bien! La respuesta es que vamos a definir una interface (de hecho, dos de ellas), que especifican los metodos que deben escribir (ustedes). Las dos interfaces son Ball y Animate. Juntas estas dos interfaces especifican siete metodos que debe escribir para su clase. Mas adelante veremos los metodos. Recuerde, las interfaces solo son contratos que especifican que comportamiento se proveera. Pero una interface no provee comportamiento.

Puede ser que su clase necesite hablar con nuestra clase para obtener informacion, como el tamaño de la ventana. Como les permitimos acceso a informacion importante de nuestra clase sin exponer toda la enredadera de las graficas y otros metodos? Lo hacemos con una interface mas, World. Ahora, hemos implementado la interface World interface; puede utilizar los metodos proveidos aqui y no tiene que escribir ninguno de ellos usted mismo.

Aqui esta la interface World:

public interface World
{
   public double getMinX();
   public double getMinY();
   public double getMaxX();
   public double getMaxY();

   public Ball getClosestBall(Ball fromBall);
   public void addBall(Ball aNewBall);
   public void removeBall(Ball toBeRemoved);
}
Si usted tuviera un objeto que implementa World puede usarlo para saber los valores minimos y maximos posibles de x y y en la ventana. (No se alarme!. Nuestro codigo le dará un objeto World con todas las de la ley en uno de los metods que debe implementar. Vea mas abajo.) Tambien puede pedirle al objeto World que le de el objeto Ball mas cerca de otro objeto Ball. Por ejemplo, suponga que esta escribiendo un metodo en una clase Ball, y que uno de los campos (atributos) de su clase es World theWorld;. Entonce puede decir:
   Ball myNeighbor= theWorld.getClosestBall(this)
y ahora la variable myNeighbor apunta a la pelota mas cercana de esta pelota. Tenga mucho cuidado! Si la pelota es la unica pelota en la pantalla, entonces, getClosestBall devuelve null.

El metodo addBall es de utilidad si ha creado una nueva pelota de la nada, y quiere decirle al objeto World que la agregue en la pantalla. El metodo removeBall le indica al mundo que borre la pelota que le manda como parametro, y se olvidan de que existio alguna vez.

Interfaces que usted debe implementar

Al fin! llegamos a las dos interfaces que ustedes deben implementar para cualquier clase que escriban en este lab.
public interface Ball
{
   public double getX();
   public double getY();
   public double getRadius();

   public void setWorld(World theWorld);

   public void userClicked(double atX, double atY);
   public void userTyped(char key);
}


public interface Animate
{
   public void act();
}
Algunos de estos son bastante obvios: debe hacer que getX(), getY() y getRadius() devuelvan los valores apropiados. De donde obtiene estos valores? Usted tiene completo control de como se fijan los valores (queda a su discresion). Puede sacarselos de la manga. Estos metodos son utilizados por nuestra clase que manipula las graficas, para averiguar donde y como poner su pelota en la pantalla. Puede decirle lo que quiera a la clase de graficas acerca de donde poner su pelota, y la clase de graficas le hará caso (tal vez quiera verificar que es visible en la ventana del mundo, asi que vea los metodos getMinX, etc. de la clase World).

Cuando el usuario hace click sobre la representacion en pantalla de su pelota, nuestra clase de graficas encuentra su objeto pelota, y le invoca el metodo userClicked(...) que provee su pelota. Le indica cuales fueron las coordenadas 'x' y 'y'. De igual manera, cuando el usuario le hace click a su pelota y despues presiona una tecla, nuestra clase invoca el metodo userTyped(...) con la tecla que presiono el usuario. Usted necesita implementar metodos que le digan a su pelota que hacer en estas circumstancias. Al principio talvez quiera hacer absolutamente nada, de todas maneras debera incluir un metodo (porque esta en el contrato!) pero el metodo puede hacer nada. Por supuesto, si mas adelante usted desea hacer mas interactiva a su pelota, ahi esta el metodo, solo ganas.

El ultimo metodo de la clase Ball es el metodo setWorld. Nuestra "clase detras de las cortinas" manda a llamar su metodo setWorld con un objeto que implementa la interface World como argumento. Si usted quiere usarlo mas adelante para averiguar el tamaño de la ventana, por ejemplo, entonces sera mejor que lo almacene en un campo.

Una de las cosas que no sea encarga la interface Ball es hacer que su pelota tenga un comportamiento independiente (animado). Para esto usamos la interface Animate. Mientras su pelota siga siendo desplegada en la pantalla, el metodo act se va a invocar una y otra ves (lo invocará la clase de graficas automaticamente, no usted). El metodo act es el corazon del comportamiento de su pelota: es la regla que usara su pelota para averiguar que debe hacer.

Recuerde que una clase puede implementar mas de una interface, asi que solo debe especificar una clase para cualquier tipo de pelota.

Preguntas Pre-laboratorio

Puede trabajar junto con sus compañeros para resolver las preguntas, pero las respuestas deben ser suyas (propias).

Q: Sus clases que modelan pelotas implementaran ambas la interface Ball y la interface Animate. Suponga que quiere modelar una pelota que rebota y decide escribir una clase llamada Bouncer. Como seria la primer linea de la clase?

Q: Que metodos debe especificar (implementar) su clase?

Q: Escriba una clase completa que se llame Dud (Dud significa algo que hace nada), y que implemente las interfaces Ball y Animate. La pelota representada por un objeto Dud debe hacer nada. Cuando se crea, solo debe quedarse sentada en el punto (0,0) con un radio de 15 y hacer nada absolutamente nada.

Q: Que pasaria si crea dos instancias de la clase Dud?

Q: La clase Dud que escribio, no necesitaba campos (atributos), si los incluyo, probablmente estaba viendo al futuro. Suponga que queremos guardar los datos de 'x', 'y', 'radio' en campos (atributos) para posteriormente cambiar su valor, aplique los cambios necesarios a la calse Dud para realizar esta tarea.

Q: Adelantese a leer esta pagina al lugar donde define el metodo act de la clase Bouncer (en la seccion Zooooooom!). Escriba los campos (atributos) que necesita para implementar la clase Bouncer, tambien escriba el cuerpo del metodo act y el cuerpo del constructor de la clase.

Q: Revise la siguiente porcion de codigo, asuma que theWorld es un campo que ya tiene un valor apropiado (o sea, hace referencia al "Mundo" de pelotas) y que el codigo que esta viendo es parte de un metodo en una clase que implementa la interface Ball. La pregunta es: Puede usted decirme que podira fallar? (Pista: revise detenidamente los comentarios que se hicieron en la documentacion de la interface World, arriba). Como lo podria arreglar? (responda las 2 preguntas).

   Ball closest = theWorld.getClosestBall(this);
   double xdist = closest.getX() - this.getX();

El Laboratorio

Lo que deben traer al laboratorio

Deben traer sus ejercicios en papel y lapiz, sus respuestas a las preguntas del pre-laboratorio, cualquier codigo fuente para el resto del laboratorio que desee (tanto como pueda), y algunas ideas para las clases que modelan pelotas que quiera escribir (lease algoritmos, etc).

Configurando BallWorld

Nota: No vamos a configurar Linux para poder trabajar, solo que no encontre una mejor traduccion para "Setting up BallWorld". Pero para poder trabajar el labortorio de hacer esto:

Escriba su primer clase: Dud

Para que su codigo funcione apropiadamente debe estar en el directorio ballworld.

Cuales son los metodos que debe proveer la clase Dud? Recuerde que debe implementar las interfaces Ball y Animate. Mantenga los metodos tan simples como pueda.

Complile su codigo fuente y asegurese de que ya no tenga errores. Si tiene un error y no encuentra la causa del mismo, preguntele a su compañero, si ya no hay mas recursos, preguntele a un auxiliar o a mi (JG). "Debuguear" es un arte que necesita mucha practica y experiencia. Ayudar a otas personas a encontrar errores en su codigo es una manera excelente para obtener esta experiencia.

$ javac Dud.java

Para correr el programa:

$ java BallWorld Dud
Esto debe levantar una ventana, de fondo gramoso, y un solo boton (en la parte inferior) etiquetado Dud. Presione el boton Dud. Voila!, un punto negro en el centro del area verde.

Puchis mano, que aburrido.

Ok, ok, pues eso fue un monton de trabajo para un punto negro mendigo. Hagamos que el usuario pueda interactuar con el puntito.

Cambie el metodo userTyped para que cuando el usuario presione la techa 'd' (el usaurio en realidad le escribe una 'd' a la clase que manipula las graficas. Esta clase manda a llamar el metodo userTyped(..), usted solo recibe el caracter que presiono!), bueno, cuando el usuario presiona 'd', este metodo debe indicarle al "mundo" que quite esta pelota. Se recordo que debió haber almacenado la referencia al objeto World que se lo mandaron en el metodo setWorld en un campo (atributo)!? Si su campo se llama world, puede escribir this.world.removeBall(this);. El metodo removeBall quita su punto negro de la pantalla y para de llamar el metodo act.

Compile Dud.java de nuevo y corra el programa. Si le funciono esto podemos hacer que Dud sea aun mas interactiva cambiando las coordenadas 'x' y 'y' cuando se invoca el metodo userClicked. Asegurese de tener campos (atributos) que almacenan la posición 'x' y 'y'. En este caso userClicked recibe como parametros el punto en el plano donde el usuario desea poner la pelota (x,y). Recuerde que la clase que manipula las graficas (o sea la que pide los valores actuales de x, y, radio) invoca los metodos getX(), getY(), gerRadius(). Estos metodos devuelven el estado actual del objeto, y si almacena el estado en atributos, necesita modificar los atributos!

Compile y corra el programa de nuevo, ahora tiene un punto negro que lo puede mover con el mouse. Pruebelo!

Zoooooooom!

Ahora hagamos algo interesante con el metodo act. Pero al terminar de hacer esto, ya no tendremos un Dud; tendremos una pelota con un comportamiento diferente. Dado que tendrá comportamiento diferente, merece su propia clase.

De nuevo, abra un nuevo "buffer" para editar un nuevo archivo (si esta usando XEmacs, lo puede hacer presionando ctrl+x ctrl+f y despues escriba el nombre del archivo que desea, como ejemplo: Bouncer.java. Despues presione ENTER). Haga Copy-Paste desde el archivo Dud.java al nuevo archivo (Bouncer.java). Cambie el nombre de la clase y el constructor (o sea cambie "class Dud" por "class Bouncer", etc). Guarde el archivo (ctrl+x ctrl+s) y continúe haciendo los cambios necesarios.

Nota: tenga mucho cuidado al copiar grandes cantidades de codigo de una clase a otra, o incluso de un metodo a otro. Hay una variedad de cosas malas que pueden suceder (ay les cuento de un Laboratorio que tuve que re-hacer en 5to. Bach., haha!). La unica razon por la que estamos haciendo esto es porque es un dolor de #()+^*$ escribir todo de nuevo otra ves. Mas adelante veremos como aprovechar el codigo ya escrito para no tener que hacer estos masivos y peligrosos copy-paste.

Esta nueva clase Bouncer modela una pelota que se mueve a traves de la pantalla y cada ves que toca una pared, rebota y continua moviendose.

Ahh, entonces tengo que mover la pelota... Hago un ciclo!... NOO!!!!

Si esta pensando en que debe haber un ciclo para mover la pelota, esta pensando bien, su logica deductiva lo llevo a un resultado correcto. Pero segun las especificaciones que hemos dado en el laboratorio, quien se encarga de manejar las cosas?

Muy bien! Esto lo hace la clase que manipula las graficas (especificamente WorldCanvas). Si tienen razon: en algun lado hay un ciclo que mueve la pelota. El ciclo esta en la clase WorldCanvas, cada vez que desea mover la pelota, manda a llamar el metodo act() de una pelota especifica. Despues de que ya la movió necesita desplegarla en la pantalla, entonces la clase WorldCanvas manda a llamar los metodos getX(), getY(), getRadius() para saber donde esta y que tamaño tiene actualmente su pelota.

El cuerpo del metodo act debe contener instrucciones para modificar el estado de la pelota. No debe contener un ciclo. Mas bien, debe contener instrucciones simples que modifican el estado (o el valor, si lo quiere ver asi) de los atributos privados de la clase.

Tome el siguiente ejemplo, esta especificacion del metodo act hace que una pelota oscile entre los puntos (-3,5) y (10,2):

public void act()
{
   if (this.x() == 10d)
   {
      this.x = -3d;
      this.y = 5d;
   }
   else
   {
      this.x = 10d;
      this.y = 2d;
   }
}

Para que funcione adecuadamente el metodo act() necesitamos tener velocidades en ambas direcciones (velX, velY) que indican cuanto cambia la posicion, cada vez que se ejecuta el metodo. Necesitamos almacenar estas velocidades como atributos privados porque si los definimos adentro del metodo act(), se pierden al terminar la ejecucion del metodo. Esta es la segunda condicion suficiente para hacer un atributo privado (la primera es cuando necesitabamos la informacion en dos o mas metodos).

Inicialicemos los valores de velX, velY a 0.1 y 0.2 respectivamente. hay dos maneras de hacer esto. Una es simplemente de asignarle un valor cuando lo estamos definiendo en la lista de atributos privados:

private double velX= 0.1;
private double vely= 0.2;

Una manera mejor de hacerlo es en el constructor. El constructor de la clase Bouncer se miraria algo asi:

public Bouncer()
{
   // sentencias a ejecutar en el constructor
   // ...
}
Puede inicializar velX, velY aleatoriamente (entre -0.5 y 0.5) diciendo:
  velX= Math.random()-0.5;
Math.random() devuelve un double entre 0.0 y 1.0

Recuerde que el constructor no tiene tipo de retorno, solo un nombre, que debe ser el mismo que el de la clase, exactamente. En el cuerpo del constructor (donde esta el comentario) puede agregar cualquier inicializacion que desee. Este codigo se ejecutara caval despues de que se crea el objeto y antes de que suceda cualquier otra cosa. En particular, tome nota de que no hay un objeto World que conozca todavia! Asi que no puede utilizar world.getMaxX(), por ejemplo. Puede usted Descifrar que pasaría si prueba? Tampoco debe crear un nuevo objeto World, porque no seria el mismo para todas las pelotas!

Compile y corra su programa. Ya que escribió una nueva clase, agregue Bouncer a la lista de argumentos del programa:

$ java BallWorld Dud Bouncer
Ahora su pelota se mueve hacia la esquina superior derecha de la pantalla, hasta que se pierde (asumiento que tiene velX = 0.1d, velY = 0.2). Fijese que sus Duds siguen siendo duds. Modifique el metodo act() de la clase Bouncer para que la pelota no se pierda, sino que rebote en las paredes de la pantalla (o sea, las paredes del mundo).

A este momento podiría tener la preocupacion de que si puede usar su campo (atributo) world en el metodo act. Si esta preocupado, maravilloso! Si no lo esta, seria aconsejable de que tratara de averiguar porque deberí estar preocupado. Ya que este preocupado, deje de preocuparse, la clase WorldCanvas (la que manipula las graficas) de tal manera que le podemos garantizar que su metodo setWorld(..) se va invocar antes que su metodo act().

Compile y corra el programa con capacidad de rebote. Presione el boton Bouncer bastantes veces. Yeeeeha!

Otras cosas que son cool

Copie de nuevo el codigo fuente de Bouncer.java a un archivo que se llame Exploder.java y cambie el nombre de la clase y el constructor.

Cambie el metodo act para que ademas de moverse (caminar), el radio poco a poco se haga cada ves mas grande (digamos 0.03 por cada invocation al metodo). Haga que el radio empeize en 2. Cuando llegue a 5, haga que la pelota explote... como? simplemente quitandola del mundo (World).

Compile y corra el programa de nuevo, pero agregue Exploder a la lista de argumentos del programa. Asegurese de que funcione.

Ahora unos fuegos pirotecnicos: justamente antes de que la pelota vaya a explotar (y se remueva del mundo), vamos a hacer que agregue dos pelotas explotadoras nuevas. Recuerde que para crear un nuevo objeto de una clase usamos new:

Exploder exp1= new Exploder();
Exploder exp2= new Exploder();
Podemos agregar estas pelotas explotadoras asi:
world.addBall(exp1);
world.addBall(exp2);
Desafortunadamente, esto pone todas las nuevas pelotas explotadoras en el centro de la pantalla (0,0), en ves de donde la ultima haya llegado. Como podemos fiijar los valores de x, y del nuevo objeto con los valores del objeto viejo?

En realidad podemos escribir un segundo constructor para esta clase:

public Exploder(double startX, double startY)
{
   // sentencias de inicializacion
   // ...
}
La inicializacion debe fiajr a x con el valor de startX, a y con startY, ademas de cualquier inicializacion que hayan hecho en el otro constructor. De donde vienen startX y startY? Puede mandarlos como argumentos al constructor cuando crea el objeto con new:
Exploder exp1= new Exploder(this.getX(), this.getY());
Convénzase de que esto funciona y pruebelo. Lo que debe obtener es una explosion masiva! en la pantalla, y despues rapidamente sufrir una muerte dolorosa, tratando de lidiar con miles de pelotitas individuales en la pantalla!

Aqui termina el ejercicio de este laboratorio. cualquier cosa que hagan de aqui en adelante es gravy.

Gravy

Diviertanse!

Lo que deben entregar

El trabajo completo consta de:

Tambien lo siguiente:

Este laboratorio se entregará el Lunes, 19 de Febrero -- antes de las 11:59 p.m.

This course is a part of Lynn Andrea Stein's Rethinking CS101 project at the MIT AI Lab and the Department of Electrical Engineering and Computer Science at the Massachusetts Institute of Technology.

Questions or comments:
<cs101-webmaster@ai.mit.edu>