viernes, 29 de abril de 2016

Cómo escribir juegos para el ZX Spectrum. Capítulo 11

Índice de entradas

Esta serie de artículos han sido traducidos a partir del documento "How to Write ZX Spectrum Games" con permiso de su autor, Jonathan Cauldwell, un gran desarrollador de juegos para el Spectrum, os recomiendo visitar su Web donde está el texto original. El documento original, y por tanto esta traducción, tiene © Jonathan Cauldwell y solo puede duplicarse con permiso expreso por escrito de su autor.

Movimientos de enemigos

Ya tenemos nuestro fondo cargado y permitimos al jugador mover un sprite a su alrededor, lo que ahora necesitamos son algunos sprites de enemigos que el jugador deba evitar. Un programador novato podría pelear mucho con esto, pero en realidad es mucho más simple de lo que parece.

Enemigos que patrullan

El tipo de enemigo más fácil de programar es el que usa un algoritmo fijo a seguir, o patrulla una ruta predeterminada. Hemos cubierto una de estas técnicas en el juego del ciempiés anterior. Otro ejemplo muy sencillo es la que se encuentra en juegos tales como Jet Set Willy, donde un sprite se desplaza en una sola dirección hasta que llega al final de su zona de patrulla, a continuación cambia de dirección y se dirige de nuevo a su punto de partida, antes de cambiar de dirección otra vez e iniciar el ciclo de nuevo. Como se puede imaginar, estas rutinas son increíblemente fáciles de escribir.

En primer lugar hemos de crear en nuestra tabla con la estructura del alien una coordenada de posición mínima y máxima, y la dirección actual. Es generalmente buena idea comentar estas tablas, así que vamos a hacerlo.

; tabla de datos del Alien, 6 bytes por alien.
; ix     = tipo de gráfico, como pollo/medusa etc.
; ix + 1 = dirección, 0=arriba, 1=derecha, 2=abajo, 3=izquierda.
; ix + 2 = coordenada x actual.
; ix + 3 = coordenada y actual.
; ix + 4 = mínima coordenada x o y , dependiendo de la dirección.
; ix + 5 = máxima coordenada x o y , dependiendo de la dirección.

altab  defb 0,0,0,0,0,0
       defb 0,0,0,0,0,0
       defb 0,0,0,0,0,0

luego para manejar su sprite podríamos escribir algo como esto

       ld a,(ix+1)         ; dirección del movimiento del alien.
       rra                 ; rotar bit bajo con acarreo.
       jr nc,movav         ; si no hay acarreo = 0 o 2, debe ser vertical.

; dirección es 1 o 3 por lo que es horizontal.

       rra                 ; rotar siguiente bit con para probar.
       jr nc,movar         ; dirección 1 = mover a la derecha el alien.

; Mover alien a la izquierda.

moval  ld a,(ix+3)         ; obtener la coordenada y.
       sub 2               ; mover hacia la izquierda.
       ld (ix+3),a
       cp (ix+4)           ; ¿se ha llegado al mínimo?
       jr z,movax          ; sí, cambio de dirección.
       jr c,movax          ; Hay, nos hemos pasado.
       ret

; Mover alien a la derecha.

movar  ld a,(ix+3)         ; obtener la coordenada y.
       add a,2             ; mover hacia la derecha.
       ld (ix+3),a
       cp (ix+5)           ; ¿se ha llegado al máximo?
       jr nc,movax         ; sí, cambio de dirección.
       ret

; Mover alien verticalmente.

movav  rra                 ; probar la dirección.
       jr c,movad          ; dirección 2 es abajo.

; Mover alien hacia arriba.

movau  ld a,(ix+2)         ; obtener la coordenada x.
       sub 2               ; mover hacia arriba.
       ld (ix+2),a
       cp (ix+4)           ; ¿se ha llegado al mínimo?
       jr z,movax          ; sí, cambio de dirección.
       ret

; Mover alien hacia abajo.

movad  ld a,(ix+2)         ; obtener la coordenada x.
       add a,2             ; mover hacia abajo.
       ld (ix+2),a         ; nueva coordenada.
       cp (ix+5)           ; ¿se ha llegado al máximo?
       jr nc,movax         ; sí, cambio de dirección.
       ret

; Cambiar dirección del alien.

movax  ld a,(ix+1)         ; indicador de dirección.
       xor 2               ; cambiar dirección, ya sea
                           ; horizontal o verticalmente.
       ld (ix+1),a         ; establecer una nueva dirección.
       ret

Si quisiéramos ir más lejos podríamos introducir una bandera extra en nuestra tabla, ix + 6, para controlar la velocidad del sprite, y sólo moverlo, por ejemplo, cada dos cuadros si se establece el indicador. Aunque es simple de escribir y con bajo uso de memoria, este tipo de movimiento es bastante básico, predecible y de uso limitado. Para enemigos con patrullaje más complicado, por ejemplo, las olas de ataque de alienígenas en un shoot-em-up, necesitamos tablas de coordenadas, y mientras que el código es también fácil de escribir, coordinan las tablas rápidamente se come la memoria, sobre todo si se almacenan ambas coordenadas x e y. Para acceder a una tabla de este tipo se necesitan dos bytes por sprites que actúan como un puntero a la tabla de coordenadas.

Una sección típica de código se vería así:

                           ; NdT: little endian = primero byte bajo
       ld l,(ix+2)         ; puntero de byte bajo, little endian.
       ld h,(ix+3)         ; puntero byte alto.
       ld c,(hl)           ; poner coordenada x en c.
       inc hl              ; punto a la coordenada y.
       ld b,(hl)           ; poner la coordenada y en b.
       inc hl              ; apuntar a la siguiente posición.
       ld (ix+2),l         ; siguiente puntero al byte bajo.
       ld (ix+3),h         ; siguiente puntero al byte alto.

A continuación un ejemplo un poco más complicado, muestra una oleada de 8 naves de ataque usando una tabla de coordenadas verticales. La posición horizontal de cada sprite se mueve a la izquierda a una velocidad constante de 2 píxeles por imagen, así que no hay necesidad de preocuparse de guardarla. Se utiliza la rutina de sprites precargados del capítulo 8, de manera que los sprites son un poco "peliculeros", pero eso no es importante ahora.

mloop  halt                ; esperar al haz de TV.
       ld ix,entab         ; apuntar a las naves impares.
       call mship          ; mover las naves.
       halt
       ld ix,entab+4       ; apuntar a las naves pares.
       call mship          ; mover las naves de nuevo.
       call gwave          ; generar ondas frescas.
       jp mloop            ; volver al inicio del bucle.

; Mover las naves enemigas.

mship  ld b,4              ; Número a procesar.
mship0 push bc             ; guardar contador.
       ld a,(ix)           ; obtener puntero bajo.
       ld l,a              ; ponerlo en l.
       ld h,(ix+1)         ; obtener byte alto.
       or h                ; comprobar que el puntero está configurado.
       and a               ; ¿lo está?
       call nz,mship1      ; sí, procesarlo.
       ld de,8             ; saltar a la siguiente solo-pero-una entrada.
       add ix,de           ; apuntar al siguiente enemigo.
       pop bc              ; restaurar contador.
       djnz mship0         ; repetir para todos los enemigos.
       ret

mship1 push hl             ; guardar puntero a la coordenada.
       call dship          ; Eliminar esta nave.
       pop hl              ; restaurar coordenadas.
       ld a,(hl)           ; recuperar la siguiente coordenada.
       inc hl              ; mover el puntero allí.
       ld (ix),l           ; nuevo puntero al byte bajo.
       ld (ix+1),h         ; puntero al byte alto.
       ld (ix+2),a         ; establecer coordenada x.
       ld a,(ix+3)         ; recuperar la posición horizontal.
       sub 2               ; mover hacia la izquierda 2 píxeles.
       ld (ix+3),a         ; establecer nueva posición.
       cp 240              ; ¿alcanzamos el borde de la pantalla?
       jp c,dship          ; no por el momento, mostrar en la nueva posición.
       xor a               ; poner a cero el acumulador.
       ld (ix),a           ; borrar el byte bajo del puntero.
       ld (ix+1),a         ; limpiar el byte alto del puntero.
       ld hl,numenm        ; número de enemigos en pantalla.
       dec (hl)            ; uno menos a los que hacer frente.
       ret

gwave  ld hl,shipc         ; contador de naves.
       dec (hl)            ; una menos.
       ld a,(hl)           ; comprobar nuevo valor.
       cp 128              ; ¿esperando el siguiente ataque?
       jr z,gwave2         ; ataque es inminente por lo que hay que configurarlo.
       ret nc              ; si.
       and 7               ; ¿es hora de generar una nueva nave?
       ret nz              ; aún no lo es
       ld ix,entab         ; tabla de enemigos.
       ld de,4             ; tamaño de cada entrada.
       ld b,8              ; Número a comprobar.
gwave0 ld a,(ix)           ; byte bajo del puntero.
       ld h,(ix+1)         ; byte alto.
       or h                ; ¿están a cero?
       jr z,gwave1         ; sí, esta entrada está vacía.
       add ix,de           ; apuntar a la siguiente nave.
       djnz gwave0         ; repetir hasta que encontremos una.
       ret
gwave2 ld hl,wavnum        ; presentar el numero de la oleada.
       ld a,(hl)           ; recuperar la configuración actual.
       inc a               ; siguiente a lo largo.
       and 3               ; empezar de nuevo después de la 4ª oleada.
       ld (hl),a           ; escribir nuevo ajuste.
       ret
gwave1 ld hl,numenm        ; número de enemigos en pantalla.
       inc (hl)            ; uno más con que pelear.
       ld a,(wavnum)       ; numero de oleada.
       ld hl,wavlst        ; punteros de datos de oleadas.
       rlca                ; multiplico por 2.
       rlca                ; multiplico por 4.
       ld e,a              ; desplazamiento en e.
       ld d,0              ; sin byte alto.
       add hl,de           ; encontrar la dirección de la oleada.
       ld a,(shipc)        ; contador de naves.
       and 8               ; ¿ataque par o impar?
       rrca                ; hacerlo múltiplo de 2 en consecuencia.
       rrca
       ld e,a              ; desplazamiento en e.
       ld d,0              ; sin byte alto.
       add hl,de           ; apunto a la primera o a la segunda mitad del ataque.
       ld e,(hl)           ; byte bajo del puntero del ataque.
       inc hl              ; segundo byte.
       ld d,(hl)           ; byte alto del puntero del ataque.
       ld (ix),e           ; byte bajo del puntero.
       ld (ix+1),d         ; byte alto.
       ld a,(de)           ; Recuperar la primera coordenada.
       ld (ix+2),a         ; establecer x.
       ld (ix+3),240       ; comenzar en el borde derecho de la pantalla.

; Visualización de las naves enemigas.

dship  ld hl,shipg         ; Dirección de los sprites.
       ld b,(ix+3)         ; coordenada y.
       ld c,(ix+2)         ; coordenada x.
       ld (xcoord),bc      ; establecer coordenadas de la rutina de sprites.
       jp sprite           ; llamar a la rutina de sprites.
shipc  defb 128            ; contador de naves.
numenm defb 0              ; número de enemigos.


; Coordenadas de las oleadas de ataque.
; Sólo la coordenada vertical se almacena ya que todas las naves
; se mueven hacia la izquierda 2 píxeles en cada fotograma.

coord0 defb 40,40,40,40,40,40,40,40
       defb 40,40,40,40,40,40,40,40
       defb 42,44,46,48,50,52,54,56
       defb 58,60,62,64,66,68,70,72
       defb 72,72,72,72,72,72,72,72
       defb 72,72,72,72,72,72,72,72
       defb 70,68,66,64,62,60,58,56
       defb 54,52,50,48,46,44,42,40
       defb 40,40,40,40,40,40,40,40
       defb 40,40,40,40,40,40,40,40
       defb 38,36,34,32,30,28,26,24
       defb 22,20,18,16,14,12,10,8
       defb 6,4,2,0,2,4,6,8
       defb 10,12,14,16,18,20,22,24
       defb 26,28,30,32,34,36,38,40
coord1 defb 136,136,136,136,136,136,136,136
       defb 136,136,136,136,136,136,136,136
       defb 134,132,130,128,126,124,122,120
       defb 118,116,114,112,110,108,106,104
       defb 104,104,104,104,104,104,104,104
       defb 104,104,104,104,104,104,104,104
       defb 106,108,110,112,114,116,118,120
       defb 122,124,126,128,130,132,134,136
       defb 136,136,136,136,136,136,136,136
       defb 136,136,136,136,136,136,136,136
       defb 138,140,142,144,146,148,150,152
       defb 154,156,158,160,162,164,166,168
       defb 170,172,174,176,174,172,170,168
       defb 166,164,162,160,158,156,154,152
       defb 150,148,146,144,142,140,138,136


; Lista de oleadas de ataque.

wavlst defw coord0,coord0,coord1,coord1
       defw coord1,coord0,coord0,coord1

wavnum defb 0              ; puntero actual a la oleada wave pointer.


; Sprite de la nave.

shipg  defb 248,252,48,24,24,48,12,96,24,48,31,243,127,247,255,247
       defb 255,247,127,247,31,243,24,48,12,96,24,48,48,24,248,252

sprit7 xor 7               ; complementa los últimos 3 bits.
       inc a               ; ¡agrega uno por suerte!
sprit3 rl d                ; rotar izquierda...
       rl c                ; ...en el centro del byte...
       rl e                ; ...y a la izquierda de la celda de carácter.
       dec a               ; contar los cambios que hemos hecho.
       jr nz,sprit3        ; regresar hasta que los movimientos estén completos.

; Línea de la imagen de sprite ahora en e+c+d, lo necesitamos en forma c+d+e

       ld a,e              ; borde izquierdo de la imagen está en e.
       ld e,d              ; poner borde derecho en su lugar.
       ld d,c              ; bit central va en d
       ld c,a              ; y el borde izquierdo de nuevo en c.
       jr sprit0           ; hemos hecho el cambio para transferir a la pantalla.

sprite ld a,(xcoord)        ; dibuja el sprite (hl).
       ld (tmp1),a         ; guardar vertical.
       call scadd          ; calcular dirección de la pantalla.
       ld a,16             ; altura del sprite en pixeles.
sprit1 ex af,af'           ; guardar el contador de bucles.
       push de             ; guardar dirección de pantalla.
       ld c,(hl)           ; primer gráfico del sprite.
       inc hl              ; incrementar el puntero de datos del sprite.
       ld d,(hl)           ; siguiente bit de la imagen del sprite.
       inc hl              ; apuntar a la siguiente fila de datos del sprite.
       ld (tmp0),hl        ; guardar en tmp0 para más adelante.
       ld e,0              ; byte derecho en blanco por ahora.
       ld a,b              ; b guarda la posición y.
       and 7               ; ¿estamos a caballo entre celdas de caracteres?
       jr z,sprit0         ; no estamos a caballo, no molesta al desplazamiento.
       cp 5                ; ¿necesitamos 5 o más desplazamientos a la derecha?
       jr nc,sprit7        ; sí, desplazar a la izquierda que es más rápido.
       and a               ; hay, la bandera de acarreo se establece.
sprit2 rr c                ; rotar a la izquierda el byte derecho...
       rr d                ; ...Hasta el byte medio...
       rr e                ; ...la byte derecho.
       dec a               ; un turno menos que hacer.
       jr nz,sprit2        ; repetir hasta que todos los turnos estén completos.
sprit0 pop hl              ; sacar la dirección de la pantalla en la pila.
       ld a,(hl)           ; ya está lista.
       xor c               ; fusionar con los datos de la imagen.
       ld (hl),a           ; colocar en la pantalla.
       inc l               ; siguiente celda de carácter por la derecha.
       ld a,(hl)           ; ya estaba antes.
       xor d               ; fusionarse con el centro de la imagen.
       ld (hl),a           ; poner de nuevo en la pantalla.
       inc hl              ; siguiente bit del área de la pantalla.
       ld a,(hl)           ; lo que ya está allí.
       xor e               ; borde derecho de los datos de imagen del sprite.
       ld (hl),a           ; poner en pantalla.
       ld a,(tmp1)         ; coordenada vertical temporal.
       inc a               ; siguiente línea de abajo.
       ld (tmp1),a         ; almacenar nueva posición.
       and 63              ; ¿nos movemos al siguiente tercio de pantalla?
       jr z,sprit4         ; sí, encontrar el próximo segmento.
       and 7               ; ¿entrando en celda de carácter siguiente?
       jr z,sprit5         ; Sí, encuentre siguiente fila.
       dec hl              ; izquierda 2 bytes.
       dec l               ; es está el límite a horcajadas de 256 bytes aquí.
       inc h               ; siguiente fila de esta celda de carácter.
sprit6 ex de,hl            ; Dirección de pantalla en de.
       ld hl,(tmp0)        ; restaurar la dirección del gráfico.
       ex af,af'           ; restaurar el contador del bucle.
       dec a               ; decrementarlo.
       jp nz,sprit1        ; no alcanzado el borde inferior del sprite, repetir.
       ret                 ; trabajo hecho.
sprit4 ld de,30            ; el siguiente segmento es de 30 bytes.
       add hl,de           ; añadir a la dirección de la pantalla.
       jp sprit6           ; repetir.
sprit5 ld de,63774         ; menos 1762.
       add hl,de           ; restar 1762 de la dirección física de la pantalla.
       jp sprit6           ; volver al bucle.

scadd  ld a,(xcoord)       ; recuperar la coordenada vertical.
       ld e,a              ; guardarla en e.

; Encuentra línea dentro de la celda.

       and 7               ; línea 0-7 en el cuadrado de caracteres.
       add a,64            ; 64 * 256 = 16384 = inicio de la pantalla.
       ld d,a              ; d = línea * 256.

; Encuentra en que tercio de la pantalla estamos.

       ld a,e              ; restaurar la vertical.
       and 192             ; segmento 0, 1 o 2 multiplicamos por 64.
       rrca                ; dividirlo por 8.
       rrca
       rrca                ; segmento 0-2 multiplicado por 8.
       add a,d             ; sumar a+d obtiene dirección de inicio del segmento.
       ld d,a

; Encuentra la celda de carácter dentro del segmento.

       ld a,e              ; 8 casillas de caracteres por segmento.
       rlca                ; dividir x por 8 y multiplicarlo por 32,
       rlca                ; siguiente cálculo: multiplicar por 4.
       and 224             ; enmascarar los bits que no queremos.
       ld e,a              ; cálculo de la coordenada vertical completo.

; Agregar el elemento horizontal.

       ld a,(ycoord)       ; coordenada y.
       rrca                ; sólo es necesario dividir por 8.
       rrca
       rrca
       and 31              ; cuadrados 0 a 31 a través de la pantalla.
       add a,e             ; añadir al total de la medida.
       ld e,a              ; de = dirección de la pantalla.
       ret

xcoord defb 0              ; coordenada de pantalla.
ycoord defb 0              ; coordenada de pantalla.
tmp0   defw 0              ; espacio de trabajo.
tmp1   defb 0              ; posición vertical temporal.

; tabla de naves enemigas, 8 entradas x 4 bytes cada una.

entab  defb 0,0,0,0
       defb 0,0,0,0
       defb 0,0,0,0
       defb 0,0,0,0
       defb 0,0,0,0
       defb 0,0,0,0
       defb 0,0,0,0
       defb 0,0,0,0

 Enemigos inteligentes

Hasta ahora nos hemos ocupado de drones predecibles, pero ¿queremos dar al jugador la ilusión de que los sprites enemigos piensan por sí mismos? Una vía que podemos utilizar para esto sería para darles una decisión totalmente al azar en el momento de su creación.

Aquí está el código fuente de Turbomania, un juego escrito originalmente para la 1K coding competition (concursos de código en 1K) de 2005. Es muy simple, pero incorpora movimiento puramente aleatorio. Los coches enemigos viajan en una dirección hasta que ya no se pueden mover, a continuación seleccionan otra dirección al azar. Además, un coche puede cambiar de dirección al azar incluso si puede continuar en su dirección actual. Es muy primitivo por supuesto, solo echa un vistazo a la rutina MCAR y verá exactamente lo quiero decir.

       org 24576

; Constantes.

YELLOW equ 49              ; atributo de color amarillo normal.
YELLOB equ YELLOW + 64     ; atributo de color amarillo brillante.

; Código principal del juego.

; Borrar la pantalla para dar color verde alrededor de los bordes.

       ld hl,23693         ; variable de sistema para atributos.
       ld (hl),36          ; quiero fondo verde.

waitk  ld a,(23560)        ; leer teclado.
       cp 32               ; ¿pulsado ESPACIO?
       jr nz,waitk         ; no, espera.
       call nexlev         ; jugar,
       jr waitk            ; ESPACIO para reiniciar el juego.


; Borrar los datos por nivel.

nexlev call 3503           ; borrar la pantalla.
       ld hl,rmdat         ; Datos de las salas.
       ld de,rmdat+1
       ld (hl),1           ; configurar un bloque en la sombra.
       ld bc,16            ; longitud de sala menos el primer byte.
       ldir                ; copiar al resto de la primera fila.
       ld bc,160           ; longitud de la sala menos la primera fila.
       ld (hl),b           ; borrar primer byte.
       ldir                ; borrar datos de la sala.

; Configurar los bloques predeterminados.

       ld c,15             ; última posición del bloque.
popbl0 ld b,9              ; última fila.
popbl1 call filblk         ; llenar el bloque.
       dec b               ; una columna hacia arriba.
       jr z,popbl2         ; columna hecha, seguir adelante.
       dec b               ; y de nuevo.
       jr popbl1
popbl2 dec c               ; moverse en la fila.
       jr z,popbl3         ; columna completa, seguir adelante.
       dec c               ; siguiente fila.
       jr popbl0

; Ahora dibujar los bits únicos para este nivel.

popbl3 ld b,7              ; número de bloques para insertar.
popbl5 push bc             ; guardar contador.
       call random         ; obtener un número aleatorio.
       and 6               ; números en el rango 0-6 por favor.
       add a,2             ; cambiar a 2-8.
       ld b,a              ; esa es la columna.
popbl4 call random         ; otro número.
       and 14              ; buscamos números pares 0-12.
       cp 14               ; ¿más alto de lo que queremos?
       jr nc,popbl4        ; si, inténtelo de nuevo.
       inc a               ; colocarlo en el rango 1-13.
       ld c,a              ; esa es la fila.
       call filblk         ; llenar bloque.
popbl6 call random         ; otro número aleatorio.
       and 14              ; Sólo queremos 0-8.
       cp 9                ; ¿por encima de número que queremos?
       jr nc,popbl6        ; inténtelo de nuevo.
       inc a               ; convertirlo a 1-9.
       ld b,a              ; coordenada vertical.
       call random         ; obtener bloque horizontal.
       and 14              ; par, 0-14.
       ld c,a              ; posición y.
       call filblk         ; llenar en ese cuadrado.
       pop bc              ; restaurar el contador.
       djnz popbl5         ; uno menos que hacer.

       xor a               ; cero.
       ld hl,playi         ; dirección deseada del jugador.
       ld (hl),a           ; dirección por defecto.
       inc hl              ; apuntar a la dirección a presentar.
       ld (hl),a           ; dirección por defecto.
       inc hl              ; siguiente dirección del jugador.
       ld (hl),a           ; dirección por defecto.
       out (254),a         ; establecer el color del borde mientras tanto.
       call atroom         ; mostrar disposición del nivel actual.

       ld hl,168+8*256     ; coordenadas.
       ld (encar2+1),hl    ; establecer la posición del segundo coche.
       ld h,l              ; coordenada y a la derecha.
       ld (encar1+1),hl    ; establecer la posición del primer coche.
       ld l,40             ; x en la parte superior de la pantalla.
       ld (playx),hl       ; iniciar al jugador aquí.
       ld hl,encar1        ; primer coche.
       call scar           ; presentarlo.
       ld hl,encar2        ; segundo coche.
       call scar           ; presentarlo.
       call dplayr         ; mostrar el sprite del jugador.
       call blkcar         ; hacer el coche del jugador de color negro.

; Retardo de dos segundos antes de empezar.

       ld b,100            ; longitud del retardo.
waitt  halt                ; esperar una interrupción.
       djnz waitt          ; repetir.

mloop  halt                ; haz de electrones en la parte superior izquierda.
       call dplayr         ; Eliminar jugador.

; Poner como atributo tinta azul de nuevo.

       call gpatts         ; obtener atributo del jugador.
       defb 17,239,41      ; retirar el fondo verde, añadir fondo y tinta azul.
       call attblk         ; establecer el color de la carretera.

; Mover el coche del jugador.

       ld a,(playd)        ; dirección .
       ld bc,(playx)       ; coordenadas del jugador.
       call movc           ; mover coordenadas.
       ld hl,(dispx)       ; nuevas coordenadas.
       ld (playx),hl       ; establecer una nueva posición del jugador.

; ¿Podemos cambiar de dirección?

       ld a,(playi)        ; dirección deseada del jugador.
       ld bc,(playx)       ; coordenadas del jugador.
       call movc           ; mover coordenadas.
       call z,setpn        ; establecer la nueva dirección del jugador.

; Cambia la dirección.

       ld a,(nplayd)       ; nueva dirección del jugador.
       ld (playd),a        ; fijar la dirección actual.

       call dplayr         ; volver a mostrar en la nueva posición.

; Establecer los atributos de los coches.

       call blkcar         ; hacer el coche del jugador negro.

; Controles.

       ld a,239            ; fila del teclado 6-0 = 61438.
       ld e,1              ; dirección derecha.
       in a,(254)          ; leer teclas.
       rra                 ; ¿jugador mueve a la derecha?
       call nc,setpd       ; sí, establecer la dirección del jugador.
       ld e,3              ; dirección izquierda.
       rra                 ; ¿jugador movió a la izquierda
       call nc,setpd       ; sí, establecer la dirección del jugador.
       ld a,247            ; 63486 es el puerto para la fila 1-5.
       ld e,0              ; dirección hacia arriba.
       in a,(254)          ; leer teclas.
       and 2               ; comprobar segunda tecla en (2).
       call z,setpd        ; establecer la dirección.
       ld a,251            ; 64510 es el puerto para la fila Q-T.
       ld e,2              ; dirección hacia abajo.
       in a,(254)          ; leer teclas.
       and e               ; comprobar segunda tecla del borde (W)..
       call z,setpd        ; establecer la dirección.

; Coches enemigos.

       ld hl,encar1        ; coche del enemigo 1.
       push hl             ; guardar puntero.
       call procar         ; procesar el coche.
       pop hl              ; recuperar el puntero del coche.
       call coldet         ; comprobar colisiones.
       ld hl,encar2        ; coche del enemigo 2.
       push hl             ; guardar puntero.
       halt                ; sincronizar con pantalla.
       call procar         ; procesar el coche.
       pop hl              ; recuperar el puntero del coche.
       call coldet         ; comprobar colisiones.

; Contar espacio amarillo restante.

       ld hl,22560         ; dirección.
       ld bc,704           ; atributos que contar.
       ld a,YELLOB         ; atributos que estamos buscando.
       cpir                ; contar caracteres.
       ld a,b              ; byte alto del resultado.
       or c                ; combinan con el byte bajo.
       jp z,nexlev         ; nada a la izquierda, ir al siguiente nivel.

; Fin del bucle principal.

       jp mloop

; Coche negro sobre fondo cian.

blkcar call gpatts         ; obtener atributos de los jugadores.
       defb 17,232,40      ; quitar el fondo rojo/tinta azul, añadir fondo azul.

; Establecer los atributos de bloque de 16x16 píxeles.

attblk call attlin         ; pintar línea horizontal.
       call attlin         ; pintar otra línea.
       ld a,c              ; posición vertical.
       and 7               ; ¿está a caballo entre celdas?
       ret z               ; no, entonces no hay tercera línea.

attlin call setatt         ; pintar la carretera.
       call setatt         ; y otra vez.
       ld a,b              ; posición horizontal.
       and 7               ; ¿a caballo entre los bloques?
       jr z,attln0         ; no, dejar tercera celda como está.
       call setatt         ; establecer atributo.
       dec l               ; atrás de nuevo una celda.
attln0 push de             ; conservar los colores.
       ld de,30            ; distancia al siguiente.
       add hl,de           ; puntero a la siguiente fila hacia abajo.
       pop de              ; restaurar máscaras de color.
       ret

; Establecer el atributo de una sola celda.

setatt ld a,(hl)           ; recuperar el atributo de la celda que lo contiene.
       and e               ; eliminar los elementos de color en el registro c.
       or d                ; añadir los de b para formar nuevo color.
       ld (hl),a           ; establecer color.
       inc l               ; siguiente celda.
       ret

; Detección de colisiones, basada en coordenadas.

coldet call getabc         ; obtener coordenadas.
       ld a,(playx)        ; posición horizontal.
       sub c               ; comparar con el coche x.
       jr nc,coldt0        ; resultado fue positivo.
       neg                 ; era negativo, revertir el signo.
coldt0 cp 16               ; ¿dentro de los 15 píxeles?
       ret nc              ; no hubo colisión.
       ld a,(playy)        ; jugador y.
       sub b               ; comparar con el coche y.
       jr nc,coldt1        ; resultado fue positivo.
       neg                 ; era negativo, revertir el signo.
coldt1 cp 16               ; ¿dentro de los 15 píxeles?
       ret nc              ; no hubo colisión.
       pop de              ; eliminar la dirección de retorno de la pila.
       ret

setpd  ex af,af'
       ld a,e              ; dirección.
       ld (playi),a        ; fijar la dirección prevista.
       ex af,af'
       ret

setpn  ld a,(playi)        ; nueva dirección deseada.
       ld (nplayd),a       ; establecer siguiente dirección.
       ret

; Mover las coordenadas del sprites en la dirección correspondiente.

movc   ld (dispx),bc       ; posición por defecto.
       and a               ; dirección 0.
       jr z,movcu          ; mover hacia arriba.
       dec a               ; dirección 1.
       jr z,movcr          ; mover hacia arriba.
       dec a               ; dirección 2.
       jr z,movcd          ; mover hacia arriba.
movcl  dec b               ; izquierda un píxel.
       dec b               ; izquierda de nuevo.
movc0  call chkpix         ; comprobar los atributos de píxeles.
       ld (dispx),bc       ; nuevas coordenadas.
       ret
movcu  dec c               ; arriba un pixel.
       dec c               ; y de nuevo.
       jr movc0
movcr  inc b               ; derecha un píxel.
       inc b               ; derecha de nuevo.
       jr movc0
movcd  inc c               ; abajo un píxel.
       inc c               ; una vez más.
       jr movc0


; Comprobar los atributos del pixel por colisión.
; Cualquier celda con tinta verde es sólidas.

chkpix call ataddp         ; obtener la dirección del atributo de píxeles.
       and 4               ; comprobar color de la tinta.
       jr nz,chkpx0        ; inválida, bloquear el movimiento.
       inc hl              ; siguiente cuadrado a la derecha.
       ld a,(hl)           ; obtener atributos.
       and 4               ; comprobar colores de la tinta.
       jr nz,chkpx0        ; inválida, bloquear el movimiento.
       inc hl              ; siguiente cuadrado a la derecha.
       ld a,b              ; posición horizontal.
       and 7               ; ¿a caballo entre las celdas?
       jr z,chkpx1         ; no, mirar abajo entonces.
       ld a,(hl)           ; obtener atributos. 
       and 4               ; comprobar color de la tinta.
       jr nz,chkpx0        ; inválida, bloquear el movimiento.
chkpx1 ld de,30            ; distancia hasta la siguiente celda hacia abajo.
       add hl,de           ; apuntar aquí.
       ld a,(hl)           ; obtener atributos.
       and 4               ; comprobar color de la tinta.
       jr nz,chkpx0        ; inválida, bloquear el movimiento.
       inc hl              ; siguiente cuadrado a la derecha.
       ld a,(hl)           ; obtener atributos.
       and 4               ; comprobar color de la tinta.
       jr nz,chkpx0        ; inválida, bloquear el movimiento.
       inc hl              ; siguiente cuadrado a la derecha.
       ld a,b              ; posición horizontal.
       and 7               ; ¿a caballo entre las celdas?
       jr z,chkpx2         ; no, mirar abajo entonces.
       ld a,(hl)           ; obtener atributos.
       and 4               ; comprobar color de la tinta.
       jr nz,chkpx0        ; inválida, bloquear el movimiento.
chkpx2 ld a,c              ; distancia desde la parte superior de la pantalla.
       and 7               ; ¿estamos a caballo entre celdas verticales?
       ret z               ; no, el movimiento es bueno.
       add hl,de           ; apuntar allí.
       ld a,(hl)           ; obtener atributos.
       and 4               ; comprobar color de la tinta.
       jr nz,chkpx0        ; inválida, bloquear el movimiento.
       inc hl              ; siguiente cuadrado a la derecha.
       ld a,(hl)           ; obtener atributos.
       and 4               ; comprobar color de la tinta.
       jr nz,chkpx0        ; inválida, bloquear el movimiento.
       inc hl              ; siguiente cuadrado a la derecha.
       ld a,b              ; posición horizontal.
       and 7               ; ¿a caballo entre las celdas?
       ret z               ; no, el movimiento es correcto.
       ld a,(hl)           ; obtener atributos.
       and 4               ; comprobar color de la tinta.
       jr nz,chkpx0        ; inválida, bloquear el movimiento.
       ret                 ; adelante.

chkpx0 pop de              ; eliminar la dirección de retorno de la pila.
       ret

; Llenar un bloque en el mapa.

; Obtener dirección del bloque.

filblk ld a,b              ; número de fila.
       rlca                ; multiplicar por 16.
       rlca
       rlca
       rlca
       add a,c             ; añadir al desplazamiento total.
       ld e,a              ; este desplazamiento es pequeño.
       ld d,0              ; no necesita byte alto.
       ld hl,rmdat         ; dirección de datos de la sala.
       add hl,de           ; añadir al bloque de dirección.

; dirección de bloque está en hl, vamos a llenarlo.

       ld (hl),2           ; poner el bloque en on.
       ld de,16            ; distancia al siguiente bloque de abajo.
       add hl,de           ; apuntar allí.
       ld a,(hl)           ; chequearlo.
       and a               ; ¿está establecido?
       ret nz              ; sí, no sobrescribir.
       ld (hl),1           ; ajustar la sombra.
       ret

; Dibujar una pantalla compuesta enteramente de bloques de atributos.

atroom ld hl,rmdat         ; Datos de la sala.
       ld a,1              ; comenzar en la fila 1.
       ld (dispx),a        ; establecer coordenadas.
       ld b,11             ; contar filas.
atrm0  push bc             ; guardar el contador.
       ld b,15             ; contador de columnas.
       ld a,1              ; número de columna.
       ld (dispy),a        ; ajustado a la izquierda de la pantalla.
atrm1  push bc             ; guardar el contador.
       ld a,(hl)           ; obtener siguiente tipo de bloque.
       push hl             ; guardar dirección de datos.
       rlca                ; numero de bloque doblado.
       rlca                ; y de nuevo para múltiplo de 4.
       ld e,a              ; desplazamiento para dirección del bloque.
       ld d,0              ; no necesita byte alto.
       ld hl,blkatt        ; atributos del bloque.
       add hl,de           ; apuntar al bloque que queremos.
       call atadd          ; obtener dirección de la posición de la pantalla.
       ldi                 ; transferir primer bloque.
       ldi                 ; y el segundo.
       ld bc,30            ; distancia hasta la siguiente fila.
       ex de,hl            ; conmutar celda y dirección de pantalla.
       add hl,bc           ; apuntar a la siguiente fila por abajo.
       ex de,hl            ; intercambiarlas de nuevo.
       ldi                 ; hacer la tercera celda.
       ldi                 ; atributo de la cuarta celda.
       ld hl,dispy         ; número de columna.
       inc (hl)            ; moverse a través de una celda.
       inc (hl)            ; y otra.
       pop hl              ; restaurar la dirección de la sala.
       pop bc              ; restablecer contador de columna.
       inc hl              ; apuntar al siguiente bloque.
       djnz atrm1          ; hacer resto de la hilera.
       inc hl              ; saltar un carácter, las líneas son múltiplos 16.
       ld a,(dispx)        ; posición vertical.
       add a,2             ; mirar 2 celdas hacia abajo.
       ld (dispx),a        ; nueva fila.
       pop bc              ; restablecer contador de columna.
       djnz atrm0          ; hacer las filas restantes.
       ret

; Atributos del bloque de fondo.

blkatt defb YELLOB,YELLOB  ; espacio.
       defb YELLOB,YELLOB
       defb YELLOW,YELLOW  ; espacio sombreado.
       defb YELLOB,YELLOB
       defb 124,68         ; Modelo blanco/negro bandera de cuadros.
       defb 68,124

; Calcular la dirección del atributo de carácter en (dispx, dispy).

atadd  push hl             ; necesitamos preservar el par hl.
       ld hl,(dispx)       ; coordenadas a comprobar, en coordenadas de carácter.
       add hl,hl           ; multiplicar  x e y por 8.
       add hl,hl
       add hl,hl
       ld b,h              ; copiar coordenada y a b.
       ld c,l              ; poner coordenada x en c.
       call ataddp         ; obtener la dirección del pixel.
       ex de,hl            ; poner la dirección en de.
       pop hl              ; restaurar hl.
       ret


; Obtener atributos del jugador.

gpatts ld bc,(playx)       ; coordenadas del jugador.

; Calcular la dirección del atributo para el píxel en (c, b).

ataddp ld a,c              ; mira primero la vertical.
       rlca                ; divida por 64.
       rlca                ; mas rápido que 6 operaciones rrca.
       ld l,a              ; almacenar en el registro l por ahora.
       and 3               ; enmascarar para encontrar el segmento.
       add a,88            ; los atributos comienzan a partir de 88*256=22528.
       ld h,a              ; así está ordenado el byte alto.
       ld a,l              ; vertical/64 es igual que vertical*4.
       and 224             ; buscamos un múltiplo de 32.
       ld l,a              ; elemento vertical calculado.
       ld a,b              ; obtener la posición horizontal.
       rra                 ; dividir por 8.
       rra
       rra
       and 31              ; queremos el resultado en el rango 0-31.
       add a,l             ; añadir a byte bajo existente.
       ld l,a              ; completado el byte bajo.
       ld a,(hl)           ; obtener contenido de la celda.
       ret                 ; dirección del atributo ahora en hl.

; Mover el coche - cambio de dirección requerida.

mcarcd ld a,(hl)           ; dirección actual.
       inc a               ; gira en el sentido de las manecillas del reloj.
       and 3               ; sólo 4 direcciones.
       ld (hl),a           ; nueva dirección.

; Mover un coche enemigo.

mcar   push hl             ; preservar puntero al coche.
       call getabc         ; recuperar coordenadas y dirección.
       call movc           ; mover el coche.
       pop hl              ; refrescar puntero del coche
       jr nz,mcarcd        ; no se puede moverse mas, dar la vuelta.
       inc hl              ; apuntar a x.
       ld a,c              ; guardar posición x en c.
       ld (hl),a           ; posición x.
       inc hl              ; apuntar a y.
       ld (hl),b           ; nueva colocación.
       or b                ; combinar ambos.
       and 31              ; encontrar celdas de posición a caballo.
       cp 8                ; ¿estamos en un punto de giro válido?
       ret nz              ; no, no se puede cambiar de dirección.
       ld a,r              ; falso número aleatorio.
       cp 23               ; compruebe que está por debajo de este valor.
       ret nc              ; no lo está, no cambia.
       push hl             ; guardar puntero del coche.
       call random         ; obtener número aleatorio.
       pop hl              ; restaurar coche.
       dec hl              ; volver a la coordenada x.
       dec hl              ; volver de nuevo a la dirección.
       and 3               ; dirección en el rango de  0-3.
       ld (hl),a           ; nueva dirección.
       ret

; Obtener coordenadas de automóviles y su dirección.

getabc ld a,(hl)           ; obtener dirección.
       inc hl              ; apuntar a la posición x.
       ld c,(hl)           ; coordenada x.
       inc hl              ; apuntar a y.
       ld b,(hl)           ; posición y.
       ret


; Procesar coche en el punto hl.

procar push hl             ; guardar puntero.
       push hl             ; guardar puntero.
       call scar           ; Eliminar coche.
       pop hl              ; restablecer puntero a coche.
       call mcar           ; mover coche.
       pop hl              ; restablecer puntero a coche.


; Mostrar coche enemigo.

scar   call getabc         ; obtener coordenadas y dirección.
       jr dplay0

; Presentar sprite del jugador.

dplayr ld bc,(playx)       ; coordenadas del jugador.
       ld a,(playd)        ; dirección del jugador.
dplay0 rrca                ; multiplicar por 32.
       rrca
       rrca
       ld e,a              ; sprite * 32 en el byte bajo.
       ld d,0              ; sin byte alto.
       ld hl,cargfx        ; gráfico del coche.
       add hl,de           ; añadir desplazamiento al sprite.
       jr sprite           ; mostrar el sprite.


; Esta es la rutina de sprites y espera las coordenadas en la forma (c, b),
; donde c es la coordenada vertical desde la parte superior de la pantalla 
; (0-176), y b es la coordenada horizontal desde la izquierda de la pantalla
; (0 a 240). Los datos del sprite se almacenan como se espera en su forma no
; desplazada ya que esta rutina se encarga de todo el desplazamiento por sí 
; misma. Esto significa que direccionar el sprite no es especialmente rápido,
; pero los gráficos sólo ocupan 1/8 del espacio que requerirían en forma
; pre-desplazada.

; La entrada HL debe apuntar a los datos del sprite sin desplazamiento.

sprit7 xor 7               ; complementa los últimos 3 bits.
       inc a               ; ¡agrega uno por suerte!
sprit3 rl d                ; rotar izquierda...
       rl c                ; ...en el centro del byte...
       rl e                ; ...y a la izquierda de la celda de carácter.
       dec a               ; contar los cambios que hemos hecho.
       jr nz,sprit3        ; regresar hasta que los movimientos estén completos.

; Línea de la imagen de sprite ahora en e+c+d, lo necesitamos en forma c+d+e

       ld a,e              ; borde izquierdo de la imagen está en e.
       ld e,d              ; poner borde derecho en su lugar.
       ld d,c              ; bit central va en d
       ld c,a              ; y el borde izquierdo de nuevo en c.
       jr sprit0           ; hemos hecho el cambio para transferir a la pantalla.

sprite ld (dispx),bc       ; guardar ahora coordenadas en dispx.
       call scadd          ; calcular dirección de la pantalla.
       ld a,16             ; altura del sprite en pixeles.
sprit1 ex af,af'           ; guardar el contador de bucles.
       push de             ; guardar dirección de pantalla.
       ld c,(hl)           ; primer gráfico del sprite.
       inc hl              ; incrementar el puntero de datos del sprite.
       ld d,(hl)           ; siguiente bit de la imagen del sprite.
       inc hl              ; apuntar a la siguiente fila de datos del sprite.
       ld (sprtmp),hl      ; guardar para más adelante.
       ld e,0              ; byte derecho en blanco por ahora.
       ld a,b              ; b guarda la posición y.
       and 7               ; ¿estamos a caballo entre celdas de caracteres?
       jr z,sprit0         ; no estamos a caballo, no molesta al desplazamiento.
       cp 5                ; ¿necesitamos 5 o más desplazamientos a la derecha?
       jr nc,sprit7        ; sí, desplazar a la izquierda que es más rápido.
       and a               ; hay, la bandera de acarreo se establece.
sprit2 rr c                ; rotar a la izquierda el byte derecho...
       rr d                ; ...Hasta el byte medio...
       rr e                ; ...la byte derecho.
       dec a               ; un turno menos que hacer.
       jr nz,sprit2        ; repetir hasta que todos los turnos estén completos.
sprit0 pop hl              ; sacar la dirección de la pantalla en la pila.
       ld a,(hl)           ; ya está lista.
       xor c               ; fusionar con los datos de la imagen.
       ld (hl),a           ; colocar en la pantalla.
       inc l               ; siguiente celda de carácter por la derecha.
       ld a,(hl)           ; ya estaba antes.
       xor d               ; fusionarse con el centro de la imagen.
       ld (hl),a           ; poner de nuevo en la pantalla.
       inc l               ; siguiente bit del área de la pantalla.
       ld a,(hl)           ; lo que ya está allí.
       xor e               ; borde derecho de los datos de imagen del sprite.
       ld (hl),a           ; poner en pantalla.
       ld a,(dispx)        ; coordenada vertical temporal.
       inc a               ; siguiente línea de abajo.
       ld (dispx),a        ; almacenar nueva posición.
       and 63              ; ¿nos movemos al siguiente tercio de pantalla?
       jr z,sprit4         ; sí, encontrar el próximo segmento.
       and 7               ; ¿entrando en celda de carácter siguiente?
       jr z,sprit5         ; Sí, encuentre siguiente fila.
       dec l               ; izquierda 2 bytes.
       dec l               ; es está el límite a horcajadas de 256 bytes aquí.
       inc h               ; siguiente fila de esta celda de carácter.
sprit6 ex de,hl            ; Dirección de pantalla en de.
       ld hl,(sprtmp)      ; restaurar la dirección del gráfico.
       ex af,af'           ; restaurar el contador del bucle.
       dec a               ; decrementarlo.
       jp nz,sprit1        ; no alcanzado el borde inferior del sprite, repetir.
       ret                 ; trabajo hecho.
sprit4 ld de,30            ; el siguiente segmento es de 30 bytes.
       add hl,de           ; añadir a la dirección de la pantalla.
       jp sprit6           ; repetir.
sprit5 ld de,63774         ; menos 1762.
       add hl,de           ; restar 1762 de la dirección física de la pantalla.
       jp sprit6           ; volver al bucle.

; Esta rutina devuelve la dirección de pantalla de (c, b) en de.

scadd  ld a,c              ; obtener la posición vertical.
       and 7               ; línea 0-7 en el cuadro del carácter.
       add a,64            ; 64 * 256 = 16384 (inicio de la pantalla)
       ld d,a              ; línea * 256.
       ld a,c              ; obtener verticales de nuevo.
       rrca                ; multiplicar por 32.
       rrca
       rrca
       and 24              ; byte alto del desplazamiento de segmento.
       add a,d             ; añadir a byte alto de pantalla existente.
       ld d,a              ; ese es el byte alto ordenado.
       ld a,c              ; 8 casillas de carácter por segmento
       rlca                ; 8 píxeles por celda, multiplicado por 4 = 32.
       rlca                ; celda x 32 da la posición dentro del segmento.
       and 224             ; asegurarse de que es un múltiplo de 32.
       ld e,a              ; cálculo de coordenada vertical hecho.
       ld a,b              ; coordenada y.
       rrca                ; Sólo es necesario dividir por 8.
       rrca
       rrca
       and 31              ; cuadrados 0 - 31 a través de la pantalla.
       add a,e             ; añadirse al total de la medida.
       ld e,a              ; hl = dirección de la pantalla.
       ret

; Generador de números seudo-aleatorio.
; Llevando un puntero a través de la ROM (a partir de una semillas), 
; devuelve el contenido del byte en esa ubicación.

random ld hl,(seed)        ; puntero a la ROM.
       res 5,h             ; mantenerse dentro de los primeros 8K de ROM.
       ld a,(hl)           ; obtener el número "aleatorio" de esa ubicación.
       xor l               ; más aleatoriedad.
       inc hl              ; incrementar el puntero.
       ld (seed),hl        ; nueva posición.
       ret


; Datos gráficos del Sprite.
; Primero apunta subiendo.

cargfx defb 49,140,123,222,123,222,127,254,55,236,15,240,31,248,30,120
       defb 29,184,108,54,246,111,255,255,247,239,246,111,103,230,3,192

; Segunda imagen apunta a la derecha.

       defb 60,0,126,14,126,31,61,223,11,238,127,248,252,254,217,127
       defb 217,127,252,254,127,248,11,238,61,223,126,31,126,14,60,0

; La tercera apunta hacia abajo.

       defb 3,192,103,230,246,111,247,239,255,255,246,111,108,54,29,184
       defb 30,120,31,248,15,240,55,236,127,254,123,222,123,222,49,140

; La última el coche apunta a la izquierda.

       defb 0,60,112,126,248,126,251,188,119,208,31,254,127,63,254,155
       defb 254,155,127,63,31,254,119,208,251,188,248,126,112,126,0,60

; Variables usadas por el juego.

       org 32768

playi  equ $               ; dirección deseada cuando el giro es posible.
playd  equ playi+1         ; dirección actual del jugador.
nplayd equ playd+1         ; siguiente dirección jugador.
playx  equ nplayd+1        ; coordenada x del jugador
playy  equ playx+1         ; coordenada y del jugador.

encar1 equ playy+1         ; coche enemigo 1.
encar2 equ encar1+3        ; coche enemigo 2.

dispx  equ encar2+3        ; coordenadas de uso general.
dispy  equ dispx+1
seed   equ dispy+1         ; semilla de números aleatorios.
sprtmp equ seed+2          ; dirección temporal del sprite.
termin equ sprtmp+2        ; fin de variables.

rmdat  equ 49152



Si has ensamblado este juego y lo has probado, te darás cuenta de que rápidamente se vuelve aburrido. Es muy fácil mantenerse fuera del alcance de los enemigos cubriendo un lado de la pista, y luego esperar hasta que se muevan y cubrir el otro lado. Este algoritmo no tiene aspecto de cazador-asesino por lo que el jugador nunca es perseguido. Es más, esta rutina es de simples vehículos que no cambian de dirección sin advertencia. En la mayoría de juegos esto sólo es aceptable si un sprite llega a un callejón sin salida y no se puede mover en cualquier otra dirección.


Tal vez debemos en su lugar escribir rutinas en que los aliens interactúan con el jugador, y lo persiguen. Así, el algoritmo más básico sería seguir una línea comprobando las coordenadas base x/y, y mover el sprite del alien hacia el jugador. La rutina siguiente muestra cómo podría lograrse este objetivo, la rutina buscadora de blancos almov es la que mueve el sprite alrededor del jugador. Intente guiar al bloque número 1 por la pantalla con las teclas A, S, D y F, y el bloque número 2 te seguirá alrededor de la pantalla. Sin embargo, al hacer esto pronto descubrimos el defecto básico con este tipo de persecución, es muy fácil atrapar el sprite enemigo en una esquina porque la rutina no es lo suficientemente inteligente para moverse hacia atrás con el fin de conseguir rodear los obstáculos.

; Aleatoriamente cubrir la pantalla con bloques amarillos.

       ld de,1000          ; dirección en la ROM.
       ld b,64             ; número de celdas de color.
yello0 push bc             ; almacenas el registro.
       ld a,(de)           ; obtener la primera coordenada al azar.
       and 127             ; la mitad de la altura de la pantalla.
       add a,32            ; al menos 32 píxeles hacia abajo.
       ld c,a              ; coordenada x.
       inc de              ; siguiente byte de ROM.
       ld a,(de)           ; recuperar valor.
       inc de              ; siguiente byte de ROM.
       ld b,a              ; coordenada y.
       call ataddp         ; encuentra la dirección del atributo.
       ld (hl),48          ; establecer atributos.
       pop bc              ; restaurar el contador del bucle.
       djnz yello0         ; repetir varias veces.

       ld ix,aldat         ; datos del alien.
       call dal            ; mostrar alien.
       call dpl            ; presentar jugador.

mloop  halt                ; esperar al haz de electrones.
       call dal            ; eliminar alien.
       call almov          ; movimiento del alien.
       call dal            ; mostrar alien.
       halt                ; esperar al haz de electrones.
       call dpl            ; eliminar jugador.
       call plcon          ; controles del jugador.
       call dpl            ; mostrar al jugador.
       jp mloop            ; volver al inicio del bucle principal.

aldat  defb 0,0,0          ; datos del alien.

; Visualizar/eliminar alien.

dal    ld c,(ix)           ; posición vertical.
       ld b,(ix+1)         ; posición horizontal.
       ld (xcoord),bc      ; establecer las coordenadas del sprite.
       ld hl,algfx         ; gráfico del alien.
       jp sprite           ; XOR del sprite con la pantalla.

; Visualizar/eliminar sprite del jugador.

dpl    ld bc,(playx)       ; coordenadas.
       ld (xcoord),bc      ; establecer las coordenadas de pantalla.
       ld hl,plgfx         ; gráfico del jugador.
       jp sprite           ; xor del sprite dentro o fuera de la pantalla.

; control del jugador.

plcon  ld bc,65022         ; puerto para la fila del teclado.
       in a,(c)            ; leer teclado.
       ld b,a              ; almacenar el resultado en el registro b.
       rr b                ; verificación la tecla más externa.
       call nc,mpl         ; jugador a la izquierda.
       rr b                ; comprobar siguiente tecla.
       call nc,mpr         ; jugador de la derecha.
       rr b                ; comprobar siguiente tecla.
       call nc,mpd         ; jugador abajo.
       rr b                ; comprobar siguiente tecla.
       call nc,mpu         ; jugador arriba.
       ret
mpl    ld hl,playy         ; coordenada.
       ld a,(hl)           ; comprobar el valor.
       and a               ; ¿en el borde de la pantalla?
       ret z               ; sí, no se puede mover en esa dirección.
       sub 2               ; moverse 2 pixeles.
       ld (hl),a           ; nuevo ajuste.
       ret
mpr    ld hl,playy         ; coordenada.
       ld a,(hl)           ; comprobar el valor.
       cp 240              ; ¿en el borde de la pantalla?
       ret z               ; sí, no se puede mover en esa dirección.
       add a,2             ; moverse 2 pixeles.
       ld (hl),a           ; nuevo ajuste.
       ret
mpu    ld hl,playx         ; coordenada.
       ld a,(hl)           ; comprobar el valor.
       and a               ; ¿en el borde de la pantalla?
       ret z               ; sí, no se puede mover en esa dirección.
       sub 2               ; moverse 2 pixeles.
       ld (hl),a           ; nuevo ajuste.
       ret
mpd    ld hl,playx         ; coordenada.
       ld a,(hl)           ; comprobar el valor.
       cp 176              ; ¿en el borde de la pantalla?
       ret z               ; sí, no se puede mover en esa dirección.
       add a,2             ; moverse 2 pixeles.
       ld (hl),a           ; nuevo ajuste.
       ret


; Rutina de movimiento del alien.

almov  ld a,(playx)        ; coordenada x del jugador.
       ld c,(ix)           ; alien x.
       ld b,(ix+1)         ; alien y.
       cp c                ; revisar x del alien
       jr z,alv0           ; son iguales, seguir en horizontal.
       jr c,alu            ; el alien está más abajo, moverlo hacia arriba.
ald    inc c               ; el alien está más arriba, moverlo hacia abajo.
       jr alv0             ; Ahora comprobar la posición de las paredes.
alu    dec c               ; mover hacia abajo.
alv0   call alchk          ; comprobar atributos.
       cp 56               ; ¿están bien?
       jr z,alv1           ; Sí, establecer la coordenada x.
       ld c,(ix)           ; restaurar la anterior coordenada x.
       jr alh              ; ahora ir en horizontal.
alv1   ld (ix),c           ; nueva coordenada x.
alh    ld a,(playy)        ; horizontal del jugador.
       cp b                ; comprobar horizontal del alien.
       jr z,alok           ; son iguales, verificar la colisión.
       jr c,all            ; alien a la derecha, mover a la izquierda.
alr    inc b               ; alien a la izquierda, mover a la derecha.
       jr alok             ; comprobar las paredes.
all    dec b               ; mover a la derecha.
alok   call alchk          ; comprobar atributos.
       cp 56               ; ¿están bien?
       ret nz              ; no, establecer la nueva coordenada y.
       ld (ix+1),b         ; establecer nueva y.
       ret


; Comprueba atributos en la posición de alien (c,b).

alchk  call ataddp         ; conseguir dirección del atributo.
       ld a,3              ; celda de arriba.
alchk0 ex af,af'           ; guardar el contador de bucles.
       ld a,(hl)           ; verificar color de la celda.
       cp 56               ; ¿es negro sobre blanco?
       ret nz              ; no, no se puede mover aquí.
       inc hl              ; celda de la derecha.
       ld a,(hl)           ; verificar color de la celda.
       cp 56               ; ¿es negro sobre blanco?
       ret nz              ; no, no se puede mover aquí.
       inc hl              ; celda de la izquierda.
       ld a,(hl)           ; verificar color de la celda.
       cp 56               ; ¿es negro sobre blanco?
       ret nz              ; no, no se puede mover aquí.
       ld de,30            ; distancia hasta la siguiente celda hacia abajo.
       add hl,de           ; mira aquí.
       ex af,af'           ; contador de alturas.
       dec a               ; una menos a la que ir.
       jr nz,alchk0        ; repetir para todas las filas.
       ld (ix),c           ; establecer nueva x.
       ld (ix+1),b         ; establecer nueva y.
       ret

; Calcular la dirección del atributo para el píxel en (c,b).

ataddp ld a,c              ; Mira primero en vertical.
       rlca                ; dividir por 64.
       rlca                ; más rápido que 6 instrucciones rrca.
       ld l,a              ; almacenar en el registro l por ahora.
       and 3               ; enmascarar para encontrar segmento.
       add a,88            ; atributos comienzan a partir de 88*256=22528.
       ld h,a              ; ese es nuestro byte alto ordenado.
       ld a,l              ; vertical/64, es lo mismo que vertical*4.
       and 224             ; deseamos un múltiplo de  32.
       ld l,a              ; elemento vertical calculado.
       ld a,b              ; obtener la posición horizontal.
       rra                 ; dividir por 8.
       rra
       rra
       and 31              ; queremos dar lugar a rango 0-31.
       add a,l             ; añadir al byte bajo existente.
       ld l,a              ; tenemos el byte bajo hecho.
       ret                 ; dirección de atributo ahora en hl.


playx  defb 80             ; coordenadas del jugador.
playy  defb 120
xcoord defb 0              ; coordenadas generales multi propósito.
ycoord defb 0

; Rutina de sprites desplazados.

sprit7 xor 7               ; complementa los últimos 3 bits.
       inc a               ; ¡agrega uno por suerte!
sprit3 rl d                ; rotar izquierda...
       rl c                ; ...en el centro del byte...
       rl e                ; ...y a la izquierda de la celda de carácter.
       dec a               ; contar los cambios que hemos hecho.
       jr nz,sprit3        ; regresar hasta que los movimientos estén completos.

; Línea de la imagen de sprite ahora en e+c+d, lo necesitamos en forma c+d+e

       ld a,e              ; borde izquierdo de la imagen está en e.
       ld e,d              ; poner borde derecho en su lugar.
       ld d,c              ; bit central va en d
       ld c,a              ; y el borde izquierdo de nuevo en c.
       jr sprit0           ; hemos hecho el cambio para transferir a la pantalla.

sprite ld a,(xcoord)       ; dibuja el sprite (hl).
       ld (tmp1),a         ; guardar vertical.
       call scadd          ; calcular dirección de la pantalla.
       ld a,16             ; altura del sprite en pixeles.
sprit1 ex af,af'           ; guardar el contador de bucles.
       push de             ; guardar dirección de pantalla.
       ld c,(hl)           ; primer gráfico del sprite.
       inc hl              ; incrementar el puntero de datos del sprite.
       ld d,(hl)           ; siguiente bit de la imagen del sprite.
       inc hl              ; apuntar a la siguiente fila de datos del sprite.
       ld (tmp0),hl        ; guardar en tmp0 para más adelante.
       ld e,0              ; byte derecho en blanco por ahora.
       ld a,b              ; b guarda la posición y.
       and 7               ; ¿estamos a caballo entre celdas de caracteres?
       jr z,sprit0         ; no estamos a caballo, no molesta al desplazamiento.
       cp 5                ; ¿necesitamos 5 o más desplazamientos a la derecha?
       jr nc,sprit7        ; sí, desplazar a la izquierda que es más rápido.
       and a               ; hay, la bandera de acarreo se establece.
sprit2 rr c                ; rotar a la izquierda el byte derecho...
       rr d                ; ...Hasta el byte medio...
       rr e                ; ...la byte derecho.
       dec a               ; un turno menos que hacer.
       jr nz,sprit2        ; repetir hasta que todos los turnos estén completos.
sprit0 pop hl              ; sacar la dirección de la pantalla en la pila.
       ld a,(hl)           ; ya está lista.
       xor c               ; fusionar con los datos de la imagen.
       ld (hl),a           ; colocar en la pantalla.
       inc l               ; siguiente celda de carácter por la derecha.
       ld a,(hl)           ; ya estaba antes.
       xor d               ; fusionarse con el centro de la imagen.
       ld (hl),a           ; poner de nuevo en la pantalla.
       inc hl              ; siguiente bit del área de la pantalla.
       ld a,(hl)           ; lo que ya está allí.
       xor e               ; borde derecho de los datos de imagen del sprite.
       ld (hl),a           ; poner en pantalla.
       ld a,(tmp1)         ; coordenada vertical temporal.
       inc a               ; siguiente línea de abajo.
       ld (tmp1),a         ; almacenar nueva posición.
       and 63              ; ¿nos movemos al siguiente tercio de pantalla?
       jr z,sprit4         ; sí, encontrar el próximo segmento.
       and 7               ; ¿entrando en celda de carácter siguiente?
       jr z,sprit5         ; Sí, encuentre siguiente fila.
       dec hl              ; izquierda 2 bytes.
       dec l               ; es está el límite a horcajadas de 256 bytes aquí.
       inc h               ; siguiente fila de esta celda de carácter.
sprit6 ex de,hl            ; Dirección de pantalla en de.
       ld hl,(tmp0)        ; restaurar la dirección del gráfico.
       ex af,af'           ; restaurar el contador del bucle.
       dec a               ; decrementarlo.
       jp nz,sprit1        ; no alcanzado el borde inferior del sprite, repetir.
       ret                 ; trabajo hecho.
sprit4 ld de,30            ; el siguiente segmento es de 30 bytes.
       add hl,de           ; añadir a la dirección de la pantalla.
       jp sprit6           ; repetir.
sprit5 ld de,63774         ; menos 1762.
       add hl,de           ; restar 1762 de la dirección física de la pantalla.
       jp sprit6           ; volver al bucle.

scadd  ld a,(xcoord)       ; recuperar la coordenada vertical.
       ld e,a              ; guardarla en e.
       
; Encuentra la línea dentro de la celda.

       and 7               ; línea 0-7 en el cuadro del carácter.
       add a,64            ; 64 * 256 = 16384 = inicio de la pantalla.
       ld d,a              ; línea * 256.

; Encuentra en que tercio de pantalla estamos.

       ld a,e              ; restaurar la vertical.
       and 192             ; segmento 0, 1 o 2 multiplicado por 64.
       rrca                ; dividirlo por 8.
       rrca
       rrca                ; segmento 0-2 multiplicado por 8.
       add a,d             ; añadir a d da la dirección inicial del segmento.
       ld d,a

; Encuentra celda de carácter dentro del segmento.

       ld a,e              ; 8 casillas de caracteres por segmento.
       rlca                ; dividir por 8 y multiplicar por 32,
       rlca                ; cálculo neto: multiplicar por 4.
       and 224             ; enmascarar los bits que no queremos.
       ld e,a              ; cálculo de coordenada vertical hecho.

; Añadir el elemento horizontal.

       ld a,(ycoord)       ; coordenada y.
       rrca                ; sólo es necesario dividir por 8.
       rrca
       rrca
       and 31              ; cuadrados 0 - 31 por la pantalla.
       add a,e             ; añadirse al total de la medida.
       ld e,a              ; de = dirección de la pantalla.
       ret

tmp0   defw 0
tmp1   defb 0


plgfx  defb 127,254,255,255,254,127,252,127,248,127,248,127,254,127,254,127
       defb 254,127,254,127,254,127,254,127,248,31,248,31,255,255,127,254
algfx  defb 127,254,254,63,248,15,240,135,227,231,231,231,255,199,255,15
       defb 252,31,248,127,241,255,227,255,224,7,224,7,255,255,127,254

Las mejores rutinas de movimiento de aliens usan una combinación de elementos aleatorios y algoritmos de cazador-asesino. Para superar el problema en el listado superior necesitamos un nuevo indicador adicional para indicar el estado actual del enemigo o en su caso su dirección. Podemos mover el sprite a lo largo de una dirección determinada hasta que sea posible y cambiar el curso vertical u horizontal, con lo cual se selecciona una nueva dirección dependiendo de la posición del jugador. Sin embargo, en caso de que no sea posible mover en la dirección deseada iremos en la dirección opuesta en su lugar. Utilizando este método un sprite puede encontrar su propio camino en la mayoría de los laberintos sin colgarse con demasiada frecuencia. De hecho, para garantizar absolutamente que el sprite no permanecerá atrapados podemos añadir un elemento al azar de modo que cada cierto tiempo la nueva dirección es elegido de forma aleatoria en lugar de la diferencia en las coordenadas x e y

Subiendo la dificultad por niveles

La ponderación aplicada a la decisión sobre los cambios de dirección determinarán los niveles de inteligencia de los sprites. Si la nueva dirección tiene una probabilidad del 90% de ser elegido de manera aleatoria y una probabilidad del 10% sobre la base de coordenadas, el alien paseará sin rumbo por un tiempo y sólo se acerca cuando el jugador se mueve lento. Así, una decisión aleatoria a veces puede ser la más adecuada cuando persigue al jugador. Un alien en una pantalla más difícil podría tener una probabilidad del 60% de elegir una nueva dirección al azar, y un 40% de posibilidades de elegir la dirección en base a la posición relativa de jugador. Este alien hará un seguimiento un poco más de cercano al jugador. Ajustando estos niveles porcentuales, es posible determinar los niveles de dificultad a lo largo de un partido y asegurar una transición suave desde el las más simples pantallas del inicio de la partida a niveles finales diabólicamente difíciles.

martes, 26 de abril de 2016

Cómo escribir juegos para el ZX Spectrum. Capítulo 10

Índice de entradas

Esta serie de artículos han sido traducidos a partir del documento "How to Write ZX Spectrum Games" con permiso de su autor, Jonathan Cauldwell, un gran desarrollador de juegos para el Spectrum, os recomiendo visitar su Web donde está el texto original. El documento original, y por tanto esta traducción, tiene © Jonathan Cauldwell y solo puede duplicarse con permiso expreso por escrito de su autor.

Marcadores y puntuación más alta

Más rutinas de marcadores

Hasta ahora hemos usado una rutina de puntuación poco sofisticada. Nuestro marcador se guarda como un número de 16 bits almacenado en un par de registros, y para que aparezca hemos hecho uso de la rutina de impresión por pantalla del número de línea de la ROM Sinclair. Ahí están los dos principales inconvenientes de este método, en primer lugar que se limita a números de 0 al 9999, y en segundo lugar que se ve horrible.

Podríamos convertir un número de 16 bits a ASCII por nosotros mismos de esta manera:

; Mostrar número pasado en hl, justificado a la derecha.

shwnum ld a,48             ; ceros a la izquierda (usa 32 si prefieres espacios).
       ld de,10000         ; columna de diez miles.
       call shwdg          ; presentar el dígito.
       ld de,1000          ; columna de miles.
       call shwdg          ; presentar el dígito.
       ld de,100           ; columna de cientos.
       call shwdg          ; presentar el dígito.
       ld de,10            ; columna de decenas.
       call shwdg          ; presentar el dígito.
       or 16               ; el último dígito siempre se presenta.
       ld de,1             ; columna de unidades.
shwdg  and 48              ; borrar acarreo, borrar dígito.
shwdg1 sbc hl,de           ; restar de la columna.
       jr c,shwdg0         ; nada que mostrar.
       or 16               ; algo que mostrar, lo convierto en un dígito.
       inc a               ; incrementar digito.
       jr shwdg1           ; repetir hasta que la columna sea cero.
shwdg0 add hl,de           ; restaurar total.
       push af
       rst 16              ; presentar caracter.
       pop af
       ret

Este método funciona bien, aunque todavía estamos limitados a una puntuación de cinco dígitos que no supere 65535. Por un tema de apariencia más profesional completo con ceros a la izquierda, lo que necesitamos para mantener la puntuación como una cadena de dígitos ASCII.

He utilizado la misma técnica de puntuación por algo así como 15 años, no es terriblemente sofisticada pero es lo suficientemente buena para hacer lo que necesitamos. Este método utiliza un carácter ASCII por dígito, lo que hace que sea fácil de visualizar. De paso, esta rutina la tomo de mi  juego estilo Shoot 'em up More Tea, Vicar? (NdT: Para llamarse ¿Mas te, párroco? es un arcade de disparos a naves espaciales con scrool horizontal).

score  defb '000000'
uscor  ld a,(hl)           ; valor actual de los dígitos.
       add a,b             ; añadir puntero a este dígito.
       ld (hl),a           ; lugar del nuevo dígito en la cadena.
       cp 58               ; ¿mayor que el valor ASCII para nueve?
       ret c               ; no, tranquilidad.
       sub 10              ; restar 10.
       ld (hl),a           ; meter el nuevo carácter en la cadena.
uscor0 dec hl              ; anterior carácter en la cadena.
       inc (hl)            ; incrementar en uno.
       ld a,(hl)           ; ¿cuál es el nuevo valor?
       cp 58               ; ¿se pasa del ASCII para nueve?
       ret c               ; no, puntuación efectuada.
       sub 10              ; restarle diez.
       ld (hl),a           ; ponerlo de nuevo.
       jp uscor0           ; volver al bucle.


Para utilizar esto apunto en hl al dígito que nos gustaría aumentar, coloco la cantidad que queremos añadir en el registro b, luego llamo a uscor. Por ejemplo, para agregar 250 a la puntuación se requieren 6 líneas:

; Añadir 250 a la puntuación.

       ld hl,score+3       ; apunto a la columna de las centenas.
       ld b,2              ; 2 cienes = 200.
       call uscor          ; incrementar la puntuación.
       ld hl,score+4       ; apunto a la columna de las decenas.
       ld b,5              ; 5 dieces = 50.
       call uscor          ; incrementar la puntuación.

Simple, pero hace el trabajo. Los pedantes no dudarían en señalar que podría hacer utilizando BCD, y que los códigos de operación para esto se encuentran en el conjunto de instrucciones del Z80.

Tablas de puntuación máxima

Las rutinas de puntuación máxima no son especialmente fáciles de escribir para un principiante, pero una vez que has escrito una puede ser reutilizada una y otra vez. El principio básico es que empezamos en la parte inferior de la tabla y seguimos nuestro camino hacia arriba hasta que encontramos una puntuación que es mayor o igual a la puntuación del jugador. Entonces desplazamos todos los datos de la tabla hacia abajo a partir de ese punto una posición, y copiamos el nombre del jugador y la puntuación en ese punto de la tabla.

Podemos apuntar con los registros hl o ix al primer dígito de la puntuación inferior en la tabla y seguir nuestro camino con la comparación de cada dígito con el correspondiente en la puntuación del jugador. Si el dígito en la puntuación del jugador es mayor nos movemos hacia arriba una posición, si es inferior nos detenemos allí y copiamos la puntuación del jugador en la tabla un lugar hacia arriba. Si los dígitos son los mismos nos movemos al siguiente dígito y repetimos la comprobación hasta que los dígitos son diferentes o hemos comprobado todos los dígitos en la puntuación. Si los resultados son idénticos colocamos la entrada del jugador en el lugar de la tabla que aparece a continuación. Esto se repite hasta que una puntuación en la tabla es mayor que la puntuación del jugador, o se llega a la parte superior de la tabla.

En la creación inicial de la tabla de puntuación puede ser tentador colocar tu propio nombre en la parte superior con una puntuación que sea muy difícil de superar. Trata de resistir esta tentación. Las tablas de puntuación más alta son para que el jugador pueda juzgar su propio desempeño, y no hay necesidad de frustrar al jugador haciendo muy difícil llegar a la primera posición.

lunes, 25 de abril de 2016

Cómo escribir juegos para el ZX Spectrum. Capítulo 9

Índice de entradas

Esta serie de artículos han sido traducidos a partir del documento "How to Write ZX Spectrum Games" con permiso de su autor, Jonathan Cauldwell, un gran desarrollador de juegos para el Spectrum, os recomiendo visitar su Web donde está el texto original. El documento original, y por tanto esta traducción, tiene © Jonathan Cauldwell y solo puede duplicarse con permiso expreso por escrito de su autor.

Gráficos de fondo

Presentando bloques

Digamos que queremos escribir un sencillo juego de laberinto por pantalla. Tenemos que mostrar las paredes alrededor de las cuales el sprite del jugador debe ser movido, y la mejor manera de hacer esto es crear una tabla de bloques que se transfieren a la pantalla secuencialmente. A medida que avanzamos a través de la tabla encontramos la dirección del bloque gráfico, cálculamos su dirección de pantalla y volcamos el carácter a la pantalla.

Vamos a empezar con la rutina de visualización de caracteres. A diferencia de la rutina que necesitamos para que el sprite se acople a posiciones de caracteres, por suerte es más fácil calcular una dirección de pantalla para una posición de carácter de lo que lo es para píxeles individuales.

Hay 24 posiciones de celdas de caracter verticales y 32 posiciones horizontales en la pantalla del spectrum, por lo que nuestras coordenadas estarán entre (0,0) y (23,31). Las filas 0-7 caen en el primer segmento de pantalla, 8-15 en la sección media de la pantalla, y las posiciones 16-23 en la tercera parte de la pantalla. Tenemos suerte, el byte alto de la dirección de pantalla para cada segmento se incrementa en 8 de un segmento a otro, por lo que tomando el número de celdas verticales y realizando un and 24 inmediatamente obtenemos el desplazamiento del comienzo del segmento de pantalla que debemos abordar allí mismo. Añadir 64 para el inicio de la pantalla del Spectrum y tenemos el byte alto de nuestra dirección. Entonces tenemos que encontrar la celda de carácter correcta dentro de cada segmento, por lo que tomamos de nuevo la coordenada vertical, y esta vez usamos and 7 para determinar cuál de las siete filas estamos tratando de encontrar. Multiplicamos esto por el ancho en caracteres de la pantalla (32) y añadimos el número de celda horizontal para encontrar el byte bajo de la dirección de la pantalla. Un ejemplo adecuado es el siguiente:

; retorna dirección de la celda de carácter en (b, c).

chadd  ld a,b              ; posición vertical.
       and 24              ; ¿qué segmento, 0, 1 o 2?
       add a,64            ; 64*256 = 16384, memoria de pantalla del Spectrum.
       ld d,a              ; este es nuestro byte alto.
       ld a,b              ; ¿cuan es la posición vertical ahora?
       and 7               ; ¿qué fila dentro del segmento?
       rrca                ; multiplcar fila por 32.
       rrca
       rrca
       ld e,a              ; byte bajo.
       ld a,c              ; añadir la coordenada y.
       add a,e             ; mezclar con el byte bajo.
       ld e,a              ; dirección de pantalla en de.

Una vez que tenemos nuestra dirección de pantalla es un proceso sencillo el volcar el carácter a pantalla. Mientras que no estamos cruzando los límites de las celdas de caracteres la siguiente línea de la pantalla siempre caerá 256 bytes después de la de su predecesor, por lo que hay que incrementar el byte alto de dirección para encontrar la siguiente línea.

; Mostrar el caracter hl en (b, c).

char   call chadd          ; encontrar la dirección en pantalla del caracter.
       ld b,8              ; numero del pixel alto.
char0  ld a,(hl)           ; fuente del grafico.
       ld (de),a           ; transferir a la pantalla.
       inc hl              ; siguiente trozo de datos.
       inc d               ; siguiente linea de pixels.
       djnz char0          ; repetir
       ret

En cuanto a la coloración de nuestro bloque, lo hemos cubierto en el capítulo sobre detección de colisiones de atributos sencilla. La rutina atadd nos dará la dirección de un atributo de celda de carácter en (b, c).

Por último, tenemos que decidir qué bloque mostrar en cada celda de pantalla. Podemos pensar que tenemos 3 tipos de bloques para nuestro juego, podríamos usar un bloque de tipo 0 para un espacio, tipo 1 para el muro y tipo 2 para una puerta. Arreglaríamos los gráficos y atributos para cada bloque en tablas separadas en el mismo orden:

blocks equ $

; block 0 = caracter para espacio.

       defb 0,0,0,0,0,0,0,0

; block 1 = muro.

       defb 1,1,1,255,16,16,16,255

; block 2 = puerta.

       defb 6,9,9,14,16,32,80,32

attrs  equ $

; block 0 = espacio.

       defb 71

; block 1 = muro.

       defb 22

; block 2 = puerta.

       defb 70

A medida que avanzamos a través de nuestro tablero de hasta 24 filas y 32 columnas de bloques de laberinto cargamos el número del bloque en el acumulador, y llamamos a las rutinas fblock y fattr a continuación para obtener las direcciones del gráfico de origen y su atributo.

; Encontrar celda del gráfico.

fblock rlca                ; multiplicar el número de bloque por 8.
       rlca
       rlca
       ld e,a              ; despalzar a la dirección del grafico.
       ld d,0              ; sin byte alto.
       ld hl,blocks        ; dirección del bloque de caracteres.
       add hl,de           ; apuntar al bloque.
       ret

; Encontrar celda de atributo.

fattr  ld e,a              ; desplazamiento a la dirección del atributo.
       ld d,0              ; sin byte alto.
       ld hl,attrs         ; dirección del bloque de atributos.
       add hl,de           ; apuntar al bloque.
       ret

Usar este método significa que nuestros datos del laberinto requiere un byte de RAM por cada celda de carácter. Para un área de juego de 32 celdas de ancho y 16 bloques de alto esto significaría que cada pantalla ocupa 512 bytes de memoria. Eso estaría bien para 20 pantallas de plataformas como en el Manic Miner, pero si quieres un centenar o más de pantallas deberías considerar el uso de bloques más grandes de modo que requiera menos bloques cada pantalla. Mediante el uso bloques de celdas de caracteres de 16 x 16 píxeles en lugar de los 8 x 8 en nuestro ejemplo, cada tabla de pantalla requeriría sólo 128 bytes, lo que significa que podremos exprimir mas la memoria del Spectrum.