0x04 Ensamblador ZX Spectrum Marciano – Nave

En este capítulo de Ensamblador ZX Spectrum Marciano, vamos a implementar la nave, su movimiento y, por tanto, los controles.

Antes de nada, creamos la carpeta Paso04 y copiamos, desde la carpeta Paso03, los archivos Const.asm, Graph.asm, Main.asm, Print.asm y Var.asm.

Posicionamiento en pantalla

Hasta ahora, hemos usado el carácter de control de la instrucción AT y las coordenadas para posicionarnos por la pantalla, pero esto resulta lento.

Abrimos Graph.asm y al inicio del archivo vamos a implementar una rutina que hace lo mismo, pero es más rápida.

; -----------------------------------------------------------------------------
; Posiciona el cursor en las coordenadas especificadas.
;
; Entrada:	B = Coordenada Y (24 a 3).
;			C = Coordenada X (32 a 1).
; Altera el valor de los registros AF y BC.
; -----------------------------------------------------------------------------
At:
push	de	; Preserva el valor de DE
push	hl	; Preserva el valor de HL
call	$0a23	; Llama a la rutina de la ROM
pop		hl	; Recupera el valor de HL
pop		de	; Recupera el valor de DE

ret

En esta rutina hacemos uso de la rutina de la ROM que posiciona el cursor. Preservamos el valor de DE, PUSH DE, y el de HL, PUSH HL. Una vez preservados estos valores, llamamos a la rutina de la ROM, CALL $0a23, y recuperamos los valores de HL, POP HL, y también el de DE, POP DE. Finalmente, salimos, RET.

En los comentarios podéis observar que esta rutina, en realidad la de la ROM, también altera el valor de AF y BC, pero no los preservamos; no nos va a afectar que lo altere y por eso nos ahorramos dos PUSH y dos POP.

Otra cosa a tener muy en cuenta, y una pista os la dan los comentarios, es que para la rutina de la ROM la esquina superior izquierda está en las coordenadas Y=24 y X=32, por lo que trabajaremos con las coordenadas invertidas con respecto a la instrucción AT.

Vamos a abrir el archivo Const.asm y añadimos las constantes de las coordenadas.

; Coordenadas de la pantalla para la rutina de la ROM que posiciona el cursor,
; relativas al área de juego (el marco).
COR_X: EQU $20  ; Coordenada X de la esquina superior izquierda
COR_Y: EQU $18  ; Coordenada Y de la esquina superior izquierda
MIN_X: EQU $00  ; A restar de COR_X para X esquina superior izquierda
MIN_Y: EQU $00  ; A restar de COR_Y para Y esquina superior izquierda
MAX_X: EQU $1f  ; A restar de COR_X para X esquina inferior derecha
MAX_Y: EQU $15  ; A restar de COR_Y para Y esquina inferior derecha

Recordemos que la directiva EQU no se compila, por lo que no aumenta el tamaño del binario, lo que hace es sustituir la etiqueta por el valor en aquellos lugares en dónde se encuentre.

Pintamos la nave

Lo primero que vamos a hacer es poner la nave en nuestra zona de juego; la nave se va a mover de izquierda a derecha en la parte inferior de la zona de juego.

Al ser la nave una parte móvil, necesitamos saber en todo momento en que posición está, y la posición inicial, tal y como vimos con las palas en PorompomPong.

Abrimos el archivo Var.asm (el que hay en la carpeta Paso04), y añadimos, tras las declaraciones de los títulos de información de la partida, las líneas siguientes:

; ----------------------------------------------------------------------------
; Declaraciones de los gráficos de los distintos personajes
; y la configuración de coordenadas (Y, X)
; ----------------------------------------------------------------------------

; ----------------------------------------------------------------------------
; Nave
; ----------------------------------------------------------------------------
shipPos:
dw $0511

En el caso de la nave, solo vamos a definir la posición, DW $0511, un valor de dos bytes, primero la coordenada Y y luego la X, que durante la partida cargaremos en BC para posicionar la nave. La posición $0511 es el resultado de restar 19 ($13) y 15 ($0f) de las coordenadas de la esquina superior derecha que usa la rutina de la ROM ($1820).

Abrimos el archivo Const.asm e incluimos constantes para el carácter de la nave, la posición inicial y los topes a izquierda y derecha.

; Código de carácter de la nave, posición inicial y topes
SHIP_GRAPH: EQU $90
SHIP_INI:   EQU $0511
SHIP_TOP_L:	EQU $1e
SHIP_TOP_R: EQU $01

Para pintar los colores correctos, y para no repetir el código una y otra vez, vamos a implementar una rutina para cambiar el color de tinta, la vamos a implementar en el archivo Graph.asm. Esta rutina recibe en A el valor de la tinta.

Antes de implementar la rutina, abrimos el archivo Const.asm y añadimos la constante de la posición de memoria donde se guardan los atributos de color actuales. Estos atributos son los usados por RST $10 para asignar el color al carácter que pinta.

; Variable de sistema donde están los atributos de color actuales
ATTR_T:     EQU $5c8f

Y ahora sí, abrimos el archivo Graph.asm e implementamos la rutina Ink.

; -----------------------------------------------------------------------------
; Cambia la tinta
;
; Entrada:  A -> Color de la tinta
; Altera el valor del registro A.
; -----------------------------------------------------------------------------
Ink:
exx                     ; Preserva el valor de BC, DE y HL
ld      b, a            ; Carga la tinta en B
ld      a, (ATTR_T)     ; Carga los atributos actuales en A
and     $f8             ; Desecha los bits de la tinta
or      b               ; Añade la tinta
ld      (ATTR_T), a     ; Carga los atributos actuales
exx                     ; Recupera el valor de BC, DE y HL

ret

Dado que necesitamos apoyarnos en el registro B, lo primero que hacemos es preservar su valor con la instrucción EXX, que lo que hace es intercambiar el valor de los registros BC, DE y HL con los registros alternativos ‘BC, ‘DE y ‘HL con tan solo un byte y cuatro ciclos de reloj, siendo más rápido y ocupando menos que si hubiéramos usado la pila.

Cargamos en B el valor de la tinta, LD B, A, cargamos en A los atributos actuales, LD A, (ATTR_T), desechamos la tinta, AND $F8, añadimos la tinta, OR B, y cargamos el resultado en los atributos actuales, LD (ATTR_T), A. Por último, recuperamos el valor de los registros BC, DE y HL, EXX, y salimos, RET.

Ahora necesitamos una rutina que pinte la nave; la vamos a implementar en el archivo Print.asm.

PrintShip:
ld		a, $07
call	Ink

Cargamos en A la tinta blanca, LD A, $07, y llamamos a cambiar la tinta, CALL Ink.

ld		bc, (shipPos)
call	At

Cargamos en BC la posición actual de la nave, LD BC, (shipPos), y llamamos a posicionar el cursor, CALL At.

ld		a, SHIP_GRAPH
rst		$10

ret

Cargamos en A el código de carácter de la nave, LD A, SHIP_GRAPH, la pintamos, RST $10, y salimos, RET.

Ya tenemos lista la rutina que pinta la nave, cuyo aspecto final es el siguiente:

; ----------------------------------------------------------------------------
; Pinta la nave en la posición actual.
; Altera el valor de los registros A y BC.
; ----------------------------------------------------------------------------
PrintShip:
ld			a, $07			; Carga en A la tinta blanca
call		Ink			    ; Llama al cambio de tinta

ld			bc, (shipPos)	; Carga en BC la posición actual de la nave
call		At			    ; Llama a posicionar el cursor

ld			a, SHIP_GRAPH	; Carga en A el carácter de la nave
rst			$10			    ; La pinta

ret

Antes de dejar el archivo Print.asm, vamos a volver sobre la rutina que pinta el marco, en concreto a la parte en la que se hacemos un bucle para pintar los laterales.

printFrame_loop:
ld		a, $16 			        ; Carga en A el carácter de control de AT
rst		$10 				    ; Lo "pinta"
ld		a, b 				    ; Carga en A la línea
rst		$10 				    ; La "pinta"
ld		a, $00 			        ; Carga en A la columna
rst		$10 				    ; La "pinta"
ld		a, $99 			        ; Carga en A el carácter lateral izquierdo
rst		$10 				    ; Lo pinta

ld		a, $16 			        ; Carga en A el carácter de control de AT
rst		$10 				    ; Lo "pinta"
ld		a, b 				    ; Carga en A la línea
rst		$10 				    ; La "pinta"
ld		a, $1f 			        ; Carga en A la columna
rst		$10 				    ; La "pinta"
ld		a, $9a 			        ; Carga en A el carácter lateral derecho
rst		$10 				    ; Lo pinta

inc		b 				        ; Apunta B a la línea siguiente
ld		a, b 				    ; Carga el valor de B en A
cp		$14			            ; Comprueba si está en la línea veinte
jr		nz, printFrame_loop 	; Si no es así, sigue con el bucle

Como podemos observar, las seis lineas siguientes a printFrame_loop posicionan el cursor para pintar en el lateral izquierdo, más adelante vemos otras seis líneas que hacen lo mismo para el lateral derecho.

Yo estoy usando Visual Studio Code con la extensión Z80 Assembly meter, y por eso sé que esta rutina, desde printFrame_loop hasta JR NZ, printFrame_loop, consume ciento sesenta y cinco ciclos de reloj y veintiocho bytes.

Dado que ya hemos implementado una rutina que posiciona el cursor, acabamos de implementar At, vamos a sustituir estas líneas por llamadas a esa rutina.

Lo primero es, justo encima de printFrame_loop, modificar la línea LD B, $01, y dejarla como sigue:

ld      b, COR_Y - $01 

Recordad que con la rutina de la ROM las coordenadas están invertidas. Apuntamos B a la línea uno, LD B, COR_Y – $01.

Borramos la primeras seis líneas justo debajo de printFrame_loop y las sustituimos por las siguientes:

ld      c, COR_X - $01
call    At

Unas líneas más abajo, borramos desde LD A, $16 hasta el RST $10 que hay justo encima de LD A, $9a, y sustituimos estas líneas por las siguientes:

ld      c, COR_X - MAX_X
call    At

Ahora vamos a sustituir desde INC B hasta JR NZ, printFrame_loop.

dec     b
ld      a, COR_Y - MAX_Y + $01
sub     b
jr      nz, printFrame_loop

De esta manera, el aspecto final de la parte modificada es el siguiente:

ld      b, COR_Y - $01          ; Apunta B a la línea 1
printFrame_loop:
ld      c, COR_X - MIN_X        ; Apunta C a la columna 0
call    At                      ; Posiciona el cursor
ld      a, $99                  ; Carga en A el carácter lateral izquierdo
rst     $10                     ; Lo pinta

ld      c, COR_X - MAX_X        ; Apunta C a la columna 31
call    At                      ; Posiciona el cursor
ld      a, $9a                  ; Carga en A el carácter lateral derecho
rst     $10                     ; Lo pinta

dec     b                       ; Decrementa B
ld      a, COR_Y - MAX_Y + $01  ; Apunta A a la línea 20
sub     b                       ; Resta la siguiente línea
jr      nz, printFrame_loop     ; Si el resultado no es cero, sigue con el bucle
ret

A simple vista, la rutina es más corta, consumiendo veintidós bytes y ciento once ciclos de reloj, pero cuidado, no es oro todo lo que reluce, a esos ciclos de reloj hay que sumarle los ciclos de reloj de la rutina At, que son sesenta y nueve (los bytes no los añadimos pues la rutina la vamos a usar desde más sitios).

Una vez sumados todos los valores, la nueva rutina ocupa veintidós bytes y cada iteración del bucle consume ciento ochenta ciclos de reloj, quince más que la implementación anterior, aunque hemos ahorrado seis bytes. Además, la rutina de la ROM tarda menos que posicionarnos usando el código de control de AT.

¿Qué hacemos? ¿Cómo lo dejamos?

Dado que la rutina que pinta el borde no es crítica ya que solo lo pintamos al inicio de cada nivel, y cuatro ciclos de reloj no se van a notar, optamos por el ahorro de seis bytes, nos quedamos con la nueva implementación.

Solo nos queda pintar la nave, vamos a Main.asm y justo debajo de CALL PrintInfoGame, añadimos la llamada a pintar la nave.

call    PrintShip

Compilamos, cargamos en el emulador y vemos los resultados.

Ensamblador ZX Spectrum, nave
Ensamblador ZX Spectrum, nave

Movemos la nave

La nave se tiene que mover como respuesta a alguna acción del jugador, en nuestro caso a la pulsación de tres teclas: Z para mover la nave hacia la izquierda, X para mover la nave hacia la derecha y V para disparar.

Creamos el archivos Ctrl.asm e implementamos la rutina que lee el teclado y devuelve las teclas de control pulsadas.

La rutina que vamos a implementar lee el teclado y devuelve en el registro D las teclas de control pulsadas, parecido a como se hizo en PorompomPong, poniendo a uno el bit cero si se ha pulsado la tecla Z, el bit uno se ha pulsado la tecla X y el bit dos si se ha pulsado la tecla V.

CheckCtrl:
ld		d, $00
ld		a, $fe
in		a, ($fe)

Primero ponemos D a cero, LD D, $00, cargamos en A la semifila Cs-V, LD A, $FE, y leemos el teclado, IN A, ($FE).

checkCtrl_fire:
bit		$04, a
jr		nz, checkCtrl_left
set		$02, d

Comprobamos si se ha pulsado la tecla V, BIT $04, A. En el caso de que no se haya pulsado, saltamos a comprobar si se ha pulsado la tecla para mover hacia la izquierda, JR NZ, checkCtrl_left. Si se ha pulsado, pone a uno el bit dos del registro D, marcando que se ha pulsado el disparo, SET $02, D.

Cuando leemos del teclado, el estado de las teclas de la semifila leída está en el registro A, a uno las teclas que no se han pulsado y cero las que sí (el bit cero hace referencia a la tecla más alejada del centro del teclado y el cuatro a la más cercana).

La instrucción BIT evalúa el estado del bit especificado ($04), del registro especificado (A), y según esté a cero o uno, activa o desactiva el flag Z. La instrucción SET pone a uno el bit especificado ($02), del registro especificado (D). La instrucción RES es la contraria a SET, pone el bit a cero. RES y SET no afectan al registro F.

checkCtrl_left:
bit		$01, a
jr		nz, checkCtrl_right
set		$00, d

Comprobamos si se ha pulsado la tecla Z, BIT $01, A. En el caso de que no se haya pulsado, saltamos a comprobar si se ha pulsado la tecla para mover hacia la derecha, JR NZ, checkCtrl_right. Si se ha pulsado, pone a uno el bit cero del registro D, marcando que se ha pulsado izquierda, SET $00, D.

checkCtrl_right:
bit		$02, a
ret		nz
set		$01, d

Comprobamos si se ha pulsado la tecla X, BIT $02, A. En el caso de que no se haya pulsado, salimos. Si se ha pulsado, pone a uno el bit cero del registro D, marcando que se ha pulsado derecha, SET $00, D.

checkCtrl_testLR:
ld		a, d
and		$03
sub		$03
ret		nz
ld		a, d
and		$04
ld		d, a

checkCtrl_end:
ret

Para finalizar, comprobamos si se ha pulsado al mismo tiempo izquierda y derecha, en cuyo caso desactivamos ambas.

Cargamos el A el valor de D, LD A, D, nos quedamos el valor de los bits cero y uno, AND $03, y le restamos tres, SUB $03. Si el resultado no es cero salimos, RET NZ, ya que no estaban los dos bits a uno y no tenemos que hacer nada.

Si no hemos salido, cargamos de nuevo el valor de D en A, LD A, D, nos quedamos solo con el valor del bit dos (disparo), AND $04, y cargamos el valor en D, LD D, A, desactivando de este modo la pulsación simultánea de izquierda y derecha. Finalmente, salimos, RET.

La última etiqueta, checkCtrl_end, no es necesaria, pero la ponemos para clarificar cual es el final de la rutina.

El aspecto final de la rutina es el siguiente:

; ----------------------------------------------------------------------------
; Evalúa si se ha pulsado alguna de la teclas de dirección.
; Las teclas de dirección son:
;	Z 	->	Izquierda
;	X 	->	Derecha
;	V	->	Disparo
;
; Retorna:	D	->	Teclas pulsadas.
;			Bit 0	->	Izquierda
;			Bit 1	->	Derecha
;			Bit 2	->	Disparo
;
; Altera el valor de los registros A y D
; ----------------------------------------------------------------------------
CheckCtrl:
ld		d, $00			    ; Pone D a 0
ld      a, $fe             	; Carga la semifila Cs-V en A
in      a, ($fe)           	; Leee el teclado

checkCtrl_fire:
bit     $04, a             	; Evalúa si se ha pulsado la V
jr      nz, checkCtrl_left 	; Si no se ha pulsado, salta
set     $02, d             	; Activa el bit 2 de D

checkCtrl_left:
bit     $01, a             	; Evalúa si se ha pulsado la Z
jr      nz, checkCtrl_right	; Si no se ha pulsado, salta
set     $00, d             	; Activa el bit 0 de D

checkCtrl_right:
bit     $02, a             	; Evalúa si se ha pulsado la X
ret     nz                 	; Si no se ha pulsado, sale
set     $01, d              ; Activa el bit 1 de D

checkCtrl_testLR:
ld      a, d               	; Carga el valor de D en A
and     $03                	; Se queda con el valor de los bits 0 y 1
sub     $03                	; Comprueba si están activos los dos bits
ret     nz                 	; Si el resultad no es cero, no están
                        	; activos los dos bits y sale
ld      a, d            	; Carga el valor de D en A
and     $04             	; Desactiva los bits 0 y 1
ld      d, a            	; Carga el valor de A en D

checkCtrl_end:
ret

Abrimos el archivo Main.asm y en cada iteración del bucle Main_loop vamos a llamar a la rutina que acabamos de implementar. Justo debajo de la etiqueta Main_loop añadimos la línea siguiente:

call    CheckCtrl

Un poco más abajo, en la parte donde tenemos los includes, añadimos el include para el archivo Ctrl.asm.

include	"Ctrl.asm"

Compilamos y comprobamos que compila bien; no hay errores.

Cuando se mueva la nave, primero hay que borrarla de la posición actual y volver a pintarla en la nueva posición.

En Const.asm, justo encima de las constantes que definimos para la nave, añadimos la siguiente constante:

; Código de carácter del carácter en blanco
WHITE_GRAPH:EQU $9e

Abrimos el archivo Print.asm y, al inicio del mismo, implementamos la rutina que borra la nave. Como esta rutina la vamos a usar también para borrar los enemigos y el disparo, recibe en BC las coordenadas del carácter que vamos a borrar.

DeleteChar:
call    At

Como DeleteChar recibe en BC las coordenadas del carácter que vamos a borrar, el primer paso es posicionar el cursor, CALL At.

ld      a, WHITE_GRAPH
rst     $10

ret

Lo siguiente es cargar en A el carácter en banco, LD A, WHITE_GRAPH, y lo pintamos, RST $10, borrando así lo que hubiera pintado en esas coordenadas.

El aspecto final de la rutina es el siguiente:

; ----------------------------------------------------------------------------
; Borra un carácter de la pantalla
;
; Entrada:  BC -> Coordenadas Y/X del carácter
; Altera el valor de los resgistros AF
; ----------------------------------------------------------------------------
DeleteChar:
call    At              ; Llama a posicionar el cursor

ld      a, WHITE_GRAPH  ; Carga en A el carácter de blanco
rst     $10			    ; Lo pinta y borra la nave

ret

Para probar que funciona, abrimos Main.asm y, justo debajo de CALL CheckCtrl, añadimos las líneas siguientes:

ld      bc, (shipPos)
call    DeleteChar
call    PrintShip

Con estas líneas borramos y pintamos la nave en cada iteración de Main_loop. Si compilamos y cargamos en el emulador, veremos que la nave parpadea constantemente, señal de que el borrado de la nave funciona.

Ahora vamos a mover la nave. Creamos un nuevo archivo, Game.asm, dónde empezamos por implementar la rutina que cambia la posición de la nave y la pinta, esta rutina recibe en el registro D el estado de los controles (antes de continuar añadimos en la sección de includes de Main.asm el archivo Game.asm).

MoveShip:
ld      bc, (shipPos)
bit     $01, d
jr      nz, moveShip_right

Cargamos la posición actual de la nave en BC, LD BC, (shipPos), comprobamos si se ha pulsado el control derecha, BIT $01, D, en cuyo caso saltamos a la parte que controla el movimiento hacia la derecha, JR NZ, moveShip_right.

bit     $00, d
ret     z

Comprobamos si se ha pulsado el control izquierda, BIT $00, D, y si no se ha pulsado salimos, RET Z.

moveShip_left:
ld      a, SHIP_TOP_L + $01
sub     c
ret     z
call    DeleteChar
inc     c
ld      (shipPos), bc
jr      moveShip_print

Si se pulsado el control izquierda, comprobamos si podemos mover la nave. Cargamos en A el tope al que se puede mover la nave hacia la izquierda, LD A, SHIP_TOP_L + $01, y le restamos la columna actual de la posición de la nave, SUB C. Si el resultado es cero, la nave ya está en el tope y no se puede mover más hacia la derecha, así que salimos, RET Z.

Si no hemos salido, borramos la nave de la posición actual, CALL DeleteChar, incrementamos C para apuntar a la columna justo a la izquierda de la posición actual, INC C, actualizamos en memoria la nueva posición de la nave, LD (shipPos), BC, y saltamos al final de la rutina, jr moveShip_print.

La rutina que controla el movimiento de la nave hacia la derecha es prácticamente igual a la que controla el movimiento hacia la izquierda, por lo que solo vamos a marcar y explicar los cambios.

moveShip_right:
ld      a, SHIP_TOP_R + $01	; ¡CAMBIO!
sub     c
ret     z
call    DeleteChar
dec     c             		; ¡CAMBIO!
ld      (shipPos), bc

ret

Cargamos en A el tope al que se puede mover la nave hacia la derecha, LD A, SHIP_TOP_R. Decrementamos C para apuntar a la columna justo a la derecha de la posición actual, DEC C.

El aspecto final de la rutina es el siguiente:

; -----------------------------------------------------------------------------
; Mueve la nave
;
; Entrada:  D -> Estado de los controles
; Altera el valor de los registros AF y BC
; -----------------------------------------------------------------------------
MoveShip:
ld      bc, (shipPos)       ; Carga la posición actual de la nave en BC
bit     $01, d              ; Comprueba si el control derecha viene activo
jr      nz, moveShip_right  ; En cuyo caso, sale

bit     $00, d              ; Comprueba si el control izquierda viene activo
ret     z                   ; Si no es así, sale

moveShip_left:
ld      a, SHIP_TOP_L + $01 ; Carga en A el tope para la nave por la izquierda
sub     c                   ; Le resta la columna actual de la nave
ret     z                   ; Si es la misma columna, sale
call    DeleteChar          ; Borra la nave de la posición actual
inc     c                   ; Apunta C a la culumna a la izquierda a la actual
ld      (shipPos), bc       ; Actualiza la posición de la nave
jr      moveShip_print      ; Salta al final de la rutina

moveShip_right:
ld      a, SHIP_TOP_R + $01 ; Carga en A el tope para la nave por la derecha
sub     c                   ; Le resta la columna actual de la nave
ret     z                   ; Si es la misma columna, sale
call    DeleteChar          ; Borra la nave de la posición actual
dec     c                   ; Apunta C a la culumna a la derecha a la actual
ld      (shipPos), bc       ; Actualiza la posición de la nave

moveShip_print:
call    PrintShip           ; Pintamos la nave

ret

Antes de continuar, recordemos que anteriormente comentamos que At alteraba el valor de los resgistros BC y AF pero que no nos afectaba. Ahora que At se llama desde varios puntos, el cambio del registro BC si que nos afecta. La solución es tan sencilla como añadir a At PUSH BC y POP BC para preservar y recuperar el valor de BC, aunque vamos a hacer otra implementación y de paso vamos a ahorrar bytes y ciclos de reloj.

La nueva implementación de At queda de la siguiente manera:

; -----------------------------------------------------------------------------
; Posiciona el cursor en las coordenadas especificadas.
;
; Entrada:	B = Coordenada Y (24 a 3).
;			C = Coordenada X (32 a 1).
; Altera el valor de los registros AF
; -----------------------------------------------------------------------------
At:
push    bc      ; Preservamos el valor de BC
exx             ; Preservamos el valor de BC, DE y HL
pop     bc      ; Recuperamos el valor de BC
call    $0a23   ; Llama a la rutina de la ROM
exx             ; Recuperamos el valor de BC, DE y HL

ret

Preservamos el valor de BC que es donde están las coordenadas, PUSH BC, preservamos el valor de los registros BC, DE y HL intercambiando su valor con los de los registros alternativos, EXX, recuperamos el valor de BC (coordenadas) de la pila, POP BC, y llamamos a la rutina de la ROM que posiciona el cursor, CALL $0A23.

Llegados a este punto, el valor de BC, DE y HL ha cambiado, lo recuperamo desde los registros alternativos, EXX, y salimos, RET.

La rutina ahora ocupa ocho bytes y tarda cincuenta y seis ciclos de reloj en ejecutarse, frente a los diez bytes y noventa ciclos de reloj que ocuparía usando la pila para los tres registros.

Es hora de comprobar si se mueve la nave, volvemos a Main.asm y sustituimos las líneas que hemos añadido antes:

ld      bc, (shipPos)
call    DeleteChar
call    PrintShip

por:

call    MoveShip

Compilamos, cargamos en el emulador y vemos los resultados.

Ensamblador ZX Spectum, nave
Ensamblador ZX Spectum, nave

La nave se mueve tanto a izquierda como a derecha, pero volvemos a tener el mismo problema que tuvimos en PorompomPong, se mueve extremadamente rápido, más rápido que las palas de Porompompong, ya que las palas se movían píxel a píxel y la nave se mueve carácter a carácter.

Podríamos solucionarlo igual que hicimos entonces, no moviendo la nave en cada iteración del bucle, pero dado que vamos a usar las interrupciones para más cosas, lo vamos a hacer a través de ellas y así vemos algo que no vimos en PorompomPong.

Ensamblador ZX Spectrum, conclusión

Ya tenemos la nave en el área de juego y la movemos, pero hemos observado un problema que ya tuvimos en PorompomPong, se mueve extremadamente rápido.

En el próximo capítulo solucionaremos esto con las interrupciones e implementaremos la parte del disparo.

Puedes descargar desde aquí el código que hemos generado.

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: