6. Evitemos
esperar al teclado. Tercer
juego: motos de luz.
Contenido
de este apartado:
6.1.
Ideas generales.
En
la mayoría de los juegos no ocurre lo que en el anterior: no
podemos
permitirnos que el ordenador que "parado " esperando a que se pulse una
tecla, sino que la acción debe proseguir aunque nosotros no
toquemos
el teclado.
Esto
es muy fácil de conseguir. Entre la rutinas
estándar de casi
cualquier compilador de C
tenemos la función kbhit(
), que
devuelve "verdadero" (distinto de cero) si se ha pulsado alguna tecla y
"falso" (cero) si no se ha pulsado ninguna. Posteriormente, con getch(
) sabríamos
exactamente qué tecla es la que se ha pulsado
y vaciaríamos el "buffer" (memoria intermedia) del teclado
para
que se pudieran seguir comprobando nuevas teclas.
En
el caso de Allegro,
la rutina que comprueba si hemos pulsado alguna
tecla
se llama keypressed( ),
y en la práctica podríamos
hacer cosas como esta para que el programa no se pare pero a la vez
esté
pendiente de qué teclas pulsemos:
do
{
/*Actualizar reloj y
pantalla, mover
nave, etc */
}
while
( ! keypressed () );
teclaPulsada
= readkey();
Al
principio del programa deberemos añadir la línea
"install_keyboard()"
para tener acceso a estas rutinas de manejo de teclado.
En
el caso de Pascal
(Free Pascal) es muy similar, incluso con los mismos nombres de
funciones auxiliares (que están en la unidad CRT):
repeat
(*
Actualizar reloj y
pantalla, mover
nave, etc *)
until
keypressed;
teclaPulsada
:= readkey;
Para Java.. existe una dificultad
en este juego con una de las cosas que no hemos comentado aún, y
que surgirá enseguida...
Vamos
a aplicarlo a un primer juego de habilidad, el clásico de
las "motos
de luz" (si alguien recuerda la
película Tron,
quizá sepa
a qué me refiero). La idea es la siguiente:
- Participan
dos jugadores.
- Cada
uno de los jugadores maneja una "moto", que va dejando un rastro cuando
se desplaza.
- Si
una de las motos "choca" con el rastro dejado por la otra, o por ella
misma,
o con el borde de la pantalla, estalla y pierde la partida.
- Por
tanto, el objetivo es "arrinconar" al contrario sin ser arrinconado
antes
por él.
Con
un poco más de detalle, lo que tendría que hacer
nuestro
programa será lo siguiente:
- Borrar
la pantalla y dibujar un recuadro alrededor de ella.
- Preparar
las variables iniciales: dirección de cada jugador
(incremento en
X e Y de la posición de cada uno). En el instante inicial,
las motos
se desplazarán en direcciones contrarias. En un juego
"más
real", este incremento podría ser variable (o disminuir el
tiempo
de pausa entre un movimiento y otro), de modo que las motos circularan
cada vez más deprisa, para mayor dificultad.
- Parte repetitiva:
- Si
la siguiente posición de alguna de las motos está
ocupada,
la moto estalla y ese jugador ha perdido: se acabó el juego.
- Desplazar
las motos a su siguiente posición.
- Si
se ha pulsado alguna tecla, analizar esta tecla y cambiar la
dirección
de la moto según corresponda.
- Pausa
fija (o variable según la dificultad) antes de la siguiente
repetición,
fijada por nuestro programa, para que la velocidad no dependa de la
rapidez
del ordenador que se utilice.
- Repetir indefinidamente
(la condición de salida la comprobamos "dentro").
Eso de que una moto choque con otra... suena difícil, pero en
este primer caso nos bastará con mirar el punto de la pantalla
al que nos vamos a mover. Si ese punto tiene un color distinto del
fondo, habremos chocado (quizá sea con el borde de la pantalla,
quizá con nuestra estela, quizá con la del otro
jugador... pero eso nos da igual).
Ese es el problema en el caso de Java: no se puede leer un
punto de la pantalla con esa facilidad, lo que nos obligaría a
"memorizar" de otra forma las posiciones que no se pueden tocar. No es
difícil, pero es algo que veremos dentro de poco, así que
aplazamos un poco la versión en Java de este juego.
Lo
único que aún no sabemos hacer es la pausa. Con
C y Allegro
usaremos la función
rest(n);
(donde
"n" es el tiempo en milisegundos), que requiere que antes hayamos
instalado
el manejador de temporizadores (también será
necesario para
otras cosas, por ejemplo cuando accedamos al ratón, y al
reproducir
ciertos tipos de animaciones y de ficheros musicales) con:
install_timer():
En Pascal,
la pausa de "n" milisegundos se haría con
delay(n);
Y
en Java
(que insisto que no usaremos aún en este ejemplo), la pausa
sería
try
{
Thread.sleep( n );
}
catch ( InterruptedException e )
{
}
pero nos obligará
también a hacer algunos cambios más en la esctructura del
fuente.
La
apariencia del juego será también sencilla,
así:

6.2 Las motos
en
C.
Debería ser fácil de seguir...
/*----------------------------*/
/*
Intro a la programac
de
*/
/*
juegos, por Nacho
Cabanes */
/*
*/
/*
IPJ06C.C
*/
/*
*/
/*
Sexto ejemplo: juego
de
*/
/*
"motos de
luz"
*/
/*
*/
/*
Comprobado
con:
*/
/*
- Djgpp 2.03 (gcc
3.2)
*/
/*
y
Allegro 4.02 -
MsDos */
/*
- MinGW 2.0.0-3 (gcc
3.2) */
/*
y
Allegro 4.02 -
Win 98 */
/*
- MinGW DevStudio 2.05 */
/*
(gcc 3.4.2) y Allegro */
/*
4.03, Windows XP */
/*----------------------------*/
#include
<allegro.h>
/*
Posiciones X e Y iniciales de
ambos jugadores */
#define
POS_X_INI_1 150
#define
POS_X_INI_2 170
#define
POS_Y_INI_1 100
#define
POS_Y_INI_2 100
#define
INC_X_INI_1 -1
#define
INC_X_INI_2 1
#define
INC_Y_INI_1 0
#define
INC_Y_INI_2 0
/*
Pausa en milisegundos entre un
"fotograma" y otro */
#define
PAUSA 150
/*
Teclas predefinidas para cada
jugador */
#define
TEC_ARRIBA_1 KEY_E
#define
TEC_ABAJO_1 KEY_X
#define
TEC_IZQDA_1 KEY_S
#define
TEC_DCHA_1 KEY_D
#define
TEC_ARRIBA_2 KEY_8_PAD
#define
TEC_ABAJO_2 KEY_2_PAD
#define
TEC_IZQDA_2 KEY_4_PAD
#define
TEC_DCHA_2 KEY_6_PAD
int
posX1,
posY1,
posX2,
posY2; /*
Posiciones actuales */
int
incX1,
incY1,
incX2,
incY2; /*
Incremento de la posicion */
int
futX1,
futY1,
futX2,
futY2; /*
Posiciones futuras */
/*
Si ha chocado alguna moto */
int
chocado;
/*
La tecla pulsada */
int
tecla;
int
main()
{
allegro_init();
/*
Inicializamos Allegro */
install_keyboard();
install_timer();
/*
Intentamos entrar a modo grafico */
if (set_gfx_mode(GFX_SAFE,320,200,0,0)!=0){
set_gfx_mode(GFX_TEXT,0,0,0,0);
allegro_message(
"Incapaz
de
entrar a modo grafico\n%s\n",
allegro_error);
return 1;
}
/*
Si todo ha ido bien: empezamos */
/*
Rectangulo amarillo alrededor */
rect(screen,0,0,319,199,
palette_color[14]);
/*
Valores iniciales */
posX1 =
POS_X_INI_1;
posX2 =
POS_X_INI_2;
posY1 =
POS_Y_INI_1;
posY2 =
POS_Y_INI_2;
incX1 =
INC_X_INI_1;
incX2 =
INC_X_INI_2;
incY1 =
INC_Y_INI_1;
incY2 =
INC_Y_INI_2;
/*
Parte repetitiva: */
do {
chocado =
FALSE;
/*
Compruebo si alguna
va a colisionar */
futX1 =
posX1 +
incX1;
futX2 =
posX2 +
incX2;
futY1 =
posY1 +
incY1;
futY2 =
posY2 +
incY2;
if (getpixel(screen,
futX1,
futY1)!=0){
textout(screen,
font,
"La
moto 1 ha chocado!", 100,90,
palette_color[13]);
chocado =TRUE;
}
if (getpixel(screen,
futX2,
futY2)!=0){
textout(screen,
font,
"La
moto 2 ha chocado!", 100,110,
palette_color[12]);
chocado =
TRUE;
}
if (chocado)break;
/*
Si ninguna ha colisionado,
avanzan */
line (screen,
posX1,
posY1,
futX1,
futY1,
palette_color[13]);
posX1 =
futX1;
posY1 =
futY1;
line (screen,
posX2,
posY2,
futX2,
futY2,
palette_color[12]);
posX2 =
futX2;
posY2 =
futY2;
/*
Compruebo si se ha
pulsado alguna tecla */
if (
keypressed() ){
tecla =
readkey() >>8;
switch(tecla){
case
TEC_ARRIBA_1:
incX1 = 0;
incY1 = -1;break;
case
TEC_ABAJO_1:
incX1 = 0;
incY1 = 1;break;
case
TEC_IZQDA_1:
incX1 = -1;
incY1 = 0;break;
case
TEC_DCHA_1:
incX1 = 1;
incY1 = 0;break;
case
TEC_ARRIBA_2:
incX2 = 0;
incY2 = -1;break;
case
TEC_ABAJO_2:
incX2 = 0;
incY2 = 1;break;
case
TEC_IZQDA_2:
incX2 = -1;
incY2 = 0;break;
case
TEC_DCHA_2:
incX2 = 1;
incY2 = 0;break;
}
}
/*
Pequeña pausa
antes de seguir */
rest (
PAUSA );
}
while (TRUE);/*
Repetimos indefininamente */
/*
(la condición de salida
la comprobamos
"dentro") */
readkey();
return 0;
}
/*
Termino con la "macro" que me pide
Allegro
*/
END_OF_MAIN();
|
6.3.
Las motos desde Pascal.
Muuuuy parecido a la versión en C:
(*----------------------------*) (* Intro a la programac de *) (* juegos, por Nacho Cabanes *) (* *) (* IPJ06P.PAS *) (* *) (* Sexto ejemplo: juego de *) (* "motos de luz" *) (* *) (* Comprobado con: *) (* - FreePascal 1.10 (Dos) *) (* - FreePascal 2.0 -Windows *) (*----------------------------*)
uses graph, crt; (* Cambiar por "uses wincrt, ..." bajo Windows *)
const
(* Posiciones X e Y iniciales de ambos jugadores *) POS_X_INI_1 = 150; POS_X_INI_2 = 170; POS_Y_INI_1 = 100; POS_Y_INI_2 = 100;
INC_X_INI_1 = -1; INC_X_INI_2 = 1; INC_Y_INI_1 = 0; INC_Y_INI_2 = 0;
(* Pausa en milisegundos entre un "fotograma" y otro *) PAUSA = 150;
(* Teclas predefinidas para cada jugador *) TEC_ARRIBA_1 = 'E'; TEC_ABAJO_1 = 'X'; TEC_IZQDA_1 = 'S'; TEC_DCHA_1 = 'D';
TEC_ARRIBA_2 = '8'; TEC_ABAJO_2 = '2'; TEC_IZQDA_2 = '4'; TEC_DCHA_2 = '6';
var posX1, posY1, posX2, posY2: integer; (* Posiciones actuales *) incX1, incY1, incX2, incY2: integer; (* Incremento de la posicion *) futX1, futY1, futX2, futY2: integer; (* Posiciones futuras *)
(* Si ha chocado alguna moto *) chocado: boolean;
(* La tecla pulsada *) tecla:char;
var gd,gm, error : integer;
BEGIN gd := D8bit; gm := m320x200; (* Si falla bajo Windows, probar gd:=0; gm:=0; *) initgraph(gd, gm, '');
(* Intentamos entrar a modo grafico *) error := graphResult; if error <> grOk then begin writeLn('No se pudo entrar a modo grafico'); writeLn('Error encontrado: '+ graphErrorMsg(error) ); halt(1); end;
(* Si todo ha ido bien: empezamos *)
(* Rectangulo amarillo alrededor *) setcolor(14); rectangle(0,0, 319, 199);
(* Valores iniciales *) posX1 := POS_X_INI_1; posX2 := POS_X_INI_2; posY1 := POS_Y_INI_1; posY2 := POS_Y_INI_2;
incX1 := INC_X_INI_1; incX2 := INC_X_INI_2; incY1 := INC_Y_INI_1; incY2 := INC_Y_INI_2;
(* Parte repetitiva: *) repeat chocado := FALSE;
(* Compruebo si alguna va a colisionar *) futX1 := posX1 + incX1; futX2 := posX2 + incX2; futY1 := posY1 + incY1; futY2 := posY2 + incY2;
if (getpixel(futX1, futY1) <> 0) then begin SetColor(13); OutTextXY( 100, 90, 'La moto 1 ha chocado!'); chocado := TRUE; end;
if (getpixel(futX2, futY2) <> 0) then begin SetColor(12); OutTextXY( 100, 110, 'La moto 2 ha chocado!'); chocado := TRUE; end;
if chocado then break;
(* Si ninguna ha colisionado, avanzan *) setColor(13); line (posX1, posY1, futX1, futY1); posX1 := futX1; posY1 := futY1;
setColor(12); line (posX2, posY2, futX2, futY2); posX2 := futX2; posY2 := futY2;
(* Compruebo si se ha pulsado alguna tecla *) if keypressed then begin tecla := upcase(readkey);
case tecla of TEC_ARRIBA_1: begin incX1 := 0; incY1 := -1; end; TEC_ABAJO_1: begin incX1 := 0; incY1 := 1; end; TEC_IZQDA_1: begin incX1 := -1; incY1 := 0; end; TEC_DCHA_1: begin incX1 := 1; incY1 := 0; end;
TEC_ARRIBA_2: begin incX2 := 0; incY2 := -1; end; TEC_ABAJO_2: begin incX2 := 0; incY2 := 1; end; TEC_IZQDA_2: begin incX2 := -1; incY2 := 0; end; TEC_DCHA_2: begin incX2 := 1; incY2 := 0; end; end; end;
(* Pequeña pausa antes de seguir *) delay ( PAUSA );
until FALSE; (* Repetimos indefininamente *) (* (la condición de salida la comprobamos "dentro") *)
readkey();
end.
|
6.4.
Problemas en el caso de Java.
Como hemos dicho en la
introducción, en el caso de Java, la
situación se complica ligeramente, porque no tenemos ninguna
función que nos diga cual es el color de un punto de la
pantalla. Así que para imitar el funcionamiento de las versiones
anteriores, tendríamos que "memorizar" el contenido de cada
punto de la pantalla. Supone guardar información sobre 320x200 =
64.000 puntos. Hay al menos un par de formas de hacerlo, y las dos son
sencillas, pero para llevar un poco de orden, lo aplazamos... el
próximo tema nos ayudará a entender lo que nos falta.
También será en el próximo apartado donde veamos
cómo hacer eso de que nuestro juego "avance" cada cierto tiempo,
incluso aunque no se pulse una tecla.
 
|