Curso Programación ZX SpectrumCursos

Ensamblador para ZX Spectrum – Pong: $07 Movemos la bola por la pantalla

Moviendo la bola por toda la pantalla.

5
(3)

Ensamblador para ZX Spectrum – Pong: Paso 5, movemos la bola por la pantalla

Creamos la carpeta Paso05, dentro de la misma creamos los archivos Main.asm y Game.asm, y copiamos los archivos Sprite.asm y Video.asm que tenemos en la carpeta Paso04.

Empezamos editando el archivo Sprite.asm para añadir dos nuevas constantes que vamos a necesitar para mover la bola por la pantalla:

MARGIN_LEFT:    EQU $00
MARGIN_RIGHT:   EQU $1e

Igual que tenemos los límites verticales y horizontales, necesitamos los límites derecho e izquierdo para que la bola se mantenga dentro de los mismos.

El siguiente paso es implementar la lógica del movimiento de la bola, lo que haremos en Game.asm:

MoveBall:
ld      a, (ballSetting)	
and     $80
jr      nz, moveBall_down

Primero cargamos en A la configuración actual de la bola, LD A, (ballSetting), y nos quedamos con el bit 7, AND $80, que indica si la bola se desplaza hacia arriba o hacia abajo. Si el bit no está a 0, la bola se desplaza hacia abajo y salta, JR NZ, moveBall_down.

Si el bit está a 0, la bola se desplaza hacia arriba:

moveBall_up:
ld      hl, (ballPos)
ld      a, BALL_TOP
call    CheckTop
jr      z, moveBall_upChg
call    PreviousScan
ld      (ballPos), hl
jr      moveBall_x

Cargamos la posición actual de la bola en HL, LD HL, (ballPos), el límite vertical en A, LD A, BALL_TOP, y comprobamos si se ha alcanzado dicho límite, CALL CheckTop. Si se activa el flag Z, se ha alcanzado el límite y salta para cambiar la dirección vertical de la bola, JR Z, moveBall_upChg.

Si la bola no ha llegado al límite vertical, calcula la nueva posición, CALL PreviousScan, la carga en memoria, LD (ballPos), HL, y salta a comprobar el desplazamiento horizontal, JR moveBall_x.

En el caso de haber alcanzado el límite superior, hay que cambiar la dirección vertical de la bola:

moveBall_upChg:
ld      a, (ballSetting)
or      $80
ld      (ballSetting), a
call    NextScan
ld      (ballPos), hl
jr      moveBall_x

Primero cargamos la configuración de la bola en A, LD A, (ballSetting), luego activamos el bit 7, OR $80, para indicar que ahora la bola debe ir hacia abajo, y cargamos el valor en memoria, LD (ballSetting), A. Calculamos la nueva posición vertical de la bola, CALL NextScan, cargamos el valor en memoria, LD (ballPos), HL, y saltamos a comprobar el desplazamiento horizontal, JR movelBall_x.

Para activar el bit 7 hemos hecho un OR con $80 (10000000). Es conveniente recordar el resultado de la operación OR, dependiendo del valor de los bits:

Bit 1Bit 2Resultado
000
101
011
111
Ensamblador para ZX Spectrum, resultados de OR

Según se ve en la tabla, al aplicar OR $80, se pone el bit 7 a 1 y el resto los deja como estaban.

Si al iniciar la rutina la bola iba hacia abajo, hay que hacer algo parecido a lo visto anteriormente:

moveBall_down:
ld      hl, (ballPos)
ld      a, BALL_BOTTOM
call    CheckBottom
jr      z, moveBall_downChg
call    NextScan
ld      (ballPos), hl
jr      moveBall_x

Primero cargamos la posición de la bola en HL, LD HL, (ballPos), el límite inferior en A, LD A, BALL_BOTTOM, y comprobamos si se ha alcanzado, CALL CheckBottom, en cuyo caso saltamos para cambiar la dirección de la bola, JR Z, movelBall_downChg.

Si no se ha alcanzado el límite inferior, calculamos las nueva posición de la bola, CALL NextScan, la cargamos en memoria, LD (ballPos), HL, y saltamos a comprobar el desplazamiento horizontal, JR moveBall_x.

En el caso de haber alcanzado el límite inferior, hay que cambiar la dirección vertical de la bola:

moveBall_downChg:
ld      a, (ballSetting)
and     $7f
ld      (ballSetting), a	
call    PreviousScan
ld      (ballPos), hl

Primero cargamos la configuración de la bola en A, LD A, (ballSetting), luego desactivamos el bit 7, AND $7F, para indicar que ahora la bola debe ir hacia arriba, y cargamos el valor en memoria, LD (ballSetting), A. Calculamos la nueva posición vertical de la bola, CALL PreviousScan, y cargamos el valor en memoria, LD (ballPos), HL.

Para desactivar el bit 7 hemos hecho un AND con $7F (01111111). Es conveniente recordar el resultado de la operación AND, dependiendo del valor de los bits:

Bit 1Bit 2Resultado
000
100
010
111
Ensamblador para ZX Spectrum, resultados de AND

Según se ve en la tabla, al aplicar AND $7F, pone el bit 7 a 0 y el resto los deja como estaban.

Empezamos a calcular el desplazamiento horizontal:

moveBall_x:
ld      a, (ballSetting)
and     $40
jr      nz, moveBall_left

Cargamos la configuración de la bola en A, LD A, (ballSetting), comprobamos el estado del bit 6, AND $40, y si no está a 0, la bola va hacia la izquierda y salta, JR NZ, moveBall_left.

Si el bit 6 está a 0, la bola va hacia la derecha:

moveBall_right:
ld      a, (ballRotation)
cp      $08
jr      z, moveBall_rightLast
inc     a 
ld      (ballRotation), a
jr      moveBall_end

Cargamos la rotación en A, LD A, (ballRotation), y comprobamos si está en la última, CP $08, en cuyo caso saltamos, JR Z, movelBall_rightLast.

Si no está en la última rotación, incrementamos la misma, INC A, la cargamos en memoria, LD (ballRotation), A, y saltamos al final de la rutina, JR moveBall_end.

Si, por el contrario, ha llegado a la última rotación y no está en el límite derecho, desplazamos la bola a la siguiente columna:

moveBall_rightLast:
ld      a, (ballPos)
and     $1f
cp      MARGIN_RIGHT
jr      z, moveBall_rightChg
ld      hl, ballPos
inc     (hl)
ld      a, $01
ld      (ballRotation), a
jr      moveBall_end

Cargamos la línea y la columna en A, LD A, (ballPos), nos quedamos con la columna, AND $1F, y evaluamos si ha llegado al límite derecho, CP MARGIN_RIGHT, en cuyo caso saltamos para cambiar la dirección de la bola, JR Z, moveBall_rightChg.

Si no se ha llegado al límite derecho, desplazamos la bola a la siguiente columna. Cargamos la dirección donde se encuentra la posición de la bola en HL, LD HL, ballPos, e incrementamos la columna, INC (HL).

Ponemos la rotación de la bola a 1, LD A, $01, lo cargamos en memoria, LD (ballRotation), A, y saltamos al final de la rutina, JR moveBall_end.

Como se puede ver, para cargar la columna en A, la instrucción usada ha sido LD A, (ballPos), y para incrementar la columna LD HL, ballPos y INC (HL).

Teniendo en cuenta que las posiciones de memoria de la VideoRAM se codifican 010TTSSS LLLCCCCC, ¿no estaríamos cargando y alterando el scanline? No, y ello se debe a que el Z80 es un micro de tipo Little Endian.

Un micro Little Endian, cuando carga valores de 16 bits en memoria, carga en la primera posición de memoria el byte menos significativo, y en la siguiente el más significativo, de tal manera que si en la posición de memoria $C000 se carga el valor $4000, en la posición $C000 se carga $00 y en la $C001 se carga $40. Es por eso que cuando se carga en A el valor desde (ballPos), lo que carga es el byte menos significativo que es donde están la línea y la columna. De igual modo al incrementar (HL), incrementa la columna.

Si la carga se hace sobre un registro de 16 bits, carga el byte menos significativo en la parte baja del registro, y el más significativo en la parte alta. Es por eso que al cargar ballPos en HL, carga en H el byte más significativo de la dirección de memoria y en L el menos significativo.

Seguimos con la rutina…

Si ha llegado al límite derecho, hay que cambiar la dirección de la bola:

moveBall_rightChg:
ld      a, (ballSetting)
or      $40
ld      (ballSetting), a	
ld      a, $ff
ld      (ballRotation), a
jr      moveBall_end

Cargamos la configuración de la bola en A, LD A, (ballSetting), activamos el bit 6 para cambiar la dirección hacia la izquierda, OR $40, y cargamos el valor en memoria, LD (ballSetting), A.

Ponemos la rotación de la bola a -1, LD A, $FF, la cargamos en memoria, LD (ballRotation), A, y saltamos al fin de la rutina, JR moveBall_end.

Si la bola va hacia la izquierda, hay que hacer algo parecido a lo visto anteriormente:

moveBall_left:
ld      a, (ballRotation)
cp      $f8
jr      z, moveBall_leftLast
dec     a 
ld      (ballRotation), a
jr      moveBall_end

Cargamos la rotación en A, LD A, (ballRotation), comprobamos si está en la última, CP $F8, y de ser así saltamos, JR Z, moveBall_leftLast.

Si no está en la última rotación la decrementamos, DEC A, cargamos el valor en memoria, LD (ballRotation), A, y saltamos al fin de la rutina, JR moveBall_end.

Si ha llegado a la última rotación y no ha alcanzado el límite izquierdo, desplazamos la bola a la columna anterior:

moveBall_leftLast:
ld      a, (ballPos)
and     $1f
cp      MARGIN_LEFT
jr      z, moveBall_leftChg
ld      hl, ballPos
dec     (hl)
ld      a, $ff
ld      (ballRotation), a
jr      moveBall_end

Cargamos la línea y la columna en A, LD A, (ballPos), nos quedamos con la columna, AND $1F, y comprobamos si ha llegado al límite izquierdo, CP MARGIN_LEFT, en cuyo caso saltamos, JR Z, moveBall_leftChg.

Si no ha llegado al límite izquierdo, cargamos la dirección dónde está la posición de la bola en HL, LD HL, ballPos, y decrementamos la columna, DEC (HL).

Ponemos la rotación de la bola a -1, LD A, $FF, cargamos el valor en memoria, LD (ballRotation), A, y saltamos al fin de la rutina.

Terminamos la rutina con el cambio de dirección, si se ha alcanzado el límite izquierdo:

moveBall_leftChg:
ld      a, $01
ld      (ballRotation), a
ld      a, (ballSetting)
and     $bf
ld      (ballSetting), a

moveBall_end:
ret

Ponemos la rotación de la bola a 1, LD A, $01, y la cargamos en memoria, LD (ballRotation), A. Cargamos la configuración de la bola en A, LD A, (ballSetting), desactivamos el bit 6 para que la dirección sea hacia la derecha, AND $BF, y cargamos el valor en memoria, LD (ballSetting), A.

Podemos ahorrar 2 ciclos de reloj y 5 bytes haciendo una pequeña modificación. Lo dejamos en vuestras manos y veremos la forma de hacerlo en la última entrega.

El aspecto final de la rutina es el siguiente:

; – ---------------------------------------------------------------------------
; Calcula la posición, rotación y dirección de la bola para pintarla.
; Altera el valor de los registros AF y HL.
; – ---------------------------------------------------------------------------
MoveBall:
ld      a, (ballSetting)        ; Carga en A la dirección y velocidad de la bola
and     $80                     ; Comprueba la dirección vertical
jr      nz, moveBall_down       ; Si el bit 7 está a uno, va hacia abajo

moveBall_up:
; La bola va hacia arriba
ld      hl, (ballPos)           ; Carga la posición de la bola en HL
ld      a, BALL_TOP             ; Carga en A el margen superior
call    CheckTop                ; Evalúa si se ha alcanzado el margen superior
jr      z, moveBall_upChg       ; Si se ha alcanzado salta
call    PreviousScan            ; Obtiene el scanline anterior a la posición de la bola
ld      (ballPos), hl           ; Carga en memoria la nueva posición de la bola
jr      moveBall_x              ; Salta

moveBall_upChg:
; La bola va hacia arriba, pero ha llegado al tope y cambia de dirección
ld      a, (ballSetting)        ; Carga en A la dirección y velocidad de la bola
or      $80                     ; Pone la dirección vertical hacia abajo
ld      (ballSetting), a        ; Carga en memoria la nueva dirección de la bola
call    NextScan                ; Obtiene el scanline siguiente a la posición de la bola
ld      (ballPos), hl           ; Carga en memoria la nueva posición de la bola
jr      moveBall_x              ; Salta

moveBall_down:
; La bola va hacia abajo
ld      hl, (ballPos)           ; Carga la posición de la bola en HL
ld      a, BALL_BOTTOM          ; Carga en A el margen superior
call    CheckBottom             ; Evalúa si se ha alcanzado el margen superior
jr      z, moveBall_downChg     ; Si se ha alcanzado salta
call    NextScan                ; Obtiene el scanline siguiente a la posición de la bola
ld      (ballPos), hl           ; Carga en memoria la nueva posición de la bola
jr      moveBall_x              ; Salta

moveBall_downChg:
; La bola va hacia abajo, pero ha llegado al tope y cambia de dirección
ld      a, (ballSetting)        ; Carga en A la dirección y velocidad de la bola
and     $7f                     ; Pone la dirección vertical hacia arriba
ld      (ballSetting), a        ; Carga en memoria la nueva dirección de la bola
call    PreviousScan            ; Obtiene el scanline anterior a la posición de la bola
ld      (ballPos), hl           ; Carga en memoria la nueva posición de la bola

moveBall_x:
ld      a, (ballSetting)        ; Carga en A la dirección y velocidad de la bola
and     $40                     ; Comprueba la dirección horizontal
jr      nz, moveBall_left       ; Si el bit 6 está a uno, va hacia la izquierda

moveBall_right:
; La bola va hacia la derecha
ld      a, (ballRotation)       ; Carga la rotación actual de la bola
cp      $08                     ; Comprueba si ya está en la última rotación
jr      z, moveBall_rightLast   ; Si está en la última rotación salta
inc     a                       ; Incrementa la rotación 
ld      (ballRotation), a       ; La carga en memoria
jr      moveBall_end            ; Fin de la rutina

moveBall_rightLast:
; Está en la última rotación
; Si no ha llegado al límite derecho pone la rotación a 1 
; y pone la bola en la siguiente columna
ld      a, (ballPos)            ; Carga la línea y columna de la bola en A
and     $1f                     ; Se queda solo con la columna
cp      MARGIN_RIGHT            ; Lo comprara con el límite derecho
jr      z, moveBall_rightChg    ; Si lo ha alcanzado salta
ld      hl, ballPos             ; Carga la dirección de la posición de la bola en HL
inc     (hl)                    ; Incrementa la columna
ld      a, $01                  ; Pone la rotación a 1
ld      (ballRotation), a       ; Carga el valor en memoria
jr      moveBall_end            ; Fin de la rutina

