02 octubre 2014

Un mini-juego en BASIC de Amstrad CPC (8: varios niveles, subrutinas, sonido, pantallas de carga, mejoras de velocidad...)

33. Varios niveles prefijados

Con la estructura que tenemos y nuestros conocimientos, no es complicado crear varios niveles que el usuario vaya recorriendo de forma sucesiva: cuando no queden premios en el nivel actual, leemos un nuevo mapa desde DATA, rellenando los arrays correspondientes.
Hay dos posibles comportamientos cuando el usuario llegue al último nivel: dar la partida por terminada o volver a comenzar desde el primer nivel. Lo primero es fácil de hacer, pero lo segundo tampoco es difícil: como hemos visto, podemos usar "RESTORE", para que la siguiente lectura desde las líneas DATA se haga desde la primera de ellas (si no lo hacemos así, llegará un momento en el que no haya más DATA que leer y obtengamos el mensaje de error "Data exhausted".
Los datos de dos niveles más podrían ser éstos:
5095 '
5100 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
5150 DATA PPxo--PPP----o--------o---A--PP
5200 DATA PP-----PP---x----PPPP--------PP
5250 DATA PP------P--e-----PPPP--e-----PP
5300 DATA PP---------------PPPP--------PP
5350 DATA PP--e-------------PPPPPPPPPPPPP
5400 DATA PPPPPPPPPPPPP--PPPPPPPP----PPPP
5450 DATA PPPPPPPPP------------------PPPP
5500 DATA PP----------------x---e-----PPP
5550 DATA PPP------------------------PPPP
5600 DATA PPPPP--e------------PPPPP----PP
5650 DATA PPPPPP------P-------PPP------PP
5700 DATA PPP---------PP--o-----PPPPPP-PP
5750 DATA PP---o----PP-------------PPPPPP
5800 DATA PP-X-----PP------------x----PPP
5850 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
4295 '
5900 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
5950 DATA PP-x-------------------------PP
6000 DATA PP------o-----x---o--------e-PP
6050 DATA PP----PPPPPPPPPPPPPPP--------PP
6100 DATA PP---------------------------PP
6150 DATA PPo---e--x---PP--x--e----o---PP
6200 DATA PP---PPPPPPPPPPPPPPPPPP----PPPP
6250 DATA PPP----PPPPPPPPPPPPPPPPP---PPPP
6300 DATA PP--------------------------PPP
6350 DATA PPP-------------------e---x-PPP
6400 DATA PPPPP-----------PPPPPPPPP----PP
6450 DATA PPPPPP--e--------------e-----PP
6500 DATA PPP--------------------------PP
6550 DATA PP--A-----o-----x--o---------PP
6600 DATA PP-o------------------------PPP
6650 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
Y cuando terminen los premios de un nivel, deberemos avanzar de nivel, volviendo a calcular las posiciones de obstáculos y enemigos y volviendo al principio, pero llevando cuidado de no dejar a cero los puntos, ni volver a dejar 3 vidas, sino que se debe continuar con los puntos y las vidas que teníamos, así:
3110 IF premios=0 THEN nivel=nivel+1: ERASE xE, yE, velocE, pantalla$: CLS: GOTO 1455
Si queremos que tras el último nivel se vuelva al primero, haríamos:
1070 nivel=1
1458 if nivel>3 THEN nivel=1: RESTORE
De modo el programa completo quedaría así:
1 ' Laberinto 2014
2 ' Ejemplo de juego en BASIC de CPC
3 ' Desarrollado paso a paso por Nacho
1000 ' 
1010 ' Inicializacion
1020 '
1030 ' -- Colores de pantalla --
1050 MODE 1: INK 0,0: INK 1,20: INK 2,6: INK 3,25: PAPER 0: PEN 1
1060 colorPared=1: colorEnemigo=2: colorPremio=3
1070 nivel=1
1090 ' -- Definicion de caracteres --
1100 SYMBOL 240, &X11111100, &X11111100, &X11111100, &X0, &X11001111, &X11001111, &X11001111, &X0
1150 SYMBOL 241, &X11000, &X101100, &X1000110, &X10000011, &X10000011, &X1000110, &X101100, &X11000
1200 SYMBOL 242, &X10000, &X10010010, &X1010100, &X111000, &X11111111, &X111000, &X1010100, &X10010010
1250 SYMBOL 243, &X10111101, &X1111110, &X11011011, &X11111111, &X11100111, &X11011011, &X1100110, &X11000011
1260 pared$=CHR$(240): premio$=CHR$(241): obstaculo$=CHR$(242): enemigo$=CHR$(243): jugador$=CHR$(248)
1265 ' -- Pantalla de bienvenida --
1270 LOCATE 10,4: PEN colorPared: PRINT "Bienvenido al laberinto"
1272 LOCATE 10,7: PEN colorPremio: PRINT "Recoge los premios: "; premio$
1274 LOCATE 10,9: PEN colorEnemigo: PRINT "Esquiva los obstaculos: "; obstaculo$
1276 LOCATE 10,11: PRINT "Y evita a los enemigos: "; enemigo$
1278 LOCATE 4,20: PEN colorPared: PRINT "Pulsa una tecla para empezar... ";
1280 PLOT 0,0,colorPremio: DRAW 639,0: DRAW 639,399: DRAW 0,399: DRAW 0,0
1285 PLOT 5,5,colorPared: DRAW 634,5: DRAW 634,394: DRAW 5,394: DRAW 5,5
1300 WHILE INKEY$="":WEND: 'Pausa
1305 CLS
1310 ' -- Teclas ---
1320 arriba=0: abajo=2: derecha=1: izqda=8: salir=63
1330 joyArriba=72: joyAbajo=73: joyDerecha=75: joyIzqda=74
1350 ' -- Datos del juego --
1400 terminado=0:chocado=0
1450 puntos=0: vidas=3
1455 premios=0: enemigos=0
1458 if nivel>3 THEN nivel=1: RESTORE
1460 ' -- Lectura del mapa desde DATA --
1550 DIM xE(10), yE(10), velocE(10)
1600 DIM pantalla$(25,40)
1650 LOCATE 15,22: PRINT"Generando..."
1810 ' Vamos a vaciar el array, para evitar basura
1820 FOR f = 1 TO 25: FOR c = 1 TO 40: pantalla$(f,c)=" ": NEXT c: NEXT f
1840 ' Y a rellenar los datos reales
1850 FOR fila = 1 TO 16
1900   READ linea$
1950   FOR columna = 1 TO 32
2050     IF MID$(linea$,columna,1) = "P" THEN pantalla$(fila+2, columna+4)=pared$
2060     IF MID$(linea$,columna,1) = "x" THEN pantalla$(fila+2, columna+4)=obstaculo$
2070     IF MID$(linea$,columna,1) = "A" THEN y=fila+2: x=columna+4: yInicial=y: xInicial=x
2080     IF MID$(linea$,columna,1) = "o" THEN premios = premios+1: pantalla$(fila+2, columna+4)=premio$
2090     IF MID$(linea$,columna,1) = "e" THEN enemigos = enemigos+1: yE(enemigos)=fila+2: xE(enemigos)=columna+4: velocE(enemigos)=1
2100   NEXT columna
2150 NEXT fila
2160 ' -- Dibujado de la parte estatica del mapa --
2162 FOR f = 1 TO 25
2164   FOR c = 1 TO 40
2166     LOCATE c, f
2168     IF pantalla$(f,c) = pared$ THEN PEN colorPared: PRINT pared$;
2170     IF pantalla$(f,c) = obstaculo$ THEN PEN colorEnemigo: PRINT obstaculo$;
2172     IF pantalla$(f,c) = premio$ THEN PEN colorPremio: PRINT premio$;
2174   NEXT c
2176 NEXT f
2200 ' ----- Bucle de juego -----
2250 WHILE terminado = 0
2300   ' -- Borrar personaje y enemigos de su posicion anterior --
2350   FOR n = 1 TO enemigos:LOCATE xE(n),yE(n):PRINT" ": NEXT n
2400   LOCATE x,y: PRINT " "
2500   ' -- Comprobar teclas --
2550   IF (INKEY(arriba)<>-1 OR INKEY(joyArriba)<>-1) AND pantalla$(y-1,x)<>pared$ THEN y=y-1
2600   IF (INKEY(abajo) >-1 OR INKEY(joyAbajo)<>-1) AND pantalla$(y+1,x)<>pared$ THEN y=y+1
2650   IF (INKEY(derecha)<>-1 OR INKEY(joyDerecha)<>-1) AND pantalla$(y,x+1)<>pared$ THEN x=x+1
2700   IF (INKEY(izqda)<>-1 OR INKEY(joyIzqda)<>-1) AND pantalla$(y,x-1)<>pared$ THEN x=x-1
2750   IF INKEY(salir) <> -1 THEN terminado=1
2800   ' -- Mover enemigos, entorno --
2850   FOR n = 1 TO enemigos
2860      ' Avanzar si no es pared; dar la vuelta si lo es
2865      obj$=pantalla$(yE(n),xE(n)+velocE(n))
2870      IF obj$<>pared$ AND obj$<>premio$ AND obj$<>obstaculo$ THEN xE(n)=xE(n)+velocE(n) ELSE velocE(n)=-velocE(n)
3000   NEXT n
3050   ' -- Colisiones, perder vidas, etc --
3100   IF pantalla$(y,x) = premio$ THEN puntos=puntos+10: premios=premios-1: pantalla$(y,x) = " "
3110   IF premios=0 THEN nivel=nivel+1: ERASE xE, yE, velocE, pantalla$: CLS: GOTO 1455
3150   IF pantalla$(y,x) = obstaculo$ THEN chocado = 1
3350   FOR n = 1 TO enemigos
3400     IF x=xE(n) AND y=yE(n) THEN chocado = 1
3450   NEXT n
3460   IF chocado = 1 THEN vidas = vidas-1: chocado = 0: y = yInicial: x = xInicial
3470   IF vidas = 0 THEN terminado = 1
3500   ' -- Dibujar en nueva posicion --
3550   PEN colorPremio: LOCATE x,y: PRINT jugador$
3950   PEN colorEnemigo: FOR n = 1 TO enemigos:LOCATE xE(n),yE(n):PRINT enemigo$:  NEXT n
4200   LOCATE 15,22: PRINT "Puntos "; puntos ; "   "
4210   LOCATE 15,23: PRINT "Vidas "; vidas
4250 WEND
4260 LOCATE 12,10: PEN colorPremio: PRINT " Partida terminada! "
4265 FOR i=1 TO 500: NEXT i: 'Pausa por tiempo
4270 WHILE INKEY$<>"":WEND: 'Vaciamos buffer del teclado
4275 WHILE INKEY$="":WEND: 'Y esperamos una tecla
4280 CLS: RESTORE: ERASE xE, yE, velocE, pantalla$
4290 GOTO 1265
4295 '
4300 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
4350 DATA PP-o--PPP----o---PPPP-o------PP
4400 DATA PP---xPPP---x----PPPP---x----PP
4450 DATA PP----PPP--e-----PPPP--------PP
4500 DATA PP----PPPPP------PPPP--------PP
4550 DATA PP--PPPPPPPPPP--PPPPP----PPPPPP
4600 DATA PP---PPPPPPPP--PPPPPPPP----PPPP
4650 DATA PPP-A--PPPPP---PPPPPPPPP---PPPP
4700 DATA PP----------------x---e-----PPP
4750 DATA PPP------------------------PPPP
4800 DATA PPPPP-----------PPPPPPPPP----PP
4850 DATA PPPPPP--e--------PPPPPP---P--PP
4900 DATA PPP----x--------------PPP-PP-PP
4950 DATA PP--------------x--o-----PPPPPP
5000 DATA PP-o------------------------PPP
5050 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
5095 '
5100 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
5150 DATA PPxo--PPP----o--------o---A--PP
5200 DATA PP-----PP---x----PPPP--------PP
5250 DATA PP------P--e-----PPPP--e-----PP
5300 DATA PP---------------PPPP--------PP
5350 DATA PP--e-------------PPPPPPPPPPPPP
5400 DATA PPPPPPPPPPPPP--PPPPPPPP----PPPP
5450 DATA PPPPPPPPP------------------PPPP
5500 DATA PP----------------x---e-----PPP
5550 DATA PPP------------------------PPPP
5600 DATA PPPPP--e------------PPPPP----PP
5650 DATA PPPPPP------P-------PPP------PP
5700 DATA PPP---------PP--o-----PPPPPP-PP
5750 DATA PP---o----PP-------------PPPPPP
5800 DATA PP-X-----PP------------x----PPP
5850 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
4295 '
5900 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
5950 DATA PP-x-------------------------PP
6000 DATA PP------o-----x---o--------e-PP
6050 DATA PP----PPPPPPPPPPPPPPP--------PP
6100 DATA PP---------------------------PP
6150 DATA PPo---e--x---PP--x--e----o---PP
6200 DATA PP---PPPPPPPPPPPPPPPPPP----PPPP
6250 DATA PPP----PPPPPPPPPPPPPPPPP---PPPP
6300 DATA PP--------------------------PPP
6350 DATA PPP-------------------e---x-PPP
6400 DATA PPPPP-----------PPPPPPPPP----PP
6450 DATA PPPPPP--e--------------e-----PP
6500 DATA PPP--------------------------PP
6550 DATA PP--A-----o-----x--o---------PP
6600 DATA PP-o------------------------PPP
6650 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
 

34. Subrutinas

En lenguajes más modernos (incluso versiones más modernas de BASIC, pero no en las de mediados de los años 80) podemos crear "subrutinas con nombre", de modo que la apariencia de nuestro programa recuerde mucho a las acciones que queremos que lleve a cabo:
Inicializar
DibujarPantallaPresentacion
ComenzarJuego
donde, a su vez, "ComenzarJuego", supondría tareas como
InicializarPartida
Repetir:
  ComprobarTeclas
  MoverEnemigosYEntorno
  ComprobarColisiones
  DibujarElementos
Esto además ayuda a repetir menos código: si debemos dibujar la pantalla de presentación desde distintos puntos de nuestro programa, bastará llamar desde cada sitio a la correspondiente subrutina.
En una versión de BASIC tan antigua, no se puede llegar a ese nivel de legibilidad, pero sí se pueden crear "subrutinas con número", a las que se salta con GOSUB y de las que se vuelve con RETURN, de modo que el principio de nuestro programa sería:
10 GOSUB 1000: ' Inicializacion del juego
20 GOSUB 1260: ' Pantalla de presentacion
30 GOSUB 1340: ' Inicializacion de partida
40 GOSUB 2190: ' Bucle de juego
50 GOTO 20: ' Y volvemos a la pantalla de present.
El resto del programa no tiene grandes cambios, pero sí algunos. Por ejemplo, podemos aprovechar para mover las órdenes DIM a la subrutina de inicialización del juego, y así evitar usar ERASE y volverlas a crear. El fuente completo podría ser:
1 ' Laberinto 2014
2 ' Ejemplo de juego en BASIC de CPC
3 ' Desarrollado paso a paso por Nacho
10 GOSUB 1000: ' Inicializacion del juego
20 GOSUB 1260: ' Pantalla de presentacion
30 GOSUB 1340: ' Inicializacion de partida
40 GOSUB 2190: ' Bucle de juego
50 GOTO 20: ' Y volvemos a la pantalla de present.
1000 ' 
1010 ' Inicializacion
1020 '
1030 ' -- Colores de pantalla --
1050 MODE 1: INK 0,0: INK 1,20: INK 2,6: INK 3,25: PAPER 0: PEN 1
1060 colorPared=1: colorEnemigo=2: colorPremio=3
1070 nivel=1
1090 ' -- Definicion de caracteres --
1100 SYMBOL 240, &X11111100, &X11111100, &X11111100, &X0, &X11001111, &X11001111, &X11001111, &X0
1110 SYMBOL 241, &X11000, &X101100, &X1000110, &X10000011, &X10000011, &X1000110, &X101100, &X11000
1120 SYMBOL 242, &X10000, &X10010010, &X1010100, &X111000, &X11111111, &X111000, &X1010100, &X10010010
1130 SYMBOL 243, &X10111101, &X1111110, &X11011011, &X11111111, &X11100111, &X11011011, &X1100110, &X11000011
1140 pared$=CHR$(240): premio$=CHR$(241): obstaculo$=CHR$(242): enemigo$=CHR$(243): jugador$=CHR$(248)
1150 ' -- Teclas ---
1160 arriba=0: abajo=2: derecha=1: izqda=8: salir=63
1170 joyArriba=72: joyAbajo=73: joyDerecha=75: joyIzqda=74
1180 ' -- Teclas ---
1190 DIM xE(10), yE(10), velocE(10)
1200 DIM pantalla$(25,40)
1210 RETURN
1260 '
1265 ' -- Pantalla de bienvenida --
1266 '
1229 CLS
1270 LOCATE 10,4: PEN colorPared: PRINT "Bienvenido al laberinto"
1272 LOCATE 10,7: PEN colorPremio: PRINT "Recoge los premios: "; premio$
1274 LOCATE 10,9: PEN colorEnemigo: PRINT "Esquiva los obstaculos: "; obstaculo$
1276 LOCATE 10,11: PRINT "Y evita a los enemigos: "; enemigo$
1278 LOCATE 4,20: PEN colorPared: PRINT "Pulsa una tecla para empezar... ";
1280 PLOT 0,0,colorPremio: DRAW 639,0: DRAW 639,399: DRAW 0,399: DRAW 0,0
1285 PLOT 5,5,colorPared: DRAW 634,5: DRAW 634,394: DRAW 5,394: DRAW 5,5
1300 WHILE INKEY$="":WEND: 'Pausa
1310 RETURN
1340 '
1350 ' -- Datos del juego al comienzo de una nueva partida --
1351 '
1400 terminado=0:chocado=0
1410 puntos=0: vidas=3
1420 '
1425 ' -- Datos del juego al comienzo de cada nivel --
1426 '
1430 premios=0: enemigos=0
1440 if nivel>3 THEN nivel=1: RESTORE
1460 ' -- Lectura del mapa desde DATA --
1650 CLS: LOCATE 15,22: PRINT"Generando..."
1810 ' Vamos a vaciar el array, para evitar basura
1820 FOR f = 1 TO 25: FOR c = 1 TO 40: pantalla$(f,c)=" ": NEXT c: NEXT f
1840 ' Y a rellenar los datos reales
1850 FOR fila = 1 TO 16
1900   READ linea$
1950   FOR columna = 1 TO 32
2050     IF MID$(linea$,columna,1) = "P" THEN pantalla$(fila+2, columna+4)=pared$
2060     IF MID$(linea$,columna,1) = "x" THEN pantalla$(fila+2, columna+4)=obstaculo$
2070     IF MID$(linea$,columna,1) = "A" THEN y=fila+2: x=columna+4: yInicial=y: xInicial=x
2080     IF MID$(linea$,columna,1) = "o" THEN premios = premios+1: pantalla$(fila+2, columna+4)=premio$
2090     IF MID$(linea$,columna,1) = "e" THEN enemigos = enemigos+1: yE(enemigos)=fila+2: xE(enemigos)=columna+4: velocE(enemigos)=1
2100   NEXT columna
2150 NEXT fila
2160 ' -- Dibujado de la parte estatica del mapa --
2162 FOR f = 1 TO 25
2164   FOR c = 1 TO 40
2166     LOCATE c, f
2168     IF pantalla$(f,c) = pared$ THEN PEN colorPared: PRINT pared$;
2170     IF pantalla$(f,c) = obstaculo$ THEN PEN colorEnemigo: PRINT obstaculo$;
2172     IF pantalla$(f,c) = premio$ THEN PEN colorPremio: PRINT premio$;
2174   NEXT c
2176 NEXT f
2180 RETURN
2190 '
2200 ' ----- Bucle de juego -----
2201 '
2250 WHILE terminado = 0
2300   ' -- Borrar personaje y enemigos de su posicion anterior --
2350   FOR n = 1 TO enemigos:LOCATE xE(n),yE(n):PRINT" ": NEXT n
2400   LOCATE x,y: PRINT " "
2500   ' -- Comprobar teclas --
2550   IF (INKEY(arriba)<>-1 OR INKEY(joyArriba)<>-1) AND pantalla$(y-1,x)<>pared$ THEN y=y-1
2600   IF (INKEY(abajo) >-1 OR INKEY(joyAbajo)<>-1) AND pantalla$(y+1,x)<>pared$ THEN y=y+1
2650   IF (INKEY(derecha)<>-1 OR INKEY(joyDerecha)<>-1) AND pantalla$(y,x+1)<>pared$ THEN x=x+1
2700   IF (INKEY(izqda)<>-1 OR INKEY(joyIzqda)<>-1) AND pantalla$(y,x-1)<>pared$ THEN x=x-1
2750   IF INKEY(salir) <> -1 THEN terminado=1
2800   ' -- Mover enemigos, entorno --
2850   FOR n = 1 TO enemigos
2860      ' Avanzar si no es pared; dar la vuelta si lo es
2865      obj$=pantalla$(yE(n),xE(n)+velocE(n))
2870      IF obj$<>pared$ AND obj$<>premio$ AND obj$<>obstaculo$ THEN xE(n)=xE(n)+velocE(n) ELSE velocE(n)=-velocE(n)
3000   NEXT n
3050   ' -- Colisiones, perder vidas, etc --
3100   IF pantalla$(y,x) = premio$ THEN puntos=puntos+10: premios=premios-1: pantalla$(y,x) = " "
3110   IF premios=0 THEN nivel=nivel+1: GOSUB 1420
3150   IF pantalla$(y,x) = obstaculo$ THEN chocado = 1
3350   FOR n = 1 TO enemigos
3400     IF x=xE(n) AND y=yE(n) THEN chocado = 1
3450   NEXT n
3460   IF chocado = 1 THEN vidas = vidas-1: chocado = 0: y = yInicial: x = xInicial
3470   IF vidas = 0 THEN terminado = 1
3500   ' -- Dibujar en nueva posicion --
3550   PEN colorPremio: LOCATE x,y: PRINT jugador$
3950   PEN colorEnemigo: FOR n = 1 TO enemigos:LOCATE xE(n),yE(n):PRINT enemigo$:  NEXT n
4200   LOCATE 15,22: PRINT "Puntos "; puntos ; "   "
4210   LOCATE 15,23: PRINT "Vidas "; vidas
4250 WEND
4260 LOCATE 12,10: PEN colorPremio: PRINT " Partida terminada! "
4265 FOR i=1 TO 500: NEXT i: 'Pausa por tiempo
4270 WHILE INKEY$<>"":WEND: 'Vaciamos buffer del teclado
4275 WHILE INKEY$="":WEND: 'Y esperamos una tecla
4280 CLS: RESTORE
4290 RETURN
4295 '
4300 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
4350 DATA PP-o--PPP----o---PPPP-o-------PP
4400 DATA PP---xPPP---x----PPPP---x-----PP
4450 DATA PP----PPP--e-----PPPP---------PP
4500 DATA PP----PPPPP------PPPP---------PP
4550 DATA PP--PPPPPPPPPP--PPPPP----PPPPPPP
4600 DATA PP---PPPPPPPP--PPPPPPPP-----PPPP
4650 DATA PPP-A--PPPPP---PPPPPPPPP----PPPP
4700 DATA PP----------------x---e------PPP
4750 DATA PPP-------------------------PPPP
4800 DATA PPPPP-----------PPPPPPPPP-----PP
4850 DATA PPPPPP--e--------PPPPPP----P--PP
4900 DATA PPP----x--------------PPP--PP-PP
4950 DATA PP--------------x--o-----PPPPPPP
5000 DATA PP-o-------------------------PPP
5050 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
5095 '
5100 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
5150 DATA PPxo--PPP----o---------o---A--PP
5200 DATA PP-----PP---x-----PPPP--------PP
5250 DATA PP------P--e------PPPP--e-----PP
5300 DATA PP----------------PPPP--------PP
5350 DATA PP--e--------------PPPPPPPPPPPPP
5400 DATA PPPPPPPPPPPPP---PPPPPPPP----PPPP
5450 DATA PPPPPPPPP-------------------PPPP
5500 DATA PP-----------------x---e-----PPP
5550 DATA PPP-------------------------PPPP
5600 DATA PPPPP--e-------------PPPPP----PP
5650 DATA PPPPPP------P--------PPP------PP
5700 DATA PPP---------PP---o-----PPPPPP-PP
5750 DATA PP---o----PP--------------PPPPPP
5800 DATA PP-X-----PP-------------x----PPP
5850 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
4295 '
5900 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
5950 DATA PP-x--------------------------PP
6000 DATA PP------o------x---o--------e-PP
6050 DATA PP----PPPP-PPPPPPPPPPP--------PP
6100 DATA PP----------------------------PP
6150 DATA PPo---e--x----PP--x--e----o---PP
6200 DATA PP---PPPPPPPPPPPPPPPPPPP----PPPP
6250 DATA PPP----PPPPPPPPPPPPPPPPPP---PPPP
6300 DATA PP---------------------------PPP
6350 DATA PPP--------------------e---x-PPP
6400 DATA PPPPP------------PPPPPPPPP----PP
6450 DATA PPPPPP--e---------------e-----PP
6500 DATA PPP---------------------------PP
6550 DATA PP--A------o-----x--o---------PP
6600 DATA PP-o-------------------------PPP
6650 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
 

35. Efectos sonoros, explosiones y música de fondo

La orden SOUND permite emitir sonidos a través del altavoz interno de un CPC o de unos altavoces externos que se hayan conectado a la salida de audio. En su formato más sencillo, indicaremos datos: el canal que queremos usar (tenemos 3 canales, lo que permite tocar varias notas a la vez) y el tono de la nota:
SOUND 1, 478
Eso toca a través del canal 1 una nota DO en una escala intermedia (el CPC permite notas dentro de una gama de 8 octavas, numeradas desde -3 hasta 4; este es el DO de la octava 0).
El resto de tonos de las notas de esta octava intermedia son:
DO = 478
RE = 426
MI = 379
FA = 358
SOL = 319
LA = 284
SI = 253
Así, si queremos tocar una melodía sencilla como RE-MI-FA-RE- RE-MI-FA-RE- FA-SOL-LA lo podríamos hacer con un programita sencillo como
10 'Usaremos variables, por legibilidad
20 DO0 = 478: RE0 = 426: MI0 = 379: FA0 = 358
30 SOL0 = 319: LA0 = 284: SI0 = 253
40 ' Y esta es la melodia
50 SOUND 1, RE0
60 SOUND 1, MI0
70 SOUND 1, FA0
80 SOUND 1, RE0
90 SOUND 1, RE0
100 SOUND 1, MI0
110 SOUND 1, FA0
120 SOUND 1, RE0
130 SOUND 1, FA0
140 SOUND 1, SOL0
150 SOUND 1, LA0
El segundo canal de sonido es el 2 y el tercero es el 4. Así, podemos hacer que una nota suene a la vez por el primer y el tercer canal indicando como canales 1+4=5, o que suene por los tres canales a la vez con 1+2+4=7.
SOUND 7, 478
Un tercer parámetro opcional es la duración de la nota, que se mide en centésimas de segundo. Si no indicamos otra cosa, la nota se escuchará durante 20 centésimas de segundo. Si queremos que suene un segundo entero, lo haríamos con
SOUND 7, 478, 100
Un cuarto parámetro es el volumen, desde 0 (silencio) a 15 (máximo), con 12 como valor por defecto. Por ejemplo, una nota de volumen medio podría ser
SOUND 7, 478, 100, 7
Hay más parámetros, pero de uso más avanzado: podemos definir una "envolvente de volumen", para hacer que el sonido suba o baje durante la reproducción de una nota, así como una "envolvente de tono", para conseguir que el sonido se vuelva más grave o más agudo en ciertos puntos de la nota. Con ambos, podemos hacer que los sonidos sean mucho más ricos, pero caen fuera del cometido de este texto. Si te atrae el tema, puedes consultar el manual oficial del Amstrad CPC o algún libro específico como el llamado "Música y sonidos con Amstrad".
También podemos añadir un séptimo parámetro a la orden SOUND: un 1, para indicar que emita ruido a la vez (un "ruido sintético", que se conoce como "ruido blanco"). Por ejemplo, podemos imitar las interferencias de una radio o televisión con
1 FOR repeticion=1 TO 6
2 frecuencia=INT(RND*20)
3 SOUND 1,frecuencia,50,15 ,0,0,1
4 NEXT
Pero estas ordenes "tal cual" nos sirven para reproducir música en una pantalla de presentación, no tanto durante el juego, porque en medio de una partida deberíamos hacer otras cosas mientras suena la música. Afortunadamente, el BASIC de los CPC permite fijar "interrupciones", que se ejecutarán al cabo de un cierto tiempo (AFTER) o de forma repetiría tras cada cierto tiempo (EVERY):
EVERY 50 GOSUB 10000
El primer dato es el tiempo que tardará en saltarse a esa subrutina, medido en 1/50 segundos. Por tanto, esa orden salta a la línea 10.000 una vez cada segundo. Un segundo dato opciones es el número de temporizador que queremos usar. Tenemos cuatro temporizadores, por si necesitáramos varias tareas simultáneas. Si no indicamos nada, usaremos el temporizador 0, que es de menor prioridad.
EVERY 50,3 GOSUB 10000
Cuando queramos que algo deje de repetirse, podemos desactivar un temporizado, usando la función REMAIN, que además nos dice el tiempo que quedaba hasta el siguiente "salto":
restante = REMAIN(3)
Por tanto, podríamos usar sentencias READ y DATA para almacenar en un array las notas de una melodía, y llamar cada cierto tiempo a una rutina que reprodujese la siguiente nota del array (o volviera a la primera, cuando lleguemos al final del array).
(No, esta vez no hay versión completa del fuente ampliado, queda propuesto si te atreves con ello)

36. Otras posibles mejoras

Por supuesto, dejamos muchas cosas en el tintero. Aquí tienes pinceladas de algunas de ellas, por si te apetece investigar y llegar más allá...

36.1. Una pantalla de carga

La pantalla de un CPC empieza en la dirección de memoria 49152 y ocupa los 16 Kb superiores, hasta la 65535. Se puede guardar dibujar cualquier cosa en pantalla, ya sea con un programa específico de dibujo o con un programa creado por nosotros mismos. Si es desde un programa nuestro, se podría guardar con la orden SAVE, a la que se le indicaría el parámetro "b" (fichero binario), la dirección de inicio (49152) y el tamaño (16384):
SAVE "imagen.bin",b,49152,16384
Aunque era más habitual usar estos parámetros en hexadecimal:
SAVE "imagen.bin",b,&C000,&4000
Para cargar la imagen bastaría con
LOAD "imagen.bin"
(si la cabecera del fichero no es la correcta, quizá fuera necesario indicar la dirección de carga:)
LOAD "imagen.bin", &C000
La alternativa más vistosa es crear la imagen desde un equipo moderno y finalmente convertirla al formato de CPC. Hay herramientas que lo permiten, como ConvImgCPC:
Si descargamos esta utilidad y la lanzamos, veremos una pantalla con textos en francés, que podemos traducir a inglés si hacemos clic en la bandera inglesa:

El primer paso será cargar una imagen ("Read image")

, elegir un modo de pantalla (Mode 0, Mode 1 o Mode 2), escoger si queremos algún tipo de "dithering" (patrón geométrico para las zonas que tengan tonalidades intermedias) y pulsar el botón "Process":

Al guardar la imagen resultante ("Save picture"), obtendremos un fichero con extensión SCR, que podremos cargar directamente en el emulador (si usamos la opción "Tape bypass" que ya vimos de CPCE)
LOAD "imagen.scr"

(Si el conversor ha cambiado la paleta de colores, como en este caso, podemos recrearla, mirando la paleta que aparece en la parte inferior de ConvImgCpc y las equivalencias que vimos en el apartado 13 (esta información aparecerá posiblemente también en la ventana de información de ConvImgCpc como "most used colors" tras pulsar el botón "Process"):
INK 0,0: INK 1,13: INK 2,14: INK 3,26

36.2. Ligeras mejoras de velocidad

Podemos medir tiempos si añadimos las líneas
1470 t=TIME
2177 LOCATE 1,25: PRINT (TIME-t)/300
Entonces, veremos que se tarda algo más de 36 segundos y medio en generar y dibujar el primer nivel.
Una primera mejora es usar números enteros en vez de números reales. Si no decimos lo contrario, BASIC supondrá que queremos trabajar con números reales (con decimales), pero estos son más lentos y en este juego no necesitamos usar cifras decimales. Para indicar que queremos que un número sea entero, podemos hacer que termine con el símbolo "%":
1820 FOR f% = 1 TO 25: FOR c% = 1 TO 40: pantalla$(f%,c%)=" ": NEXT c%: NEXT f%
Ese pequeño cambio hace ganar algo más de un segundo cuando vaciamos el mapa. De igual modo, si usamos números enteros en el redibujado ganamos 3 segundos más:
2162 FOR f% = 1 TO 25
2164   FOR c% = 1 TO 40
2166     LOCATE c%, f%
2168     IF pantalla$(f%,c%) = pared$ THEN PEN colorPared: PRINT pared$;
2170     IF pantalla$(f%,c%) = obstaculo$ THEN PEN colorEnemigo: PRINT obstaculo$;
2172     IF pantalla$(f%,c%) = premio$ THEN PEN colorPremio: PRINT premio$;
2174   NEXT c%
2176 NEXT f%
Una forma alternativa de conseguir lo mismo es definir las variables como enteras, con DEFINT
1055 DEFINT c,f
Con eso ganamos otros dos segundos más (ya estamos en 30, en vez de 36). Podríamos hacer lo mismo con la X y la Y, pero como no se usan en bucles grandes, no será una ganancia tan clara.
Una segunda mejora viene de plantear si estamos haciendo más trabajo del necesario. Por ejemplo, hemos creado un array tan grande como la pantalla para poder comprobar elementos sin hacer operaciones airtméticas, pero no necesitamos recorrer ese array tan grande a la hora de dibujar, nos basta con recorrer la zona que corresponde al mapa:
2162 FOR f% = 3 TO 18
2164   FOR c% = 5 TO 36
Con eso rebajamos de 30 segundos a 24. Podemos hacer lo mismo en la línea que vacía el contenido del array de pantalla:
1820 FOR f% = 3 TO 18: FOR c% = 5 TO 36: pantalla$(f%,c%)=" ": NEXT c%: NEXT f%
Pero esa es una operación más sencilla, así que ganamos poco más de un segundo. Podemos arañar otro poco si evitamos hacer operaciones repetitivas: no necesitamos sumar 2 a cada fila y sumar 4 a cada columna cada vez que analizamos, ni necesitamos volver a comprobar cuál es el carácter que hay en cierta posición del mapa:
1850 FOR fila = 1 TO 16
1900   READ linea$
1950   FOR columna = 1 TO 32
2000     filaPantalla = fila+2: columnaPantalla = columna+4
2010     simboloActual$ = MID$(linea$,columna,1)
2050     IF simboloActual$ = "P" THEN pantalla$(filaPantalla, columnaPantalla)=pared$
2060     IF simboloActual$ = "x" THEN pantalla$(filaPantalla, columnaPantalla)=obstaculo$
2070     IF simboloActual$ = "A" THEN y=filaPantalla: x=columnaPantalla: yInicial=y: xInicial=x
2080     IF simboloActual$ = "o" THEN premios = premios+1: pantalla$(filaPantalla, columnaPantalla)=premio$
2090     IF simboloActual$ = "e" THEN enemigos = enemigos+1: yE(enemigos)=filaPantalla: xE(enemigos)=columnaPantalla: velocE(enemigos)=1
2100   NEXT columna
2150 NEXT fila
Hemos rebajado hasta 21 segundos. Pero todavía queda espacio para la mejora, incluso sin dejar de usar BASIC: podemos evitar comprobar varias veces el valor de misma variable (por ejemplo, una posición de la pantalla), usando ELSE tras cada IF para indicar que se trata de casos contrarios o, si las líneas son largas (es nuestro caso, porque el BASIC de los CPC no permite más de 255 caracteres por línea) usando GOTO para saltar al final de las comprobaciones en cuanto una se cumpla:
2050     IF simboloActual$ = "P" THEN simboloPantalla$=pared$: GOTO 2095
2060     IF simboloActual$ = "x" THEN simboloPantalla$=obstaculo$: GOTO 2095
De hecho, si comprobamos también si hay que escribir un espacio, podemos evitar vaciar antes el array de pantalla y eliminar la línea 1820:
1820
2020     simboloPantalla$=" "
2040     IF simboloActual$ = "-" THEN GOTO 2095
2050     IF simboloActual$ = "P" THEN simboloPantalla$=pared$: GOTO 2095
2060     IF simboloActual$ = "x" THEN simboloPantalla$=obstaculo$: GOTO 2095
2070     IF simboloActual$ = "A" THEN y=filaPantalla: x=columnaPantalla: yInicial=y: xInicial=x: GOTO 2095
2080     IF simboloActual$ = "o" THEN premios = premios+1: simboloPantalla$=premio$: GOTO 2095
2090     IF simboloActual$ = "e" THEN enemigos = enemigos+1: yE(enemigos)=filaPantalla: xE(enemigos)=columnaPantalla: velocE(enemigos)=1
2095     pantalla$(filaPantalla, columnaPantalla) = simboloPantalla$
Y podemos hacer mejoras similares en el dibujado: comenzar por el caso más probable y saltar al final tras cada caso que se cumpla. De hecho, ya que vamos a dibujar todos los caracteres (incluido espacios), podríamos hacer un único LOCATE al principio de cada fila y escribir con "punto y coma" para no bajar de línea tras cada símbolo:
2162 FOR f% = 3 TO 18
2163   LOCATE 5, f%
2164   FOR c% = 5 TO 36
2167     IF pantalla$(f%,c%) = " " THEN PRINT " ";: GOTO 2174
2168     IF pantalla$(f%,c%) = pared$ THEN PEN colorPared: PRINT pared$;: GOTO 2174
2170     IF pantalla$(f%,c%) = obstaculo$ THEN PEN colorEnemigo: PRINT obstaculo$;: GOTO 2174
2172     IF pantalla$(f%,c%) = premio$ THEN PEN colorPremio: PRINT premio$;: GOTO 2174
2174   NEXT c%
2176 NEXT f%
Con eso llegamos hasta 14 segundos. Como mejora final, podemos juntar la parte que rellena el array y la parte que lo muestra
1650 CLS: LOCATE 15,22: PRINT"Generando..."
1850 FOR fila = 1 TO 16
1860   filaPantalla = fila+2: LOCATE 5,filapantalla
1900   READ linea$
1950   FOR columna = 1 TO 32
2000     columnaPantalla = columna+4
2010     simboloActual$ = MID$(linea$,columna,1)
2020     simboloPantalla$=" "
2040     IF simboloActual$ = "-" THEN GOTO 2095
2050     IF simboloActual$ = "P" THEN PEN colorPared:simboloPantalla$=pared$: GOTO 2095
2060     IF simboloActual$ = "x" THEN PEN colorEnemigo:simboloPantalla$=obstaculo$: GOTO 2095
2070     IF simboloActual$ = "A" THEN y=filaPantalla: x=columnaPantalla: yInicial=y: xInicial=x: GOTO 2095
2080     IF simboloActual$ = "o" THEN PEN colorPremio:premios = premios+1: simboloPantalla$=premio$: GOTO 2095
2090     IF simboloActual$ = "e" THEN enemigos = enemigos+1: yE(enemigos)=filaPantalla: xE(enemigos)=columnaPantalla: velocE(enemigos)=1
2095     pantalla$(filaPantalla, columnaPantalla) = simboloPantalla$
2096     PRINT simboloPantalla$;
2100   NEXT columna
2150 NEXT fila
Ya estamos en 10 segundos para generar y dibujar cada nivel. Mucho más razonable...
1 ' Laberinto 2014
2 ' Ejemplo de juego en BASIC de CPC
3 ' Desarrollado paso a paso por Nacho
10 GOSUB 1000: ' Inicializacion del juego
20 GOSUB 1260: ' Pantalla de presentacion
30 GOSUB 1340: ' Inicializacion de partida
40 GOSUB 2190: ' Bucle de juego
50 GOTO 20: ' Y volvemos a la pantalla de present.
1000 ' 
1010 ' Inicializacion
1020 '
1030 ' -- Colores de pantalla --
1050 MODE 1: INK 0,0: INK 1,20: INK 2,6: INK 3,25: PAPER 0: PEN 1
1055 DEFINT c,f,n
1060 colorPared=1: colorEnemigo=2: colorPremio=3
1070 nivel=1
1090 ' -- Definicion de caracteres --
1100 SYMBOL 240, &X11111100, &X11111100, &X11111100, &X0, &X11001111, &X11001111, &X11001111, &X0
1110 SYMBOL 241, &X11000, &X101100, &X1000110, &X10000011, &X10000011, &X1000110, &X101100, &X11000
1120 SYMBOL 242, &X10000, &X10010010, &X1010100, &X111000, &X11111111, &X111000, &X1010100, &X10010010
1130 SYMBOL 243, &X10111101, &X1111110, &X11011011, &X11111111, &X11100111, &X11011011, &X1100110, &X11000011
1140 pared$=CHR$(240): premio$=CHR$(241): obstaculo$=CHR$(242): enemigo$=CHR$(243): jugador$=CHR$(248)
1150 ' -- Teclas ---
1160 arriba=0: abajo=2: derecha=1: izqda=8: salir=63
1170 joyArriba=72: joyAbajo=73: joyDerecha=75: joyIzqda=74
1180 ' -- Teclas ---
1190 DIM xE(10), yE(10), velocE(10)
1200 DIM pantalla$(25,40)
1210 RETURN
1260 '
1265 ' -- Pantalla de bienvenida --
1266 '
1229 CLS
1270 LOCATE 10,4: PEN colorPared: PRINT "Bienvenido al laberinto"
1272 LOCATE 10,7: PEN colorPremio: PRINT "Recoge los premios: "; premio$
1274 LOCATE 10,9: PEN colorEnemigo: PRINT "Esquiva los obstaculos: "; obstaculo$
1276 LOCATE 10,11: PRINT "Y evita a los enemigos: "; enemigo$
1278 LOCATE 4,20: PEN colorPared: PRINT "Pulsa una tecla para empezar... ";
1280 PLOT 0,0,colorPremio: DRAW 639,0: DRAW 639,399: DRAW 0,399: DRAW 0,0
1285 PLOT 5,5,colorPared: DRAW 634,5: DRAW 634,394: DRAW 5,394: DRAW 5,5
1300 WHILE INKEY$="":WEND: 'Pausa
1310 RETURN
1340 '
1350 ' -- Datos del juego al comienzo de una nueva partida --
1351 '
1400 terminado=0:chocado=0
1410 puntos=0: vidas=3
1420 '
1425 ' -- Datos del juego al comienzo de cada nivel --
1426 '
1430 premios=0: enemigos=0
1440 if nivel>3 THEN nivel=1: RESTORE
1460 ' -- Lectura del mapa desde DATA y dibujado de la parte estatica --
1470 't=TIME
1650 CLS: LOCATE 15,22: PRINT"Generando..."
1850 FOR fila = 1 TO 16
1860   filaPantalla = fila+2: LOCATE 5,filapantalla
1900   READ linea$
1950   FOR columna = 1 TO 32
2000     columnaPantalla = columna+4
2010     simboloActual$ = MID$(linea$,columna,1)
2020     simboloPantalla$=" "
2040     IF simboloActual$ = "-" THEN GOTO 2095
2050     IF simboloActual$ = "P" THEN PEN colorPared:simboloPantalla$=pared$: GOTO 2095
2060     IF simboloActual$ = "x" THEN PEN colorEnemigo:simboloPantalla$=obstaculo$: GOTO 2095
2070     IF simboloActual$ = "A" THEN y=filaPantalla: x=columnaPantalla: yInicial=y: xInicial=x: GOTO 2095
2080     IF simboloActual$ = "o" THEN PEN colorPremio:premios = premios+1: simboloPantalla$=premio$: GOTO 2095
2090     IF simboloActual$ = "e" THEN enemigos = enemigos+1: yE(enemigos)=filaPantalla: xE(enemigos)=columnaPantalla: velocE(enemigos)=1
2095     pantalla$(filaPantalla, columnaPantalla) = simboloPantalla$
2096     PRINT simboloPantalla$;
2100   NEXT columna
2150 NEXT fila
2177 'LOCATE 1,25: PRINT (TIME-t)/300
2180 RETURN
2190 '
2200 ' ----- Bucle de juego -----
2201 '
2250 WHILE terminado = 0
2300   ' -- Borrar personaje y enemigos de su posicion anterior --
2350   FOR n = 1 TO enemigos:LOCATE xE(n),yE(n):PRINT" ": NEXT n
2400   LOCATE x,y: PRINT " "
2500   ' -- Comprobar teclas --
2550   IF (INKEY(arriba)<>-1 OR INKEY(joyArriba)<>-1) AND pantalla$(y-1,x)<>pared$ THEN y=y-1
2600   IF (INKEY(abajo) >-1 OR INKEY(joyAbajo)<>-1) AND pantalla$(y+1,x)<>pared$ THEN y=y+1
2650   IF (INKEY(derecha)<>-1 OR INKEY(joyDerecha)<>-1) AND pantalla$(y,x+1)<>pared$ THEN x=x+1
2700   IF (INKEY(izqda)<>-1 OR INKEY(joyIzqda)<>-1) AND pantalla$(y,x-1)<>pared$ THEN x=x-1
2750   IF INKEY(salir) <> -1 THEN terminado=1
2800   ' -- Mover enemigos, entorno --
2850   FOR n = 1 TO enemigos
2860      ' Avanzar si no es pared; dar la vuelta si lo es
2865      obj$=pantalla$(yE(n),xE(n)+velocE(n))
2870      IF obj$<>pared$ AND obj$<>premio$ AND obj$<>obstaculo$ THEN xE(n)=xE(n)+velocE(n) ELSE velocE(n)=-velocE(n)
3000   NEXT n
3050   ' -- Colisiones, perder vidas, etc --
3100   IF pantalla$(y,x) = premio$ THEN puntos=puntos+10: premios=premios-1: pantalla$(y,x) = " "
3110   IF premios=0 THEN nivel=nivel+1: GOSUB 1420
3150   IF pantalla$(y,x) = obstaculo$ THEN chocado = 1
3350   FOR n = 1 TO enemigos
3400     IF x=xE(n) AND y=yE(n) THEN chocado = 1
3450   NEXT n
3460   IF chocado = 1 THEN vidas = vidas-1: chocado = 0: y = yInicial: x = xInicial
3470   IF vidas = 0 THEN terminado = 1
3500   ' -- Dibujar en nueva posicion --
3550   PEN colorPremio: LOCATE x,y: PRINT jugador$
3950   PEN colorEnemigo: FOR n = 1 TO enemigos:LOCATE xE(n),yE(n):PRINT enemigo$:  NEXT n
4200   LOCATE 15,22: PRINT "Puntos "; puntos ; "   "
4210   LOCATE 15,23: PRINT "Vidas "; vidas
4250 WEND
4260 LOCATE 12,10: PEN colorPremio: PRINT " Partida terminada! "
4265 FOR i=1 TO 500: NEXT i: 'Pausa por tiempo
4270 WHILE INKEY$<>"":WEND: 'Vaciamos buffer del teclado
4275 WHILE INKEY$="":WEND: 'Y esperamos una tecla
4280 CLS: RESTORE
4290 RETURN
4295 '
4300 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
4350 DATA PP-o--PPP----o---PPPP-o-------PP
4400 DATA PP---xPPP---x----PPPP---x-----PP
4450 DATA PP----PPP--e-----PPPP---------PP
4500 DATA PP----PPPPP------PPPP---------PP
4550 DATA PP--PPPPPPPPPP--PPPPP----PPPPPPP
4600 DATA PP---PPPPPPPP--PPPPPPPP-----PPPP
4650 DATA PPP-A--PPPPP---PPPPPPPPP----PPPP
4700 DATA PP----------------x---e------PPP
4750 DATA PPP-------------------------PPPP
4800 DATA PPPPP-----------PPPPPPPPP-----PP
4850 DATA PPPPPP--e--------PPPPPP----P--PP
4900 DATA PPP----x--------------PPP--PP-PP
4950 DATA PP--------------x--o-----PPPPPPP
5000 DATA PP-o-------------------------PPP
5050 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
5095 '
5100 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
5150 DATA PPxo--PPP----o---------o---A--PP
5200 DATA PP-----PP---x-----PPPP--------PP
5250 DATA PP------P--e------PPPP--e-----PP
5300 DATA PP----------------PPPP--------PP
5350 DATA PP--e--------------PPPPPPPPPPPPP
5400 DATA PPPPPPPPPPPPP---PPPPPPPP----PPPP
5450 DATA PPPPPPPPP-------------------PPPP
5500 DATA PP-----------------x---e-----PPP
5550 DATA PPP-------------------------PPPP
5600 DATA PPPPP--e-------------PPPPP----PP
5650 DATA PPPPPP------P--------PPP------PP
5700 DATA PPP---------PP---o-----PPPPPP-PP
5750 DATA PP---o----PP--------------PPPPPP
5800 DATA PP-X-----PP-------------x----PPP
5850 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
4295 '
5900 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
5950 DATA PP-x--------------------------PP
6000 DATA PP------o------x---o--------e-PP
6050 DATA PP----PPPP-PPPPPPPPPPP--------PP
6100 DATA PP----------------------------PP
6150 DATA PPo---e--x----PP--x--e----o---PP
6200 DATA PP---PPPPPPPPPPPPPPPPPPP----PPPP
6250 DATA PPP----PPPPPPPPPPPPPPPPPP---PPPP
6300 DATA PP---------------------------PPP
6350 DATA PPP--------------------e---x-PPP
6400 DATA PPPPP------------PPPPPPPPP----PP
6450 DATA PPPPPP--e---------------e-----PP
6500 DATA PPP---------------------------PP
6550 DATA PP--A------o-----x--o---------PP
6600 DATA PP-o-------------------------PPP
6650 DATA PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
 

36.3. Movimientos más suaves

Nuestro personaje y nuestros enemigos parpadean al moverse. Una forma de evitarlo (o al menos de hacer que se note un poco menos) es no borrar al principio de cada pasada por el bucle de juego, sino borrar justo antes de escribir, usando tanto las variables X e Y como otras Xantiguo e Yantiguo, que guarden los valores anteriores.
Otra ayuda para que la imagen en pantalla sea más suave es sincronizar el dibujado en pantalla con el momento en el que el haz de electrones está actualizando el contenido de ésta. Esto se consigue en un CPC usando la orden CALL &BD19 justo antes de empezar a escribir. Pero como no dibujamos la pantalla en orden estricto de arriba a abajo, el resultado tampoco será tan bueno como podría.
Pero además se mueven a saltos. Cada carácter ocupa una posición dentro de una cuadrícula de 25x40. Pero en los CPC no hay distinción entre modo de texto y modo gráfico, por lo que podemos dibujar líneas o puntos con una precisión de 320x200 puntos, pero al escribir con PRINT y movernos con LOCATE, avanzamos o retrocedemos de 8 en 8 píxeles. ¿No podemos dibujar también un carácter como el de nuestro personaje en cualquiera de esas 320x200 posiciones de la pantalla gráfica? La respuesta es que sí. Para eso usaremos la orden TAG. Desde que utilicemos esa orden y hasta que la desactivemos con "TAGOFF", las órdenes PRINT escribirán el texto en coordenadas gráficas de 640x400. Basta indicar antes la posición con una orden MOVE y terminar la orden PRINT con un punto y coma (de lo contrario, se verían en pantalla los símbolos de avance de línea):
10 MODE 1
20 TAG : ' Escritura en coordenadas graficas
30 DEG : ' Angulos en grados
40 FOR x = 1 TO 640 STEP 4
50   y = 200 + 150*SIN(x)
60   MOVE x, y
70   PRINT "Hola";
80 NEXT x
90 TAGOFF
 

Pero no veremos ningún ejemplo aplicado directamente a nuestro juego, porque supondría demasiados cambios... Si tienes inquietud, puedes intentar crear una nueva versión, en la que los elementos se muevan de 4 en 4 píxeles, por ejemplo.

36.4. Laberintos comprimidos

Una pantalla de 25x40 caracteres puede contiene 1.000 caracteres, lo que supondría que pudieramos almacenar más de 30 pantallas con facilidad en nuestro programa, sin necesidad siquiera de recurrir a guardar datos de pantallas en disco para cargarlos más adelante.
Si además nuestras pantallas son más pequeñas (16x32 en nuestro caso, = 512 bytes), podemos duplicar con facilidad la cantidad de pantallas que podemos almacenar.
Pero si queremos guardar aún más pantallas, podemos comprimir la información. Hay formas muy simples con las que podemos ocupar menos de la mitad del espacio: ahora mismo estamos usando un carácter para representar cada tipo de elemento, pero apenas tenemos 5 tipos de elementos...
Una alternativa es que cada carácter represente varias posiciones de pantalla. Por ejemplo, en vez de que "p" represente una casilla de pared, podríamos hacer que "p" fueran dos casillas de pared, "q" fuera una seguida por un espacio y "r" fuera una precedida por un espacio. Usaremos muchos más símbolos, pero los datos de los mapas ocuparán la mitad.
Otra alternativa es usar pares de caracteres, para indicar qué carácter debe haber y cuántas veces se debe repetir: p1 podría indicar una casilla de pared y p9 nueve casillas seguidas de pared. En un mapa totalmente irregular, esto supondría gastar el doble de espacio que en un mapa normal, pero si el mapa tiene muchos datos repetitivos, el ahorro puede ser grande.
Por supuesto, existen algoritmos mucho más eficientes en el aprovechamiento del espacio, pero también son más complejos, totalmente fuera de la intención de este texto.

36.5. Imágenes multicolor

Los CPC no incluyen soporte de sprites (figuras móviles multicolor) por hardware. Bueno... esto sólo es cierto para la gama original, que es la que nos interesa, pero no para los modelos Plus. Tampoco se puede mover figuras en color por software a suficiente velocidad usando sólo BASIC...
Pero existe la alternativa de incluir rutinas en código máquina dentro de un programa en BASIC. Hay algunas bibliotecas de Sprites que permiten hacerlo de forma no demasiado compleja. Si eres realmente atrevido, puedes echar un vistazo a Sprites Alive o a las rutinas creadas por Sean McManus.

36.x ...

(Sí, seguro que quedan todavía más cosas... pero ya está bien, ¿no?)