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?)
No hay comentarios:
Publicar un comentario