Libro: Ensamblador para ZX‐Spectrum - Obijuan/Learn-zx-spectrum-asm GitHub Wiki

Experimentos y log de aprendizaje del libro "Ensamblador para ZX Spectrum ¿Hacemos un juego?", de Juan Antonio Rubio García

CONTENIDO

Introducción

Empezamos por el Hola mundo de la página 31. Lo primero es instalar las herramientas

El ensamblador pasmo se puede instalar directamente desde ubuntu (la versión 0.5.3)

sudo apt install pasmo

Ahora lo probamos:

obijuan@Hoth:~/Develop/Learn-zx-spectrum-asm
$ pasmo
Pasmo v. 0.5.3 (C) 2004-2005 Julian Albo

Usage:

	pasmo [options] source object [symbol]

See the README file for details.
obijuan@Hoth:~/Develop/Learn-zx-spectrum-asm
$

Para instalarlo me he bajado el paquete ZEsarUX_linux-10.3-ubuntu22_x86_64.tar.gz

Lo he decomprimido y ejecutado el install.sh

obijuan@Hoth:~/Bin/ZEsarUX-10.3
$ sudo ./install.sh
Installing ZEsarUX under /usr ...
Install done
obijuan@Hoth:~/Bin/ZEsarUX-10.3
$ 

Ahora ya se puede arrancar zesaurx desde la línea de comandos

HOLA MUNDO

Este es el hola mundo:

org $8000
ret
end $8000

Lo compilamos con esta línea

pasmo --name HolaMundo --tapbas holamundo.asm holamundo.tap --log

Se genera el ejecutable holanumdo.tap y lo abrimos con el emulador

obijuan@Hoth:~/Develop/Learn-zx-spectrum-asm/01-Hello-world
$ zesarux holamundo.tap 
ZEsarUX - ZX Second-Emulator And Released for UniX
https://github.com/chernandezba/zesarux

Copyright (C) 2013 Cesar Hernandez Bano

ZEsarUX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

Please read the other licenses used in ZEsarUX, from the menu Help->Licenses or just open files from folder licenses/


ZEsarUX v.10.3 - La Abadia del Crimen edition. 10 May 2023

Uncompressing Linux... done, booting the kernel ... Just kidding ;)

Y nos aparece esta pantalla

En realidad me aparece la pantalla de inicio (que sólo debe aparecer la primera vez) pero no consigo que se quita. Al volver a arrancar siempre aparece... es molesto

Con esto ha mejorado un poco...

zesarux --disable-all-first-aid holamundo.tap

Pero todavía sale el mensaje bienvenida (que se quita a los pocos segundos)

OK!!! Ya he dado con la tecla:

zesarux --disable-all-first-aid --nowelcomemessage  holamundo.tap

He añadido los scripts build.sh y run.sh para hacerlo más sencillo. Estamos listos para seguir con los ejemplos!!!

Escritura en la memoria de pantalla

Este es el programa holamundo2.asm que escribe el valor 0xFF en la dirección 0x4000 y por tanto se encienden 8 pixeles con los atributos actuales (color negro sobre fondo blanco)

;-- Programa hola mundo

;--- Comienzo de la RAM
    org $8000

    ld hl, $4000   ;--- Direccion de comienzo de la memoria de video
    ld (hl), $FF   ;--- Activar 8 pixeles
    ret

    end $8000

Este es el resultado:

Impresión de caracteres

Para imprimir caracteres en la pantalla usamos una rutina almacenada en la memoria ROM, a la que accedemos usando la instrucción rst $10. El caracter almacenado en el acumulador se imprime en la posición actual de la pantalla

  • holamundo3.asm:
    org $8000

    ;----- Escribir el caracter 'A' en la posicion actual
    ;----- de la pantalla
    ld a, 'A'
    rst $10
    ret

    end $8000

  • holamundo4.asm:
;--------------------------------------
;-- Ejemplo para imprimir el mensaje HI
;-- en la pantalla, caracter a caracter
;-- Cada caracter se lee de memoria y se imprime

    org $8000

    ;-- Direccion de la cadena en HL
    ld hl, msg

    ;-- Imprimir el primer caracter
    ld a, (hl)  ;-- Leer caracter
    rst $10     ;-- Imprimirlo!

    ;-- Apuntar al siguiente caracter
    inc hl

    ;-- Imprimir el siguiente carcter
    ld a, (hl)
    rst $10

    ;-- Terminar
    ret


msg:  defm 'HI'

    end $8000

  • holamundo5.asm
;------------------------------------------------------------------------------
;-- Impresión de una cadena en la pantalla
;------------------------------------------------------------------------------
    org $8000

    ;-- Cargar la direccion de la cadena
    ld hl, msg

bucle:
    ;-- Leer caracter
    ld a, (hl)

    ;-- Si es 0, terminar
    or a    ;-- Evaluar z
    jr z, fin  

    ;-- Imprimir caracter
    rst $10

    ;-- Apuntar al siguiente caracter
    inc hl

    ;-- Repetir
    jr bucle

fin:
    ret

      ;--- Cadena terminada en 0
msg:  defm 'Holi...',$00

    end $8000   

Subrutinas en ROM

LOCATE

Cuando se trabaja en Basic, la esquina superior izuierda tiene las coordenadas 0,0. Usando el print AT podemos situar caracteres en las diferentes posiciones. El primer parametro es Y (Filas) y el segundo X las columnas. Las filas van de 0 a 21 y las columnas de 0 a 31. Este programa en basic sirve para imprimir una "A" en cada esquina. Se cambia el borde a otro color para visualizar los límites

5 BORDER 1
10 PRINT AT 0,0;"A"
20 PRINT AT 21,0;"A"
30 PRINT AT 0,31;"A"
40 PRINT AT 21,31;"A"

Para introducir el programa es necesario tener a mano un teclado del spectrum:

La tecla Symbol-shift es el "Control". Para introducir PRINT hay que dar a la P. Para el comando AT hay que pulsar Ctrl-I. Para el punto y coma ; Ctrl-O y para las comillas " Ctrl-P

Esto es lo que sale al ejecutarlo:

Vamos a hacerlo en ensamblador. Lo primero es aprender a cambiar el borde. Eso se hace escribiendo el color en el puerto 0xFE

  • holamundo6.asm
;-----------------------------------------------------------------------------
;-- Ejemplo para cambiar el borde a otro color
;-----------------------------------------------------------------------------

    ;--- Puerto a escribir para cambiar el color
    ;--- del borde
BORDER: EQU $FE

    org $8000

    ld a, 1  ;-- Color azul

    ;-- Escribir en el puerto
    out (BORDER), a

    ;-- Terminar
    ret

    end $8000

Ahora ya podemos usar la rutina de locate para la ROM. Esta rutina usa un sistema de coordenadas diferentes. La esquina superior izquierda se encuentra en la posición (24, 33), la que nosotros llamamos (0,0) en Basic con Print-AT

Este es el programa que escribe 4 Aes en las esquinas. Vemos que las As inferiores no están abajo del todo: esa es la parte que usa el spectrum para comunicarse con el usuario. Si imprimimos ahí se produce un scroll de la pantalla

;-----------------------------------------------------------------------------
;-- Impresion del caracter 'A' en cada esquina de la pantalla, usando
;-- la rutina ROM de locate
;-----------------------------------------------------------------------------

    ;--- Puerto a escribir para cambiar el color
    ;--- del borde
BORDER: EQU $FE

;-------------------------------------------------------------
;-- RUTINAS DE LA MEMORIA ROM
;-------------------------------------------------------------

;-------------------------------------------------------
;-- LOCATE: Posicionar el cursor (x,y)
;--
;-- ENTRADA: 
;--    B : Coordenada y
;-     C : Coordenada X
;--
;--  Esquina superior izquierda: (y=24, x=33)
;--  Esquina superior derecha: (y=24, x=2)
;--  Esquina inferior izquierda (y=3, x=33)
;--  Esquina inferior derecha: (y=3, x=2)
;-------------------------------------------------------
LOCATE: EQU $0DD9


;-----------------------------------
;-- MAIN
;-----------------------------------
    org $8000

    ;-- Poner borde Azul
    ld a, 1
    out (BORDER), a

    ;-- Caracter en la esquina superior izquierda (24, 33)
    ld b, 24
    ld c, 33
    call LOCATE

    ld a, 'A'
    rst $10

    ;-- Esquina superior derecha (24, 2)
    ld b, 24
    ld c, 2
    call LOCATE

    ld a, 'A'
    rst $10

    ;-- Esquina inferior izquierda (3, 33)
    ld b, 3
    ld c, 33
    call LOCATE

    ld a, 'A'
    rst $10

    ;-- Esquina inferior derecha (3, 2)
    ld b, 3
    ld c, 2
    call LOCATE

    ld a, 'A'
    rst $10


    ;-- Terminar
    ;-- (bucle infinito)
inf: jr inf

    end $8000

Para podemos escribir en las mismas coordenadas que con el PRINT AT, hay que hacer una pequeña transformación. Este es el nuevo programa:

;-----------------------------------------------------------------------------
;-- Impresion del caracter 'A' en cada esquina de la pantalla, usando
;-- la rutina ROM de locate
;-----------------------------------------------------------------------------

    ;--- Puerto a escribir para cambiar el color
    ;--- del borde
BORDER: EQU $FE

;-------------------------------------------------------------
;-- RUTINAS DE LA MEMORIA ROM
;-------------------------------------------------------------

;-------------------------------------------------------
;-- LOCATE: Posicionar el cursor (x,y)
;--
;-- ENTRADA: 
;--    B : Coordenada y
;-     C : Coordenada X
;--
;--  Esquina superior izquierda: (y=24, x=33)
;--  Esquina superior derecha: (y=24, x=2)
;--  Esquina inferior izquierda (y=3, x=33)
;--  Esquina inferior derecha: (y=3, x=2)
;-------------------------------------------------------
LOCATE: EQU $0DD9


;-----------------------------------
;-- MAIN
;-----------------------------------
    org $8000

    ;-- Poner borde Azul
    ld a, 1
    out (BORDER), a

    ;-- Caracter en la esquina superior izquierda (0, 0)
    ld b, 24 - 0
    ld c, 33 - 0
    call LOCATE

    ld a, 'A'
    rst $10

    ;-- Esquina superior derecha (0, 31)
    ld b, 24 - 0
    ld c, 33 - 31
    call LOCATE

    ld a, 'A'
    rst $10

    ;-- Esquina inferior izquierda (21, 0)
    ld b, 24 - 21
    ld c, 33 - 0
    call LOCATE

    ld a, 'A'
    rst $10

    ;-- Esquina inferior derecha (21, 31)
    ld b, 24 - 21
    ld c, 33 - 31
    call LOCATE

    ld a, 'A'
    rst $10


    ;-- Terminar
    ;-- (bucle infinito)
inf: jr inf

    end $8000

El resultado es el mismo que antes

CLS

Vamos a probar la rutina de CLS de la ROM. Lo que hace es eliminar todos los caracteres que haya y poner los atributos que hay almacenados en la variable del sistema ATTR_S

;-----------------------------------------------------------------------------
;-- Ejemmplo de borrado de la pantalla llamando a la rutina ROM CLS
;-----------------------------------------------------------------------------

;------------------------------------
;--- VARIABLES DEL SISTEMA
;------------------------------------

;-- Atributos permanentes del sistema. Es lo que se usa cuando se llama 
;-- a CLS
;-- Formato:  Flash, Bright, paper (3-bits), ink (3-bits)
ATTR_S: EQU $5C8D

;-------------------------------------------------
;-- Rutina de CLS
;-- Altera el valor de los registros AF, BC, DE Y HL
;----------------------------------------------------
CLS: EQU $0DAF


    org $8000

    ;-- Establecer los atributos permanentes
    ld a, $0e      ;-- Paper=1 (azul) ink = 6 (amarillo?)
    ld hl, ATTR_S
    ld (hl), a 

    ;-- Borrar la pantalla
    CALL CLS

    ;-- Terminar
    ret

    end $8000

Hola mundo final

;-----------------------------------------------------------------------------
;-- Hola mundo final: Se cambia el color de la pantalla y se imprime
;-- un mensaje en la parte central
;-----------------------------------------------------------------------------

    ;--- Puerto a escribir para cambiar el color
    ;--- del borde
BORDER: EQU $FE


;------------------------------------
;--- VARIABLES DEL SISTEMA
;------------------------------------

;-- Atributos permanentes del sistema. Es lo que se usa cuando se llama 
;-- a CLS
;-- Formato:  Flash, Bright, paper (3-bits), ink (3-bits)
ATTR_S: EQU $5C8D

;-- Atributos de las impresiones actuales
ATTR_T: EQU $5C8F


;-------------------------------------------------------------
;-- RUTINAS DE LA MEMORIA ROM
;-------------------------------------------------------------

;-------------------------------------------------
;-- Rutina de CLS
;-- Altera el valor de los registros AF, BC, DE Y HL
;----------------------------------------------------
CLS: EQU $0DAF

;-------------------------------------------------------
;-- LOCATE: Posicionar el cursor (x,y)
;--
;-- ENTRADA: 
;--    B : Coordenada y
;-     C : Coordenada X
;--
;--  Esquina superior izquierda: (y=24, x=33)
;--  Esquina superior derecha: (y=24, x=2)
;--  Esquina inferior izquierda (y=3, x=33)
;--  Esquina inferior derecha: (y=3, x=2)
;-------------------------------------------------------
LOCATE: EQU $0DD9



    org $8000

    ;--- Cambiar el borde a color azul
    ld a, 1
    out (BORDER), a

    ;-- Establecer atributos del sistema y actuales
    ld a, $0e
    ld hl, ATTR_T
    ld (hl), a

    ld hl, ATTR_S
    ld (hl), a

    ;-- Limpiar la pantalla
    CALL CLS

    ;-- Situarse en (10, 2)
    ld b, 24 - 10
    ld c, 33 - 2
    call LOCATE

    ;-- Imprimir el mensaje en la pantalla
    ld hl, msg

bucle:
    ld a, (hl)    ;-- Leer caracter
    or a          ;-- Comprobar si a = 0
    jr z, fin     ;-- Si es 0, terminar

    ;--Imprimir caracter
    rst $10

    ;-- Apuntar al siguiente
    inc hl

    ;-- Repetir
    jr bucle

fin:

    ;-- Terminar
inf: jr inf

msg: defm "Hola ensamblador zx Spectrum", $00


    end $8000

Experimentos

Antes de seguir con los siguientes capítulos del libo voy a hacer algunos experimentos por mi cuenta... El primero va a ser rellenar la memoria de vídeo... para ver las diferentes secciones

  • 01-fill.asm
;-----------------------------------------------------------------------------
;-- Rellenar toda la memoria de video con unos para encender todos
;-- los pixeles
;-----------------------------------------------------------------------------

;-- Puerto para el borde
BORDER: EQU $FE

    org $8000

    ;--- Poner el borde amarillo
    ld a, 6
    out (BORDER), a

    ld hl, $4000   ;-- Apuntar a la memoria de video

bucle:
    ;-- Activar los 8 pixeles
    ld a, $ff
    ld (hl), a

    ;-- Condicion de terminacion
    ld a, h
    cp $57
    jr nz, next

    ;-- Comprobar si l=0xFF
    ld a, l
    cp $FF
    jr z, fin 

next:
    ;-- Incrementar la direccion
    inc hl

    ;-- Hacer una pausa para ver cómo se rellena
    ;-- la pantalla
    call wait

    ;-- Repetir
    jr bucle

fin:
    jr fin

;-------------------------------
;-- Realizar un pausa
;--------------------------------
wait:
    ld a,$ff
loop:
    dec a
    nop
    jr nz, loop
    ret


    end $8000

Se ha añadido una rutina de pausa para ver el relleno

  • 02-two-lines.asm

En este ejemplo se dibujan dos lineas horizontales,uno justo debajo de la otra. Para ello hay que sumar 0x100 a la dirección de HL

;----------------------------------------------------------------------------
;-- Programa para dibujar dos líneas horizontales, una debajo de la otra
;----------------------------------------------------------------------------

;-- Puerto para el borde
BORDER: EQU $FE

    org $8000

    ;--- Cambiar color del fondo
    ld a, 6
    out (BORDER), a

    ;-- Puntero a la memoria de video
    ld hl, $4000

    ;-- Numero de lineas horizontales a dibujar
    ;-- Entre 1 y 8
    ld b,2
    
loop:
    call lineah

    ;-- Incrementar en 0x100 (256 bytes)
    ;-- Ahí empieza la linea justo debajo
    inc h

    ;-- Una linea menos
    dec b
    jr nz, loop 

    ret


;------------------------------------------------------------
;-- LINEAH: Pintar una linea horizontal
;--
;-- ENTRADAS:
;--   * HL: Puntero a la memoria de video
;-- MODIFICA:
;--   * Registros AF, B
;------------------------------------------------------------
lineah:
    ;-- Guardar direccion inicial
    push hl

    ;-- Guardar bc
    push bc

    ;-- Numero de bytes para completar una linea horizontal 
    ld b, $20

lineah_loop:
    ld a, $ff
    ld (hl), a

    ;-- Pausa para ver la linea dibujarse
    halt

    ;-- Apuntar a la siguiente posicion
    inc hl

    ;-- Decrementar contador de bytes
    dec b

    ld a, b
    or a

    jr nz, lineah_loop

    ;-- Recuperar bc
    pop bc

    ;-- Recuperar direccion inicial
    pop hl

    ;-- Retornar
    ret

    end $8000

ZX PONG

Paso 1: Dibujando por pantalla

  • Memoria de vídeo: 0x4000 - 0x57FF
  • Pixeles: 256 * 192
  • Celdas: 32 x 24

El formato de las direcciones es:

  • 010T-TSSS-LLLC-CCCC
    • TT: Tercio (0-2)
    • SSS: Scanline (0-7)
    • LLL: Línea (0-7)
    • CCCCC: Columna (0-31)

Rutina NextScan

Esta rutina parte de una dirección de la memoria de video y nos devuelve la dirección de la siguiente scanline. La rutina está guardada en el fichero Video.asm:

;--------------------------------------------------------------------
;--  NextScan: Obtener la direccion del siguiente scanline
;--
;--  ENTRADA:
;--    * HL: Scanline actual
;--  SALIDA:
;--    * HL: Scanline siguiente
;--
;--  Formato direccion: 010TTSSS LLLCCCCC
;---------------------------------------------------------------------
NextScan:

    ;-- Incrementar scanline (Módulo 7)
    inc h
    ld a,h
    and $7
    ret nz  ;-- Si no es cero hemos terminado

    ;-- El scanline es 8 (mayor de 3 bits)
    ;-- Hay que pasar a la siguiente linea (incrementar LLL)
    ld a,l  ;-- A = LLLC CCCC
    add a,$20 ;-- +   0010 0000
    ld l,a
    ret c   ;-- Si hay acarreo hemos terminado

    ;-- Decrementar el tercio (se había incrementado en el paso 1)
    ld a, h  ;-- A = 010T TSSS
    sub $08  ;--     0000 1000
    ld h, a
    ret

Desde el fichero Main.asm hacemos un bucle para dibujar una línea vertical de 192 scanlines (192 píxeles de altura).

   org $8000

    ;-- Dibujar Linea vertical izquierda
    ;-- Apuntar a la celda (0,0)
    ld hl, $4000
    ld b, 192  ;-- Recorrer las 192 scanlines...
loop:
    ld (hl), $3c  ;-- Escribir en memoria de video
    call NextScan ;-- Siguiente scanline

    ;-- DEBUG: Hacer una pausa para ver como se
    ;-- dibuja la linea de arriba hacia abajo
    ;halt  
    djnz loop

    include "Video.asm"
    end $8000

Este es el resultado:

Rutina PreviousScan

Esta rutina es similar a NextScan pero nos devuelve el scanline anterior, en vez del siguiente:

;--------------------------------------------------------------------
;--  PreviousScan: Obtener la direccion scanline anterior
;--
;--  ENTRADA:
;--    * HL: Scanline actual
;--  SALIDA:
;--    * HL: Scanline anterior
;--
;--  Formato direccion: 010TTSSS LLLCCCCC
;---------------------------------------------------------------------
PreviousScan:

    ;-- Decrementar el scanline
    ld a,h
    dec h
    and $7
    ret nz  ;-- Nos quedamos en la fila actual

    ;-- El scanline está en la fila anterior. Hay que decrementar
    ;-- la fila
    ld a, l
    sub $20
    ld l,a
    ret c  ;-- Hemos cambiado de fila, y de tercio

    ;-- Hay que incrementar el tercio para que se quede como está
    ld a,h 
    add a, $08
    ld h,a

    ret

Ampliamos el programa Main.asm para dibujar una línea vertical por la derecha, de abajo hacia arriba para probar PreviousScan

    ;--------------- Dibujar linea vertical derecha
    ;-- La dibujamos de abajo hacia arriba, para probar la
    ;-- rutina PreviousScan
    ld hl, $57FF  ;-- Posicion: (255,192)
    ld b,192      ;-- Recorrer las 192 scanlines

loop2:
    ld (hl), $3c
    call PreviousScan

    ;-- DEBUG: Hacer una pausa para ver el dibujo
    ;-- de la linea
    ;-- halt
    djnz loop2
    ret

Este es el resultado:

Paso 2: Teclas de control

Hay 8 semifilas. Para el escaneo primero se envía el número de semifila (1-8) pero en lógica negativa: la semifila activa se pone a 0. El resultado se lee por el puerto del teclado ($FE). Si la tecla está pulsada se lee un bit a 0, ó un 1 si NO está pulsada

Rutina ScanKeys

El código de Controls.asm es:

;--- Puerto del teclado
TECLADO EQU $FE

;-- Semifila 1. Teclas: CAPS Z X C V
;-- b4  b3  b2  b1  b0
;--  V  C   X   Z   CAPS
SFILA_CAPS_V EQU $FE
  KEY_Z  EQU  $01

;-- Semifila 2: Teclas: A S D F G
;-- b4  b3  b2  b1 b0
;--  G   F   D  S   A
SFILA_AG EQU $FD
  KEY_A  EQU $00  ;-- Mascara para tecla A

;-- Semifila 5: Teclas: 6 7 8 9 0
;-- b4  b3  b2  b1  b0
;--  6   7   8   9    0
SFILA_6_0 EQU $EF
  KEY_0  EQU $00

;-- Semifila 6: Teclas P O I U Y
;-- b4  b3  b2  b1  b0
;--  Y   U   I   O   P
SFILA_P_Y EQU $DF
  KEY_O EQU $01 

;-- Mascara de los bits en el registro D
;-- Para la lectura de las teclas
BIT_A EQU $00   ;-- Bit para la tecla A
BIT_Z EQU $01   ;-- Bit para la tecla Z
BIT_0 EQU $02   ;-- Bit para la tecla 0
BIT_O EQU $03   ;-- Bit para la tecla O

;----------------------------------------------------------------------------
;-- ScanKeys:
;--
;-- Registro D:
;--  b4 b3 b2 b1 b0
;--           Z   A
;--- Bit a 1: Tecla pulsada
;--  Bit a 0: NO pulsada
;----------------------------------------------------------------------------
ScanKeys:
    ld d, 0  ;-- Registro D a 0

ScanKeys_A:
    ld a, SFILA_AG     ;-- Semifila donde esta la tecla A
    in a, (TECLADO)    ;-- Leer teclado!
    bit KEY_A, a       ;-- Comprobar si Bit tecla A esta a 0
    jr nz, ScanKeys_Z  ;-- Tecla A NO pulsada... pasar a la siguiente

    ;-- Tecla A pulsada
    ;-- Activar el flag de D correspondiente
    set BIT_A, d

    ;-- Continuar el scaneo...
ScanKeys_Z:
    ld a, SFILA_CAPS_V
    in a, (TECLADO)
    bit KEY_Z, a
    jr nz, ScanKeys_0

    ;-- Tecla Z pulsada
    ;-- Activar el flag de D correspondiente
    set BIT_Z, d

    ;-- Comprobacion de las dos teclas A-Z a la vez
    ld a, d
    sub $03
    jr nz, ScanKeys_O  ;-- NO se ha pulsado a la vez
    ld d, a   ;-- Poner D a 0

ScanKeys_0:
    ld a, SFILA_6_0
    in a, (TECLADO)
    bit KEY_0, a
    jr nz, ScanKeys_O

    ;-- Tecla 0 pulsada
    ;-- Activar el flag de D correspondiente
    set BIT_0, d

ScanKeys_O:
    ld a, SFILA_P_Y
    in a, (TECLADO)
    bit KEY_O, a
    ret nz

    ;-- Tecla O pulsada
    ;-- Activar el flag de D correspondiente
    set BIT_O,d

    ;-- Comprobar las dos teclas 0-O
    ld a, d
    and $0c  ;-- Aislar los bits de 0 y O
    cp $0c   ;-- Comprobar si las dos teclas se ha pulsado a la vez
    ret nz
    ld a, d  ;-- Se han pulsado...
    and $03  ;-- Quedarse con los bits de A y Z
    ld d, a 

    ret

Este es el Main.asm:

   org $8000

    ;-- HL direccion esquina superior izquierda del video
    ld hl, $4000

bucle:
    call ScanKeys
    ld (hl), d
    jr bucle

    include "Controls.asm"
    end $8000

Al apretar las teclas A,Z,O,0 se activan los píxeles correspondientes. En esta animación se muestra el funcionamiento cuando se aprietan las diferentes teclas:

Paso 3: Palas y línea central

Los atributos de la pantalla empiezan en la dirección $5800. Hay 32 columnas y 24 líneas. El atributo es el mismo para cada celda (grupo de 8x8 píxeles)

Rutina CLS

Primero se ponen todos los bytes de la memoria de vídeo a 0 (para borrar todos los píxeles) y luego se establecen los atributos a fondo negro y tinta blanca

;----------------------------------------------------------------------------
;-- Cls: Borrar la pantalla con tinta blanca y fondo negro
;----------------------------------------------------------------------------
Cls:
    ;-- Rellenar toda la memoria de video con ceros para limpiar... 
    ld hl, $4000  ;-- HL: Puntero a la memoria de Video
    ld (hl), $00  ;-- Borrar posicion actual
    ld de, $4001  ;-- Siguiente posicion
    ld bc, $17ff  ;-- Tamaño de la memoria de video
    ldir

    ;-- Establecer los atributos de la memoria de video
    ld hl, $5800
    ld (hl), $07  ;-- Fondo negro, tinta blanca
    ld de, $5801
    ld bc, $2ff    ;-- Tamaño de la memoria de atributo
    ldir

    ret

En el programa principal se pone el borde rojo y se llama a CLS para poner el fondo negro

;--- Puerto del borde
 BORDE EQU $FE

    org $8000

    ;-- Borde rojo
    ld a, $02
    out (BORDE), a 

    ;-- Borrar pantalla: Tinta blanca, Fondo negro
    call Cls

    ;-- Terminar
    ret

    include "Video.asm"    
    end $8000

Rutina PrintLine

La red está formada por 24 caracteres. El Sprite de la red (8x8) tiene la línea superior en blanco, la inferior en blanco y la central es una línea vertical de 4 píxeles de ancho. Los sprintes se define en el fichero Sprites.asm

;-- Red
ZERO:  EQU  $00
LINE:  EQU  $80

Esta es la rutina:

;-----------------------------------------------------------------------------
;-- PrintLine:  Imprimir la linea central de la red
;-----------------------------------------------------------------------------
PrintLine:
    ld b, 24      ;-- Tamaño: 24 líneas
    ld hl, $4010  ;-- Columna central, Primera línea (16,0)

PrintLine_loop:
    ;-- No hay parte superior de la linea
    ld (hl), ZERO
    inc h
    push bc

    ld b, 6        ;-- Pintar 6 lineas verticales  
PrintLine_loop2:
    ld (hl), LINE  ;-- Pintar linea vertical
    inc h          ;-- Siguiente scanline
    djnz PrintLine_loop2

    pop bc

    ;-- Pintar ultimo tramo: sin linea
    ld (hl), ZERO
    call NextScan

    ;-- Repetir para las 24 líneas
    djnz PrintLine_loop

    ret

Este es el resultado:

Rutina PrintPaddle

Las raquetas son de 24 scanlines de alto y un byte de ancho. El sprite de la raqueta tiene 4 bits de ancho. Utilizamos dos variables para almacenar las posiciones de las raquetas de los jugadores. Este es el fichero Sprite.asm

;-- Red
ZERO:  EQU  $00
LINE:  EQU  $80

;--- Raquetas
PADDLE: EQU $3C
PADDLE_TOP:    EQU $00  ;-- Posicion superior de la raqueta en el campo
PADDLE_BOTTOM: EQU 168  ;-- Posicion inferior de la raqueta en el campo

                       ;-- 010TTSSS LLLCCCCC
paddle1pos: DW $4861   ;-- 01001000 01100001  (1, 11)
paddle2pos: DW $487e   ;-- 01001000 01111110  (30, 11)

Esta es la rutina para pintar las raquetas:

;----------------------------------------------------------------------------
;-- PrintPaddle:  Imprimir la raqueta
;--
;--  ENTRADA:
;--    * HL: Posicion de la raqueta (direccion)
;----------------------------------------------------------------------------
PrintPaddle:

    ;-- Parte superior de la raqueta: blanco
    ld (hl), ZERO
    call NextScan

    ;-- Pintar la parte visible de la pala
    ;-- Longitud: 22 scanlines
    ld b, 22
printPaddle_loop:
    ld (hl), PADDLE
    call NextScan
    djnz printPaddle_loop

    ;-- La última línea de la pala en blanco
    ld (hl), ZERO
    ret

Este es el resultado:

Movimiento de las palas

Con las teclas 'A' y 'Z' se mueve la raqueta izquierda y con '0' y 'O' la raqueta derecha

 ;--- Puerto del borde
 BORDE EQU $FE

    org $8000

    ;-- Borde Rojo
    ld a, $02
    out (BORDE), a 

    ;-- Borrar pantalla: Tinta blanca, Fondo negro
    call Cls

    ;-- Dibujar linea
    call PrintLine

loop:
    call ScanKeys

MovePaddle1Up:
    bit 0, d  ;-- Comprobar si tecla A pulsada
    jr z, MovePaddle1Down  ;-- Si no pulsada, salta

    ;-- Tecla A pulsada: Movimiento hacia arriba
    ;-- Comprobar si hemos alcanzado el límite superior
    ld hl, (paddle1pos)
    ld a, PADDLE_TOP     ;-- Margen superior
    call CheckTop        ;-- Evaluar si limite alcanzado
    jr z, MovePaddle2Up  ;-- Limite alcanzado. No mover. Pasar
                         ;-- a la raqueta 2
    ;-- Limite no alcanzado
    ;-- Obtener la direccion
    call PreviousScan ;-- Obtener scanline anterior a la pala
    ld (paddle1pos), hl ;-- Es la nueva posicion
    jr MovePaddle2Up    ;-- Saltar


MovePaddle1Down:
    bit 1, d  ;-- Comprobar si tecla Z pulsada
    jr z, MovePaddle2Up

    ;-- Tecla Z pulsada: Movimiento hacia abajo
    ;-- Comprobar si limite inferior alcanzado
    ld hl, (paddle1pos)
    ld a, PADDLE_BOTTOM
    CALL CheckBottom
    jr z, MovePaddle2Up     ;-- Limite alcanzado. No mover. Pasar a
                            ;-- la raqueta 2

    ;-- Limite no alcanzado
    ;-- Obtener la direccion de la siguiente posicion
    call NextScan
    ld (paddle1pos), hl


MovePaddle2Up:
    bit 2, d   ;-- Comprobar si tecla 0 pulsada
    jr z, MovePaddle2Down
    ld hl, (paddle2pos)
    ld a, PADDLE_TOP
    call CheckTop
    jr Z, MovePaddleEnd
    call PreviousScan
    ld (paddle2pos), hl
    jr MovePaddleEnd

MovePaddle2Down:
    bit 3, d    ;-- Comprobar si tecla O pulsada
    jr z, MovePaddleEnd
    ld hl, (paddle2pos)
    ld a, PADDLE_BOTTOM
    call CheckBottom
    jr z, MovePaddleEnd
    call NextScan
    ld (paddle2pos),hl

MovePaddleEnd:
    ;-- Imprimir raqueta 1
    ld hl, (paddle1pos)  
    call PrintPaddle   

    ;-- Imprimir raqueta 2
    ld hl, (paddle2pos)
    call PrintPaddle

    halt

    jr loop

    ;-- Terminar
    ret

    include "Controls.asm"
    include "Sprite.asm"
    include "Video.asm"
    
    end $8000

En esta animación se muestra el resultado: