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



 



No hay comentarios: