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.

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.

Ensamblador ZX Spectrum Marciano
Ensamblador ZX Spectrum, dificultad

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:

Ensamblador ZX Spectrum Marciano
Ensamblador ZX Spectrum, cargador

La pantalla de carga, que debéis descargar desde aquí y dejar en la carpeta Paso14, debe presentar este aspecto:

Ensamblador ZX Spectrum Marciano
Ensamblador ZX Spectrum, pantalla de carga

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 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

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.

Aquí puedes ver más cosas que he desarrollado para .Net, y aquí las desarrolladas en ensamblador para Z80.

Y recuerda, si lo usas no te limites a copiarlo, intenta entenderlo y adaptarlo a tus necesidades.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

ACEPTAR
Aviso de cookies
A %d blogueros les gusta esto: