En el listado 1 tenemos todas las definiciones de constantes y variables usadas en el programa, como direcciones (en lugar de usar números dentro de nuestro código es mucho más útil usar constantes que no hagan necesario el memorizado de dichos números), códigos de teclas (scancodes recibidos en los eventos KeyPress), las dimensiones del tablero de juego y de los bloques gráficos, etc. También están declaradas allí todas las variables globales del programa, como se verá cuando comentemos el mismo.
El resto del programa (el código en sí mismo) está disponible en el listado 2, incluyendo todas las funciones de inicialización, movimiento, dibujado de gráficos, y gestión de eventos. Sin duda no hay nada más descriptivo para un programador que un sencillo ejemplo que ilustre como utilizar determinadas funciones, de modo que este programa tiene doble función, ya que además de permitirnos aprender muchas técnicas básicas, se podrá ver todo lo aprendido de forma correctamente aplicada.LISTADO 1: XSNAKE.H
/* Xsnake.h -> Fichero de cabecera para XSnake. (c) 1999 Santiago Romero Iglesias y Antonio Fernandez Fandos */ #define VELOCIDAD 70000 #define INCREMENTO 10 #define MAX_COMIDA 5 #define xmin 0 #define xmax 79 #define ymin 0 #define ymax 39 #define true 1 #define false 0 #define boolean char #define VACIO 0 #define SERPIENTE 1 #define PARED 2 #define COMIDA1 3 #define COMIDA2 4 #define COMIDA3 5 #define COMIDA4 6 #define ANCHURA_BLOQUE 7 #define ALTURA_BLOQUE 9 #define DIR_IZQ 1 #define DIR_DER 2 #define DIR_ARR 3 #define DIR_ABJO 4 /* Codigos de teclas, verlos todos en el fichero /usr/X11R6/lib/X11/xkb/keycodes/xfree86 */ #define ESC_KEY 9 #define ARRIBA 98 #define ABAJO 104 #define IZQUIERDA 100 #define DERECHA 102 /*--- Variables globales utilizadas ---------------------*/ Pixmap sprites[4]; long amarillo, verde, negro, tiempo=0, segundos=0; char done = 0, tecla = 0, comida; char pantalla[xmax+1][ymax+1], cola[xmax+1][ymax+1]; struct serpiente { int cabserx, cabsery, colaserx, colasery, espera; char izquierda, derecha, arriba, abajo; int puntos; }; /* estructuras para el cronometrado de tiempo */ struct timeval start, end; long time_dif; /*--- Declaraciones de funciones ------------------------*/ void InicializaMatrices( void ); void InicializaSerpiente( struct serpiente * ); void DibujaTablero( Display *, Pixmap, Drawable, GC ); void LeerMovimiento( char, struct serpiente * ); void Movimiento( Display *, Drawable, Drawable, GC, struct serpiente * ); void DibujaSerpiente( Display *, Pixmap, Window, GC, struct serpiente * ); void EliminaCola( Display *, Pixmap, Window, GC, struct serpiente * ); void BorraSerpiente( Display *, Pixmap, Window, GC, struct serpiente * ); void ActualizaPuntos( Display *, Pixmap, Window, GC, struct serpiente * ); void DibujaComida( Display *, Drawable, Drawable, GC ); void BorraComida( Display *, Drawable, Drawable, GC, int, int ); void GeneraComida( Display *, Drawable, Drawable, GC ); unsigned long ColorPorNombre( Display *, char *); void RedibujaPantalla( Display *, Drawable, GC, Pixmap, int, int, int, int ); void DibujaBitmap( Display *, Drawable, GC, Pixmap, int, int ); void DibujaBloque( Display *, Drawable, Drawable, GC, int, int, char );
LISTADO 2: XSNAKE.C
/*=========================================================== Xsnake.c -> Version XWindow del juego SNAKE (clásico de los primeros videojuegos arcade). El juego consiste en mover una serpiente por la pantalla sin chocarse con las paredes ni con la cola, recogiendo frutas que aumentan el tamaño de esta. Compilar mediante: gcc xsnake.c -o xsnake -I/usr/X11R6/include -I/usr/local/include -L/usr/X11/lib -L/usr/local/lib -lX11 -lXext -ljpeg -lz -lm -lImlib Lanzar desde el mismo directorio que las imagenes xsnake*.jpg . (c) 1999 Santiago Romero Iglesias y Antonio Fernandez Fandos =========================================================*/ #include <X11/Xlib.h> #include <X11/Xutil.h> #include <stdio.h> #include <X11/extensions/shape.h> #include <Imlib.h> #include <sys/time.h> #include <unistd.h> #include "xsnake.h" /*=== Funcion principal main() ==========================*/ int main() { Display *display; Window window, rootwin; int pant, anchura, altura; Pixmap pixmap, mask; GC gc; ImlibData *id; ImlibImage *imagen; XSetWindowAttributes attr; XEvent evento; Font fuente; char key; struct serpiente serpiente1; XSizeHints myhints; display = XOpenDisplay( NULL ); window = XcreateWindow(etc..); pant = DefaultScreen(display); XSelectInput( display, window, KeyPressMask | ExposureMask ); XStoreName(display, window, "X-Snake"); pixmap = XCreatePixmap( display, window, ANCHURA_BLOQUE*(xmax+1), ALTURA_BLOQUE*(ymax+1)+40, DefaultDepth(display, pant)); gc = XCreateGC( display, pixmap, 0, NULL ); XSetForeground(display, gc, 0 ); XFillRectangle( display, pixmap, gc, 0, 0, ANCHURA_BLOQUE*(xmax+1), ALTURA_BLOQUE*(ymax+1)+40 ); negro = ColorPorNombre( display, "black"); amarillo = ColorPorNombre(display, "yellow"); verde = ColorPorNombre( display, "green"); fuente = XLoadFont( display, "9x15" ); XSetFont( display, gc, fuente ); /* cargamos las imagenes JPG para el juego en pixmaps */ imagen=Imlib_load_image(id, "xsnake0.jpg"); Imlib_render( id, imagen, ANCHURA_BLOQUE, ALTURA_BLOQUE ); sprites[0] = (Pixmap) Imlib_move_image( id, imagen ); imagen=Imlib_load_image(id, "xsnake1.jpg"); Imlib_render( id, imagen, ANCHURA_BLOQUE, ALTURA_BLOQUE ); /* igual para el resto de imagenes */ srand( time(NULL) ); DibujaTablero( display, pixmap, window, gc ); InicializaMatrices(); InicializaSerpiente( &serpiente1 ); DibujaSerpiente(display, pixmap, window, gc, &serpiente1 ); ActualizaPuntos(display, pixmap, window, gc, &serpiente1 ); XMapWindow( display, window ); while ( !done ) { /* coger el tiempo actual */ gettimeofday(&start, NULL); /* a continuacion miramos si hay algun evento en la cola de eventos. Si lo hay, lo recogemos y lo interpretamos. Si no lo hay, se repite el bucle. */ while (QLength(display) > 0) { XNextEvent(display, &evento); if (evento.xany.window != window) continue; switch(evento.type) { case Expose: RedibujaPantalla(display, window, gc, pixmap, evento.xexpose.x, evento.xexpose.y, evento.xexpose.width, evento.xexpose.height ); XFlush(display); break; case KeyPress: key = XKeycodeToKeysym(display, evento.xkey.keycode, 0); if( evento.xkey.keycode == ESC_KEY ) done = 1; tecla = evento.xkey.keycode; break; } } if( tecla != 0 ) LeerMovimiento ( tecla, &serpiente1 ); GeneraComida( display, pixmap, window, gc ); Movimiento( display, pixmap, window, gc, &serpiente1 ); DibujaSerpiente(display, pixmap, window, gc, &serpiente1 ); BorraSerpiente(display, pixmap, window, gc, &serpiente1 ); /* coger el tiempo actual */ gettimeofday(&end, NULL); /* calculamos el tiempo que ha pasado desde el anterior fotograma, para poder cronometrar el estado del juego */ if( end.tv_usec < start.tv_usec ) end.tv_usec += 1000000; time_dif = end.tv_usec - start.tv_usec; XSync(display, False); usleep(VELOCIDAD-time_dif); /* implementamos un cronometro de useg a segundos */ tiempo += VELOCIDAD; if( tiempo > 1000000 ) { tiempo -= 1000000; segundos++; } } sleep(1); XUnmapWindow(display, window); XUnloadFont(display, fuente); XFreePixmap(display, pixmap); XCloseDisplay(display); exit( 0 ); } /*--- Inicializa los valores básicos para la serpiente.------------------*/ void InicializaSerpiente( struct serpiente *serp ) { serp->cabserx = serp->colaserx =xmax/2; serp->cabsery = serp->colasery =ymax/2; serp->espera = INCREMENTO/2; serp->puntos = 0; serp->izquierda = serp->arriba = serp->abajo = false; serp->derecha = true; pantalla[serp->colaserx][serp->colasery] = SERPIENTE; cola[serp->colaserx][serp->colasery] = DIR_DER; } /*--------------------------------------------------------- Dibuja el tablero tanto en pantalla como en el pixmap que hace de pantalla virtual. --------------------------------------------------------*/ void DibujaTablero( Display *display, Pixmap pixmap, Drawable window, GC gc ) { int x, y; for( x=0; x<=xmax; x++ ) { DibujaBloque( display, pixmap, window, gc, x, ymin, PARED ); DibujaBloque( display, pixmap, window, gc, x, ymax, PARED ); } for( y=0; y<=ymax; y++ ) { DibujaBloque( display, pixmap, window, gc, xmin, y, PARED ); DibujaBloque( display, pixmap, window, gc, xmax, y, PARED ); } XSetForeground(display, gc, verde); XDrawString( display, pixmap, gc, 10, ((ALTURA_BLOQUE*ymax)+40)-5, "PUNTOS: ", 8 ); XDrawString( display, window, gc, 10, ((ALTURA_BLOQUE*ymax)+40)-5, "PUNTOS: ", 8 ); XDrawString( display, pixmap, gc, ((ANCHURA_BLOQUE*(xmax-1))/2)-15, ((ALTURA_BLOQUE*ymax)+40)-5, "X-SNAKE", 7 ); XDrawString( display, window, gc, ((ANCHURA_BLOQUE*(xmax-1))/2)-15, ((ALTURA_BLOQUE*ymax)+40)-10, "X-SNAKE", 7 ); } /*--- Actualiza abajo los puntos que lleva el jugador. ----------------*/ void ActualizaPuntos( Display *display, Pixmap pixmap, Drawable window, GC gc, struct serpiente *serp ) { char puntos[7]; sprintf(puntos, "%d", serp->puntos); XSetForeground(display, gc, negro); XFillRectangle( display, window, gc, 105, ((ALTURA_BLOQUE*ymax)+40)-20, 6*9, 20 ); /* etc... sigue con mas escrituras: ver listado completo en CD. */ } /*--- Realiza la accion apropiada segun la pulsacion de direccion ------*/ void LeerMovimiento( char keycode, struct serpiente *serp ) { if( keycode == ARRIBA && serp->abajo == false ) { serp->arriba = true; serp->derecha = serp->izquierda = serp->abajo = false; } /* igual para el resto de direcciones, else if( keycode==DERECHA..) */ tecla = 0; } /*--- Realiza la accion apropiada segun la pulsacion de direccion ------*/ void Movimiento( Display *display, Pixmap pixmap, Window window, GC gc, struct serpiente *serp ) { int dircola, cabserantx, cabseranty; char dato; cabserantx = serp->cabserx; cabseranty = serp->cabsery; if ( serp->izquierda == true ) { if ( serp->cabserx > xmin+1 ) { serp->cabserx = (serp->cabserx)-1; dircola = DIR_IZQ; } else done = 1; } /* igual para el resto de direcciones (ver listado CD)*/ /* ahora comprobamos choque con pared y con comida */ if( pantalla[serp->cabserx][serp->cabsery] == SERPIENTE ) done = 1; else { if( pantalla[serp->cabserx][serp->cabsery] >= COMIDA1 ) { BorraComida( display, pixmap, window, gc, serp->cabserx, serp->cabsery ); comida--; serp->espera = serp->espera + INCREMENTO; serp->puntos = serp->puntos + 10; ActualizaPuntos(display, pixmap, window, gc, serp ); } pantalla[serp->cabserx][serp->cabsery] = SERPIENTE; cola[cabserantx][cabseranty] = dircola; } } /*--- Dibuja un bloque ----------------------------------*/ void DibujaBloque( Display *display, Drawable pixmap, Drawable window, GC gc, int x, int y, char tipo ) { DibujaBitmap( display, pixmap, gc, sprites[tipo], x*ANCHURA_BLOQUE, y*ALTURA_BLOQUE); DibujaBitmap( display, window, gc, sprites[tipo], x*ANCHURA_BLOQUE, y*ALTURA_BLOQUE); } /*--- Funcion que redibuja la pantalla necesaria ----------------*/ void RedibujaPantalla( Display *display, Drawable drawable, GC gc, Pixmap pixmap, int x, int y, int anchura, int altura ) { XCopyArea(display, pixmap, drawable, gc, x, y, anchura, altura, x, y); } /*--- Funcion que dibuja un bitmap ----------------------*/ void DibujaBitmap( Display *display, Drawable drawable, GC gc, Pixmap pixmap, int x, int y ) { XCopyArea(display, pixmap, drawable, gc, 0, 0, ANCHURA_BLOQUE, ALTURA_BLOQUE, x, y); }
gcc xsnake.c -o xsnake -I/usr/X11R6/include -I/usr/local/include -L/usr/X11/lib -L/usr/local/lib -lX11 -lXext -ljpeg -lz -lm -lImlib
Para realizar la compilación se ha creado un sencillo script llamado make con la anterior línea. Basta pues con ir al directorio del programa y ejecutar ./make, lo cual generará el binario xsnake (aunque mucho más eficiente hubiese sido un Makefile, no es make el propósito de este curso).
Como puede verse, requiere (en general) los siguientes recursos:
XFree86 XFree86-devel glibc-devel Imlib Imlib-devel libjpegPara su ejecución tan sólo hay que ir mediante una terminal de texto (xterm) al directorio del programa compilado y ejecutarlo con:
./xsnakeEl programa debe ejecutarse en el mismo directorio que las imágenes para que éste pueda cargarlas. En un programa que fuese versión final, probablemente el programa de instalación las instalaría en /usr/games, desde donde las leería el programa.
Durante la ejecución controlaremos la serpiente mediante las flechas del cursor, y nuestro objetivo será, simplemente, el de conseguir la mayoría de puntos posible. Al salir del programa, éste nos dirá la puntuación máxima conseguida y el tiempo que se ha sobrevivido:
[sromero@compiler ejemplo]$ ./xsnake XSnake - Version 1.0 para X Window. Has conseguido 240 puntos. Has sobrevivido 2 minuto(s) y 39 segundo(s).Como se verá en el comentario del próximo artículo, en el juego se controla el tiempo para que la tasa de fotogramas por segundo (fps) sea fija, asi como para contar el tiempo de juego desde el inicio de la aplicación.