7. Mapas.
Cuarto juego (aproximación "a"): MiniSerpiente 1.
Contenido
de este apartado:
7.1.
Ideas generales.
El juego de la serpiente
es casi tan sencillo de crear como el de las motos de luz: una figura
va avanzando por la pantalla; si choca con la pared exterior, con su
propia "cola" o con algún otro obstáculo, muere. La
única complicación adicional es que en la mayoría
de versiones de este juego también va apareciendo comida, que
debemos atrapar; esto nos dará puntos, pero también
hará que la serpiente sea más grande, y por eso,
más fácil chocar con nuestra propia cola.
Esta novedad hace que sea algo
más difícil de programar. En primer lugar porque la
serpiente va creciendo, y en segundo lugar porque puede que en una
posición de la pantalla exista un objeto contra el que podemos
chocar pero no debemos morir, sino aumentar nuestra puntuación.
Podríamos volver a usar
el "truco" de mirar en los puntos de la pantalla, y distinguir la
comida usando un color distinto al de los obstáculos. Pero esto
no es lo que se suele hacer. No serviría si nuestro fondo fuera
un poco más vistoso, en vez de ser una pantalla negra. En lugar
de eso, es más cómodo memorizar un "mapa" con las
posibles posiciones de la pantalla, e indicando cuales están
vacías, cuales ocupadas por obstáculos y cuales ocupadas
por comida.
Podría ser algo
así:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
X
X X
X
X
X X
X
XXX F
X X X
X
X
X
X
X F X
X
X
X
X
X
F
X
XXX X
X
X
X
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
En este ejemplo, las X
serían las casillas "peligrosas", con las que no debemos chocar
si no queremos morir; las F serían las frutas que podemos
recoger para obtener puntos extra.
El hecho de consultar este mapa
en vez del contenido de la pantalla nos permite dibujar esos
obstáculos, esas frutas, el fondo y nuestra propia serpiente con
otras imágenes más vistosas.
Vamos a aplicarlo. Tomaremos la
base del juego de las motos de luz, porque la idea básica
coincide: el objeto se debe seguir moviendo aunque no toquemos ninguna
tecla, y en cada paso se debe comprobar si hemos chocado con
algún obstáculo. A esta base le añadiremos el uso
de un mapa, aunque todavía será poco vistoso...
La
apariencia del juego, todavía muy pobre, será:

7.2
Miniserpiente 1
en
C.
Sencillito. Muy parecido al anterior, pero no miramos en pantalla sino
en nuestro "mapa":
/*----------------------------*/
/* Intro a la programac de */
/* juegos, por Nacho Cabanes */
/* */
/* IPJ07C.C */
/* */
/* Septimo ejemplo: juego de */
/* "miniSerpiente" (aprox A) */
/* */
/* Comprobado con: */ /* - MinGW DevStudio 2.05 */
/* (gcc 3.4.2) y Allegro */
/* 4.03, Windows XP */
/*----------------------------*/
#include <allegro.h>
/* Posiciones X e Y iniciales */
#define POS_X_INI 16
#define POS_Y_INI 10
#define INC_X_INI 1
#define INC_Y_INI 0
/* Pausa en milisegundos entre un "fotograma" y otro */
#define PAUSA 350
/* Teclas predefinidas */
#define TEC_ARRIBA KEY_E #define TEC_ABAJO KEY_X #define TEC_IZQDA KEY_S #define TEC_DCHA KEY_D
/* Ahora las imagenes de cada elemento */
#define LADRILLO "#"
#define COMIDA "X"
#define JUGADOR "O"
int posX, posY; /* Posicion actual */
int incX, incY; /* Incremento de la posicion */
/* Terminado: Si ha chocado o comida todas las frutas */
int terminado;
/* La tecla pulsada */
int tecla;
/* Escala: relacion entre tamaño de mapa y de pantalla */
#define ESCALA 10
/* Ancho y alto de los sprites */
#define ANCHOSPRITE 10
#define ALTOSPRITE 10
/* Y el mapa que representa a la pantalla */
/* Como usaremos modo grafico de 320x200 puntos */
/* y una escala de 10, el tablero medira 32x20 */
#define MAXFILAS 20
#define MAXCOLS 33
char mapa[MAXFILAS][MAXCOLS]={ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "X X X", "X F X X", "X F X F X", "X XXXXX X X", "X X X X", "X X X X X", "X X X X XXXX", "X X X X", "X X X X", "X X X X", "X F X X", "X X X", "X X F X", "X X X X", "X X X X", "X X F X X", "X F X X X", "X X F X", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" };
int numFrutas = 8;
/* -------------- Rutina de dibujar el fondo ------------- */
void dibujaFondo()
{
int i, j;
clear_bitmap(screen);
for(i=0; i<MAXCOLS; i++)
for (j=0; j<MAXFILAS; j++) {
if (mapa[j][i] == 'X')
textout(screen, font,
LADRILLO, i*ESCALA, j*ESCALA, palette_color[14]);
if (mapa[j][i] == 'F')
textout(screen, font,
COMIDA, i*ESCALA, j*ESCALA, palette_color[10]);
}
}
/* ------------------------------------------------ */
/* */
/* -------------- Cuerpo del programa ------------- */
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 */
dibujaFondo();
/* Valores iniciales */
posX = POS_X_INI;
posY = POS_Y_INI;
incX = INC_X_INI;
incY = INC_Y_INI;
/* Parte repetitiva: */
do {
dibujaFondo();
textout(screen, font,
JUGADOR, posX*ESCALA, posY*ESCALA, palette_color[15]);
terminado = FALSE;
/* Si paso por una fruta: la borro y falta una menos */
if (mapa[posY][posX] == 'F') {
mapa[posY][posX] = ' ';
numFrutas --;
if (numFrutas == 0) {
textout(screen, font,
"Ganaste!", 100, 90, palette_color[14]);
terminado = TRUE;
}
}
/* Si choco con la pared, se acabo */
if (mapa[posY][posX] == 'X') {
textout(screen, font,
"Chocaste!", 100, 90, palette_color[13]);
terminado = TRUE;
}
if (terminado) break;
/* Compruebo si se ha pulsado alguna tecla */
if ( keypressed() ) {
tecla = readkey() >> 8;
switch (tecla) {
case TEC_ARRIBA:
incX = 0; incY = -1; break;
case TEC_ABAJO:
incX = 0; incY = 1; break;
case TEC_IZQDA:
incX = -1; incY = 0; break;
case TEC_DCHA:
incX = 1; incY = 0; break;
}
}
posX += incX;
posY += incY;
/* 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();
|
7.3.
Miniserpiente 1 en Pascal.
Muuuuy parecido a la versión en C, como era de esperar:
(*----------------------------*)
(* Intro a la programac de *)
(* juegos, por Nacho Cabanes *)
(* *)
(* IPJ07P.PAS *)
(* *)
(* Septimo ejemplo: juego de *)
(* 'miniSerpiente' *)
(* *)
(* Comprobado con: *)
(* - FreePascal 1.06 (Dos) *) (* - FreePascal 2.0 -Windows *)
(*----------------------------*)
uses graph, crt; (* Cambiar por "uses wincrt, ..." bajo Windows *) (* Posiciones X e Y iniciales *)
const
POS_X_INI = 17;
POS_Y_INI = 11;
INC_X_INI = 1;
INC_Y_INI = 0;
(* Pausa en milisegundos entre un 'fotograma' y otro *)
PAUSA = 350;
(* Teclas predefinidas *)
TEC_ARRIBA = 'E';
TEC_ABAJO = 'X';
TEC_IZQDA = 'S';
TEC_DCHA = 'D';
var
posX, posY: word; (* Posicion actual *)
incX, incY: integer; (* Incremento de la posicion *)
(* Terminado: Si ha chocado o comida todas las frutas *)
terminado: boolean;
(* La tecla pulsada *)
tecla: char;
(* Escala: relacion entre tamaño de mapa y de pantalla *)
const
ESCALA = 10;
(* Y el mapa que representa a la pantalla *)
(* Como usaremos modo grafico de 320x200 puntos *)
(* y una escala de 10, el tablero medira 32x20 *)
MAXFILAS = 20;
MAXCOLS = 33;
mapa: array[1..MAXFILAS, 1..MAXCOLS] of char = (
'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
'X X X',
'X F X X',
'X F X F X',
'X XXXXX X X',
'X X X X',
'X X X X X',
'X X X X XXXX',
'X X X X',
'X X X X',
'X X X X',
'X F X X',
'X X X',
'X X F X',
'X X X X',
'X X X X',
'X X F X X',
'X F X X X',
'X X F X',
'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
);
(* Ahora las imagenes de cada elemento *)
LADRILLO = '#';
COMIDA = 'X';
JUGADOR = 'O';
const numFrutas:word = 8;
(* -------------- Rutina de dibujar el fondo ------------- *)
procedure dibujaFondo;
var
i, j: word;
begin
clearDevice;
for i:= 1 to MAXCOLS do
for j := 1 to MAXFILAS do
begin
if mapa[j,i] = 'X' then
begin
setColor(15);
outTextXY( (i-1)*ESCALA, (j-1)*ESCALA, LADRILLO );
end;
if mapa[j,i] = 'F' then
begin
setColor(10);
outTextXY( (i-1)*ESCALA, (j-1)*ESCALA, COMIDA );
end;
end;
end;
(* ------------------------------------------------ *)
(* *)
(* -------------- Cuerpo del programa ------------- *)
var
gd,gm, error : integer;
BEGIN
gd := D8bit;
gm := m320x200;
initgraph(gd, gm, ''); (* Si falla bajo Windows, probar gd:=0; gm:=0; *)
(* 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 *)
dibujaFondo;
(* Valores iniciales *)
posX := POS_X_INI;
posY := POS_Y_INI;
incX := INC_X_INI;
incY := INC_Y_INI;
(* Parte repetitiva: *)
repeat
dibujaFondo;
setColor(14);
outTextXY( (posX-1)*ESCALA, (posY-1)*ESCALA, JUGADOR );
terminado := FALSE;
(* Si paso por una fruta: la borro y falta una menos *)
if (mapa[posY,posX] = 'F') then
begin
mapa[posY,posX] := ' ';
numFrutas := numFrutas - 1;
if (numFrutas = 0) then
begin
setColor(14);
outTextXY( 100, 90, 'Ganaste!' );
terminado := TRUE;
end;
end;
(* Si choco con la pared, se acabo *)
if (mapa[posY,posX] = 'X') then
begin
setColor(13);
outTextXY( 100, 90, 'Chocaste!' );
terminado := TRUE;
end;
if terminado then break;
(* Compruebo si se ha pulsado alguna tecla *)
if keypressed then
begin
tecla := upcase(readkey);
case tecla of
TEC_ARRIBA:
begin incX := 0; incY := -1; end;
TEC_ABAJO:
begin incX := 0; incY := 1; end;
TEC_IZQDA:
begin incX := -1; incY := 0; end;
TEC_DCHA:
begin incX := 1; incY := 0; end;
end;
end;
posX := posX + incX;
posY := posY + incY;
(* Pequeña pausa antes de seguir *)
delay ( PAUSA );
until FALSE; (* Repetimos indefininamente *)
(* (la condición de salida la comprobamos 'dentro') *)
readkey;
closegraph;
end.
|
7.4.
Miniserpiente 1 en Java.
Eso de que el juego no se pare
en Java es algo más complicado: deberemos usar un "Thread", un
hilo, que esa la parte que sí podremos parar cuando queramos,
durante un cierto tiempo o por completo. Resumamos los cambios
más importantes:
- En la declaración de
la clase deberemos añadir "implements Runnable"
- Tendremos nuevos
métodos (funciones): "start" pondrá en marcha la
ejecución del hilo, "stop" lo parará, y "run"
indicará lo que se debe hacer durante la ejecución del
hilo.
- Dentro de este "run" haremos
la pausa que necesitamos en cada "fotograma" del juego, y redibujaremos
después, así:
try { Thread.sleep(PAUSA); } catch (InterruptedException e){ }
Los demás cambios, que no
son muchos, son los debidos a la forma de trabajar de los Applets, que
ya conocemos (funciones init, paint y las de manejo de teclado), y las
propias del lenguaje Java (por ejemplo, va a ser más
cómodo definir el mapa como un array de Strings que como un
array bidimensional de caracteres).
La apariencia será casi
idéntica a las anteriores:

/*----------------------------*/ /* Intro a la programac de */ /* juegos, por Nacho Cabanes */ /* */ /* ipj07j.java */ /* */ /* Septimo ejemplo: juego de */ /* "miniSerpiente" (aprox A) */ /* */ /* Comprobado con: */ /* - JDK 1.5.0 */ /*----------------------------*/
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public class ipj07j extends Applet implements Runnable, KeyListener {
// Posiciones X e Y iniciales
final int POS_X_INI = 16;
final int POS_Y_INI = 10;
final int INC_X_INI = 1;
final int INC_Y_INI = 0;
// Pausa en milisegundos entre un "fotograma" y otro
final int PAUSA = 350;
// Ahora las imagenes de cada elemento
final String LADRILLO = "#";
final String COMIDA = "X";
final String JUGADOR ="O";
// Escala: relacion entre tamaño de mapa y de pantalla
final int ESCALA = 10;
// Y el mapa que representa a la pantalla
// Como usaremos modo grafico de 320x200 puntos
// y una escala de 10, el tablero medira 32x20
final int MAXFILAS = 20;
final int MAXCOLS = 32;
String mapa[]={
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"X X X",
"X F X X",
"X F X F X",
"X XXXXX X X",
"X X X X",
"X X X X X",
"X X X X XXXX",
"X X X X",
"X X X X",
"X X X X",
"X F X X",
"X X X",
"X X F X",
"X X X X",
"X X X X",
"X X F X X",
"X F X X X",
"X X F X",
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
};
Thread hilo = null; // El "hilo" de la animacion
int posX, posY; // Posicion actual
int incX, incY; // Incremento de la posicion
// Terminado: Si ha chocado o comido todas las frutas
boolean terminado;
int tecla; // La tecla pulsada
// Y las teclas por defecto
final char TEC_ARRIBA = 'e';
final char TEC_ABAJO = 'x';
final char TEC_IZQDA = 's';
final char TEC_DCHA = 'd';
int numFrutas = 8;
// Inicializacion
public void init() {
// Valores iniciales
posX = POS_X_INI;
posY = POS_Y_INI;
incX = INC_X_INI;
incY = INC_Y_INI;
terminado = false;
requestFocus();
addKeyListener(this);
}
// Escritura en pantalla
public void paint(Graphics g) {
int i, j; // Primero borro el fondo en negro
g.setColor( Color.black );
g.fillRect( 0, 0, 639, 479 );
// Ahora dibujo paredes y comida
for(i=0; i<MAXCOLS; i++)
for (j=0; j<MAXFILAS; j++) {
g.setColor( Color.blue );
if (mapa[j].charAt(i) == 'X')
g.drawString(LADRILLO, i*ESCALA, j*ESCALA+10);
g.setColor( Color.green );
if (mapa[j].charAt(i) == 'F')
g.drawString(COMIDA, i*ESCALA, j*ESCALA+10);
}
// Finalmente, el jugador
g.setColor( Color.white );
g.drawString(JUGADOR, posX*ESCALA, posY*ESCALA+10);
// Si no quedan frutas, se acabo
g.setColor( Color.yellow );
if (numFrutas == 0) {
g.drawString("Ganaste!", 100, 90);
terminado = true;
} // Si choco con la pared, se acabo
g.setColor( Color.magenta );
if (mapa[posY].charAt(posX) == 'X') {
g.drawString("Chocaste!", 100, 90);
terminado = true;
}
if (terminado) hilo=null; }
// La rutina que comienza el "Thread"
public void start() {
hilo = new Thread(this);
hilo.start();
} // La rutina que para el "Thread"
public synchronized void stop() {
hilo = null;
} // Y lo que hay que hacer cada cierto tiempo
public void run() {
Thread yo = Thread.currentThread();
while (hilo == yo) {
try {
Thread.sleep(PAUSA);
} catch (InterruptedException e){
}
posX += incX;
posY += incY;
// Si paso por una fruta: la borro y falta una menos
if (mapa[posY].charAt(posX) == 'F') {
// La borro en el mapa
StringBuffer temp = new StringBuffer(mapa[posY]);
temp.setCharAt(posX, ' ');
mapa[posY] = temp.toString();
// y en el contador
numFrutas --;
}
// En cualquier caso, redibujo
repaint();
} }
// Comprobacion de teclado
public void keyTyped(KeyEvent e) {
tecla=e.getKeyChar();
switch (tecla) {
case TEC_ARRIBA:
incX = 0; incY = -1; break;
case TEC_ABAJO:
incX = 0; incY = 1; break;
case TEC_IZQDA:
incX = -1; incY = 0; break;
case TEC_DCHA:
incX = 1; incY = 0; break;
}
repaint();
e.consume();
}
public void keyReleased(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
} }
|
 
|