0x0E Ensamblador ZX Spectrum Marciano – Dificultad, mute y pantalla de carga
En este capítulo de Ensamblador ZX Spectrum Marciano vamos a dar la posibilidad de seleccionar entre cinco niveles de dificultad, silenciar la música durante la partida e incluir la pantalla de carga.
Creamos la carpeta Paso14 y copiamos desde la carpeta Paso13 los archivos Cargador.tap, Const.asm, Ctrl.asm, Game.asm, Graph.asm, Int.asm, Main.asm, make o make.bat, Print.asm y Var.asm.
Tabla de contenidos
Dificultad
Dependiendo del nivel de dificultad seleccionada, el comportamiento de los enemigos va a variar, así como el número de vidas.
En los niveles uno y dos, las naves enemigas no llegan hasta la posición de nuestra nave, y los disparos enemigos simultáneos es uno en el nivel uno y cinco en el nivel dos.
En el resto de niveles, las naves enemigas llegan hasta la posición de nuestra nave (ese es el comportamiento que tienen ahora), y en el caso del nivel tres los disparos enemigos simultáneos es uno, mientras que en los niveles cuatro y cinco son cinco. La diferencia entre el nivel cuatro y cinco es que en el cuatro, cada vez que se supera un nivel volvemos a tener cinco vidas, al más puro estilo Plaga Galáctica.
Ya que vamos a dar la opción de seleccionar entre cinco niveles de dificultad, debemos modificar la pantalla de inicio.
Vamos al archivo Var.asm, y modificamos las etiquetas title y firstScreen, dejándolas de la siguiente manera:
title:
db $10, $02, $16, $00, $08, "BATALLA ESPACIAL", $0d, $0d, $ff
firstScreen:
db $10, $06, "Las naves alienigenas atacan la", $0d
db "Tierra, el futuro depende de ti.", $0d, $d
db "Destruye todos los enemigos que", $0d
db "puedas, y protege el planeta.", $0d, $0d
db $10, $03, "Z - Izquierda", $16, $08, $15,"X - Derecha"
db $0d, $0d, "V - Disparo", $16, $0a, $15, "M - Sonido", $0d, $0d
db $10, $04, "1 - Teclado 3 - Sinclair 1", $0d, $0d
db "2 - Kempston 4 - Sinclair 2", $0d, $0d
db $10, $07, $16, $10, $07, "5 - Dificultad ", $0d, $0d
db $10, $05, "Apunta, dispara, esquiva a las", $0d
db "naves enemigas, vence y libera", $0d
db "al planeta de la amenza."
db $ff
Como añadimos nuevas opciones, quitamos un retorno de carro en el título y varios en el resto de la pantalla para que quepa todo.
Seguimos en Var.asm, localizamos la etiqueta enemiesColor y debajo del valor (DB $06) añadimos la etiqueta que vamos a usar para guardar la dificultad seleccionada.
hardness:
db $03
Ahora que tenemos la pantalla de inicio modificada, es necesario mostrar en la misma el nivel de dificultad seleccionado. Vamos a Print.asm y tras la rutina PrintFrame implementamos la rutina que pinta la dificultad seleccionada:
; -----------------------------------------------------------------------------
; Pinta la dificultad seleccionada en el menú
;
; Altera el valor de los registros AF y BC.
; -----------------------------------------------------------------------------
PrintHardness:
ld a, $02 ; Carga en A la tinta
call Ink ; Asigna la tinta
ld b, $08 ; Carga en B la coordenada Y (invertida)
ld c, $0a ; Carga en X la coordenada X (invertida)
call At ; Posiciona el cursor
ld a, (hardness) ; Carga en A la dificultad
add a, '0' ; Le suma el carácter 0
rst $10 ; Pinta la dificultad
ret
En estos momentos ya debemos tener cierto nivel de conocimientos, así que vamos a explicar la rutina solo por encima.
Ponemos la tinta en rojo (2), posicionamos el cursor, cargamos la dificultad, le sumamos el carácter ‘0’ para calcular el código de carácter de la dificultad y lo pintamos.
Seguimos en Print.asm, localizamos la rutina PrintFirstScreen y tras quinta línea, CALL PrintString, añadimos la llamada para pintar la dificultad seleccionada.
call PrintHardness
Compilamos, cargamos en el emulador y vemos los resultados.
Como podemos observar, hemos quitado retornos de carro, añadido una nueva tecla de control para activar/desactivar la música y añadido la opción de seleccionar la dificultad.
Ahora vamos a añadir la implementación de la selección de dificultad. Seguimos en Print.asm, localizamos la etiqueta printFirstSreen_end, y justo por encima de ella tenemos la línea JR C, printFirstScreen_op. Justo por encima de esta línea, añadimos las siguientes:
jr nc, printFirstScreen_end
rra
Si se ha pulsado la tecla 4 saltamos al fin de la rutina, JR NC, printFirstScreen_end. Si no se ha pulsado, rotamos A a la derecha para saber si se ha pulsado el 5.
La siguiente línea ya estaba, JR C, printFirstScreen_op, y la dejamos como está, si no se ha pulsado el 5 salta para seguir en el bucle.
Por debajo de esta línea, añadimos las siguientes, que se ejecutaran en el caso de haber pulsado el 5:
ld a, (hardness)
inc a
cp $06
jr nz, printFirstScreen_opCont
ld a, $01
Cargamos la dificultad en A, LD A, (hardness), la incrementamos, INC A, evaluamos si ha llegado a seis, CP $06, saltamos si no ha llegado, JR NZ, printFirstScreen_opCont, y si sí hemos llegado la ponemos a uno, LD A, $01.
printFirstScreen_opCont:
ld (hardness), a
call PrintHardness
jr printFirstScreen_op
Actualizamos la dificultad en memoria, LD (hardness), A, la pintamos, CALL PrintHardess, y seguimos en el bucle hasta que se pulse una tecla del 1 al 4.
El aspecto de la rutina es el siguiente:
; -----------------------------------------------------------------------------
; Pantalla de presentación y selección de controles.
;
; Altera el valor de los registros AF, BC y HL.
; -----------------------------------------------------------------------------
PrintFirstScreen:
call CLS ; Limpia la pantalla
ld hl, title ; Carga en HL la definición del título
call PrintString ; Pinta el título
ld hl, firstScreen ; Carga en HL la definición de la pantalla
call PrintString ; Pinta la pantalla
call PrintHardness ; Pinta la dificultad
printFirstScreen_op:
ld b, $01 ; Carga 1 en B, opción teclas
ld a, $f7 ; Carga en A la semifila 1-5
in a, ($fe) ; Lee el teclado
rra ; Rota A a la derecha para saber si ha pulsado 1
jr nc, printFirstScreen_end ; Si no hay acarreo, se ha pulsado y salta
inc b ; Incrementa B, opción Kempston
rra ; Rota A a la derecha para saber si ha pulsado 2
jr nc, printFirstScreen_end ; Si no hay acarreo, se ha pulsado y salta
inc b ; Incrementa B, opción Sinclar 1
rra ; Rota A a la derecha para saber si ha pulsado 3
jr nc, printFirstScreen_end ; Si no hay acarreo, se ha pulsado y salta
inc b ; Incrementa B, opción Sinclar 2
rra ; Rota A a la derecha para saber si ha pulsado 4
jr nc, printFirstScreen_end ; Si no hay acarreo, se ha pulsado y salta
rra ; Rota A a la derecha para saber si ha pulsado 5
jr c, printFirstScreen_op ; Si hay acarreo, no se ha pulsado, bucle
ld a, (hardness) ; Carga la dificultad en A
inc a ; La incrementa
cp $06 ; Comprueba si hemos pasado de 5
jr nz, printFirstScreen_opCont ; Si no hemos pasado, salta
ld a, $01 ; Pone A a 1
printFirstScreen_opCont:
ld (hardness), a ; Actualiza la dificultad en memoria
call PrintHardness ; La pinta
jr printFirstScreen_op ; Bucle hasta que pulse tecla del 1 al 4
printFirstScreen_end:
ld a, b ; Carga en A la opción seleccionada
ld (controls), a ; Lo carga en memoria
call FadeScreen ; Fundido de pantalla
ret
Compilamos, cargamos en el emulador y probamos la selección de dificultad.
¡No funciona! O al menos, no como nos gustaría. Cambia tan rápido de dificultad que es extremadamente difícil seleccionar la dificultad deseada.
Vamos a cambiar la parte de la rutina que evalúa las teclas pulsadas apoyándonos en las rutinas de la ROM, que controlan que haya una pausa entre cada detección de tecla para evitar su repetición, y vamos a cambiar al modo de interrupción 1 para que las variables de sistema se actualicen automáticamente.
Vamos al archivo Const.am y vamos a añadir dos contantes que apuntan a dos variables de sistema, y que nos hacen falta para saber cual es la última tecla pulsada usando las rutinas de la ROM.
;-------------------------------------------------------------------------------
; Dirección de memoria donde están los flags de estado del teclado cuando
; están activas las interrupciones en modo 1.
;
; Bit 3 = 1 entrada en modo L, 0 entrada en modo K.
; Bit 5 = 1 se ha pulsado una tecla, 0 no se ha pulsado.
; Bit 6 = 1 carácter numérico, 0 alfanumérico.
;-------------------------------------------------------------------------------
FLAGS_KEY: equ $5c3b
;-------------------------------------------------------------------------------
; Dirección de memoria dónde está la última tecla pulsada
; cuando están activas las interrupciones en modo 1.
;-------------------------------------------------------------------------------
LAST_KEY: equ $5c08
Si leemos los comentarios podremos saber que es cada cosa.
Volvemos a Print.asm, localizamos la etiqueta printFirstScreen_op y borramos desde LD B, $01 hasta JR C, printFirstScreen_op, que está justo antes de LD A , (hardness).
Justo por encima de printFirstScreen_op añadimos las líneas siguientes:
di
im 1
ei
ld hl, FLAGS_KEY
set $03, (hl)
Desactivamos las interrupciones, DI, cambiamos a modo uno, IM 1, reactivamos las interrupciones, EI, cargamos en HL la dirección de los indicadores del teclado, LD HL, FLAGS_KEY, y ponemos la entrada en modo L, SET $03, (HL).
Justo debajo de printFirstScreen_op implementamos la lectura del teclado usando la ROM:
bit $05, (hl)
jr z, printFirstScreen_op
res $05, (hl)
Comprobamos si se ha pulsado una tecla, BIT $05, (HL), si no se ha pulsado vuelve al bucle, JR Z, printFirstScreen_op, y si se ha pulsado ponemos el bit 5 a cero para futuras inspecciones, RES $05, (HL).
ld b, $01
ld c, '0' + $01
ld a, (LAST_KEY)
cp c
jr z, printFirstScreen_end
Cargamos uno en B (opción teclado), LD B, $01, cargamos en C el código ASCII del uno, LD C, ‘0’ + $01, cargamos en A la última tecla pulsada, LD A, (LAST_KEY), comprobamos si es el uno, CP C, y saltamos de ser así, JR Z, printFirstScreen_end.
inc b
inc c
cp c
jr z, printFirstScreen_end
Incrementamos B (opción Kempston), INC B, incrementamos C (tecla dos), INC C, comprobamos si se ha pulsado, CP C, y saltamos de ser así, JR Z, printFirstScreen_end.
Hacemos lo mismo para comprobar si se ha pulsado el tres o el cuatro (Sinclair 1 y 2):
inc b
inc c
cp c
jr z, printFirstScreen_end
inc b
inc c
cp c
jr z, printFirstScreen_end
Ya solo nos queda comprobar si se ha pulsado el cinco (dificultad):
inc c
cp c
jr nz, printFirstScreen_op
Incrementamos C (tecla 5), INC C, comprobamos si se ha pulsado, CP C, y volvemos al inicio del bucle si no ha sido así, JR NZ, printFirstScreen_op.
Ya solo nos queda un aspecto muy importante. Localizamos la etiqueta printFirstScreen_end, y justo antes de RET añadimos las líneas siguientes:
di
im 2
ei
Desactivamos las interrupciones, DI, pasamos a modo dos, IM 2, y activamos las interrupciones, EI.
El aspecto final de la rutina es el siguiente:
; -----------------------------------------------------------------------------
; Pantalla de presentación, selección de controles y dificultad.
;
; Altera el valor de los registros AF, BC y HL.
; -----------------------------------------------------------------------------
PrintFirstScreen:
call CLS ; Limpia la pantalla
ld hl, title ; Carga en HL la definición del título
call PrintString ; Pinta el título
ld hl, firstScreen ; Carga en HL la definición de la pantalla
call PrintString ; Pinta la pantalla
call PrintHardness ; Pinta la dificultad
di ; Desactiva las interrupciones
im 1 ; Cambia a modo 1
ei ; Reactiva las interrupciones
ld hl, FLAGS_KEY ; Carga en HL la dirección de los indicadores del teclado
set $03, (hl) ; Pone entrada en modo L
printFirstScreen_op:
bit $05, (hl) ; Comprueba si se ha pulsado una tecla
jr z, printFirstScreen_op ; Si no se ha pulsado, vuelve al bucle
res $05, (hl) ; Es necesario poner el bit a 0 para futuras inspecciones
ld b, $01 ; Carga 1 en B, opción teclas
ld c, '0' + $01 ; Carga el código ASCII del 1 en C
ld a, (LAST_KEY) ; Carga en A la última tecla pulsada
cp c ; Comprueba si ha pulsado el 1
jr z, printFirstScreen_end ; Si se ha pulsado el 1, sale
inc b ; Incrementa B, opción Kempston
inc c ; Incrementa C, tecla 2
cp c ; Comprueba si ha pulsado el 2
jr z, printFirstScreen_end ; Si se ha pulsado el 2, sale
inc b ; Incrementa B, opción Sinclair 1
inc c ; Incrementa C, tecla 3
cp c ; Comprueba si ha pulsado el 3
jr z, printFirstScreen_end ; Si se ha pulsado el 3, sale
inc b ; Incrementa B, opción Sinclair 2
inc c ; Incrementa C, tecla 4
cp c ; Comprueba si ha pulsado el 4
jr z, printFirstScreen_end ; Si se ha pulsado el 4, sale
inc c ; Incrementa C, tecla 5
cp c ; Comprueba si ha pulsado el 5
jr nz, printFirstScreen_op ; Si no se ha pulsado sigue en el bucle
ld a, (hardness) ; Carga la dificultad en A
inc a ; La incrementa
cp $06 ; Comprueba si ha pasado de 5
jr nz, printFirstScreen_opCont ; Si no ha pasado, salta
ld a, $01 ; Pone A a 1
printFirstScreen_opCont:
ld (hardness), a ; Actualiza la dificultad en memoria
call PrintHardness ; La pinta
jr printFirstScreen_op ; Bucle hasta que pulse tecla del 1 al 4
printFirstScreen_end:
ld a, b ; Carga en A la opción seleccionada
ld (controls), a ; Lo carga en memoria
call FadeScreen ; Fundido de pantalla
di ; Desactiva las interrupciones
im 2 ; Cambia a modo 2
ei ; Activa las interrupciones
ret
Compilamos, cargamos en el emulador y comprobamos que podemos seleccionar la dificultad deseada.
Ahora que ya seleccionamos la dificultad, vamos a cambiar el comportamiento de nuestro juego en función de la dificultad seleccionada. Según la dificultad, las naves llegan o no hasta la línea donde está nuestra nave, y puede haber uno o cinco disparos enemigos a un mismo tiempo; estos dos aspectos los controlamos con las constantes ENEMY_TOP_B y FIRES, necesitamos que esos valores sean variables, vamos a Var.asm, localizamos la etiqueta hardness, y justo por encima de ella añadimos estas líneas:
enemiesTopB:
db ENEMY_TOP_B
firesTop:
db FIRES
Ya tenemos nuestras variables, ahora hay que usarlas. Vamos a Game.asm, localizamos la etiqueta moveEnemies_Y_down y la línea SUB ENEMY_TOP_B. Vamos a sustituir esta línea por las siguientes, leyendo los comentarios sabemos que hace:
push hl ; Preserva el valor de HL
ld hl, enemiesTopB ; Apunta HL al tope por abajo
sub (hl) ; Lo resta
pop hl ; Recupera el valor de HL
Seguimos en Game.asm, localizamos la etiqueta enableEnemiesFire_loop y la línea CP FIRES. Vamos a sustituir esta línea por las siguientes:
push hl ; Preserva HL
ld hl, firesTop ; Apunta HL al máximo de disparos
ld c, (hl) ; Lo carga en C
pop hl ; Recupera el valor de HL
cp c ; Compara el máximo de disparos con los activos
Con esto ya controlamos hasta donde llegan las naves enemigas por abajo, y el número de disparos simultáneos que puede haber, pero necesitamos una rutina que cambie los valores de enemiesTop y firesTop en función de la dificultad seleccionada.
Seguimos en Game.asm, localizamos la rutina Sleep e implementamos justo por encima de ella:
SetHardness:
ld hl, enemiesTopB
ld (hl), ENEMY_TOP_B
ld a, (hardness)
cp $03
jr nc, setHardness_Fire
inc (hl)
Apuntamos HL al tope de la posición de los enemigos por abajo, LD HL, enemiesTopB, actualizamos con el tope por defecto, LD (HL), ENEMY_TOP_B, cargamos la dificultad en A, LD A, (hardness), comprobamos si es tres, CP $03, si no hay acarreo es mayor o igual que tres y saltamos, JR NC, setHardness_Fire. Si hay acarreo, la dificultad es menor que tres, incrementamos en una línea el tope de la posición de los enemigos por abajo, INC (HL). Recordad que trabajamos con las coordenadas invertidas.
setHardness_Fire:
ld hl, firesTop
ld (hl), $01
cp $01
ret z
cp $03
ret z
ld (hl), FIRES
ret
Apuntamos HL al número máximo de disparos simultáneos, LD HL firesTop, lo ponemos a uno, LD (HL), $01, comprobamos si estamos en dificultad uno, CP $01, salimos si es así, RET Z, comprobamos si la dificultad en tres, CP $03, salimos si es así. Si no hemos salido, cargamos el número máximo de disparos por defecto, LD (HL), FIRES, y salimos, RET.
El aspecto final de la rutina es el siguiente:
; -----------------------------------------------------------------------------
; Asigna la dificultad
;
; Altera el valor de los registros AF y HL
; -----------------------------------------------------------------------------
SetHardness:
ld hl, enemiesTopB ; Apunta HL a tope por abajo de los enemigos
ld (hl), ENEMY_TOP_B ; Lo actualiza con el tope por defecto
ld a, (hardness) ; Carga la dificultad en A
cp $03 ; La compara con 3
jr nc, setHardness_Fire ; Si no hay acarreo A => 3, salta
inc (hl) ; Sube una línea el tope por abajo de los enemigos
setHardness_Fire:
ld hl, firesTop ; Apunta HL al máximo de disparos
ld (hl), $01 ; Lo pone a 1
cp $01 ; Comprueba si la dificultad es 1
ret z ; Sale si es 1
cp $03 ; Comprueba si la dificultad es 3
ret z ; Sale si es 3
ld (hl), FIRES ; Carga los disparos máximos por defecto
ret
Ha llegado el momento de probar si la selección de dificultad se comporta como pretendemos. Vamos a Main.asm, localizamos la etiqueta Main_start y la línea CALL PrintFirstScreen. Justo después de está línea incluimos la llamada a la asignación de la dificultad:
call SetHardness ; Asigna la dificultad
Ya solo queda uno de los aspectos que señalamos al principio; si el nivel de dificultad seleccionado es el cuatro, cada nivel lo empezamos con cinco vidas.
Seguimos en Main.asm, localizamos la etiqueta Main_restart y la línea JR Z, Win. Justo debajo de esta línea vamos a implementar el último aspecto de la dificultad:
ld a, (hardness)
cp $04
jr nz, main_restartCont
ld a, $05
ld (livesCounter), a
main_restartCont:
Cargamos la dificultad en A, LD A, (hardmess), comprobamos si es cuatro, CP $04, y saltamos si no lo es, JR NZ, main_restartCont.
Si no hemos saltado, cargamos cinco en A, LD A, $05, y actualizamos el número de vidas en memoria para empezar cada nivel con cinco, LD (livesCounter), A.
El aspecto final de la rutina es el siguiente:
Main_restart:
ld a, (levelCounter) ; Carga el número de nivel en A
cp $1e ; Comprueba si es el 31 (tenemos 30)
jr z, Win ; Si es el 31 salta, ¡VICTORIA!
ld a, (hardness) ; Carga la dificultad en A
cp $04 ; Comprueba si es 4
jr nz, main_restartCont ; Si no es 4, salta
ld a, $05 ; Carga 5 en A
ld (livesCounter), a ; Pone cinco vidas
main_restartCont:
call FadeScreen ; Hace el fundido de la pantalla
call ChangeLevel ; Cambia de nivel
call PrintFrame ; Pinta el marco
call PrintInfoGame ; Pinta los títulos de información
call PrintShip ; Pinta la nave
call PrintInfoValue ; Pinta la información
call PrintEnemies ; Pinta los enemigos
call ResetEnemiesFire ; Reinicia los disparos de los enemigos
; Retardo
call Sleep ; Produce un retardo
jr Main_loop ; Bucle principal
Compilamos, cargamos en el emulador y comprobamos que los distintos niveles de dificultad se comportan tal y como lo hemos definido.
Mute
La implementación de activar o desactivar la música es relativamente sencilla. Vamos a Main.asm y añadimos un nuevo comentario a la etiqueta flags:
; Bit 5 -> mute 0 = No, 1 = Sí
En el bit cinco de flags vamos a indicar si el mute está o no activo.
Ahora tenemos que implementar la activación o desactivación de este bit. Localizamos la etiqueta Main_loop, y justo debajo de ella añadimos la nueva implementación:
rst $38
ld hl, FLAGS_KEY
set $03, (hl)
bit $05, (hl)
jr z, main_loopCheck
Actualizamos las variables del sistema, RST $38, apuntamos HL a los indicadores del teclado, LD HL, FLAGS_KEY, ponemos la entrada en modo L, SET $03, (HL), comprobamos si se ha pulsado alguna tecla, BIT $05, (HL), y si no se ha pulsado saltamos.
res $05, (hl)
ld a, (LAST_KEY)
cp 'M'
jr z, main_loopMute
cp 'm'
jr nz, main_loopCheck
Ponemos el bit cinco a cero para futuras inspecciones, RES $05, (HL), cargamos en A la última tecla pulsada, LD A, (LAST_KEY), comprobamos si se ha pulsado la m mayúscula, CP ‘M’, saltamos si se ha pulsado, JR Z, main_loopMute, comprobamos si se ha pulsado la m minúscula, CP ‘m’, y saltamos si no se ha pulsado, JR NZ, main_loopCheck.
main_loopMute:
ld a, (flags)
xor $20
ld (flags), a
main_loopCheck:
Si se ha pulsado la m, ya sea mayúscula o minúscula, cargamos los indicadores en A, LD A, (flags), invertimos el valor del bit cinco, XOR $20, y actualizamos el valor en memoria, LD (flags), A. Finalmente, incluimos la etiqueta a la que saltamos y que no existía, main_loopCheck.
Es buen momento para recordar como actúa XOR a nivel de bit:
0 XOR 0 = 0
0 XOR 1 = 1
1 XOR 0 = 1
1 XOR 1 = 0
Si los dos bit son iguales el resultado es cero, si son distintos el resultado es 1. Al hacer XOR $20, si el bit cinco está a uno, el resultado es cero, si está a cero el resultado es uno, el resto de bits se quedan como estaban.
El aspecto final del inicio de la rutina Main_loop es el siguiente:
; Bucle principal
Main_loop:
rst $38 ; Actualiza las variables de sistema
ld hl, FLAGS_KEY ; Carga en HL la dirección de los indicadores del teclado
set $03, (hl) ; Pone entrada en modo L
bit $05, (hl) ; Comprueba si se ha pulsado una tecla
jr z, main_loopCheck ; Si no se ha pulsado, salta
res $05, (hl) ; Es necesario poner el bit a 0 para futuras inspecciones
ld a, (LAST_KEY) ; Carga en A la última tecla pulsada
cp 'M' ; Comprueba si ha pulsado la M
jr z, main_loopMute ; Si se ha pulsado la M, salta
cp 'm' ; Comprueba si ha pulsado la m
jr nz, main_loopCheck ; Si no se ha pulsado la m, salta
main_loopMute:
ld a, (flags) ; Carga en A los indicadores
xor $20 ; Invierte el bit 5 (mute)
ld (flags), a ; Actualiza el valor en memoria
main_loopCheck:
call CheckCtrl ; Comprueba la pulsación de los controles
call MoveFire ; Mueve el disparo
Por último, localizamos la etiqueta GameOver, vemos que la línea de arriba es ésta:
jr Main_loop ; Bucle principal
Sustituimos JR por JP ya que al haber añadido varias líneas, con JR nos daría un error de salto fuera de rango.
Ahora que ya activamos o desactivamos el bit del mute al pulsar la tecla M, vamos a tenerlo en cuenta para hacer sonar, o no, la música. Vamos a Int.asm, localizamos la etiqueta Isr_sound y, justo debajo de ella, añadimos la líneas siguientes:
bit $05, (hl) ; Evalúa si el bit 5 (mute) está activo
jr nz, Isr_end ; Si lo está, salta
Cuando llegamos a Isr_soung HL apunta a flags de Main.asm. Evaluamos si el bit cinco (mute) está activo, BIT $05, (HL), y saltamos si es así, JR NZ, Isr_end.
Es hora de comprobar si hemos implementado correctamente el mute. Compilamos, cargamos en el emulador, comenzamos la partida y verificamos que al pulsar la M (sin pulsar otra tecla a la vez) la música se silencia, si volvemos a pulsar, vuelve a sonar. Los efectos de sonido siguen sonando.
Pantalla de carga
Llegamos al punto final del desarrollo de Batalla Espacial, vamos a añadir la pantalla de carga.
Desde el inicio del tutorial llevamos arrastrando un aspecto que a la hora de incluir la pantalla de carga nos va a producir un error, ya lo vimos en PoromponPong, lo corregimos, pero otra vez he vuelto a caer en él; tenemos que cambiar la dirección de inicio de nuestro juego.
Vamos a Main.asm y cambiamos la dirección de inicio que ahora es ORG $5DAD, por ORG $5DFD.
Vamos al archivo Int.asm y cambiamos FLAGS: EQU $5DAD, y la dejamos como FLAGS: EQU $5DFD. También cambiamos MUSIC: EQU $5DAE, la dejamos como MUSIC: EQU $5DFE.
Si ahora compilamos y cargamos en el emulador nos dará un error, no hemos cambiado las direcciones en el cargador. En el cargador, a parte de cambiar las direcciones, vamos a meter un POKE que ya usamos en PorompomPong, y la carga a la pantalla de carga.
El aspecto final del cargador tiene que ser este:
La pantalla de carga, que debéis descargar desde aquí y dejar en la carpeta Paso14, debe presentar este aspecto:
Ya tenemos el cargador modificado y la pantalla de carga en el directorio. Solo nos queda incluir la pantalla de carga en BatallaEspacial.tap.
Si estamos trabajando en Linux, editamos el archivo make y lo dejamos así:
pasmo --name Marciano --tap Main.asm Marciano.tap --public
pasmo --name Int --tap Int.asm Int.tap
cat Cargador.tap MarcianoScr.tap Marciano.tap Int.tap > BatallaEspacial.tap
Si trabajamos con Windows, editamos el archivo make.bat y lo dejamos así:
pasmo --name Marciano --tap Main.asm Marciano.tap --public
pasmo --name Int --tap Int.asm Int.tap
copy /b /y Cargador.tap + MarcianoScr.tap + Marciano.tap + Int.tap BatallaEspacial.tap
Como podéis observar, hemos añadido MarcianoScr.tap entre Cargador.tap y Marciano.tap.
Ejecutamos make o make.bat, cargamos en el emulador y hemos terminado, a no ser que vosotros queráis cambiar algo…
Ensamblador ZX Spectrum, conclusión
En este capítulo hemos implementado la selección del nivel de dificultad, la posibilidad de silenciar la música y hemos añadido una pantalla de carga, finalizando así nuestro juego, pero no el tutorial. Todavía nos queda un último capítulo, en el que veremos como configurar algunos aspectos de ZEsarUX y como depurar.
Todo el código que hemos generado lo podéis descargar desde aquí.
Enlaces de interés
- Notepad++
- Visual Studio Code
- Sublime Text
- ZEsarUX
- PASMO
- Git
- Curso de ensamblador Z80 de Compiler Software
- Referencia Z80
- Ensamblador Z80 en Telegram
- Tutorial completo en formato PDF y EPUB
- Proyecto en itch.io
- Archivo .dsk con los juegos de los tutoriales
- Personalización y depuración con ZEsarUX
Ensamblador para ZX Spectrum Batalla espacial por Juan Antonio Rubio García.
Esta obra está bajo licencia de Creative Commons Reconocimiento-NoComercial-CompartirIgual 4.0 Internacional License.
También puedes visitar el resto de tutoriales:
Y recuerda, si lo usas no te limites a copiarlo, intenta entenderlo y adaptarlo a tus necesidades.