viernes, 26 de octubre de 2012

Ejemplo #2: Otra versión de PONG, en JAVA.

Ya vimos cómo hacer un juego de PONG en C.
Ahora voy a mostrar otra forma de resolverlo con un código un poco mas compacto.
Para esto utilizaremos el lenguaje JAVA.



En este ejemplo sólo vamos a emplear las clases para almacenar campos de acceso público. Esto no es lo correcto de acuerdo al paradigma de la programación orientada a objetos. Ya veremos como escribir un código verdaderamente orientado a objetos en el Ejemplo #4.

Primero vamos a crear la clase Paleta y la clase Pelota.

El código de la clase Paleta:


Esta paleta tiene sólo 2 atributos: alto que guarda el tamaño de la paleta e y que guarda la posición sobre el eje Y.


La clase pelota tiene 4 atributos:


Los primeros dos, x e y guardan la posición de la pelota. veloX y veloY van a ser utilizados para darle dirección. Esto lo veremos al analizar el loop del juego en la función pelota() de la clase Pong.

Ahora que ya tenemos estas dos clases vamos a crear la clase Pong.

Presento el código completo:


Lo primero que vemos es que la clase Pong extiende de JFrame para poder crear la ventana del juego, y que ademas implementa la interfaz KeyListener para controlar los eventos del teclado.

En el método principal main instanciamos un objeto Pong().

A continuación vemos las siguientes lineas en el constructor:



En el primer bloque definimos las propiedades de la ventana y la hacemos visible.

En la siguiente linea: this.createBufferStrategy(2); lo que hacemos es crear un buffer doble, de manera que uno esta en pantalla y el otro no. Esto nos permite redibujar la pantalla sin que el usuario lo note.

La próxima linea: this.addKeyListener(this); la tenemos que agregar para poder utilizar la interfaz KeyListener.

Antes de entrar en el loop del juego creamos los objetos Pelota y Paleta llamando a inicializoObjetos().

Lo próximo es crear un loop infinito. En cada vuelta del loop llamamos a pelota() que es la función donde esta toda la lógica del juego y a sleep() para producir las demoras necesarias.



Ahora vemos la función pelota():



Las primeras dos lineas mantienen la posición de la pelota siempre actualizada. Si pelota.veloX o pelota.veloY fueran negativos, la pelota se movería a la izquierda sobre el eje X y hacia abajo sobre el eje Y.

La llamada a chequearColision() comprueba que la pelota no haya alcanzado una de las paletas.

El siguiente if comprueba si la pelota alcanzó una pared lateral. En tal caso invierte el valor de veloX. Hay que aclarar que pelota.veloX = -pelota.veloX lo que hace no es negativizar el valor de veloX sino invertirlo.

En caso de que la pelota toque un lateral incrementa Malas en 1.

Realizamos a continuación la misma comprobación para los lados superior e inferior.

Esta forma de setear la dirección de la pelota es un poco mas compleja que la del ejemplo anterior en C, pero tiene la ventaja de ser un código mas compacto y económico en cuanto a recursos.


La siguiente función, chequearColision() esta construida igual que la del ejemplo anterior.



Cada bloque if es para una paleta. Si la posición de la pelota coincide con el area ocupada por la paleta entonces cambia la dirección. Si la pelota proviene de adentro de la cancha incrementa en 1 el valor de Buenas.


La próxima función es dibujoPantalla(). Alli vamos a inicializar todos los objetos gráficos para poder utilizarlos con las otras funciones


En las primeras dos lineas inicializamos los objetos g y bf.

Luego abrimos un try. Ya que JAVA maneja las excepciones automáticamente (a diferencia de C#) es conveniente inicializar los gráficos de esta forma por si el programa arroja alguna excepción.

En la siguiente linea g=bf.getDrawGraphics(); le asignamos a g el valor devuelto por bf.getDrawGraphics(). Esta función crea un objeto de tipo GRAPHICS2D que nos permite utilizar el doble buffer. Cuando terminamos de dibujar llamamos a bf.show() para reemplazar el buffer que esta en pantalla por el de fondo.

Llamamos a las funciones que imprimen puntos, pelota, y paletas, siempre pasando como parámetro el objeto de gráficos g.

Dentro del finally llamamos a g.dispose() para deshacernos de g ya que no tenemos que volver a hacer uso del mismo.

Ahora ya terminamos de dibujar y llamamos a bf.show().

La última linea sincroniza con el refresh rate de la pantalla.


La función dibujoPelota() no nos ofrece ninguna complicación:



La función dibujoPaleta():



Contiene un switch para mantener actualizada la posición de la paleta de acuerdo a la dirección del curso vigente. En caso que el valor ingresado por teclado sea la tecla 'e' llama a System.exit(0) y termina la ejecución del programa.

Luego dibujamos la paleta en ambos lados de la pantalla. Si bien se perciben como dos paletas, al moverse juntas las controlamos como un solo objeto. Si quisiéramos agregar un segundo jugador tendríamos que crear otro objeto.


La función muestroPuntos():



Muestra los puntos en la parte superior-izquierda de la pantalla.


La ultima función que vemos es sleep().



Construida igual que en el ejemplo anterior, primero creamos el valor de referencia, lo guardamos en goal y luego lo comparamos con Sustem.currentTimeMillis() hasta que este último alcanza el valor de goal.

Los 3 métodos que estan al final son obligatorios y tenemos que sobreescribirlos para poder utilizar la interfaz KeyListener. El único que en verdad necesitamos es el primero de ellos, que corre cuando se presiona una tecla. En ese caso el valor de la tecla presionada es almacenado en key para utilizarlo en el switch que actualiza la posición de las paletas en la función dibujoPaletas().



De esta manera queda completa la exposición del código.

Hemos podido ver, en este segundo ejemplo, otra forma de abordar el problema lógico planteado en el primer ejemplo, en este caso utilizando el lenguaje JAVA para resolverlo.

También vimos como crear una ventana con los JFrame de Swing y como crear gráficos con AWT.

Links para descargar el código:

clases:
https://dl.dropbox.com/u/103165598/pongJava-src.rar

pongJava.JAR:
https://dl.dropbox.com/u/103165598/pongJava.jar



jueves, 25 de octubre de 2012

Ejemplo #1: PONG en lenguaje C.

Quisiera comenzar esta serie de ejemplos con uno de los videojuegos más conocidos del mundo. El clásico PONG, creado por Allan Alcorn para la compañía Atari en 1972.

Voy a demostrar a continuación como hacer una versión de este juego.

El lenguaje utilizado en esta ocasión es C, simple y puro, sin gráficos ni complejidades.
La pelota y las paletas serán representadas por caracteres ASCII.

Primero presento el código completo:
Ahora pasamos a explicar todo el código en detalle, función por función.

En principio utilizamos 3 librerías:
stdio.h - la librería estándar de C para funciones de input/output
time.h – esta librería la necesitamos para la función clock()
conio.h - utilizamos esta librería para tener acceso a las siguientes funciones:
     -kbhit(): devuelve verdadero si una tecla es presionada
     -getch(): devuelve un valor ingresado por teclado sin buffer ni echo
     -clrscr(): limpia la pantalla
     -gotoxy(int x, int y): mueve el cursor a la posición indicada. Recibe 2 ints como parámetro.

La librería conio.h no es parte del ANSI C sino que es una librería de Borland que de cualquier manera es muy común y esta incluida en muchos compiladores.
Si queremos compilar el ejemplo en un sistema UNIX/Linux podemos encontrar funciones parecidas en la librería ncurses.h. En este ejemplo no vamos a cubrir esas funciones.


Vamos a empezar a analizar el código desde afuera hacia adentro.

La primera función es sleep(), cuya declaración es void sleep (float).
A continuación la función completa:


Esta función puede parecer un poco complicada.
Esta construida para demorar la ejecución del loop del juego. Para esto recibe como parámetro un valor de tipo float que será el que determine la velocidad.

Para poder utilizarla lo que tenemos que saber es que al llamarla le pasamos n segundos y realiza una pausa de ese largo.

Si con eso es suficiente pueden pasar a la función siguiente... sino la explicación completa:

clock() devuelve la cantidad de pulsos de reloj transcurridos desde el inicio del programa.
Lo que hacemos es multiplicar el valor que le pasamos a sleep() en segundos por la constante CLOCKS_PER_SEC para pasarlo a pulsos de reloj, esto se lo sumamos al valor devuelto por clock() para establecer un punto de referencia y lo guardamos en la variable goal de tipo de dato clock_t.
A continuación haceoms un bucle while vacío para esperar que el valor devuelto por las llamadas a clock() alcance el valor de referencia goal.


Ahora continuamos con una función muy sencilla que es la que utilizaremos para mostrar los puntos, void muestroPuntos();


Sencillísimo, las variables Buenas y Malas son las que tienen almacenados los puntos. Esta función los imprime en pantalla en la posición indicada por gotoxy().


La próxima función es void leoTecla(); que es la que utilizamos para leer los caracteres ingresados por teclado.


Lo primero que vemos es que la función kbhit() devuelve verdadero si se presionó una tecla. En ese caso el valor ingresado se almacena en tecla.

El switch determina si tecla es equivalente al código para la flecha de abajo del teclado (numero 72, almacenado en la constante TECLA_ABA declarada al comienzo del programa) y en ese caso incrementa el valor de posPaleta que determina la posición de la paleta sobre el eje Y.
Lo mismo ocurre para la tecla de la flecha hacia arriba.
El ultimo case corre si la tecla presionada fue 'e', entonces llama a exit() para salir del programa y le pasa 0 como parámetro (0 indica que el programa termino sin errores).


Pasamos a int chequearColision(int donde); que nos sirve para hacer rebotar la pelota contra la paleta y contar los puntos.


Cada uno de los dos grandes bloques if son para cada paleta. Lo que hacen es comprobar primero si la posición de la pelota es igual a la de cualquiera de las partes de la paleta determinadas por donde.

En ese caso cambia la dirección de la pelota. Por ejemplo: si flagIzq es igual a 1 eso significa que la pelota esta yendo hacia la izquierda y entonces modifica el valor de flagIzq por 0 y el de flagDer por 1 para cambiar la trayectoria, caso contrario invierte los valores. Esto mismo para las dos paletas. Si el rebote es hacia adentro de la cancha entonces el valor de Buenas es incrementado en 1.


Seguimos con void dibujoPaletas(int); que como su nombre lo indica imprime las paletas en pantalla.


Antes que nada esta función recibe como parámetro un int que será la posición de la paleta sobre el eje Y.

char x almacena el valor ASCII del caracter utilizado para representar la paleta.

El bucle for imprime en pantalla dos paletas de 3 caracteres de longitud, una del lado izquierdo y otra del lado derecho de la pantalla.


Ahora que tenemos armadas todas las funciones que necesitamos vamos a poner la pelota a rebotar por la pantalla y combinarlas todas en la función void pelota().


Podemos dividir esta parte del código en 4 grandes bucles while. Cada uno representa una de las 4 posibles trayectorias de la pelota (arriba-izquierda, arriba-derecha, abajo-izquierda, abajo-derecha) representadas por las 4 variables flarArr, flagAba, fladIzq y flagDer y que tendrán un valor de 1 si están encendidas o 0 si están apagadas.

Tomamos el primer bucle while. Primero limpia la pantalla con clrscr(). Luego actualiza de acuerdo al rumbo establecido el valor de pelotaX y pelotaY que es donde almacenamos la posición de la pelota, entonces va a la posición con gotoxy(pelotaX, pelotaY) e imprime en pantalla pelotaChar que contiene el caracter ASCII que utilizamos para la pelota (se lo asignamos en la declaración al comienzo del programa).

Después de imprimir en pantalla la pelota llamamos a la función leoTecla() para comprobar si el jugador movió las paletas, dibujamos entonces las paletas con la posición actualizada llamando a dibujoPaletas(). Comprobamos si la pelota rebota en la paleta, para eso llamamos a chequearColision(posPaleta) y le pasamos la posición de las paletas sobre el eje Y.

Finalmente llamamos a muestroPuntos() y después a sleep( (float) velo) donde velo es el valor que fijamos en la declaración de dicha variable para demorar entre una vuelta del juego y la siguiente.

Los dos bloques if que hay antes de cerrar el while comprueban si la posición de la pelota alcanzó alguna de las cuatro paredes de la cancha y en ese caso realizan las modificaciones de valores necesarias en las variables de dirección para cambiar la trayectoria. Si alguna de las paredes laterales es alcanzada se incrementa el valor de Malas en 1.

En cada vuelta del loop del juego se ejecuta el bucle while correspondiente a la dirección de la pelota en esa vuelta.


Por ultimo la función principal:


Este bucle es el que mantiene la pelota en movimiento todo el tiempo y el programa ejecutándose. Al pasarle el valor 1 al while lo que hacemos es producir un loop infinito ya que le pasamos un estado que no se modifica nunca.

Esto es todo en lo que respecta al funcionamiento de este programa. He tratado de mantenerlo lo mas sencillo posible para que quede expuesto el funcionamiento básico del juego. Como siempre, existen otras formas de resolver los problemas planteados por el programa. Espero que la forma elegida en este ejemplo haya resultado clara.


Link para descargar el código: 

https://dl.dropbox.com/u/103165598/PALETAS.C?dl=1