Curso: Dominando Ensamblador Z80, para ZX Spectrum - Obijuan/Learn-zx-spectrum-asm GitHub Wiki

El curso DEZ80, de Fran Gallego está realizado sobre un Amstrad CPC. Voy a re-hacer los retos planteados pero aplicados al ZX-Spectrum

Contenido

Nivel 1

En esta wiki están los Retos originales del Nivel 1

Reto 1: Pinta un pixel en pantalla

Los pixeles se pintan de 8 en 8 accediendo a la memoria de vídeo, que comienza en posición 0x4000. Si escribimos en esa dirección, los bits a 1 se visualizan y los bits a 0 no. Por eso, para mostrar el primer pixel, el de la posición 0,0 hay que escribir el valor 0x80 en 0x4000

El color del pixel es NEGRO porque son los atributos por defecto

  • 18-reto1-pixel-negro.asm
;--------------------------------------------
;-- Reto 1: Pixel negro en posicion (0,0)
;--------------------------------------------

    org $8000

    ;-- Poner borde amarillo para ver bien la posicion del pixel
    ;-- (y comprobar que está en 0,0)
    ld a, 6
    out ($FE), a

    ;-- Escribir valor 0x80 en direccion 0x4000
    ld a,$80
    ld ($4000),a

    ;-- Bucle infinito
inf:
    jr inf

    end $8000

Si aumentamos el area vemos que efectivamente está en la posición 0,0

Reto 1-2: Pinta un pixel negro en (1,0)

Si queremos pintar un pixel en la posición siguiente, la dirección de memoria es la misma: En la dirección 0x4000 se guarda un paquete con los 8 primeros píxeles. Para mostrar el pixel del a posicion x=1, hay que guardar el valor 0x40. En binario es: 01000000. Es decir, sólo es segundo pixel, comenzando por la izquierda, estará encendido. El resto apagados

  • 19-reto-1-2-pixel-negro.asm
;--------------------------------------------
;-- Reto 1: Pixel negro en posicion (1,0)
;--------------------------------------------

    org $8000

    ;-- Poner borde amarillo para ver bien la posicion del pixel
    ;-- (y comprobar que está en 0,0)
    ld a, 6
    out ($FE), a

    ;-- Escribir valor 0x40 en direccion 0x4000
    ld a,$40
    ld ($4000),a

    ;-- Bucle infinito
inf:
    jr inf

    end $8000

En la imagen ampliada comprobamos que efectivamente el pixel está encendido en la posición siguiente a la 0 (la del anterior reto)

Reto 1-3: Pinta un pixel rojo en (7,0)

En este reto hay que resolver dos cosas... por un lado hay que pintar un pixel negro en la posición 7. Dentro de la posición de memoria 0x4000 hay 8 pixeles, uno por bit. El número 0x80 se corresponde con el pixel de la izquierda (posicion 0). El número 0x40 con el pixel de la posición 1... y así sucesivamente hasta llegar al valor 0x01 que se corresponde con el pixel de la posición 7

Por tanto, hay que escribir el valor 0x01 en la direción 0x4000. Esto nos dibuja un pixel negro

El siguiente paso es modificar sus atributos que se encuentra en una dirección diferente: 0x5800. Ahí tendremos que escribir el atributo que tiene el papel blanco y la tinta roja: 00111010: el número 0x3a

  • 20-reto-1-3-pixel-rojo.asm
;--------------------------------------------
;-- Reto 1-3: Pixel rojo en posicion (7,0)
;--------------------------------------------

    org $8000

    ;-- Poner borde amarillo para ver bien la posicion del pixel
    ;-- (y comprobar que está en 0,0)
    ld a, 6
    out ($FE), a

    ;-- Escribir valor 0x01 en direccion 0x4000
    ld a,$01
    ld ($4000),a

    ;-- Cambiar sus atributos al color rojo
    ;-- Papel: blanco (111)
    ;-- Tinta: roja (002)
    ;-- Valor atributo: 00111010 = $40
    ;-- Direccion memoria atributos: $5800
    ld a, $3a
    ld ($5800), a

    ;-- Bucle infinito
inf:
    jr inf

    end $8000

Reto 1-4: Pinta cuatro pixeles rojos en (1,0), (3,0), (5,0) y (7,0)

Como la posición x de todos los píxeles es menor a 8, están todos en el mismo bloque situado en la dirección 0x4000. Por tanto, los 4 píxeles se pintan todos a la vez escribiendo este valor binario: 01010101 (es decir, el valor 0x55)

Además hay que escribir el valor 0x3a para establecer el atributo de color rojo (sobre fondo blanco)

  • 21-Reto-1-4-pixels-x1-3-5-7-rojo.asm
;---------------------------------------------------------------------
;-- Reto 1-4: Pintar 4 pixeles rojos en (1,0), (3,0), (5,0) y (7,0)
;---------------------------------------------------------------------

    org $8000

    ;-- Poner borde amarillo para ver bien la posicion del pixel
    ;-- (y comprobar que está en 0,0)
    ld a, 6
    out ($FE), a

    ;-- Escribir valor 0x55 en direccion 0x4000
    ld a,$55
    ld ($4000),a

    ;-- Cambiar sus atributos al color rojo
    ;-- Papel: blanco (111)
    ;-- Tinta: roja (002)
    ;-- Valor atributo: 00111010 = $40
    ;-- Direccion memoria atributos: $5800
    ld a, $3a
    ld ($5800), a

    ;-- Bucle infinito
inf:
    jr inf

    end $8000

Reto 1-5: Linea horizontal de 16 pixeles

  • Reto: Dibujar una línea de 16 pixeles de anchura, que vaya desde la posicion (0,0) hasta (15,0). La primera mitad de color rojo y la otra mitad amarilla (dejar el fondo azul)

Una linea de 8 pixeles consecuivos se corresponde con el valor binario: 11111111, es decir, $FF. Este valor hay que escribirlo en la dirección 0x4000 y en 0x5800 sus atributos correspondientes

Para pintar en los pixeles que están a la derecha hay que hacerlo en la posición de memoria siguiente: 0x4001 y sus atributos en 0x5801

  • 22-Reto-1-5-linea-16.asm
;---------------------------------------------------------------------
;-- Reto 1-5: Pintar linea horizontal de 16 pixeles y dos colores
;--   (rojo y amarillo)
;---------------------------------------------------------------------

    org $8000

    ;-- Poner borde azul
    ld a, 1
    out ($FE), a

    ;-- Dibujar primer mitad de la linea (8 bits)
    ld a,$ff
    ld ($4000),a

    ;-- Dibujar segunda mita de la linea (8 bits)
    ld ($4001),a

    ;-- Primer segmento rojo (sobre fondo azul)
    ld a, $0a
    ld ($5800), a

    ;-- Segundo segmento amarillo (fondo azul)
    ld a, $0E
    ld ($5801), a

    ;-- Bucle infinito
inf:
    jr inf

    end $8000

Este código se puede hacer más compacto escribiendo los valores de dos en dos. Usamos el regitro hl para escribir de un solo golpe los dos bytes consecutivos

;---------------------------------------------------------------------
;-- Reto 1-5: Pintar linea horizontal de 16 pixeles y dos colores
;--   (rojo y amarillo)
;---------------------------------------------------------------------

    org $8000

    ;-- Poner borde azul
    ld a, 1
    out ($FE), a

    ;-- Dibujar las dos mitadas. L=bits de 0x4000. H=bits de 0x4001
    ld hl,$ffff
    ld ($4000),hl

    ;-- Almacenar los dos atributos de un golpe
    ;-- L->Atributo de la primera mitad: Rojo sobre fondo azul
    ;-- H->Atributo de la segunda mitad: Amarillo sobre fondo azul
    ld hl, $0e0a
    ld ($5800), hl

    ;-- Bucle infinito
inf:
    jr inf

    end $8000

Reto 1-6: Lineas rojas en izquierda, centro y derecha

  • Reto: Pintar 3 líneas de 8 bits en las posiciones de caracteres (0,0), (15,0) y (31,0)

El objetivo es comprener cómo se codifican las coordenadas x en las direcciones de la memoria de vídeo

  • 24-reto-1-6-tres-lineas.asm
;---------------------------------------------------------------------------
;-- Reto 1-6: Pintar tres líneas de 8 bits en las posiciones (de caracteres)
;--     (0,0), (15, 0) y (31,0)
;---------------------------------------------------------------------------

    org $8000

    ;-- Poner borde negro
    ld a, 0
    out ($FE), a

    ;-- Linea de la izquierda (0,0)
    ld a,$FF
    ld ($4000),a

    ;-- Linea central (15,0)
    ;-- La dirección es 0x4000 + F
    ld ($400F),a

    ;-- Linea de la derecha (31,0)
    ;-- La direccion es 0x4000 + 0x1F
    ld ($401F),a

    ;-- Atributos
    ld a,$3a
    ld ($5800), a  ;-- Linea izquierda
    ld ($580F),a   ;-- Linea central
    ld ($581F), a  ;-- Linea derecha

    ;-- Bucle infinito
inf:
    jr inf

    end $8000

Reto 1-7: Lineas rojas debajo de B, H y o

  • Reto: Pintar 3 líneas rojas de 8 bits en la línea 2, debajo de los caracteres B, H y o

Las líneas se encuentran en las posiciones de caracteres siguientes (x,y): (0,2), (7,2) y (15,2)

La codificacion de la línea está en el byte bajo de la dirección de memoria, en los 3 bits de mayor peso: LLLCCCCC

  • 25-reto-1-7-tres-lineas-B-H_o.asm
;---------------------------------------------------------------------------
;-- Reto 1-7: Pintar 3 líneas rojas de 8 bits en la línea 2, debajo de los
;--           caracteres B, H y o
;---------------------------------------------------------------------------

    org $8000

    ;-- Poner borde negro
    ld a, 0
    out ($FE), a

    ;-- Linea debajo de B: (0,2)
    ;-- LLLCCCCC = 010 00000 (Y=2, x=0)
    ld a, $FF
    ld ($4040),a

    ;-- Linea debajo de H: (7,2)
    ;-- LLLCCCCC = 010 00111 = 0100 0111 = 47
    ld ($4047),a

    ;-- Linea debajo de o: (15,2)
    ;-- LLLCCCCC = 010 01111 = 0100 1111 = 4F
    ld ($404F),a

    ;-- Atributos
    ld a,$3a
    ld ($5840), a  ;-- Linea B
    ld ($5847), a  ;-- Linea H
    ld ($584F), a  ;-- Linea o

    ;-- Bucle infinito
inf:
    jr inf

    end $8000

Reto 1-8: Lineas rojas encima de B, H y o (linea 1)

Es igual que el reto anterior, pero posicionando las líneas en la fila 1 (en vez de la 2)

  • 26-reto-1-8-tres-lineas-B-H-o.asm
;---------------------------------------------------------------------------
;-- Reto 1-8: Pintar 3 líneas rojas de 8 bits en la línea 2, encima de los
;--           caracteres B, H y o
;---------------------------------------------------------------------------

    org $8000

    ;-- Poner borde negro
    ld a, 0
    out ($FE), a

    ;-- Linea encima de B: (0,1)
    ;-- LLLCCCCC = 001 00000 (Y=1, x=0)
    ld a, $FF
    ld ($4020),a

    ;-- Linea encima de H: (7,1)
    ;-- LLLCCCCC = 001 00111 = 0010 0111 = 27
    ld ($4027),a

    ;-- Linea encima de o: (15,1)
    ;-- LLLCCCCC = 001 01111 = 0010 1111 = 2F
    ld ($402F),a

    ;-- Atributos
    ld a,$3a
    ld ($5820), a  ;-- Linea B
    ld ($5827), a  ;-- Linea H
    ld ($582F), a  ;-- Linea o

    ;-- Bucle infinito
inf:
    jr inf

    end $8000

Hay que observar cómo las letras B, H y o ahora se ponen de color rojo. Esto es debido a que los atributos son comunes para todos los píxeles de cada celda de 8x8

Reto 1-9: Linea roja vertical de 4 pixeles en posicion (x=7, y=0)

Cuando estamos dentro de una línea (hay 21 líneas...) la altura se controla con el scanline. El byte alto tiene el formato 010TTSSS. Como estamos en el primer tercio, TT=00. Los cuatro píxeles de altura se encuentran en los scanlines SSS=000, 001, 010 y 011. Eso nos da los valores para el byte alto de 40, 41, 42 y 43. El byte bajo es siempre 00

  • 27-reto-1-9-linea-vertical-4.asm
;---------------------------------------------------------------------------
;-- Reto 1-9: Pintar una línea vertical de 4 pixeles
;---------------------------------------------------------------------------

    org $8000

    ;-- Poner borde negro
    ld a, 0
    out ($FE), a

    ;-- La linea la ponemos en la posicion (7,0), para que se vea mejor
    ld a,$01
    ld ($4000),a  ;-- Primer pixel

    ;--- Segundo pixel: (7,1)
    ;--- Byte bajo: LLLCCCCC = 00000000 = 00
    ;--- Byte alto: 010TTSSS = 01000001 = 41
    ld ($4100), a 

    ;--- Tercer pixel: (7,2)
    ;-- Mismo byte bajo: 00
    ;-- Byte alto: 01000010 = 42
    ld ($4200), a

    ;-- Cuarto pixel: (7,3)
    ;-- Mismo byte bajo: 00
    ;-- Byte alto: 01000011 = 42
    ld ($4300), a

    ;-- Atributos
    ld a,$3a
    ld ($5800), a  

    ;-- Bucle infinito
inf:
    jr inf

    end $8000

Reto 1-10: Bandera cyan-amarilla de 8x4

En este reto se dibuja el primer sprite... muy simple

  • 28-reto-1-10-bandera.asm
;---------------------------------------------------------------------------
;-- Reto 1-10: Bandera cyan-amarilla, de 8x4
;---------------------------------------------------------------------------

    org $8000

    ;-- El sprite a dibujar ocupa 4bits de la primera celda, con atributos
    ;-- de tinta cyan y fondo blanco y 4 bits de la segunda celda, con
    ;-- atributos de tinta amarilla y fondo blanco
    ;-- Este es el sprite en blanco y negro:
    ;--
    ;-- Celda 0   Celda 1
    ;--
    ;-- 00001111  11110000  = 0F F0
    ;-- 00001111  11110000  = 0F F0
    ;-- 00001111  11110000  = 0F F0
    ;-- 00001111  11110000  = 0F F0

    ;-- El byte bajo de las direcciones (LLLCCCCC) es 00 ya que es la primera
    ;-- linea (0) y columnas 0 y 1
    ;-- El byte alto es: 01000SSS, con scanlines de 000,001,010,011
    ;-- Direcciones:
    ;--   $4000 $4001
    ;--   $4100 $4101
    ;--   $4200 $4200
    ;--   $4300 $4300
    ld hl, $F00F
    ld ($4000), hl
    ld ($4100), hl
    ld ($4200), hl
    ld ($4300), hl
    
    ;-- Atributos. Parte cyan
    ;-- 00 111 101 = 0011 1101 = 3D

    ;-- Atributos. Parte amarilla
    ;-- 00 111 110 = 0011 1110 = 3E
    ld hl,$3e3d
    ld ($5800), hl

    ;-- Bucle infinito
inf:
    jr inf

    end $8000

Reto 1-11: sprite 8x8 en posicion (0,0)

Llega el momento de hacer el primer sprite. Comenzamos por uno pequeño de 8x8. Para dibujarlo podemos usar inkscape, activando el snap a las guías:

Los valores que definen este sprite son:

  • 01111100 = 7C
  • 10000010 = 82
  • 10101010 = AA
  • 10101010 = AA
  • 10000010 = 82
  • 10000010 = 82
  • 01111100 = 7C
  • 00000000 = 00

Esta es la otra versión, similar pero con relleno:

Valores:

  • 0111 1100 = 7C

  • 1111 1110 = FE

  • 1101 0110 = D6

  • 1101 0110 = D6

  • 1111 1110 = FE

  • 1111 1110 = FE

  • 0111 1100 = 7C

  • 0000 0000 = 00

  • 29-Reto-1-11-sprite-cocoball-2.asm

;---------------------------------------------------------------------------
;-- Reto 1-10: Bandera cyan-amarilla, de 8x4
;---------------------------------------------------------------------------

;-- Sprite: Cocoball-1
;--
;-- 01111100 = 7C
;-- 10000010 = 82
;-- 10101010 = AA
;-- 10101010 = AA
;-- 10000010 = 82
;-- 10000010 = 82
;-- 01111100 = 7C
;-- 00000000 = 00

;-- Sprite: Cocoball-2
;--
;-- 0111 1100 = 7C
;-- 1111 1110 = FE
;-- 1101 0110 = D6
;-- 1101 0110 = D6
;-- 1111 1110 = FE
;-- 1111 1110 = FE
;-- 0111 1100 = 7C
;-- 0000 0000 = 00

    org $8000

    
    ;--- Dibujar el Sprite en la posicion (0,0)
    ld a, $7C
    ld ($4000), a
    ld a, $82
    ld ($4100), a
    ld a, $AA
    ld ($4200), a
    ld ($4300), a
    ld a, $82
    ld ($4400), a
    ld ($4500), a
    ld a,$7C
    ld ($4600), a
    ld a,$00
    ld ($4700), a

    ;-- Dibujar otro Sprite (cocoball-2) en la posicion (2,0)
    ld a, $7C
    ld ($4002), a
    ld a, $FE
    ld ($4102), a
    ld a, $D6
    ld ($4202), a
    ld ($4302), a
    ld a, $FE
    ld ($4402), a
    ld ($4502), a
    ld a,$7C
    ld ($4602), a
    ld a,$00
    ld ($4702), a
    
    ;-- Atributos. Fondo blanco, color azul
    ;-- 00 111 001 = 0011 1001 = 39
    ld a, $39
    ld ($5800),a
    ld ($5802),a 

    ;-- Bucle infinito
inf:
    jr inf

    end $8000

Reto 1-12: sprite 8x8: Dado, en posicion (x=10, y=15)

En este reto se trata de hacer el mismo sprite del curso DEZ80 del profesor Fran Gallego, pero para el Spectrum. Esta es la imagen:

Se puede implementar de varias formas, pero voy a usar la tinta para el cuerpo del dado y el fondo para los puntos del número 5. Tinta amarilla y papel rojo

En realidad, est dado es de 8x7. Para poder usar 2 colores hay que rellenar todo el fondo, por lo que añadimos una línea más en la parte inferior

  • 30-reto-1-12-dado.asm
;---------------------------------------------------------------------------
;-- Reto 1-12: Sprite de un dado, con el numero 5 (de 8x8), en posicion 10,15
;---------------------------------------------------------------------------

;-- Sprite: Dado, 5
;--
;-- 11111111 = FF
;-- 10011001 = 99
;-- 11111111 = FF
;-- 11100111 = E7
;-- 11111111 = FF
;-- 10011001 = 99
;-- 11111111 = FF
;-- 11111111 = FF
;--
;-- Atributos: fondo rojo (010). Tinta amarilla (110)
;-- 00 010 110 = 0001 0110 = 16
;-- 
;-- Dir atributos:  5800 + LLLLLCCCCC
;--    Linea 10: 01010 01111 -> 01 0100 1111 = 14F
;--    Col 15:
;--    Dir: 5800 + 14F = 594F

;-- Posicion 10,15:
;--   Linea: 10 --> 01010 (TTLLL) T=01, LLL=010
;--   Colu 15 --->  CCCCC = 01111
;--   Byte bajo: LLLCCCCC = 01001111 = 0100 1111 = 4F
;--   Byte alto: 010TTSSS = 01001000 = 0100 1000 = 48
;--   Direcciones: 4800, 4900, 4A00, 4B00, 4C00, 4D00, 4E00, 4F00

    org $8000

    
    ;--- Dibujar el Sprite
    ld a, $FF
    ld ($484F), a
    ld a, $99
    ld ($494F), a
    ld a, $FF
    ld ($4A4F), a
    ld a, $E7
    ld ($4B4F), a
    ld a, $FF
    ld ($4C4F), a
    ld a, $99
    ld ($4D4F), a
    ld a,$FF
    ld ($4E4F), a
    ld ($4F4F), a
    
    ;-- Atributos
    ld a, $16
    ld ($594F),a 

    ;-- Bucle infinito
inf:
    jr inf

    end $8000

Reto 1-13: sprite 16x16 en posicion (x=10, y=15)

Como último reto vamos a hacer un sprite de 16x16 (4 celdas) situado en la posicion x=10, y=15. El sprite a hacer es el mismo que utilizamos en el curso DEZ80: Un bit '1' corriendo hacia la derecha, pero adaptado a los colores del spectrum

Este es el sprite 16x16:

  • 31-retro-1-13-sprinte-16x16.asm
;---------------------------------------------------------------------------
;-- Reto 1-13: Sprite 16x16 en posicion 10,15
;---------------------------------------------------------------------------

;-- 00000000 = 00  ;-- 00000000 = 00  
;-- 00000000 = 00  ;-- 01110000 = 70
;-- 00000000 = 00  ;-- 11110000 = F0
;-- 00000001 = 01  ;-- 11110000 = F0
;-- 00000011 = 03  ;-- 10110000 = B0
;-- 00000111 = 07  ;-- 00110000 = 30
;-- 00000010 = 02  ;-- 00110000 = 30
;-- 01100000 = 60  ;-- 00110000 = 30


;-- 00011000 = 18  00110000 = 30
;-- 00000000 = 00  00110000 = 30
;-- 01111000 = 78  00110000 = 30
;-- 00000000 = 00  01001000 = 48
;-- 00011000 = 18  01000100 = 44
;-- 01100001 = 61  10000100 = 84
;-- 00000001 = 01  00000110 = 06
;-- 00000000 = 00  00000000 = 00


;-- Atributos: fondo rojo (010). Tinta amarilla (110)
;-- 00 010 110 = 0001 0110 = 16
;-- 
;-- Dir atributos:  5800 + LLLLLCCCCC
;--    Linea 10: 01010 01111 -> 01 0100 1111 = 14F
;--    Col 15:
;--    Dir: 5800 + 14F = 594F

;-- Posicion 10,15:
;--   Linea: 10 --> 01010 (TTLLL) T=01, LLL=010
;--   Colu 15 --->  CCCCC = 01111
;--   Byte bajo: LLLCCCCC = 01001111 = 0100 1111 = 4F
;--   Byte alto: 010TTSSS = 01001000 = 0100 1000 = 48
;--   Direcciones: 484F, 494F, 4A4F, 4B4F, 4C4F, 4D4F, 4E4F, 4F4F

;-- Posicion: 11,15
;--  Linea: 11 --> 01011 (TTLLL) T=01, LLL=011
;--  Col 15 ---> CCCCC = 01111
;--  Byte bajo: LLLCCCCC = 01101111 = 0110 1111 = 6F
;--  Byte alto: 010TTSSS = 01001000 = 0100 1000 = 48

    org $8000

    
    ;--- Dibujar el Sprite
    ;--- 
    ld hl, $0000
    ld ($484F), hl
    ld hl, $7000
    ld ($494F), hl
    ld hl, $F000
    ld ($4A4F), hl
    ld hl, $F001
    ld ($4B4F), hl
    ld hl, $B003
    ld ($4C4F), hl
    ld hl, $3007
    ld ($4D4F), hl
    ld hl,$3002
    ld ($4E4F), hl
    ld hl,$3060
    ld ($4F4F), hl

    ;--- 
    ld hl, $3018
    ld ($486F), hl
    ld hl, $3000
    ld ($496F), hl
    ld hl, $3078
    ld ($4A6F), hl
    ld hl, $4800
    ld ($4B6F), hl
    ld hl, $4418
    ld ($4C6F), hl
    ld hl, $8461
    ld ($4D6F), hl
    ld hl,$0601
    ld ($4E6F), hl
    ld hl,$0000
    ld ($4F6F), hl
    
;-- 00011000 = 18  00110000 = 30
;-- 00000000 = 00  00110000 = 30
;-- 01111000 = 78  00110000 = 30
;-- 00000000 = 00  01001000 = 48
;-- 00011000 = 18  01000100 = 44
;-- 01100001 = 61  10000100 = 84
;-- 00000001 = 01  00000110 = 06
;-- 00000000 = 00  00000000 = 00

    ;-- Atributos
    ld hl, $3939
    ld ($594F),hl 
    ld ($596F),hl

    ;-- Bucle infinito
inf:
    jr inf

    end $8000

Nivel 2

Ahora que ya tengo claro cómo calcular las direcciones de memoria (a pelo) asociadas a la posición de una celda, voy a empezar a hacer animaciones pixel a pixel...

Reto 2.1: Animar un pixel

El reto consiste en animar un pixel rojo que se debe mover 4 posiciones en la primera celda. Posiciones (0,0), (1,0), (2,0) y (3,0) y luego volver a comenzar

Para hacer las esperas utilizamos la instrucción halt que hace que la cpu se pare hasta que llegue la siguiente instrucción. En el ZX spectrum se produce una interrupción periódica cada 20ms, cada vez que el raster llega al final de la pantalla (y tenemos un tiempo para refrescar la memoria de video antes de que se empiece a dibujar el frame)

La dirección de memoria de video a utilizar es siempre la 0x4000, y lo que tenemos que hacer es escribir los siguientes valores: 0x80, 0x40, 0x20 y 0x10, uno por frame. Esto se repite indefinidamente

  • 32-Reto-2-1-pixel-x4.asm
;---------------------------------------------------------------------------
;-- Reto 2-1: Animacion de un pixel a lo largo de 4 posiciones
;---------------------------------------------------------------------------

    org $8000


    ;-- Esperar a que el frame actual termine
    halt

    ;-- Fijar los atributos de la primera celda
    ;-- Fondo: blanco, tinta: roja
    ld a, $3a
    ld ($5800),a

bucle:

    ;-- Pixel en posicion (0,0)
    ld a, $80
    ld ($4000), a
    call wait_100ms

    ;-- Pixel en posicion (1,0)
    ld a, $40
    ld ($4000), a
    call wait_100ms

    ;-- Pixel en posicion (2,0)
    ld a, $20
    ld ($4000), a
    call wait_100ms

    ;-- Pixel en posicion (3,0)
    ld a, $10
    ld ($4000), a
    call wait_100ms

    ;-- Repetir
    jr bucle

;---------------------------------------------------------------------------------
;-- Wait 100ms
;--
;--  Se espera a que lleguen 5 frames. Cada frame llega a los 20ms, por lo que
;--  se hace una espera de unos 100ms
;---------------------------------------------------------------------------------
wait_100ms:

    halt  ;-- 20ms
    halt  ;-- 20ms
    halt  ;-- 20ms
    halt  ;-- 20ms
    halt  ;-- 20ms
    ret 

    end $8000

Si sólo se utiliza una instrucción halt, el movimiento del pixel es muy rápido, y como sólo se mueve por 4 posiciones es mejor ralentizarlo llamando varias veces a halt. He creado una rutina que ejecuta 5 halts, generando una pausa de unos 100ms aproximadamente.

Esta es la animación del pixel

Reto 2.2: Animar un pixel (8 posiciones)

El reto es casi el mismo, pero moviendo el pixel por las 8 posiciones de la primera celda. En vez de escribir 8 valores diferentes, vamos a partir del valor 0x80 y lo rotamos hacia la derecha usando la instrucción rrca

El código es ahora mucho más compacto

  • 33-Reto-2-2-pixel-x8.asm
;---------------------------------------------------------------------------
;-- Reto 2-2: Animacion de un pixel a lo largo de 8 posiciones
;---------------------------------------------------------------------------

    org $8000


    ;-- Esperar a que el frame actual termine
    halt

    ;-- Fijar los atributos de la primera celda
    ;-- Fondo: blanco, tinta: roja
    ld a, $3a
    ld ($5800),a

    ;-- Valor inicial: 0x80
    ld a, $80

bucle:

    ;-- Mostrar pixel en posicion actual
    ld ($4000), a
    call wait_100ms

    ;-- Desplazar A un bit a la derecha
    rrca

    ;-- Repetir
    jr bucle

;---------------------------------------------------------------------------------
;-- Wait 100ms
;--
;--  Se espera a que lleguen 5 frames. Cada frame llega a los 20ms, por lo que
;--  se hace una espera de unos 100ms
;---------------------------------------------------------------------------------
wait_100ms:

    halt  ;-- 20ms
    halt  ;-- 20ms
    halt  ;-- 20ms
    halt  ;-- 20ms
    halt  ;-- 20ms
    ret 

    end $8000

En esta animación se muestra el resultado:

Reto 2.3: Animar una barra de progreso (8 posiciones)

Partimos de una barra de progreso nula (sin píxeles) y en cada iteración añadimos un pixel. Cuando la barra llega al final (8 píxeles) se queda un poco de tiempo en ese estado (para que se vea mejor) y luego vuelve a comenzar

La clave está en que ahora, en vez de una rotación a la derecha, usamos un desplazamento aritmético a la derecha. Es decir, un desplazamiento que conserva el bit de signo.

Así, si el valor del acumulador es 0x80 (10000000 en binario), como es un número negativo, al desplazarlo a la derecha un bit se conserva su signo, siendo el resultado del desplazamiento el valor 0xC0 (11000000)..y así sucesivamente hasta llegar a -1 (11111111). Si se hace un nuevo desplazamiento, el número no cambia -1, pero se activa el flag de C, que usamos para detectar queya se ha completado el progreso

  • 34-Reto-2-3-barra-progreso.asm
;---------------------------------------------------------------------------
;-- Reto 2-3: Animacion Barra de progres de 8 posiciones
;---------------------------------------------------------------------------

    org $8000


    ;-- Esperar a que el frame actual termine
    halt

    ;-- Fijar los atributos de la primera celda
    ;-- Fondo: blanco, tinta: roja
    ld a, $3a
    ld ($5800),a

start:
    ;-- La animación comienza con una barra de progreso nula
    ld a, 0
    ld ($4000),a
    call wait_100ms

    ;-- Para mover la bara usamos un bucle que comienza con el valor 0x80
    ;-- y aplicando desplazamientos aritmeticos a la derecha aparecen
    ;-- el resto de piexeles: 10000000 -> 11000000 -> 11100000 ->...
    ;--    -> 11111110 -> 11111111
    ;-- Si hacemos un desplazamiento más el Flag de C se pone a 1

    ;-- Valor inicial: 0x80
    ld a, $80

bucle:

    ;-- Mostrar pixel en posicion actual
    ld ($4000), a
    call wait_100ms

    ;-- Desplazar A un bit a la derecha 
    ;-- Desplazamiento aritmetico (el bit de signo se conserva)
    sra a

    ;-- Si el desplazamiento se ha completado,
    ;-- lo inicializamos
    jr nc,bucle

    ;-- Proceso completado.
    ;-- Repetimos desde la barra de progreso nula
    ;-- Esperamos un poco mas de tiempo para mostrar que ha finalizado
    call wait_100ms
    call wait_100ms
    call wait_100ms
    jr start

;---------------------------------------------------------------------------------
;-- Wait 100ms
;--
;--  Se espera a que lleguen 5 frames. Cada frame llega a los 20ms, por lo que
;--  se hace una espera de unos 100ms
;---------------------------------------------------------------------------------
wait_100ms:

    halt  ;-- 20ms
    halt  ;-- 20ms
    halt  ;-- 20ms
    halt  ;-- 20ms
    halt  ;-- 20ms
    ret 

    end $8000

Este es el resultado:

Reto 2.4: Animar un sprite de 8x8

El reto es animar un sprite de 8x8, con al menos 4 fotogramas. Vamos a utilizar el mismo ejemplo que para el curso DEZ80: una máquina que tiene un elemento que sube y baja. En total necesitamos 3 dibujos diferenes: La máquina en reposo, la máquina con el elemento a medio camino y la máquina con el elmeento totalmente bajado

Los sprites se han dibujado con Inkscape:

Hay 3 dibujos pero 4 Fotogramas (porque el dibujo 1 se repite en los fotogramas 1 y 3)

  • 35-Reto-2-4-sprite-8x8.asm

El código NO OPTIMIZADO es este. Los sprites se dibuja "a pelo", y para el fotograma 3 he hecho un "copy&paste" (Son las mismas instrucciones que para el fotograma 1)

;---------------------------------------------------------------------------
;-- Reto 2-4: Animacion de un sprite 8x8
;---------------------------------------------------------------------------

    org $8000


    ;-- Esperar a que el frame actual termine
    halt

    ;-- Fijar los atributos de la primera celda
    ;-- Fondo: blanco, tinta: azul
    ld a, $39
    ld ($5800),a


bucle:
    ;-- Pintar fotograma 1
    ;-- 01111110 = 7E
    ;-- 00011000 = 18
    ;-- 00011000 = 18 
    ;-- 00011000 = 18
    ;-- 00011000 = 18
    ;-- 01111110 = 7E
    ;-- 00111100 = 3C
    ;-- 00011000 = 18

    ld a, $7E
    ld ($4000), a
    ld a, $18
    ld ($4100), a
    ld ($4200), a
    ld ($4300), a
    ld ($4400), a
    ld a, $7E
    ld ($4500), a
    ld a, $3C
    ld ($4600), a
    ld a, $18
    ld ($4700), a

    call wait_100ms

    ;-- Pintar fotograma 2
    ;-- 0000 0000 = 00
    ;-- 0111 1110 = 7E
    ;-- 0001 1000 = 18
    ;-- 0001 1000 = 18
    ;-- 0001 1000 = 18
    ;-- 0111 1110 = 7E
    ;-- 0011 1100 = 3C
    ;-- 0001 1000 = 18

    ld a, $00
    ld ($4000), a
    ld a, $7E
    ld ($4100), a
    ld a, $18
    ld ($4200), a
    ld ($4300), a
    ld ($4400), a
    ld a, $7E
    ld ($4500), a
    ld a, $3C
    ld ($4600), a
    ld a, $18
    ld ($4700), a

    call wait_100ms

    ;-- Pintar fotograma 3
    ;-- 0000 0000 = 00
    ;-- 0000 0000 = 00
    ;-- 0111 1110 = 7E
    ;-- 0001 1000 = 18
    ;-- 0001 1000 = 18
    ;-- 0111 1110 = 7E
    ;-- 0011 1100 = 3C
    ;-- 0001 1000 = 18

    
    ld a, $00
    ld ($4000), a
    ld a, $00
    ld ($4100), a
    ld a, $7E
    ld ($4200), a
    ld a, $18
    ld ($4300), a
    ld ($4400), a
    ld a, $7E
    ld ($4500), a
    ld a, $3C
    ld ($4600), a
    ld a, $18
    ld ($4700), a

    call wait_100ms

    ;-- Pintar de nuevo fotograma 2
    ld a, $00
    ld ($4000), a
    ld a, $7E
    ld ($4100), a
    ld a, $18
    ld ($4200), a
    ld ($4300), a
    ld ($4400), a
    ld a, $7E
    ld ($4500), a
    ld a, $3C
    ld ($4600), a
    ld a, $18
    ld ($4700), a

    call wait_100ms

    jp bucle


;---------------------------------------------------------------------------------
;-- Wait 100ms
;--
;--  Se espera a que lleguen 5 frames. Cada frame llega a los 20ms, por lo que
;--  se hace una espera de unos 100ms
;---------------------------------------------------------------------------------
wait_100ms:

    halt  ;-- 20ms
    halt  ;-- 20ms
    halt  ;-- 20ms
    halt  ;-- 20ms
    halt  ;-- 20ms
    ret 

    end $8000

Este es el resultado:

Ahora vamos a mejorar el código. Los sprites nos los llevamos como datos a memoria, y creamos la rutina draw_sprite que dibuja el sprite indicado en la dirección de memoria de video (alineada) indicada

  • 36-Reto-2-4-sprite-8x8.asm
;---------------------------------------------------------------------------
;-- Reto 2-4: Animacion de un sprite 8x8
;-- Vesión 2: más optmizada.....
;---------------------------------------------------------------------------

    org $8000


    ;-- Esperar a que el frame actual termine
    halt

    ;-- Fijar los atributos de la primera celda
    ;-- Fondo: blanco, tinta: azul
    ld a, $39
    ld ($5800),a

bucle:
    ;-- Fotograma 0
    ld hl, $4000    ;-- Posicion 0,0
    ld de, sprite_0
    call draw_sprite
    call wait_100ms

    ;-- Fotograma 1
    ld hl, $4000
    ld de, sprite_1
    call draw_sprite
    call wait_100ms

    ;-- Fotograma 2
    ld hl, $4000
    ld de, sprite_2
    call draw_sprite
    call wait_100ms

    ;-- Fotograma 3
    ld hl, $4000
    ld de, sprite_1
    call draw_sprite
    call wait_100ms

    jr bucle

   

;----------------------------------------------------------------------
;-- Dibujar sprite
;--
;-- ENTRADAS:
;--   * HL: Direccion de la memoria de video (Alineada a celda)
;--   * DE: Direccion del sprite
;;---------------------------------------------------------------------
draw_sprite:

    ;-- Numero de bytes del sprite
    ld b, 8

    ;-- Est bucle pinta el sprite
    ;-- byte a byte. Se repite 8 veces
loop:
    ;-- Leer byte
    ld a, (de)

    ;-- Guardarlo en memoria de video
    ld (hl), a 

    ;-- Apuntar al siguient byte del sprite
    inc de

    ;-- Apuntar a la siguiente scanline
    inc h

    ;-- Repetir
    djnz loop


    ret

sprite_0:
    defb $7E, $18, $18, $18, $18, $7E, $3C, $18 

sprite_1:
    defb $00, $7E, $18, $18, $18, $7E, $3C, $18

sprite_2:
    defb $00, $00, $7E, $18, $18, $7E, $3C, $18


;---------------------------------------------------------------------------------
;-- Wait 100ms
;--
;--  Se espera a que lleguen 5 frames. Cada frame llega a los 20ms, por lo que
;--  se hace una espera de unos 100ms
;---------------------------------------------------------------------------------
wait_100ms:

    halt  ;-- 20ms
    halt  ;-- 20ms
    halt  ;-- 20ms
    halt  ;-- 20ms
    halt  ;-- 20ms
    ret 

    end $8000

El resultado es el mismo que en la animación anterior

Reto 2.5: Disparo laser con avance de 8 píxeles

  • 37-Reto-2-5-disparo-x8.asm
;---------------------------------------------------------------------------
;-- Reto 2-5: Disparo láser con avance de 8 píxeles
;---------------------------------------------------------------------------

    org $8000

start:
    ;-- Posiciones a avanzar la bala
    ld b,20

    ;-- Puntero a la posicion de la bala
    ld hl, $4000

    ;-- Puntero a los atributos de la bala
    ld de, $5800

    ;-- Dibujar la bala inicial
    ld a, $FF
    ld (hl), a

    ;-- Atributos
    ld a, $3a
    ld (de), a

    call wait

bucle:

    ;-- Borrar la bala
    ld a, 0
    ld (hl), a

    ;-- Incrementar posicion y puntero de los atributos
    inc hl
    inc de

    ;-- Dibujar la bala
    ld a, $FF
    ld (hl), a

    ;-- Atributos
    ld a, $3a
    ld (de), a

    call wait

    ;-- Repetir
    djnz bucle

    ;-- Borrar la bala
    ld a,0
    ld (hl), a

    ;-- Espera mayor
    call wait
    call wait
    call wait
    call wait
    call wait
    call wait

    jp start


;---------------------------------------------------------------------------------
;-- Wait 
;--
;--  Se espera a que lleguen N frames. Cada frame llega a los 20ms, por lo que
;--  se hace una espera de unos 100ms
;---------------------------------------------------------------------------------
wait:

    halt  ;-- 20ms
    halt  ;-- 20ms
    halt
    halt
    ret 

    end $8000

Reto 2.6: Barra de progreso de 40 bits

  • 38-Reto-2-6-barra-x40.asm
;---------------------------------------------------------------------------
;-- Reto 2-6: Barra de progreso de 32 bits
;---------------------------------------------------------------------------

    org $8000

    ;-- Sincronizarse con las interrupciones
    halt

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

    ;-- Puntero a la memoria de atributos
    ld de, $5800

    ;-- Numero de celdas totales de la barra
    ld b, 5

next:
    ;-- Meter atributo
    ld a,$39
    ld (de), a

    ;-- Barra de progreso en una celda
    ;-- Meter primer valor
    ld a,$80

loop;
    ld (hl), a
    call wait

    ;-- Desplazar a la derecha
    sra a

    ;-- Repetir hasta completar el progreso en la celda actual
    jr nc,loop

    ;-- Pasar a la siguiente celda
    inc hl

    ;-- Pasar al siguiente atributo
    inc de

    ;-- Repetir hasta completar el tamaño de la celda
    djnz next

    ;-- Esperar un tiempo hasta re-arrancar la barra de progreso
    ld b,20
wait_loop:
    call wait
    djnz wait_loop

    ;-- Borrar la barra de progreso
    ld b,5
    ld hl, $4000

clear_next:
    ld a,0
    ld (hl), a
    inc hl
    djnz clear_next
    
    jp start

;---------------------------------------------------------------------------------
;-- Wait 
;--
;--  Se espera a que lleguen N frames. Cada frame llega a los 20ms, por lo que
;--  se hace una espera de unos 100ms
;---------------------------------------------------------------------------------
wait:

    halt  ;-- 20ms
    ;halt  ;-- 20ms
    ret 

    end $8000

Experimentos

Antes de continuar con los retos, voy a hacer algunos experimentos por mi cuenta. Quiero aprender a trabajar a nivel de pixeles...

Posicionamiento x de pixel en la celda (0,0)

Voy a empezar por resolver el problema de cómo posicionar un pixel en la primera celda, en cualquiera de sus posicones horizontales de la primera scanline. Esta rutina la voy a llamar plotx(p). Como argumento se le pasa la posición del pixel en esa celda: 0-7

b7 b6 b5 b4 b3 b2 b1 b0
pos 0 pos 1 pos 2 pos 3 pos 4 pos 5 pos 6 pos 7

Así por ejemplo, si hacemos plox(0), se escribe un pixel en la primera posición (0,0):

  • plotx(0): 1 0 0 0 0 0 0 0
  • plotx(1): 0 1 0 0 0 0 0 0
  • plotx(2): 0 0 1 0 0 0 0 0
  • plotx(3): 0 0 0 1 0 0 0 0
  • ...
  • plotx(7): 0 0 0 0 0 0 0 1

En esta primera versión se borra lo que haya (se pone el pixel y el resto se dejan a cero). En las siguientes versiones se permitirá situar el pixel junto al resto de elementos

  • 39-plotx-scan1-celda-0-0.asm
;---------------------------------------------------------------------------
;-- Experimento. Dibujo de un pixel en el primer scanline de la celda (0,0)
;-- Para probar la rutina plotx() se hace una animación del pixel, moviéndose
;-- entre las posiciones 0 y 7 de la celda (0,0) (Esquina superior izquierda)
;---------------------------------------------------------------------------

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

    org $8000

    ;-- Poner borde amarillo para ver mejor la posicion
    ;-- de los píxeles
    ld a, 6
    out (BORDER), a
 
    ;-- Entrada: HL: Memoria de video
    ld hl, $4000  ;-- Celda (0,0)

    ;-- Entrada B = Posicion del pixel (0-7)
    ld b, 0

bucle
    push bc

    ;-- Dibujar el pixel!
    call plotx

    ;-- Esperar un tiempo
    call wait

    ;-- Incrementar la posicion
    pop bc
    inc b

    ;-- La posicion queremos que sea siempre modulo 8 
    ;-- (entre 0 y 7)
    ld a,b
    and 7
    ld b,a

    ;-- Repetir
    jr bucle



;-----------------------------------------------------------
;-- PLOTX(HL,pos)
;--
;-- ENTRADAS:
;--   * HL: Direccion de la memoria de video
;--   * B: Posicion del pixel (0-7) en el scanline 0 de la celda
;-----------------------------------------------------------------
plotx:
    ;-- Si es la posicion 0, se escribe el valor $80 y se termina
    ld a,b
    or a    ;-- Actualizar flag z para comproar si A es 0
    jr z,pos0  ;-- A=0, es la posicion 0

    ;-- NO es la posicion 0
    ;-- Partimos del valor $80 y lo desplazamos B posiciones
    ;-- a la derecha
    ld a, $80

to_pos
    rrca 
    djnz to_pos

    ;-- En A tenemos el valor a escribir en memoria de video
    ;-- Escribir!
    ld (hl), a
    jr fin


pos0:
    ;-- Valor a escribir en memoria video
    ld a, $80
    ld (hl), a


fin:
    ret

;----------------------------------
;-- Esperar N frames...
;-- Cada frame son 20ms aprox...
;----------------------------------
wait:
    halt  ;-- 20 ms
    halt
    halt
    halt
    halt
    ret

    end $8000

Para comprobar que la función plotx() funciona correctamente se hace una animación, para mover el pixel desde la posición 0 a la 7 (y volver a empezar). Pero ahora se hace pasando la posición del pixel (0-7). En esta animación se muestra el resultado:

Posicionamiento x de pixel en cualquier posición 0-255 del primer scanline

Ahora ampliamos la rutina de posicionamiento plotx() para permitir dibujar un pixel en cualquiera de las 256 posiciones del primer scanline... Como ejemplo vamos a animar un pixel para que se mueva en esta primera línea...

En total, en cada scanline hay 256 píxeles, desde el pixel 0 al 255. Es una coordenada x de 8 bits, que se descompone en dos campos. Uno de 5 bits: la celda, y otro de 3 bits (el pixel dentro de la celda)

b7 b6 b5 b4 b3 b2 b1 b0
C C C C C P P P

Con el primer campo, CCCCC, se calcula la dirección base de la memoria de vídeo, y con el segundo PPP se pinta el pixel en esa celda

  • 40-plotx-scan1.asm
;---------------------------------------------------------------------------
;-- Experimento. Dibujo de un pixel en el primer scanline. Posiciones 0-255
;-- Para probar la rutina plotx() se hace una animación del pixel, moviéndose
;-- entre las posiciones 0 y 255
;---------------------------------------------------------------------------

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

    org $8000

    ;-- Poner borde amarillo para ver mejor la posicion
    ;-- de los píxeles
    ld a, 6
    out (BORDER), a
 
start:

    ;-- Entrada: HL: Memoria de video
    ld hl, $4000

    ;-- Entrada B = Posicion del pixel (0-255)
    ld b, 0

bucle
    push bc

    ;-- Dibujar el pixel!
    call plotx

    ;-- Esperar un tiempo
    call wait

    ;-- Incrementar la posicion
    pop bc
    inc b

    ;-- Repetir
    jr nz, bucle

    ;-- La linea ha llegado al final
    ;-- La borramos, para volver a comenzar
    ;-- Longitud: 32 celdas
    ld b,32
    ld hl, $4000

    ;-- Valor a guardar en la memoria
    ld a,0

    ;-- Bucle de borrado
clear:
    ld (hl),a
    inc l
    djnz clear

    ;-- Comenzar!
    jp start


;--------------------------------------------------------------
;-- PLOTX(HL, POS)  (Pos: 0-255)
;-- Dibujar un pixel en la posicion 0-255 de una scanline
;--
;-- ENTRADA:
;--    * HL: Direccion base de la memoria de video
;--    * B: Posicion x: 0 - 255
;--------------------------------------------------------------
plotx:
    push hl

    ld a,b

    ;-- Desplazamiento a la derecha 3 posiciones
    ;-- Para tener el campo CCCCC
    sra a
    sra a
    sra a
    and $1F  ;-- Quedarse con los 5 bits de menor peso (000CCCCC)

    ;-- Sumar CCCCC a la dirección base
    add a,l
    ld l,a   ;-- HL apunta a la celda CCCCC (0-31)

    ld a,b   ;-- Obtener el campo PPP
    and 7
    ld b,a 

    ;-- Dibujar el pixel
    call plotxp

    pop hl
    ret


;-----------------------------------------------------------
;-- PLOTXP(HL,pos) Meter pixel en una celda (pos: 0-7)
;--
;-- ENTRADAS:
;--   * HL: Direccion de la memoria de video
;--   * B: Posicion del pixel (0-7) en el scanline 0 de la celda
;-----------------------------------------------------------------
plotxp:
    ;-- Si es la posicion 0, se escribe el valor $80 y se termina
    ld a,b
    or a    ;-- Actualizar flag z para comproar si A es 0
    jr z,pos0  ;-- A=0, es la posicion 0

    ;-- NO es la posicion 0
    ;-- Partimos del valor $80 y lo desplazamos B posiciones
    ;-- a la derecha
    ld a, $80

to_pos
    rrca 
    djnz to_pos

    ;-- En A tenemos el valor a escribir en memoria de video
    ;-- Escribir!
    jr plot


pos0:
    ;-- Valor a escribir en memoria video
    ld a, $80

plot:
    ;-- Primer leemos lo que hay
    ld b,(hl)

    ;-- Aplicamos un or para añdir a
    or b

    ;-- Guardamos el nuevo valor
    ld (hl), a


fin:
    ret

;----------------------------------
;-- Esperar N frames...
;-- Cada frame son 20ms aprox...
;----------------------------------
wait:
    halt  ;-- 20 ms
    ret

    end $8000

Este es el resultado:

Posicionamiento Y de pixel en la primera celda (0,0)

Ahora hacemos la función ploty(x,y) que permite posicionar un pixel dentro de la primera celda, en cualquier posición x,y (con x,y en el rango 0-7). Como ejemplo se rellenan las líneas verticales de esta primera celda

  • 41-ploty-celda0.asm
;---------------------------------------------------------------------------
;-- Experimento. Dibujo de lineas vertical en la primera celda (0,0)
;---------------------------------------------------------------------------

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

    org $8000

    ;-- Poner borde amarillo para ver mejor la posicion
    ;-- de los píxeles
    ld a, 6
    out (BORDER), a

start:
    ;-- Memoria de video
    ld hl, $4000

    ld b,0  ;-- x
    ld c,0  ;-- y

loop:
    ;-- Pintar pixel en posicion actual
    push hl
    push bc
    call pixely
    pop bc
    pop hl

    call wait

    ;-- Incrementar posicion
    inc c

    ;-- Restringir la posicion a 0-7 (modulo 8)
    ld a,c
    and 7
    ;-- Si la linea llega al final... pasar a la siguiente linea vertical
    jr z, next_col
    ld c,a

    jr loop

next_col:
    ;-- Pasar a la siguiente columna
    inc b

    ;-- Incremento modulo 8
    ld a,b
    and 7
    jr z,fin  ;-- Si es la linea 7 hemos terminado
    ld b,a

    ;-- Volver a y=0
    ld c,0

    jr loop

fin:
    ;-- Borrar la celda 0,0
    ld b,8
    ld a,0
    ld hl,$4000
clear:
    ld (hl),a
    inc h
    djnz clear

    ;-- Volver a empezar
    call wait
    call wait
    jp start


;----------------------------------------------------------
;-- PIXEL_Y(y)
;--
;-- ENTRADAS:
;--   * HL: Direccion de la memoria de video
;--   * B: Posicion x del pixel (0-7)
;--   * C: Posicion y del pixel (0-7)
;-----------------------------------------------------------
pixely:
    ;-- Valor correspondiente al pixel de la posicion b
    
    call pixel_mask

    ;--- h = h + c
    push aF  ;-- guardar a para no perderlo
    ld a, h
    add a,c
    ld h,a   ;-- h = h + c
    pop af

    ;-- Dibujar pixel (over)
    ld b,a
    ld a,(hl)  ;-- Leer valor actual
    or b       ;-- Añadir nuevo pixel
    ld (hl), a

    ret
 


;-----------------------------------------------------------
;-- PIXEL_MASK(x): Devolver la máscara de pixel a escribir en la
;--   memoria de video de la posición x (0-7)
;--
;-- ENTRADAS:
;--   * HL: Direccion de la memoria de video
;--   * B: Posicion x del pixel (0-7) en el scanline 0 de la celda
;--
;-- DEVUELVE:
;--   * A: Valor a escribir en la memoria de video
;-----------------------------------------------------------------
pixel_mask:
    ;-- Si es la posicion 0, se escribe el valor $80 y se termina
    ld a,b
    or a    ;-- Actualizar flag z para comproar si A es 0
    jr z,pos0  ;-- A=0, es la posicion 0

    ;-- NO es la posicion 0
    ;-- Partimos del valor $80 y lo desplazamos B posiciones
    ;-- a la derecha
    ld a, $80

to_pos
    rrca 
    djnz to_pos

    ;-- En A tenemos el valor a escribir en memoria de video
    ;-- Devolverlo
    ret


pos0:
    ;-- Valor a escribir en memoria video
    ld a, $80
    ret

;----------------------------------
;-- Esperar N frames...
;-- Cada frame son 20ms aprox...
;----------------------------------
wait:
    halt  ;-- 20 ms
    halt  ;-- 20 ms
    halt  ;-- 20 ms
    halt  ;-- 20 ms
    halt  ;-- 20 ms
    ret

    end $8000

Este es el resultado:

Rutina de Plot(x,y)

Esta es la rutina completa de plot(x,y) para píxeles, con x en el rango (0,255) e y en el rango (0,191). Hay dos versiones: plot_set() y plot_clear(). La primer pone un pixle y la segunda lo elimina

  • 42-pixel-xy-1.asm

En este programa de pruebas se recorren todos los píxeles de la pantalla, uno a uno, y se ponen negros. Luego se repite pero poniéndolos bancos, para comprobar ambas rutinas

;---------------------------------------------------------------------------
;-- Pruebas de plot(x,y) en diferentes puntos
;---------------------------------------------------------------------------

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

    org $8000

    ;-- Poner borde amarillo para ver mejor la posicion
    ;-- de los píxeles
    ld a, 6
    out (BORDER), a

    ;-- Posicion inicial: (0,0)
    ld b,0  ;-- x
    ld c,0  ;-- y

    ;-- FASE 1: Poner todos los pixeles de la pantalla de negro, uno a uno
    ;-- Recorrer todos los pixeles de la pantalla
    ;-- B: 0-255
    ;-- C: 0-191
fase1:

    ;-- Pintar pixel
    call plot_set

    ;-- Esperar un frame (20ms)
    ;-- Se deja comentado para ir mas rapido
    ;halt

    ;-- Incrementar la coordenada x (modulo 256)
    inc b

    ;-- Repetir mientras b <=255
    jr nz,fase1

    ;-- Pasar a la siguiente linea
    inc c

    ;-- Si es la linea 192, hemos llegado al final: terminar
    ld a,c
    cp 192
    ;-- c < 192, repetir
    jr nz,fase1

    ;-- Hemos terminado
    ;-- Nos quedamos en bucle infinito
    ;-- La pantalla debe estar en negro

    ;-- Posicion inicial: (0,0)
    ld b,0  ;-- x
    ld c,0  ;-- y


    ;-- FASE 2: Poner todos los pixeles de la pantalla de blanco, uno a uno
    ;-- Recorrer todos los pixeles de la pantalla
    ;-- B: 0-255
    ;-- C: 0-191
fase2:

    ;-- Borrar pixel
    call plot_clear

    ;-- Esperar un frame (20ms)
    ;-- Se deja comentado para ir mas rapido
    ;halt

    ;-- Incrementar la coordenada x (modulo 256)
    inc b

    ;-- Repetir mientras b <=255
    jr nz,fase2

    ;-- Pasar a la siguiente linea
    inc c

    ;-- Si es la linea 192, hemos llegado al final: terminar
    ld a,c
    cp 192
    ;-- c < 192, repetir
    jr nz,fase2

    ;-- Hemos terminado
    ;-- Nos quedamos en bucle infinito
    ;-- La pantalla debe estar en negro

inf: jr inf

;-----------------------------------------------------------
;-- plot_clear (x,y): Borrar un pixel de la pantalla
;--
;-- ENTRADAS:
;--   * B: Coordenada x (0-255)
;--   * C: Coordenada y (0-192)
;------------------------------------------------------------
plot_clear:
    ;-- Guardar posicion del pixel
    push bc

    ;-- Dejar en HL la direccion de la memoria de video 
    ;-- asociada a la posicion B=x, C=y
    call get_videoRam

    ;-- Recuperar posicion del pixel
    pop bc
    push bc ;-- Volver a guardarla, para luego

    ;-- Obtener la máscara del pixel
    call pixel_mask
    xor $FF

    ;-- Escribir el pixel!!
    ;-- Modo OVER
    ld d,a
    ld a, (hl) ;-- Leer el valor actual de la memoria
    and d       ;-- Restar el nuevo pixel
    ld (hl),a  ;-- Escribirlo!

    ;-- Recuperar coordenadas
    pop bc


    ret

;-----------------------------------------------------------
;-- plot_set(x,y): Pintar un pixel en pantalla (modo OVER)
;--
;-- ENTRADAS:
;--   * B: Coordenada x (0-255)
;--   * C: Coordenada y (0-192)
;------------------------------------------------------------
plot_set:
   ;-- Guardar posicion del pixel
    push bc

    ;-- Dejar en HL la direccion de la memoria de video 
    ;-- asociada a la posicion B=x, C=y
    call get_videoRam

    ;-- Recuperar posicion del pixel
    pop bc
    push bc ;-- Volver a guardarla, para luego

    ;-- Obtener la máscara del pixel
    call pixel_mask

    ;-- Escribir el pixel!!
    ;-- Modo OVER
    ld d,a
    ld a, (hl) ;-- Leer el valor actual de la memoria
    or d       ;-- Añdir el nuevo pixel
    ld (hl),a  ;-- Escribirlo!

    ;-- Recuperar coordenadas
    pop bc
    ret

;----------------------------------------------------------------------------
;-- GET_VIDEORAM:  Obtener la memoria de video asociada a una posicion 
;--    de pixel
;--
;-- ENTRADAS:
;--    * B: Coordenada x (0-255)
;--    * C: Coordenada y (0-192)
;-- DEVUELVE:
;--    * HL: Direccio de la memoria de video
;---------------------------------------------------------------------------
get_videoRam:
    ;-- HL = Direccion de video = 010TTSSS LLLCCCCC
    ;-- Convertir B y C a dirección en HL
    ;-- B = Coordenada x = CCCCCPPP
    ;-- C = Coordenada y = TTLLLSSS

    ;--------- Aislar campo CCCCC de B
    ld a,b
    ;-- Desplazar tres bits a la derecha
    sra a  ;-- XCCCCCPP
    sra a  ;-- XXCCCCCP
    sra a  ;-- XXXCCCCC
    and $1F  ;-- 000CCCCC
    ld l,a ;-- L = 000CCCCC

    ;-- Aislar LLL en los bits mas significativos
    ld a,c   ;-- TTLLLSSS
    rlca     ;-- TLLLSSS0
    rlca     ;-- LLLSSS00
    and $E0  ;-- LLL00000

    ;-- Añadir campo CCCCC
    or l     ; A = LLLCCCCC
    ld l,a   ;-- L = LLLCCCCC  (Completado!)

    ;-- Aislar y colocar campo TT
    ld a,c   ; TTLLLSSS
    sra a    ; xTTLLLSS
    sra a    ; xxTTLLLS
    sra a    ; xxxTTLLL
    and $18  ; 000TT000
    ld h,a   ; H = 000TT000

    ;-- Aislar campo SSS
    ld a,c      ; A = TTLLLSSS
    and $07     ;     00000SSS
    or h        ;     000TTSSS
    add a, $40  ;     010TTSSS
    ld h,a      ; H = 010TTSSS (Completado!)

    ;-- HL CONTIENE LA DIRECCION
    ret

;-----------------------------------------------------------
;-- PIXEL_MASK(x): Devolver la máscara de pixel a escribir en la
;--   memoria de video de la posición x (0-7)
;--
;-- ENTRADAS:
;--   * B: Posicion x del pixel (0-7) en el scanline 0 de la celda
;--
;-- DEVUELVE:
;--   * A: Valor a escribir en la memoria de video
;-----------------------------------------------------------------
pixel_mask:
    ;-- Si es la posicion 0, se escribe el valor $80 y se termina
    ld a,b
    or a    ;-- Actualizar flag z para comproar si A es 0
    jr z,pos0  ;-- A=0, es la posicion 0

    ;-- NO es la posicion 0
    ;-- Partimos del valor $80 y lo desplazamos B posiciones
    ;-- a la derecha
    ld a, $80

to_pos
    rrca 
    djnz to_pos

    ;-- En A tenemos el valor a escribir en memoria de video
    ;-- Devolverlo
    ret


pos0:
    ;-- Valor a escribir en memoria video
    ld a, $80
    ret

    end $8000

En esta animación se muestra el funcionamiento. La demo se ha restringido sólo a las primeras líneas, para que no tarde mucho:

Partícula de 1 pixel confinada en celda 0,0

  • 43-particula-celda.asm
;---------------------------------------------------------------------------
;-- Pruebas de plot(x,y) en diferentes puntos
;---------------------------------------------------------------------------

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

    org $8000

    ;-- Poner borde amarillo para ver mejor la posicion
    ;-- de los píxeles
    ld a, 6
    out (BORDER), a

    ld b,0 ;-- x
    ld c,4 ;-- y

    call plot_set

bucle:
    call wait

    ;-- Borrar pixel
    call plot_clear

    ;----------------------------------------
    ;-- Actualizar posicion horizontal (x)
    ;----------------------------------------
    ;-- Calcular la nueva posicion del pixel segun la velocidad vx
    ld a, (vx)
    add a,b

    ;-- Comprobar colision a la derecha
    cp 8
    jr nz, continue

    ;-- Colision!
    ;-- Cambiar el signo de la velocidad
    ld a, (vx)
    neg
    ld (vx), a  ;-- Guardar nueva velocidad
    add a,b     ;-- Calcular nueva posicion

continue:

    ;-- Actualizar a la nueva posicion
    ld b,a  ;--- B = B + vx

    ;-- Comprobar colision a la izquirda
    or a  ; a=0?
    jr nz, continue2
    
    ;-- Colision!
    ;-- Cambiar signo de la velocidad
    ld a, (vx)
    neg
    ld (vx), a
    add a,b

continue2:

    ;----------------------------------------
    ;-- Actualizar posicion vertical (y)
    ;----------------------------------------
     ;-- Calcular la nueva posicion del pixel segun la velocidad vy
    ld a, (vy)
    add a,c

    ;-- Comprobar colision inferior
    cp 8
    jr nz, continue3

    ;-- Colision!
    ;-- Cambiar el signo de la velocidad
    ld a, (vy)
    neg
    ld (vy), a  ;-- Guardar nueva velocidad
    add a,c     ;-- Calcular nueva posicion

continue3:

     ;-- Actualizar a la nueva posicion
    ld c,a  ;--- B = B + vy

    ;-- Comprobar colision a la izquirda
    or a  ; a=0?
    jr nz, continue4
    
    ;-- Colision!
    ;-- Cambiar signo de la velocidad
    ld a, (vy)
    neg
    ld (vy), a
    add a,c

continue4:

    ;-- Dibujar el pixel
    call plot_set

    jr bucle

;-- Velocidad
vx: defb 1 
vy: defb 1

;-----------------------------------------------------------
;-- plot_clear (x,y): Borrar un pixel de la pantalla
;--
;-- ENTRADAS:
;--   * B: Coordenada x (0-255)
;--   * C: Coordenada y (0-192)
;------------------------------------------------------------
plot_clear:
    ;-- Guardar posicion del pixel
    push bc

    ;-- Dejar en HL la direccion de la memoria de video 
    ;-- asociada a la posicion B=x, C=y
    call get_videoRam

    ;-- Recuperar posicion del pixel
    pop bc
    push bc ;-- Volver a guardarla, para luego

    ;-- Obtener la máscara del pixel
    call pixel_mask
    xor $FF

    ;-- Escribir el pixel!!
    ;-- Modo OVER
    ld d,a
    ld a, (hl) ;-- Leer el valor actual de la memoria
    and d       ;-- Restar el nuevo pixel
    ld (hl),a  ;-- Escribirlo!

    ;-- Recuperar coordenadas
    pop bc


    ret

;-----------------------------------------------------------
;-- plot_set(x,y): Pintar un pixel en pantalla (modo OVER)
;--
;-- ENTRADAS:
;--   * B: Coordenada x (0-255)
;--   * C: Coordenada y (0-192)
;------------------------------------------------------------
plot_set:
   ;-- Guardar posicion del pixel
    push bc

    ;-- Dejar en HL la direccion de la memoria de video 
    ;-- asociada a la posicion B=x, C=y
    call get_videoRam

    ;-- Recuperar posicion del pixel
    pop bc
    push bc ;-- Volver a guardarla, para luego

    ;-- Obtener la máscara del pixel
    call pixel_mask

    ;-- Escribir el pixel!!
    ;-- Modo OVER
    ld d,a
    ld a, (hl) ;-- Leer el valor actual de la memoria
    or d       ;-- Añdir el nuevo pixel
    ld (hl),a  ;-- Escribirlo!

    ;-- Recuperar coordenadas
    pop bc
    ret

;----------------------------------------------------------------------------
;-- GET_VIDEORAM:  Obtener la memoria de video asociada a una posicion 
;--    de pixel
;--
;-- ENTRADAS:
;--    * B: Coordenada x (0-255)
;--    * C: Coordenada y (0-192)
;-- DEVUELVE:
;--    * HL: Direccio de la memoria de video
;---------------------------------------------------------------------------
get_videoRam:
    ;-- HL = Direccion de video = 010TTSSS LLLCCCCC
    ;-- Convertir B y C a dirección en HL
    ;-- B = Coordenada x = CCCCCPPP
    ;-- C = Coordenada y = TTLLLSSS

    ;--------- Aislar campo CCCCC de B
    ld a,b
    ;-- Desplazar tres bits a la derecha
    sra a  ;-- XCCCCCPP
    sra a  ;-- XXCCCCCP
    sra a  ;-- XXXCCCCC
    and $1F  ;-- 000CCCCC
    ld l,a ;-- L = 000CCCCC

    ;-- Aislar LLL en los bits mas significativos
    ld a,c   ;-- TTLLLSSS
    rlca     ;-- TLLLSSS0
    rlca     ;-- LLLSSS00
    and $E0  ;-- LLL00000

    ;-- Añadir campo CCCCC
    or l     ; A = LLLCCCCC
    ld l,a   ;-- L = LLLCCCCC  (Completado!)

    ;-- Aislar y colocar campo TT
    ld a,c   ; TTLLLSSS
    sra a    ; xTTLLLSS
    sra a    ; xxTTLLLS
    sra a    ; xxxTTLLL
    and $18  ; 000TT000
    ld h,a   ; H = 000TT000

    ;-- Aislar campo SSS
    ld a,c      ; A = TTLLLSSS
    and $07     ;     00000SSS
    or h        ;     000TTSSS
    add a, $40  ;     010TTSSS
    ld h,a      ; H = 010TTSSS (Completado!)

    ;-- HL CONTIENE LA DIRECCION
    ret

;-----------------------------------------------------------
;-- PIXEL_MASK(x): Devolver la máscara de pixel a escribir en la
;--   memoria de video de la posición x (0-7)
;--
;-- ENTRADAS:
;--   * B: Posicion x del pixel (0-7) en el scanline 0 de la celda
;--
;-- DEVUELVE:
;--   * A: Valor a escribir en la memoria de video
;-----------------------------------------------------------------
pixel_mask:
    ;-- Si es la posicion 0, se escribe el valor $80 y se termina
    ld a,b
    or a    ;-- Actualizar flag z para comproar si A es 0
    jr z,pos0  ;-- A=0, es la posicion 0

    ;-- NO es la posicion 0
    ;-- Partimos del valor $80 y lo desplazamos B posiciones
    ;-- a la derecha
    ld a, $80

to_pos
    rrca 
    djnz to_pos

    ;-- En A tenemos el valor a escribir en memoria de video
    ;-- Devolverlo
    ret


pos0:
    ;-- Valor a escribir en memoria video
    ld a, $80
    ret


;----------------------------------
;-- Esperar N frames...
;-- Cada frame son 20ms aprox...
;----------------------------------
wait:
    halt  ;-- 20 ms
    halt  
    halt
    halt
    halt
    halt
    ret

    end $8000

Dos particulas confinadas

Ahora es el momento de extender el movimiento de la partícula a dos. La clave está en que ahora se definen los datos de la particula en una estructura y se hacen funciones para trabajar con esas estructuras

  • 44-particulas-2.asm
;---------------------------------------------------------------------------
;-- Dos partículas confinadas en las celdas (0,0) y (1,0)
;---------------------------------------------------------------------------

;--- Constantes de acceso para las entidades
POSX EQU 0
POSY EQU 1
VX   EQU 2
VY   EQU 3
CXMIN EQU 4
CXMAX EQU 5
CYMIN EQU 6
CYMAX EQU 7 

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

    org $8000

    ;-- Poner borde amarillo para ver mejor la posicion
    ;-- de los píxeles
    ld a, 6
    out (BORDER), a

bucle:
    ;------ Particula 1
    ld ix, particula1
    call move_particle

    ;------ Particula 2
    ld ix, particula2
    call move_particle

    ;-- Esperar a que termine el frame
    call wait

    ;-- Repetir
    jr bucle


;------------------------------------------
;-- MOVE_PARTICLE: Mover la particula
;--  
;-- ENTRADA:
;--   * IX: Puntero a la particula a mover
;-------------------------------------------
move_particle:

    ;-- Borrar pixel
    ld b, (ix + POSX)
    ld c, (ix + POSY)
    call plot_clear

    ;-- Comprobar colision
    call check_colision

    ;-- Actualizar posicion
    call update_particle

    ;-- Dibujar la particula
    ld b, (ix + POSX)
    ld c, (ix + POSY)
    call plot_set

    ret


;---------------------------------------------------------------------
;-- UPDATE_PARTICLE:  Actualizar la particula indicada por IX
;--   Se actualiza su posicion acorde a la velocidad y se comprueban
;--   las colisiones
;--  
;-- ENTRADAS:
;--   * IX: Puntero a la entidad a comprobar
;---------------------------------------------------------------------
update_particle:
    ld a, (ix + POSX)
    ld b, (ix + VX)
    add a,b   ;-- x = x + vx
    ld (ix + POSX), a

    ld a, (ix + POSY)
    ld b, (ix + VY)
    add a,b   ;-- y = y + vy
    ld (ix + POSY), a
    ret

;-----------------------------------------------------------------
;-- CHECK_COLISION: Comprobar la colision de la particula con los
;--   limites. Si hay colision se cambia el sentido de la velocidad
;--
;-- ENTRADAS:
;--    * IX: Puntero a la entidad a comprobar
;------------------------------------------------------------------
check_colision:

    ;---------- COMPROBAR COLISION EN EJE X -----------------------
    ;-- Comprobar colisión por la derecha
    ld a, (ix + POSX)   ;-- Leer x actual
    ld b, (ix + CXMAX)  ;-- Leer xmax
    cp b  ;-- x==xmax?
    jr z, rebotex

    ;-- Comprobar colision por la izquierda
    ld a, (ix + POSX)
    ld b, (ix + CXMIN)  ;-- Leer xmin
    cp b  ;-- x==xmin?
    jr z, rebotex

    ;---------- COMPROBAR COLISION EN EJE Y -----------------------
    ;--- Comprobar colision por abajo
    ld a, (ix + POSY)   ;-- Leer y actual
    ld b, (ix + CYMAX)  ;-- Leer ymax
    cp b   ;  y==ymax?
    jr z, rebotey

    ;--- Comprobar colision por arriba
    ld a, (ix + POSY)
    ld b, (ix + CYMIN)  ;-- Leer ymin
    cp b   ;  y == ymin?
    jr z, rebotey

    ret

rebotex:
    ;-- Hay colision por la derecha
    ;-- Cambiar el sentido de la velocidad: rebote
    ld a, (ix + VX)
    neg
    ld (ix + VX), a
    ret

rebotey:
    ;-- Hay colision por abajo
    ;-- Cambiar el sentido de la velocidad: rebote
    ld a, (ix + VY)
    neg
    ld (ix + VY), a
    ret


;-- Informacion sobre las particulas
;----------------------------------------------
;-- PARTICULA 1
;----------------------------------------------
particula1:
    defb 1  ; Posicion x
    defb 4  ; Posicion y
    defb 1  ; Velocidad en x 
    defb 1  ; Velocidad en y

;-- Datos de las coliciones
    defb 0  ; xmin
    defb 8  ; xmax
    defb 0  ; ymin
    defb 8  ; ymax

;----------------------------------------------
;-- PARTICULA 2
;----------------------------------------------
particula2:
    defb 10  ; Posicion x
    defb 5  ; Posicion y
    defb -1  ; Velocidad en x 
    defb -1  ; Velocidad en y

;-- Datos de las coliciones
    defb 8  ; xmin
    defb 16  ; xmax
    defb 0  ; ymin
    defb 8  ; ymax

;-----------------------------------------------------------
;-- plot_clear (x,y): Borrar un pixel de la pantalla
;--
;-- ENTRADAS:
;--   * B: Coordenada x (0-255)
;--   * C: Coordenada y (0-192)
;------------------------------------------------------------
plot_clear:
    ;-- Guardar posicion del pixel
    push bc

    ;-- Dejar en HL la direccion de la memoria de video 
    ;-- asociada a la posicion B=x, C=y
    call get_videoRam

    ;-- Recuperar posicion del pixel
    pop bc
    push bc ;-- Volver a guardarla, para luego

    ;-- Obtener la máscara del pixel
    call pixel_mask
    xor $FF

    ;-- Escribir el pixel!!
    ;-- Modo OVER
    ld d,a
    ld a, (hl) ;-- Leer el valor actual de la memoria
    and d       ;-- Restar el nuevo pixel
    ld (hl),a  ;-- Escribirlo!

    ;-- Recuperar coordenadas
    pop bc


    ret

;-----------------------------------------------------------
;-- plot_set(x,y): Pintar un pixel en pantalla (modo OVER)
;--
;-- ENTRADAS:
;--   * B: Coordenada x (0-255)
;--   * C: Coordenada y (0-192)
;------------------------------------------------------------
plot_set:
   ;-- Guardar posicion del pixel
    push bc

    ;-- Dejar en HL la direccion de la memoria de video 
    ;-- asociada a la posicion B=x, C=y
    call get_videoRam

    ;-- Recuperar posicion del pixel
    pop bc
    push bc ;-- Volver a guardarla, para luego

    ;-- Obtener la máscara del pixel
    call pixel_mask

    ;-- Escribir el pixel!!
    ;-- Modo OVER
    ld d,a
    ld a, (hl) ;-- Leer el valor actual de la memoria
    or d       ;-- Añdir el nuevo pixel
    ld (hl),a  ;-- Escribirlo!

    ;-- Recuperar coordenadas
    pop bc
    ret

;----------------------------------------------------------------------------
;-- GET_VIDEORAM:  Obtener la memoria de video asociada a una posicion 
;--    de pixel
;--
;-- ENTRADAS:
;--    * B: Coordenada x (0-255)
;--    * C: Coordenada y (0-192)
;-- DEVUELVE:
;--    * HL: Direccio de la memoria de video
;---------------------------------------------------------------------------
get_videoRam:
    ;-- HL = Direccion de video = 010TTSSS LLLCCCCC
    ;-- Convertir B y C a dirección en HL
    ;-- B = Coordenada x = CCCCCPPP
    ;-- C = Coordenada y = TTLLLSSS

    ;--------- Aislar campo CCCCC de B
    ld a,b
    ;-- Desplazar tres bits a la derecha
    sra a  ;-- XCCCCCPP
    sra a  ;-- XXCCCCCP
    sra a  ;-- XXXCCCCC
    and $1F  ;-- 000CCCCC
    ld l,a ;-- L = 000CCCCC

    ;-- Aislar LLL en los bits mas significativos
    ld a,c   ;-- TTLLLSSS
    rlca     ;-- TLLLSSS0
    rlca     ;-- LLLSSS00
    and $E0  ;-- LLL00000

    ;-- Añadir campo CCCCC
    or l     ; A = LLLCCCCC
    ld l,a   ;-- L = LLLCCCCC  (Completado!)

    ;-- Aislar y colocar campo TT
    ld a,c   ; TTLLLSSS
    sra a    ; xTTLLLSS
    sra a    ; xxTTLLLS
    sra a    ; xxxTTLLL
    and $18  ; 000TT000
    ld h,a   ; H = 000TT000

    ;-- Aislar campo SSS
    ld a,c      ; A = TTLLLSSS
    and $07     ;     00000SSS
    or h        ;     000TTSSS
    add a, $40  ;     010TTSSS
    ld h,a      ; H = 010TTSSS (Completado!)

    ;-- HL CONTIENE LA DIRECCION
    ret

;-----------------------------------------------------------
;-- PIXEL_MASK(x): Devolver la máscara de pixel a escribir en la
;--   memoria de video de la posición x (0-7)
;--
;-- ENTRADAS:
;--   * B: Posicion x del pixel (0-7) en el scanline 0 de la celda
;--
;-- DEVUELVE:
;--   * A: Valor a escribir en la memoria de video
;-----------------------------------------------------------------
pixel_mask:
    ;-- Si es la posicion 0, se escribe el valor $80 y se termina
    ld a,b
    or a    ;-- Actualizar flag z para comproar si A es 0
    jr z,pos0  ;-- A=0, es la posicion 0

    ;-- NO es la posicion 0
    ;-- Partimos del valor $80 y lo desplazamos B posiciones
    ;-- a la derecha
    ld a, $80

to_pos
    rrca 
    djnz to_pos

    ;-- En A tenemos el valor a escribir en memoria de video
    ;-- Devolverlo
    ret


pos0:
    ;-- Valor a escribir en memoria video
    ld a, $80
    ret


;----------------------------------
;-- Esperar N frames...
;-- Cada frame son 20ms aprox...
;----------------------------------
wait:
    halt  ;-- 20 ms
    halt  
    halt
    halt
    halt
    halt
    ret

    end $8000

Este es el resultado:

Reto-2.7: Trampa

Estoy usndo las rutinas desarrolladas en mis experimetnos del Log

  • Ejemplo: 01-reto-2-2.z80
include "zxspectrum.h"  

  org $8000
  
  ;------------- Pasar a Dark Mode ------------
  BORDER BLACK
  
  ;-- Establecer los atributos permanentes
  SET_ATTR_S BLACK, RED

  ;-- Borrar la pantalla
  CALL ROM_CLS
  ;--------------------------------------------
    
    ;-- Trampa fija: Izquierda
    ld hl, $4000
    ld de, sprite_trampa_izq
    call draw_sprite8

    ;----- Trampa derecha
    ld hl, $4002
    ld de, sprite_trama_der
    call draw_sprite8

loop:
    ;------ Trama central: movil
    ;-- Sprite 0
    ld hl, $4001
    ld de, sprite_trampa0
    call draw_sprite8

    ;-- Esperar
    call wait_400ms

    ;-- Sprite 1
    ld hl, $4001
    ld de, sprite_trampa1
    call draw_sprite8

    ;-- Esperar
    call wait_100ms

    ;-- Sprite 2
    ld hl, $4001
    ld de, sprite_trampa2
    call draw_sprite8

    ;-- Esperar
    call wait_100ms

    ;-- Sprite 3
    ld hl, $4001
    ld de, sprite_trampa3
    call draw_sprite8

    ;-- Esperar
    call wait_100ms

    ;-- Sprite 4
    ld hl, $4001
    ld de, sprite_trampa4
    call draw_sprite8

    ;-- Esperar
    call wait_100ms

    ;-- Sprite 3
    ld hl, $4001
    ld de, sprite_trampa3
    call draw_sprite8

    ;-- Esperar
    call wait_100ms

    ;-- Sprite 2
    ld hl, $4001
    ld de, sprite_trampa2
    call draw_sprite8

    ;-- Esperar
    call wait_100ms

    ;-- Sprite 1
    ld hl, $4001
    ld de, sprite_trampa1
    call draw_sprite8

    ;-- Esperar
    call wait_100ms

    jr loop

;-------------------
;-- SPRITES
;------------------- 
;-- Parte iquierda de la trama: Fija
sprite_trampa_izq: DEFB $00, $00, $00, $FF, $AF, $DF, $AF, $FF

;-- Parte central: Movil
sprite_trampa0: DEFB $00, $00, $00, $FF, $35, $FA, $35, $FF
sprite_trampa1: DEFB $00, $00, $00, $7F, $1D, $7A, $1D, $7F
sprite_trampa2: DEFB $00, $00, $00, $3F, $0D, $3E, $0D, $3F
sprite_trampa3: DEFB $00, $00, $00, $1F, $07, $1F, $07, $1F
sprite_trampa4: DEFB $00, $00, $00, $0F, $03, $0F, $03, $0F

;-- Parte derecha: Fija
sprite_trama_der: DEFB $00, $00, $00, $FF, $55, $AB, $55, $FF

;-- Led medio-encendido
sprite_led_half: DEFB $00, $3C, $56, $4A, $52, $66, $3C, $00

;-- Led encendido
sprite_led_on:  DEFB $00, $3C, $7E, $7E, $7A, $76, $3C, $00

    include "Video.z80"
    include "Keyboard.z80"
    include "Delay.z80"

    end $8000

Reto-2.8: Pelota con gravedad

TODO