moveBall_rightChg:
; Ha llegado al límite derecho
; Pone la rotación a -1 y cambia la dirección horizontal de la bola
ld      a, (ballSetting)        ; Carga en A la dirección y velocidad de la bola
or      $40                     ; Pone la dirección horizontal hacia la izquierda
ld      (ballSetting), a        ; Carga la nueva dirección de la bola en memoria
ld      a, $ff                  ; Carga -1 en A
ld      (ballRotation), a       ; Lo carga en memoria Rotación = -1
jr      moveBall_end            ; Fin de la rutina

moveBall_left:
; La bola va hacia la izquierda
ld      a, (ballRotation)       ; Carga la rotación actual de la bola
cp      $f8                     ; Comprueba si ya está en la última rotación
jr      z, moveBall_leftLast    ; Si está en la última rotación salta
dec     a                       ; Decrementa la rotación 
ld      (ballRotation), a       ; La carga en memoria
jr      moveBall_end            ; Fin de la rutina

moveBall_leftLast:
; Esta en la última rotación
; Si no ha llegado al límite izquierdo pone la rotación a -1 
; y pone la bola en la columna anterior
ld      a, (ballPos)            ; Carga la línea y columna en A
and     $1f                     ; Se queda solo con la columna
cp      MARGIN_LEFT             ; Lo comprara con el límite izquierdo
jr      z, moveBall_leftChg     ; Si lo ha alcanzado salta
ld      hl, ballPos             ; Carga la dirección de la posición de la bola en HL
dec     (hl)                    ; Pasa a la columna anterior
ld      a, $ff                  ; Pone la rotación a -1
ld      (ballRotation), a       ; Carga el valor en memoria
jr      moveBall_end            ; Fin de la rutina

moveBall_leftChg:
; Ha llegado al límite izquierdo
; Pone la rotación a 1 y cambia la dirección
ld      a, $01                  ; Carga la posición de la bola en HL
ld      (ballRotation), a       ; Carga el valor en memoria Rotación = 1
ld      a, (ballSetting)        ; Carga en A la dirección y velocidad de la bola
and     $bf                     ; Pone la dirección horizontal hacia la derecha
ld      (ballSetting), a        ; Carga la nueva dirección de la bola en memoria

moveBall_end:
ret

Ha llegado el momento de probar todo lo implementado; vamos a editar el archivo Main.asm. En este caso la implementación es muy sencilla:

org     $8000
ld      a, $02
out     ($fe), a
call    PrintBall

Indicamos la dirección dónde cargar el programa, ponemos el borde en rojo y pintamos la bola en la posición inicial.

Loop:
call    MoveBall
call    PrintBall
halt
jr      Loop

Implementamos un bucle infinito en el que movemos la bola, la pintamos, esperamos al refresco de la pantalla y volvemos a realizar estas tres operaciones indefinidamente.

Include "Game.asm"
Include "Sprite.asm"
Include "Video.asm"
end     $8000

Por último, incluimos los archivos necesarios e indicamos a PASMO dónde tiene que llamar al cargar el programa.

El aspecto final de Main.asm es el siguiente:

; Mueve la bola por la pantalla trazando diagonales
org     $8000

ld      a, $02      ; A = 2
out     ($fe), a    ; Pone el borde en rojo
call    PrintBall   ; Imprime la bola

Loop:
call    MoveBall    ; Mueve la bola
call    PrintBall   ; Pinta la bola
halt                ; Espera al refresco de pantalla
jr      Loop        ; Bucle infinito

include "Game.asm"
include "Sprite.asm"
include "Video.asm"

end     $8000

Llega el gran momento…compilamos y vemos el resultado en el emulador.

Ensamblador para ZX Spectrum, movemos la bola por la pantalla
Ensamblador para ZX Spectrum, movemos la bola por la pantalla

En el próximo capítulo de Ensamblador para ZX Spectrum, pintaremos el campo, las palas, la bola y temporizaremos.

Enlaces de interés

Ficheros

¿Te ha Resultado útil este artículo?

Ayúdanos a mejorar y danos tu opinión:

Mostrar más

Deja una respuesta

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

Publicaciones relacionadas

Mira también
Cerrar
Botón volver arriba