30 septiembre 2014

Un mini-juego en BASIC de Amstrad CPC (6: símbolos, arrays bidimensionales, READ-DATA, medir tiempos)

25. Definir caracteres

Nuestro juego es feo. Indudablemente. Podemos mejorar un poco su apariencia si al menos creamos nuevos símbolos cuyo aspecto recuerde más a una pared, una roca o un diamante, por ejemplo.
En primer lugar, deberemos diseñar esos símbolos a partir de una matriz de 8x8 puntos. Usaremos un 1 para indicar un punto "encendido" en la pantalla y un 0 para un punto "apagado" (que se verá con el color de fondo).
Por ejemplo, un ladrillo de una pared podría ser algo así:
11111100
11111100
11111100
00000000
11001111
11001111
11001111
00000000
Nada más encender un CPC, podemos redefinir los últimos 16 caracteres, de la posición 240 a la 255. Podríamos redefinir más, por ejemplo si queremos cambiar todo el juego de caracteres para dar otra apariencia al juego. A cambio, perderemos algo de memoria libre.
Por ejemplo, si queremos redefinir todos los caracteres "imprimibles", que son del 32 (el espacio en blanco) en adelante, lo deberíamos indicar con
SYMBOL AFTER 32
Y para definir cualquiera de esos caracteres, usaríamos orden SYMBOL, seguida del número del carácter que queremos redefinir y de los ocho números que corresponden a la secuencia de ceros y unos que habíamos diseñado anteriormente. Esta secuencia se puede indicar tal cual, en binario, si precedemos cada número por "&x", así:
SYMBOL 240, &x11111100, &x11111100, &x11111100, &x00000000, &x11001111, &x11001111, &x11001111, &x00000000
Aunque en ocasiones será preferible escribir directamente el número decimal equivalente a esa secuencia, para ocupar menos memoria. Por ejemplo, la orden anterior ocupa 49 bytes en memoria si es parte de un programa, mientras que la siguiente versión ocupa 31 bytes:
SYMBOL 240,252,252,252,0,207,207,207,0
Y podemos comprobar cómo quedaría esa "pared" si mostramos varios ladrillos:
FOR i=1 TO 100: PRINT CHR$(240);: NEXT i

El premio a recoger podría ser una figura que recordase a un diamante:
00011000
00101100
01000110
10000011
10000011
01000110
00101100
00011000
que se definiría con
SYMBOL 241, &x00011000, &x00101100, &x01000110, &x10000011, &x10000011, &x01000110, &x00101100, &x00011000
O bien
SYMBOL 241,24,44,70,131,131,70,44,24
Los obstáculos podrían ser algo con apariencia "de pinchos":
00010000
10010010
01010100
00111000
11111111
00111000
01010100
10010010
SYMBOL 242,&x00010000, &x10010010, &x01010100, &x00111000, &x11111111, &x00111000, &x01010100, &x10010010
Y los enemigos podrían ser algo como
10111101
01111110
11011011
11111111
11100111
11011011
01100110
11000011
SYMBOL 243,&x10111101, &x01111110, &x11011011, &x11111111, &x11100111, &x11011011, &x01100110, &x11000011
Los mejores resultados se obtendrían si combináramos 2 o 4 caracteres para formar imágenes de mayor tamaño, pero no lo haremos por ahora.
Podríamos aplicar esto al juego, incluyendo las definiciones de caracteres al principio de programa y editando la parte que dibuja, para que muestre esos caracteres usando colores:
20 MODE 1: INK 0,0: INK 1,20: INK 2,6: INK 3,25
21 SYMBOL 240, &x11111100, &x11111100, &x11111100, &x00000000, &x11001111, &x11001111, &x11001111, &x00000000
22 SYMBOL 241, &x00011000, &x00101100, &x01000110, &x10000011, &x10000011, &x01000110, &x00101100, &x00011000
23 SYMBOL 242, &x00010000, &x10010010, &x01010100, &x00111000, &x11111111, &x00111000, &x01010100, &x10010010
24 SYMBOL 243, &x10111101, &x01111110, &x11011011, &x11111111, &x11100111, &x11011011, &x01100110, &x11000011
25 pared$=CHR$(240): premio$=CHR$(241): obstaculo$=CHR$(242): enemigo$=CHR$(243): jugador$=CHR$(248)
 
225 PEN 2
230 LOCATE xo1,yo1:PRINT obstaculo$
240 LOCATE xo2,yo2:PRINT obstaculo$
250 LOCATE xo3,yo3:PRINT obstaculo$
255 PEN 1
260 LOCATE x,y
265 PEN 1
270 PRINT jugador$
271 PEN 2
272 FOR n = 1 TO 10:LOCATE xE(n),yE(n):PRINT enemigo$:  NEXT n
274 PEN 3
275 LOCATE xP,yP: PRINT premio$
280 PEN 1
290 LOCATE 3,1: PRINT "Puntos "; puntos

26. Un laberinto de fondo: arrays bidimensionales, READ y DATA

En un juego clásico como Manic Miner, uno de los niveles tenía esta apariencia:

que se consigue repitiendo una serie de casillas de tamaño 8x8:

Para imitar eso, podemos almacenar toda una "apariencia de pantalla" como esta usando un array bidimensional, con algo como
DIM pantalla(25,40)
Entonces podríamos dar valores a sus elementos con
pantalla(1,1)=5: pantalla(1,2)=0: pantalla(1,1)=0 
Donde el valor 5 podría indicar que se trata de un tipo concreto de pared, el 0 podría ser un espacio en blanco (o un fragmento de cielo), etc.
Pero existe una alternativa más cómoda para dar muchos valores: detallarlos en sentencias DATA y leerlos con la orden READ, normalmente con la ayuda de un FOR, así:
1 DATA 1,0,0,0: '...
2 DATA 1,0,2,0: '...
3 FOR fila = 1 TO 25
4   FOR columna = 1 TO 40
5      READ pantalla(fila, columna)
6   NEXT columna
7 NEXT fila
(Las sentencias DATA pueden estar indistintamente al principio del programa, al final... o incluso en medio).
Sería aún más legible si no usamos números separados por comas, sino letras que representen cada uno de los símbolos que habrá en pantalla, por ejemplo así:
 
1000 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
1010 DATA PP----PPP--------PPPP--------PP
1020 DATA PP----PPP--------PPPP--------PP
1030 DATA PP----PPP--------PPPP--------PP
1040 DATA PP----PPPPP------PPPP--------PP
1050 DATA PP--PPPPPPPPPP--PPPPP----PPPPPP
1060 DATA PP---PPPPPPPP--PPPPPPPP----PPPP
1070 DATA PPP----PPPPP---PPPPPPPPP---PPPP
1080 DATA PP--------------------------PPP
1090 DATA PPP------------------------PPPP
1100 DATA PPPPP-----------PPPPPPPPP----PP
1110 DATA PPPPPP-----------PPPPPP---P--PP
1120 DATA PPP-------------------PPP-PP-PP
1130 DATA PP-----------------------PPPPPP
1140 DATA PP--------------------------PPP
1150 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
(Aunque la pantalla sea de 25x40 caracteres, nuestro mapa del laberinto es sólo de 16x32, para dejar espacio para información sobre los puntos, las vidas restantes... y, de paso, gastar menos memoria, que es un bien escaso en estos equipos; por eso, dejaremos un margen superior de 2 líneas y un margen izquierdo de 4 columnas en el momento de dibujar en pantalla).
Este formato tan compacto hace que podamos comprobar de un vistazo si el laberinto tiene la apariencia que deseamos, pero también complica la lectura: cada fila es una cadena de texto, de la que deberemos extraer el contenido letra a letra, usando la función MID$, a la que hay que indicar de qué cadena queremos obtener un fragmento, en qué posición y con qué longitud:
61 DIM pantalla(25,40)
63 FOR fila = 1 TO 16
64   READ linea$
65   FOR columna = 1 TO 32
67     IF MID$(linea$,columna,1) = "P" THEN pantalla(fila+2, columna+4)=1 ELSE pantalla(fila+2, columna+4)=0
68   NEXT columna
69 NEXT fila
A la vez que extraemos esa información, podemos dibujar el laberinto en pantalla:
61 DIM pantalla(25,40)
63 FOR fila = 1 TO 16
64   READ linea$
65   FOR columna = 1 TO 32
66     LOCATE columna+4, fila+2
67     IF MID$(linea$,columna,1) = "P" THEN PRINT pared$: pantalla(fila+2, columna+4)=1 ELSE pantalla(fila+2, columna+4)=0
68   NEXT columna
69 NEXT fila
El resultado sería algo como

Pero aún no se comporta bien: podemos pasar por encima de las paredes, pueden aparecer enemigos, obstáculos y premios fuera del laberinto o en medio de las paredes, los enemigos borran las paredes al pasar por encima de ellas... poco a poco lo iremos mejorando...
10 ' Ejemplo de juego en BASIC de CPC
20 MODE 1: INK 0,0: INK 1,20: INK 2,6: INK 3,25
21 SYMBOL 240, &x11111100, &x11111100, &x11111100, &x00000000, &x11001111, &x11001111, &x11001111, &x00000000
22 SYMBOL 241, &x00011000, &x00101100, &x01000110, &x10000011, &x10000011, &x01000110, &x00101100, &x00011000
23 SYMBOL 242, &x00010000, &x10010010, &x01010100, &x00111000, &x11111111, &x00111000, &x01010100, &x10010010
24 SYMBOL 243, &x10111101, &x01111110, &x11011011, &x11111111, &x11100111, &x11011011, &x01100110, &x11000011
25 pared$=CHR$(240): premio$=CHR$(241): obstaculo$=CHR$(242): enemigo$=CHR$(243): jugador$=CHR$(248)
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
61 DIM pantalla(25,40)
63 FOR fila = 1 TO 16
64   READ linea$
65   FOR columna = 1 TO 32
66     LOCATE columna+4, fila+2
67     IF MID$(linea$,columna,1) = "P" THEN PRINT pared$: pantalla(fila+2, columna+4)=1 ELSE pantalla(fila+2, columna+4)=0
68   NEXT columna
69 NEXT fila
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 --
225 PEN 2
230 LOCATE xo1,yo1:PRINT obstaculo$
240 LOCATE xo2,yo2:PRINT obstaculo$
250 LOCATE xo3,yo3:PRINT obstaculo$
255 PEN 1
260 LOCATE x,y
265 PEN 1
270 PRINT jugador$
271 PEN 2
272 FOR n = 1 TO 10:LOCATE xE(n),yE(n):PRINT enemigo$:  NEXT n
274 PEN 3
275 LOCATE xP,yP: PRINT premio$
280 PEN 1
290 LOCATE 3,1: PRINT "Puntos "; puntos
300 WEND
1000 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
1010 DATA PP----PPP--------PPPP--------PP
1020 DATA PP----PPP--------PPPP--------PP
1030 DATA PP----PPP--------PPPP--------PP
1040 DATA PP----PPPPP------PPPP--------PP
1050 DATA PP--PPPPPPPPPP--PPPPP----PPPPPP
1060 DATA PP---PPPPPPPP--PPPPPPPP----PPPP
1070 DATA PPP----PPPPP---PPPPPPPPP---PPPP
1080 DATA PP--------------------------PPP
1090 DATA PPP------------------------PPPP
1100 DATA PPPPP-----------PPPPPPPPP----PP
1110 DATA PPPPPP-----------PPPPPP---P--PP
1120 DATA PPP-------------------PPP-PP-PP
1130 DATA PP-----------------------PPPPPP
1140 DATA PP--------------------------PPP
1150 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
 

27. Midiendo tiempos

Para mejorar el comportamiento de un programa, necesitamos poder comparar el "antes" y el "después". En la parte visual, es fácil notar los cambios, pero no tanto en la parte lógica. Aun así, podemos medir el tiempo de ejecución o el gasto de memoria antes y después de un cambio, para ver si está dentro de valores aceptables y si hemos mejorado algo.
Por ejemplo, la última versión de nuestro juego, dibujaba el fondo del laberinto fuera de la parte repetitiva. Eso va a complicar la lógica interna, pero es un mal necesario, porque no podremos redibujar la pantalla completa de un CPC a 25 fotogramas por segundo... ni a 10 fps... ni siquiera a un fotograma por segundo. Lo podemos comprobar con un programa auxiliar (independiente del juego) que dibuje 1000 asteriscos en pantalla y mida el tiempo empleado. Para eso usaremos la función, TIME, que nos dice el tiempo que hace que se encendió el equipo, medido en 1/300 de segundo:
1 t=TIME
2 FOR n = 1 TO 1000: PRINT"*";: NEXT n
3 PRINT TIME-t
El resultado de este programa es... 1961. Es decir, se tarda más de 6 segundos (1961/300=6.54) en llenar la pantalla de asteriscos. Y eso sin contar con que en un juego real habrá que realizar cálculos adicionales durante el dibujado. Claramente no es viable, no podemos esperar más de 6 segundos para "fotograma" del juego. Por eso hemos sacado el dibujado del laberinto fuera de la parte repetitiva del programa. (Se podría mejorar ligeramente la velocidad con técnicas que veremos más adelante, pero seguiría siendo demasiado lento como para permitir redibujar toda la pantalla en cada pasada).

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