29 septiembre 2014

Un mini-juego en BASIC de Amstrad CPC (5: arrays, memoria libre, guardar)

21. Más enemigos aún: arrays

Para tres obstáculos, hemos creado tres oordenadas X, tres coordenadas Y... ¿si queremos 20 obstáculos (o 20 enemigos) necesitaremos otras 40 variables? La respuesta es que NO es necesario, porque podemos usar un "array" (o tabla, o vector, o matriz) para guardar todo un bloque de datos.
Primero tenemos que "reservar espacio" para el array (o los arrays, si queremos más de uno):
52 DIM xE(10), yE(10), velocE(10)
Así, ahora la variable "xE" tiene espacio para guardar 10 números, que irán de xE(1) hasta xE(10), de modo que podemos guardar las coordenadas X de 10 enemigos. Lo mismo ocurre para "yE" (coordenadas Y) y para "velocE" (velocidades). Realmente, en el lenguaje BASIC de los CPC, la expresión DIM xE(10), reserva espacio para 11 elementos, desde la posición 0 hasta la 10, pero nosotros empezaremos a contar en 1, que suele resultar "más natural" cuando uno está empezando, aunque desperdiciemos una posición de memoria.
Ahora debemos dar valores iniciales a esos arrays, que es algo que podemos hacer de forma repetitiva, usando la orden FOR. Como ya sabemos, podemos escribir los números del 1 al 20 haciendo
FOR n = 1 TO 20: PRINT n: NEXT n
De forma similar, podemos dar el valor 5 a todos los elementos de un array con
FOR n = 1 TO 10: xE(n) = 5: NEXT n
Y si afinamos un poco esa expresión, podemos dar posiciones al azar a nuestros 10 enemigos
54 FOR n = 1 TO 10: xE(n)=INT(RND*36+2): yE(n)=INT(RND*22+2): velocE(n)=1: NEXT n
Y ahora habrá que borrarlos a todos a la vez y moverlos a todos a la vez:
92 FOR n = 1 TO 10:LOCATE xE(n),yE(n):PRINT" ": NEXT n
94
185 FOR n = 1 TO 10:xE(n) = xE(n) + velocE(n):  NEXT n
272 FOR n = 1 TO 10:LOCATE xE(n),yE(n):PRINT"e":  NEXT n
y también habrá que ver si se salen de los límites de la pantalla, cosa que podemos agrupar con la anterior línea 185:
185 FOR n = 1 TO 10
190 xE(n) = xE(n) + velocE(n):
192 IF xE(n)=1 OR xE(n)=40 THEN velocE(n) = -velocE(n)
195 NEXT n
y, de igual modo, habrá que comprobar colisiones con todos ellos:
212 FOR n = 1 TO 10
214 IF x=xE(n) AND y=yE(n) THEN terminado = 1
216 NEXT n
El resultado será:
10 ' Ejemplo de juego en BASIC de CPC
20 MODE 1: INK 0,0: INK 1,20
30 RANDOMIZE TIME
40 x=10: y=5: terminado=0
50 xo1=INT(RND*36+2): yo1=INT(RND*22+2): xo2=INT(RND*36+2): yo2=INT(RND*22+2): xo3=INT(RND*36+2): yo3=INT(RND*22+2)
52 DIM xE(10), yE(10), velocE(10)
54 FOR n = 1 TO 10: xE(n)=INT(RND*36+2): yE(n)=INT(RND*22+2): velocE(n)=1: NEXT n
60 arriba=0: abajo=2: derecha=1: izqda=8: salir=63: ' Teclas
70 ' ----- Bucle de juego -----
80 WHILE terminado = 0
90 ' -- Borrar personaje y enemigos de su posicion anterior --
92 FOR n = 1 TO 10:LOCATE xE(n),yE(n):PRINT" ": NEXT n
100 LOCATE x,y
110 PRINT " "
120 ' -- Comprobar teclas --
130 IF INKEY(arriba) <> -1 AND y > 1 THEN y=y-1
140 IF INKEY(abajo) <> -1 AND y < 25 THEN y=y+1
150 IF INKEY(derecha) <> -1 AND x < 40 THEN x=x+1
160 IF INKEY(izqda) <> -1 AND x > 1 THEN x=x-1
170 IF INKEY(salir) <> -1 THEN terminado=1
180 ' -- Mover enemigos, entorno --
185 FOR n = 1 TO 10
190 xE(n) = xE(n) + velocE(n):
192 IF xE(n)=1 OR xE(n)=40 THEN velocE(n) = -velocE(n)
195 NEXT n
200 ' -- Colisiones, perder vidas, etc --
210 IF (x=xo1 AND y=yo1) OR (x=xo2 AND y=yo2) OR (x=xo3 AND y=yo3) THEN terminado = 1
212 FOR n = 1 TO 10
214 IF x=xE(n) AND y=yE(n) THEN terminado = 1
216 NEXT n
220 ' -- Dibujar en nueva posicion --
230 LOCATE xo1,yo1:PRINT"x"
240 LOCATE xo2,yo2:PRINT"x"
250 LOCATE xo3,yo3:PRINT"x"
260 LOCATE x,y
270 PRINT CHR$(248)
272 FOR n = 1 TO 10:LOCATE xE(n),yE(n):PRINT"e":  NEXT n
280 ' -- Pausa hasta el siguiente "fotograma" del juego --
290 FOR pausa = 1 TO 200: NEXT pausa
300 WEND
 

22. Memoria libre

El Amstrad CPC es un equipo de 8 bits, con una cantidad de memoria muy limitada, comparado con equipos modernos. Los primeros CPC (464 y 664) tenían 64 Kb de memoria, mientras que los últimos (6128) tenían 128 Kb, distribuidos en dos bancos de 64K, de los cuales sólo el primero es accesible con facilidad desde BASIC.
Si además a eso le restamos la cantidad de memoria que usa la pantalla (16 Kb) y el resto del sistema, nos encontramos con que la memoria utilizable por nuestros programas es de cerca de 42 Kb. Lo podemos saber exactamente con
PRINT FRE(0)
En un CPC6128 recién encendido, eso nos diría que tenemos 42.249 bytes libres. Después de teclear nuestro juego tal y como es hasta este punto, deberíamos tener cerca de 40.164 bytes libres, lo que indica que nuestro programa está ocupando poco más de 2 Kb de esos 42 Kb.

23. Guardar nuestro programa y recuperarlo más adelante

Si has conseguido llegar hasta aquí, te debe haber llevado un rato teclear los fragmentos de programa. Lo razonable es no dar ese tiempo por perdido, sino guardar el resultado para no tener que volver a comenzar desde cero cada vez. Afortunadamente, es sencillo: introducir un disco (o una cinta, según el caso) en nuestro CPC y usar la orden SAVE, seguida del nombre que queramos dar al juego, entre comillas (con un máximo de 8 letras y sin espacios):
SAVE "juego1"
Si lo queremos recuperar más adelante para seguir trabajando con él, la orden que nos permite cargarlo desde disco o cinta es LOAD:
LOAD "juego1"
O podríamos lanzarlo directamente desde disco o cinta usando RUN seguido por el nombre del programa:
RUN "juego1"
Si usas un emulador, puedes guardar tu programa fuera de él, como ya vimos en el apartado 11, o bien puedes usar una "imagen de disco": con WinApe puedes entrar al menú "File", opción "Drive A:" y decir que quieres crear un "New Blank disk". Ese nuevo disco está todavía "sin formatear", así que si tecleas la orden CAT para ver su contenido, obtendrás un mensaje de "Read fail" (error de lectura). Pero eso también es fácil de solucionar: desde el menú "File", en la opción "Drive A:", también aparece "Format Disc Image" (formatear imagen de disco), que nos pregunta qué formato usar (el "Data" que nos propone es razonable y nos dejará libres 178 KB). A partir de ese momento, ya se podrá usar SAVE para guardar dentro de esa imagen de disco, LOAD para cargar desde ella y CAT para ver el contenido del disco y el espacio libre.

24. Recoger premios y obtener puntos

Nuestro juego todavía es poco jugable. Como primera mejora en cuanto a jugabilidad, podríamos añadir una motivación adicional: recoger premios que nos den puntos.
La idea es sencilla: habrá otros tipos de elementos en pantalla, pero la colisión con ellos no hará que la partida termine, sino que nos dará puntos y hará que ese premio desaparezca de la pantalla.
El planteamiento más habitual en un juego de laberintos es que haya varios premios que recoger y que avancemos de nivel cuando tengamos todos ellos. Nosotros llegaremos a ese punto muy pronto, pero vamos a empezar por un planteamiento más sencillo: sólo habrá un premio, y al tocarlo desaparecerá de su posición, aparecerá en otra posición distinta y obtendremos 10 puntos.
Debemos tener en cuenta un detalle: si esa posición se va a escoger al azar, deberá ser una posición alcanzable, porque si el premio está en la misma posición que un obstáculo, ganaremos 10 puntos pero perderemos una vida... nuestra única vida por ahora. Por eso, debemos evitar que esto ocurra.
Así, generaríamos la posición inicial del premio, y lo volveríamos a hacer en caso de que se solape con alguno de los obstáculos:
56 xP=INT(RND*36+2): yP=INT(RND*22+2)
58 IF (xP=xo1 AND y=yP1) OR (xP=xo2 AND yP=yo2) OR (xP=xo3 AND yP=yo3) THEN GOTO 56
(Es más elegante usar un WHILE que un GOTO para volver atrás, porque los GOTO tienden a crear programas poco legibles, pero no es grave porque dentro de poco estas líneas desaparecerán, cuando tengamos varios premios en pantalla).
Dibujar el premio es fácil
275 LOCATE xP,yP: PRINT "P"
Y comprobar colisiones es casi igual de fácil, aunque será un poco repetitivo, porque habrá que recolocar el premio en una nueva posición. Más adelante veremos cómo evitar ese código repetitivo.
202 IF x <> xP OR y <> yP THEN GOTO 210: 'Si no hay premio
204 xP=INT(RND*36+2): yP=INT(RND*22+2): 'Si lo hay, calculamos nueva posicion
206 IF (xP=xo1 AND y=yP1) OR (xP=xo2 AND yP=yo2) OR (xP=xo3 AND yP=yo3) THEN GOTO 204
Podemos eliminar ya la pausa al final de cada fotograma, para que el juego sea un poco más fluido (aunque seguirá parpadeando), borrando las líneas 280 y 290.
280
290
Anotar la puntuación obtenida es casi igual de fácil: una nueva variable, cambiaremos su valor cuando toquemos un premio y lo escribiremos en la parte superior de la pantalla:
45 puntos=0
203 puntos = puntos+10
290 LOCATE 3,1: PRINT "Puntos "; puntos
La apariencia todavía es muy fea, pero eso cambiará pronto...
10 ' Ejemplo de juego en BASIC de CPC
20 MODE 1: INK 0,0: INK 1,20
30 RANDOMIZE TIME
40 x=10: y=5: terminado=0
45 puntos=0
50 xo1=INT(RND*36+2): yo1=INT(RND*22+2): xo2=INT(RND*36+2): yo2=INT(RND*22+2): xo3=INT(RND*36+2): yo3=INT(RND*22+2)
52 DIM xE(10), yE(10), velocE(10)
54 FOR n = 1 TO 10: xE(n)=INT(RND*36+2): yE(n)=INT(RND*22+2): velocE(n)=1: NEXT n
56 xP=INT(RND*36+2): yP=INT(RND*22+2)
58 IF (xP=xo1 AND y=yP1) OR (xP=xo2 AND yP=yo2) OR (xP=xo3 AND yP=yo3) THEN GOTO 56
60 arriba=0: abajo=2: derecha=1: izqda=8: salir=63: ' Teclas
70 ' ----- Bucle de juego -----
80 WHILE terminado = 0
90 ' -- Borrar personaje y enemigos de su posicion anterior --
92 FOR n = 1 TO 10:LOCATE xE(n),yE(n):PRINT" ": NEXT n
100 LOCATE x,y
110 PRINT " "
120 ' -- Comprobar teclas --
130 IF INKEY(arriba) <> -1 AND y > 1 THEN y=y-1
140 IF INKEY(abajo) <> -1 AND y < 25 THEN y=y+1
150 IF INKEY(derecha) <> -1 AND x < 40 THEN x=x+1
160 IF INKEY(izqda) <> -1 AND x > 1 THEN x=x-1
170 IF INKEY(salir) <> -1 THEN terminado=1
180 ' -- Mover enemigos, entorno --
185 FOR n = 1 TO 10
190 xE(n) = xE(n) + velocE(n):
192 IF xE(n)=1 OR xE(n)=40 THEN velocE(n) = -velocE(n)
195 NEXT n
200 ' -- Colisiones, perder vidas, etc --
202 IF x <> xP OR y <> yP THEN GOTO 210: 'Si no hay premio
203 puntos = puntos+10
204 xP=INT(RND*36+2): yP=INT(RND*22+2): 'Si lo hay, calculamos nueva posicion
206 IF (xP=xo1 AND y=yP1) OR (xP=xo2 AND yP=yo2) OR (xP=xo3 AND yP=yo3) THEN GOTO 204
210 IF (x=xo1 AND y=yo1) OR (x=xo2 AND y=yo2) OR (x=xo3 AND y=yo3) THEN terminado = 1
212 FOR n = 1 TO 10
214 IF x=xE(n) AND y=yE(n) THEN terminado = 1
216 NEXT n
220 ' -- Dibujar en nueva posicion --
230 LOCATE xo1,yo1:PRINT"x"
240 LOCATE xo2,yo2:PRINT"x"
250 LOCATE xo3,yo3:PRINT"x"
260 LOCATE x,y
270 PRINT CHR$(248)
272 FOR n = 1 TO 10:LOCATE xE(n),yE(n):PRINT"e":  NEXT n
275 LOCATE xP,yP: PRINT "P"
290 LOCATE 3,1: PRINT "Puntos "; puntos
300 WEND